Local Multiplayer Game

@Creator27 Still got the error with tab.ip . Removed tab and still getting nil for result.

@dave1707, I really don’t know what’s going on. It works perfectly fine for me. Have you checked to see if the server on iPad #1 is even receiving a message in the first place?

@Creator27 On ipad1, where I do CreateRoom, I get a message “requesting server info” when I do FindRoom on iPad2. I put a print statement “sending server info” on ipad1 to show it was sending server info back to iPad2. I wasn’t receiving anything back on iPad2, the “result” was still nil.

@Creator27 Here’s a copy of my code showing how I keep reading messages using the “repeat until” loop. I commented out some code so it would work using 1 iPad. Tap the screen to send 10 messages each time. You can increase the “for loop” count if you want to try more messages. I sent and received 1000 messages with no trouble.

viewer.mode=STANDARD

function setup()
    fill(255)
    delay=0
    rdev=remoteDevice()
    msgRcvd,msgSent=0,0
    nbr,tt=0,0
end

function draw()
    background()
    rdev:getIp()
    
    -- keep reading messages until there's no more data
    repeat 
        rdev:receiveTheirMessage() 
    until noData
    
    text("Messages sent     "..msgSent,WIDTH/2,HEIGHT-30) 
    text("Messages received "..msgRcvd,WIDTH/2,HEIGHT-60) 
    text("Tap screen to send messages",WIDTH/2,HEIGHT/2)
end

function touched(t)
    if t.state==BEGAN then
        output.clear()
        -- send messages
        for z=1,10 do
            str=string.format("sent message %4d abcdefghijklmnopqrstuvwxyz",msgSent+1)
            rdev:sendMessage(str)
        end
    end
end

remoteDevice=class()

function remoteDevice:init()
    self.socket=require("socket")
    self.myIp="0.0.0.0"  
    self.theirIp="0.0.0.0"  
    self:getMyIp()
    self:setMySocket()
    self:setTheirSocket()
    self:getTheirIp()
end

function remoteDevice:getIp()
    if not gotMyIp or not gotTheirIp then
        if delay>60 then
            delay=0
            if not gotMyIp then
                rdev:getMyIp()
            end
            if not gotTheirIp then
                rdev:getTheirIp()
            end
        end
        delay=delay+1
    end
end

function remoteDevice:setMySocket()    
    self.server=self.socket.udp()
    self.server:setsockname(self.myIp,5544)
    self.server:settimeout(0)
end

function remoteDevice:setTheirSocket()
    self.client=self.socket.udp()
    self.client:settimeout(0)
end

function remoteDevice:getMyIp()
    self.server=self.socket.udp()
    self.server:setpeername("1.1.1.1",80)
    self.myIp,self.myPort=self.server:getsockname()
    self.ip1,self.ip2=string.match(self.myIp,"(%d+.%d+.%d+.)(%d+)")
    print("my IP = "..self.ip1..self.ip2)
    gotMyIp=true
end

function remoteDevice:getTheirIp()
    self.client=self.socket.udp()
    self.client:settimeout(0)
    
    -- send a message to everyone on this network except to myself
    for z=1,255 do
        -- commented below code to allow a message to myself
        --if z~=tonumber(self.ip2) then
            self.client:setpeername(self.ip1..z,5544)
            self.client:send("get their ip")
        --end
    end
end

function remoteDevice:receiveTheirMessage()
    noData=true
    
    local data,msg,port=self.server:receivefrom()
    
    if data~=nil then
        noData=false
        
        msgRcvd=msgRcvd+1
        
        if data=="get their ip" then
            self.client=self.socket.udp()
            self.client:settimeout(0)
            self.client:setpeername(msg,5544)
            self.client:send("got their ip"..self.myIp)  
            print("message-> get their ip")
            
        elseif string.sub(data,1,12)=="got their ip" then
            self.theirIp=msg
            print("their IP = "..self.theirIp)
            gotTheirIp=true
            print("message-> got their ip")
            
        elseif string.sub(data,1,12)=="sent message" then
            print(data)
        end
    end
end

function remoteDevice:sendMessage(tempStr)
    msgSent=msgSent+1
    self.client = self.socket.udp()
    self.client:setpeername(self.theirIp,5544)
    self.client:settimeout(0)
    self.client:send(tempStr)
end

@dave1707, try changing the findRoom function to this:

function findRoom()
    print("Attempting to find a room...")
    finding_room = true
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
    -- We need to use our listener socket and broadcast a request message to every server on the network
    listener:setsockname("*", "14285")
    -- Turn on broadcast messaging
    listener:setoption("broadcast", true)
    for z = 1, 255 do
        listener:sendto("requesting server info", ip1..z, "14285")
    end
    -- Turn off broadcast messaging after
    listener:setoption("broadcast", false)
    current_time = os.time()
end

@Creator27 I get

Attempting to find a room…

then

Attempting to join the room…

and nothing else.

I do Find Room on the second iPad. I Create Room on the first iPad.

@dave1707, I really don’t know what has happened, but the only thing I can think of right now is to remove these lines of code from the findRoom function:

listener:setoption("broadcast", true)

And this:

listener:setoption("broadcast", true)

@Creator27 Same result as above. If I get time later, I’ll put in some print statements to see what’s happening.

The variable result in receiveData() continuously prints nil when Find Room is pressed.

@dave1707, this is not gonna work, but I slightly reworked the findRoom function and the receiveData function. Here it is:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip then
            joinRoom(other_ip, false)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        findRoom()
    end)
    client = socket.udp()
    client:settimeout(0)
    listener = socket.udp()
    listener:settimeout(0)
    -- We need to create a table that can hold information about our server, if you end up making one
    server_info = {players = 0, clients = {}}
    other_ip = nil
    current_time = nil
    current_room_count = 1
    tick = 0
    finding_room = false
    server = false
end

function createRoom()
    -- We need to find your address, so we create a new UDP socket and assign it a random IP
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    client:setsockname("*", "14285")
    local ip2, port2 = client:getsockname()
    client:setoption("broadcast", true)
    your_ip = ip2
    your_port = port2
    print("Room created!")
    print("Connect to "..ip)
    server = true
end

function joinRoom(ip, finding_room)
    if server == false then
        if finding_room == false then
            print("Attempting to join the room...")
        end
        -- The port for all the servers is the same, so we only need the IP address of the server
        client:setpeername(ip, "14285")
        client:send("connection confirmation")
        current_time = os.time()
    else
        print("You are already hosting a room!")
    end
end

function findRoom()
    if server == false then
        print("Attempting to find a room...")
        finding_room = true
        local udp = socket.udp()
        udp:setpeername("192.167.188.122", "14285")
        local ip, port = udp:getsockname()
        udp:close()
        local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
        for z = 1, 255 do
            client:setpeername(ip1..z, "14285")
            client:send("requesting server info")
        end
        client:setpeername("*")
        client:setsockname("*", "14285")
        current_time = os.time()
    else
        print("You are already hosting a room!")
    end
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                if server_info["players"] < 32 then
                    -- We can now add 1 more to our player count
                    server_info["players"] = server_info["players"] + 1
                    -- We now need to store the clients IP and port, so we can send them messages later
                    local uid = {ip = ip, port = port}
                    if not server_info["clients"][uid] then
                        table.insert(server_info["clients"], uid)
                    end
                    client:sendto("valid confirmation", ip, port)
                    -- We can now send everyone that has joined this room a packet telling them the amount of players in the room
                    for i,c in pairs(server_info["clients"]) do
                        client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
                    end
                else
                    -- We don't have enough room for another player, so we send the peer a message telling them to disconnect
                    local reason = "Max players inside the room!"
                    client:sendto("invalid confirmation"..reason, ip, port)
                end
            elseif msg == "requesting server info" then
                -- We need to find your address, so we create a new UDP socket and assign it a random IP
                local udp = socket.udp()
                udp:setpeername("192.167.188.122", "14285")
                local our_ip, our_port = udp:getsockname()
                udp:close()
                -- We now know that somebody is trying to find a server, so we send a packet back to them with our server IP and port
                local uid = {ip = our_ip}
                client:sendto("server info"..json.encode(uid), ip, port)
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result == "valid confirmation" then
                -- We know that we successfully joined the room, and we can now receive messages from the sever!
                print("Successfully joined the room!")
                current_time = nil
            elseif result and string.find(result, "player count") then
                -- We now know the current amount of players in our room, for now at least
                local count = string.sub(result, 13, #result)
                print("Player count: "..count)
            elseif result and string.find(result, "invalid confirmation") then
                -- We can now see the reason for our invalid confirmation and display it
                local reason = string.sub(result, 21, #result)
                print("Could not join the room: "..reason)
                current_time = nil
                -- We can now also remove the address and port set for our peer
                client:setpeername("*")
            elseif result == nil then
                if current_time then
                    if finding_room == false then
                        if os.time() > current_time + 5 then
                            -- If we get no response back for 5 seconds or more, we know it didn't work
                            print("Failed to connect, please try again later")
                            current_time = nil
                        end
                    end
                end
            end
        else
            local result, ip, port = client:receivefrom()
            if result and string.find(result, "server info") then
                -- Now we can attempt to join the room with the given IP address
                local tab = json.decode(string.sub(result, 12, #result))
                if finding_room == true then
                    joinRoom(tab.ip, true)
                    finding_room = false
                end
            else
                if finding_room == true then
                    if current_time and os.time() > current_time + 5 then
                        -- If we get no response back for 5 seconds or more, we know it didn't work
                        print("Failed to find a room!")
                        current_time = nil
                    end
                end
            end
        end
    end 
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    
    -- This sets the line thickness
    strokeWidth(5)
    
    -- Do your drawing here
    receiveData()
    tick = tick + 1
end

@Creator27 You’re correct, it didn’t work. Got this error when pressing Find Room.

Main:70: attempt to index a nil value (global ‘player’)
stack traceback:
Main:70: in function ‘findRoom’
Main:14: in function Main:13

@dave1707, I’ve fixed the error. I used the term “player” because when I wrote the code, I allowed it to send messages to myself for testing purposes.

@Creator27 Tried the new code. When I pressed Find Room, I got:

Attempting to find a room…

Successfully joined the room!

Player count: 1

Tried the code on a third iPad. When I pressed Find Room, it printed the above lines with
Player count: 2 printing on both iPads.

@dave1707, that’s awesome! After all this time, my code works!

@Creator27 It probably would have been a lot easier if you had access to 2 iPads. Good job.

@dave1707, I have added some code that allows a player to leave the room at any time. To test if it works, I also implemented a text box and a button that shows up if you create a server. Whatever is written in the text box will be sent to every player currently connected to your server if you press the “Send Message” button. Here’s the code:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip then
            joinRoom(other_ip, false)
        else
            if server == false then
                parameter.text("IP", "", function(t)
                    other_ip = t
                end)
            else
                print("You are already hosting a server!")
            end
        end
    end)
    parameter.action("Find Room", function()
        if server == false then
            findRoom()
        else
            print("You are already hosting a server!")
        end
    end)
    client = socket.udp()
    client:settimeout(0)
    listener = socket.udp()
    listener:settimeout(0)
    -- We need to create a table that can hold information about our server, if you end up making one
    server_info = {players = 0, clients = {}}
    -- We also need to create a table that can hold info that the client has
    client_info = {serverIp = ""}
    other_ip = nil
    current_time = nil
    current_room_count = 1
    tick = 0
    finding_room = false
    server = false
    msgToSend = ""
    joinedRoom = false
end

function createRoom()
    -- We need to find your address, so we create a new UDP socket and assign it a random IP
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    client:setsockname("*", "14285")
    local ip2, port2 = client:getsockname()
    client:setoption("broadcast", true)
    your_ip = ip2
    your_port = port2
    print("Room created!")
    print("Connect to "..ip)
    server = true
    parameter.text("Message To Send", "", function(t)
        msgToSend = t
    end)
    parameter.action("Send Message", function()
        if msgToSend ~= "" then
            sendMessage(msgToSend)
        end
    end)
end

function joinRoom(ip, finding_room)
    if server == false then
        if finding_room == false then
            print("Attempting to join the room...")
        end
        -- The port for all the servers is the same, so we only need the IP address of the server
        client:setpeername(ip, "14285")
        client:send("connection confirmation")
        current_time = os.time()
    else
        print("You are already hosting a server!")
    end
end

function findRoom()
    if server == false then
        print("Attempting to find a room...")
        finding_room = true
        local udp = socket.udp()
        udp:setpeername("192.167.188.122", "14285")
        local ip, port = udp:getsockname()
        udp:close()
        local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
        for z = 1, 255 do
            client:setpeername(ip1..z, "14285")
            client:send("requesting server info")
        end
        client:setpeername("*")
        client:setsockname("*", "14285")
        current_time = os.time()
    else
        print("You are already hosting a room!")
    end
end

function sendMessage(msg)
    if server == true then
        for i,c in pairs(server_info["clients"]) do
            client:sendto(tostring(msg), c.ip, c.port)
        end
    end
end

function leaveRoom()
    if server == false then
        -- We need to notify the server that we are leaving, so we send a packet to them
        client:send("disconnection confirmation")
    end
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                if server_info["players"] < 32 then
                    -- We can now add 1 more to our player count
                    server_info["players"] = server_info["players"] + 1
                    -- We now need to store the clients IP and port, so we can send them messages later
                    local uid = {ip = ip, port = port}
                    if not server_info["clients"][uid] then
                        table.insert(server_info["clients"], uid)
                    end
                    client:sendto("valid confirmation", ip, port)
                    -- We need to send the joining client our IP, so they can send messages to us later
                    local udp = socket.udp()
                    udp:setpeername("192.167.188.122", "14285")
                    local our_ip, our_port = udp:getsockname()
                    udp:close()
                    client:sendto("server ip"..our_ip, ip, port)
                    -- We can now send everyone that has joined this room a packet telling them the amount of players in the room
                    for i,c in pairs(server_info["clients"]) do
                        client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
                    end
                else
                    -- We don't have enough room for another player, so we send the peer a message telling them to disconnect
                    local reason = "Max players inside the room!"
                    client:sendto("invalid confirmation"..reason, ip, port)
                end
            elseif msg == "requesting server info" then
                -- We need to find your address, so we create a new UDP socket and assign it a random IP
                local udp = socket.udp()
                udp:setpeername("192.167.188.122", "14285")
                local our_ip, our_port = udp:getsockname()
                udp:close()
                -- We now know that somebody is trying to find a server, so we send a packet back to them with our server IP and port
                local uid = {ip = our_ip}
                client:sendto("server info"..json.encode(uid), ip, port)
            elseif msg == "disconnection confirmation" then
                -- We know that one of the clients connected to this server wants to leave, so we send a packet back telling them that we have been notified
                client:sendto("notified of disconnection", ip, port)
                local uid = {ip = ip, port = port}
                local clientInfo
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result == "valid confirmation" then
                -- We know that we successfully joined the room, and we can now receive messages from the server!
                print("Successfully joined the room!")
                current_time = nil
                -- We now create a button that allows the player to leave the room
                parameter.action("Leave Room", function()
                    leaveRoom()
                end)
            elseif result and string.find(result, "server ip") then
                -- Now that we have the servers IP address, we can message them later if we need to
                local serverIp = string.sub(result, 10, #result)
                client_info["serverIp"] = serverIp
            elseif result and string.find(result, "player count") then
                -- We now know the current amount of players in our room, for now at least
                local count = string.sub(result, 13, #result)
                print("Player count: "..count)
            elseif result and string.find(result, "invalid confirmation") then
                -- We can now see the reason for our invalid confirmation and display it
                local reason = string.sub(result, 21, #result)
                print("Could not join the room: "..reason)
                current_time = nil
                -- We can now also remove the address and port set for our peer
                client:setpeername("*")
            elseif result == "notified of disconnection" then
                -- We know that we can now safely leave the server
                client:setpeername("*")
                print("Successfully left the room!")
            elseif result then
                -- We know that it's just a random test message from the server
                print("Server sent a message: "..result)
            elseif result == nil then
                if current_time then
                    if finding_room == false then
                        if os.time() > current_time + 5 then
                            -- If we get no response back for 5 seconds or more, we know it didn't work
                            print("Failed to connect, please try again later")
                            current_time = nil
                        end
                    end
                end
            end
        else
            local result, ip, port = client:receivefrom()
            if result and string.find(result, "server info") then
                -- Now we can attempt to join the room with the given IP address
                local tab = json.decode(string.sub(result, 12, #result))
                if finding_room == true then
                    joinRoom(tab.ip, true)
                    finding_room = false
                end
            else
                if finding_room == true then
                    if current_time and os.time() > current_time + 15 then
                        -- If we get no response back for 15 seconds or more, we know we couldn't find a room
                        print("Failed to find a room!")
                        current_time = nil
                    end
                end
            end
        end
    end 
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    
    -- This sets the line thickness
    strokeWidth(5)
    
    -- Do your drawing here
    receiveData()
    tick = tick + 1
end

@Creator27
I successfully created a room.
I successfully joined a room.
I left the room.
I rejoined the room.
I sent a message to the joined room.

Looks like everything is working.

@dave1707, if this code works you should be able to see every player currently connected to the server, and their positions should also be updated as well. Here’s the code:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip then
            joinRoom(other_ip, false)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        findRoom()
    end)
    client = socket.udp()
    client:settimeout(0)
    listener = socket.udp()
    listener:settimeout(0)
    -- We need to create a table that can hold information about our server, if you end up making one
    server_info = {players = 0, clients = {}, world_state = {}}
    -- We also need to create a table that can hold info that the client has
    client_info = {serverIp = "", world_state = {}}
    other_ip = nil
    current_time = nil
    current_room_count = 1
    tick = 0
    finding_room = false
    server = false
    joinedRoom = false
end

function createRoom()
    -- We need to find your address, so we create a new UDP socket and assign it a random IP
    local ip, port = getLocalIP()
    client:setsockname("*", "14285")
    local ip2, port2 = client:getsockname()
    client:setoption("broadcast", true)
    your_ip = ip2
    your_port = port2
    print("Room created!")
    print("Connect to "..ip)
    server = true
    local playerTab = {playerName = "server", playerPos = vec2(50, 50)}
    table.insert(server_info["world_state"], playerTab)
end

function joinRoom(ip, finding_room)
    if server == false then
        if finding_room == false then
            print("Attempting to join the room...")
        end
        -- The port for all the servers is the same, so we only need the IP address of the server
        client:setpeername(ip, "14285")
        client:send("connection confirmation")
        current_time = os.time()
    else
        print("You are alrady hosting a server!")
    end
end

function findRoom()
    if server == false then
        print("Attempting to find a room...")
        finding_room = true
        local ip, port = getLocalIP()
        local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
        local success, err = pcall(function()
            for z = 1, 255 do
                client:setpeername(ip1..z, "14285")
                client:send("requesting server info")
            end
            client:setpeername("*")
            client:setsockname("*", "14285")
        end)
        if success == false then
            -- The only error you could possibly get in this case is no internet connection, so we print it
            print("No internet connection!")
            return
        end
        current_time = os.time()
    else
        print("You are already hosting a server!")
    end
end

function sendMessage(msg)
    if server == true then
        for i,c in pairs(server_info["clients"]) do
            client:sendto(tostring(msg), c.ip, c.port)
        end
    end
end

function leaveRoom()
    if server == false then
        -- We need to notify the server that we are leaving, so we send a packet to them
        client:send("disconnection confirmation")
    end
end

function resetParameters(params, args)
    parameter.clear()
    for i = 1, #params do
        parameter.action(params[i], args[i])
    end
end

function getLocalIP()
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    return ip, port
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                if server_info["players"] < 32 then
                    -- We can now add 1 more to our player count
                    server_info["players"] = server_info["players"] + 1
                    -- We now need to store the clients IP and port, so we can send them messages later
                    local uid = {ip = ip, port = port}
                    if not server_info["clients"][uid] then
                        table.insert(server_info["clients"], uid)
                    end
                    client:sendto("valid confirmation", ip, port)
                    -- We need to send the joining client our IP, so they can send messages to us later
                    local our_ip, our_port = getLocalIP()
                    client:sendto("server ip"..our_ip, ip, port)
                    -- We can now send everyone that has joined this room a packet telling them the amount of players in the room
                    for i,c in pairs(server_info["clients"]) do
                        client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
                    end
                else
                    -- We don't have enough room for another player, so we send the peer a message telling them to disconnect
                    local reason = "Max players inside the room!"
                    client:sendto("invalid confirmation"..reason, ip, port)
                end
            elseif msg == "requesting server info" then
                -- We need to find your address, so we create a new UDP socket and assign it a random IP
                local our_ip, our_port = getLocalIP()
                -- We now know that somebody is trying to find a server, so we send a packet back to them with our server IP and port
                local uid = {ip = our_ip}
                client:sendto("server info"..json.encode(uid), ip, port)
            elseif msg == "disconnection confirmation" then
                -- We know that one of the clients connected to this server wants to leave, so we send a packet back telling them that we have been notified
                client:sendto("notified of disconnection", ip, port)
                local uid = {ip = ip, port = port}
                local clientInfo
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            elseif msg and string.find(msg, "client state") then
                -- If we don't currently have the client state, we can simply add it into our world state
                local tab = json.decode(string.sub(msg, 13, #msg))
                if not server_info["world_state"][tab] then
                    table.insert(server_info["world_state"], tab)
                end
            elseif msg and string.find(msg, "state update") then
                -- We can now update the client state, and then send that state to everybody else
                local tab = json.decode(string.sub(msg, 13, #msg))
                local existingTab
                for i,t in pairs(server_info["world_state"]) do
                    if t.playerName == tab.playerName then
                        existingTab = t
                        -- We now edit the X and Y positions of the client state on server-side
                        existingTab.playerX = tab.playerX
                        existingTab.playerY = tab.playerY
                        local uid = {ip = ip, port = port}
                        for i,c in pairs(server_info["clients"]) do
                            client:sendto("client state"..json.encode(existingTab), c.ip, c.port)
                        end
                    end
                end
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result == "valid confirmation" then
                -- We know that we successfully joined the room, and we can now receive messages from the server!
                print("Successfully joined the room!")
                joinedRoom = true
                current_time = nil
                parameter.clear()
                -- We now create a button that allows the player to leave the room
                local paramName = {"Leave Room"}
                local paramArgs = {function() leaveRoom() end}
                resetParameters(paramName, paramArgs)
                -- We now create a player for ourselves and send it to the server to be added to it's world state
                local playerTab = {playerName = "client", playerX = 50, playerY = 50}
                table.insert(client_info["world_state"], playerTab)
                client:send("client state"..json.encode(playerTab), client_info["serverIp"], "14285")
            elseif result and string.find(result, "server ip") then
                -- Now that we have the servers IP address, we can message them later if we need to
                local serverIp = string.sub(result, 10, #result)
                client_info["serverIp"] = serverIp
            elseif result and string.find(result, "player count") then
                -- We now know the current amount of players in our room, for now at least
                local count = string.sub(result, 13, #result)
                print("Player count: "..count)
            elseif result and string.find(result, "invalid confirmation") then
                -- We can now see the reason for our invalid confirmation and display it
                local reason = string.sub(result, 21, #result)
                print("Could not join the room: "..reason)
                current_time = nil
                -- We can now also remove the address and port set for our peer
                client:setpeername("*")
            elseif result == "notified of disconnection" then
                -- We know that we can now safely leave the server
                client:setpeername("*")
                print("Successfully left the room!")
                local paramNames = {"Create Room", "Join Room", "Find Room"}
                local paramArgs = {createRoom, function()
                        if other_ip then
                            joinRoom(other_ip, false)
                        else
                            parameter.text("IP", "", function(t)
                                other_ip = t
                            end)
                        end
                    end, findRoom}
                resetParameters(paramNames, paramArgs)
            elseif result and string.find(result, "client state") then
                -- We can now accurately modify the client's world state, and update them if needed in realtime
                local tab = json.decode(string.sub(result, 13, #result))
                for i,t in pairs(client_info["world_state"]) do
                    if t.playerName == tab.playerName then
                        t.playerX = tab.playerX
                        t.playerY = tab.playerY
                    end
                end
            elseif result == nil then
                if current_time then
                    if finding_room == false then
                        if os.time() > current_time + 5 then
                            -- If we get no response back for 5 seconds or more, we know it didn't work
                            print("Failed to connect, please try again later")
                            current_time = nil
                        end
                    end
                end
            end
        else
            local result, ip, port = client:receivefrom()
            if result and string.find(result, "server info") then
                -- Now we can attempt to join the room with the given IP address
                local tab = json.decode(string.sub(result, 12, #result))
                if finding_room == true then
                    joinRoom(tab.ip, true)
                    finding_room = false
                end
            else
                if finding_room == true then
                    if current_time and os.time() > current_time + 15 then
                        -- If we get no response back for 15 seconds or more, we know we couldn't find a room
                        print("Failed to find a room!")
                        current_time = nil
                    end
                end
            end
        end
    end 
end

function updatePlayers()
    if joinedRoom == true then
        -- We will continuously send the server our client state, so the server can make realtime changes
        local playerTab = {playerName = "client", playerX = CurrentTouch.x, playerY = CurrentTouch.y}
        client:send("state update"..json.encode(playerTab), client_info["serverIp"], "14285")
    end
    for i,t in pairs(client_info["world_state"]) do
        ellipse(t.playerX, t.playerY, 30)
    end
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    
    -- This sets the line thickness
    strokeWidth(5)
    
    -- Do your drawing here
    receiveData()
    updatePlayers()
    tick = tick + 1
end

@Creator27 Ran the code on 2 iPads. Created Room on 1st iPad, Find Room on 2nd iPad. Player Count 1 showed on 2nd iPad but not on 1st iPad. Moved my finger on 2nd iPad and a circle followed my finger, but with a large delay. Nothing ever appeared on 1st iPad other than Created Room and IP address.

@dave1707, I have updated the code. Also, try and use 3 iPads instead of 2 to test this. Here it is:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip then
            joinRoom(other_ip, false)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        findRoom()
    end)
    client = socket.udp()
    client:settimeout(0)
    listener = socket.udp()
    listener:settimeout(0)
    -- We need to create a table that can hold information about our server, if you end up making one
    server_info = {players = 0, clients = {}, world_state = {}}
    -- We also need to create a table that can hold info that the client has
    client_info = {serverIp = "", world_state = {}}
    other_ip = nil
    current_time = nil
    current_room_count = 1
    tick = 0
    finding_room = false
    server = false
    joinedRoom = false
    myPlayer = nil
end

function createRoom()
    -- We need to find your address, so we create a new UDP socket and assign it a random IP
    local ip, port = getLocalIP()
    client:setsockname("*", "14285")
    local ip2, port2 = client:getsockname()
    client:setoption("broadcast", true)
    your_ip = ip2
    your_port = port2
    print("Room created!")
    print("Connect to "..ip)
    server = true
    local playerTab = {playerName = "server", playerX = 50, playerY = 50}
    myPlayer = playerTab
    table.insert(server_info["world_state"], playerTab)
end

function joinRoom(ip, finding_room)
    if server == false then
        if finding_room == false then
            print("Attempting to join the room...")
        end
        -- The port for all the servers is the same, so we only need the IP address of the server
        client:setpeername(ip, "14285")
        client:send("connection confirmation")
        current_time = os.time()
    else
        print("You are already hosting a server!")
    end    
end

function findRoom()
    if server == false then
        print("Attempting to find a room...")
        finding_room = true
        local ip, port = getLocalIP()
        local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
        local success, err = pcall(function()
            for z = 1, 255 do
                client:setpeername(ip1..z, "14285")
                client:send("requesting server info")
            end
            client:setpeername("*")
            client:setsockname("*", "14285")
        end)
        if success == false then
            -- The only error you could possibly get in this case is no internet connection, so we print it
            print("No internet connection!")
            return
        end
        current_time = os.time()
    end
end

function sendMessage(msg)
    if server == true then
        for i,c in pairs(server_info["clients"]) do
            client:sendto(tostring(msg), c.ip, c.port)
        end
    end
end

function leaveRoom()
    -- We need to notify the server that we are leaving, so we send a packet to them
    client:send("disconnection confirmation")
end

function resetParameters(params, args)
    parameter.clear()
    for i = 1, #params do
        parameter.action(params[i], args[i])
    end
end

function getLocalIP()
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    return ip, port
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                if server_info["players"] < 32 then
                    -- We can now add 1 more to our player count
                    server_info["players"] = server_info["players"] + 1
                    -- We now need to store the clients IP and port, so we can send them messages later
                    local uid = {ip = ip, port = port}
                    if not server_info["clients"][uid] then
                        table.insert(server_info["clients"], uid)
                    end
                    client:sendto("valid confirmation", ip, port)
                    -- We need to send the joining client our IP, so they can send messages to us later
                    local our_ip, our_port = getLocalIP()
                    client:sendto("server ip"..our_ip, ip, port)
                    -- We can now send everyone that has joined this room a packet telling them the amount of players in the room
                    for i,c in pairs(server_info["clients"]) do
                        client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
                    end
                    -- We also need to send the joining client a packet telling them to add our client state so they can see us too
                    client:sendto("create state"..json.encode(myPlayer), ip, port)
                else
                    -- We don't have enough room for another player, so we send the peer a message telling them to disconnect
                    local reason = "Max players inside the room!"
                    client:sendto("invalid confirmation"..reason, ip, port)
                end
            elseif msg == "requesting server info" then
                -- We need to find your address, so we create a new UDP socket and assign it a random IP
                local our_ip, our_port = getLocalIP()
                -- We now know that somebody is trying to find a server, so we send a packet back to them with our server IP and port
                local uid = {ip = our_ip}
                client:sendto("server info"..json.encode(uid), ip, port)
            elseif msg == "disconnection confirmation" then
                -- We know that one of the clients connected to this server wants to leave, so we send a packet back telling them that we have been notified
                client:sendto("notified of disconnection", ip, port)
                local uid = {ip = ip, port = port}
                local clientInfo
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            elseif msg and string.find(msg, "client state") then
                -- If we don't currently have the client state, we can simply add it into our world state
                local tab = json.decode(string.sub(msg, 13, #msg))
                if not server_info["world_state"][tab] then
                    table.insert(server_info["world_state"], tab)
                end
            elseif msg and string.find(msg, "state update") then
                -- We can now update the client state, and then send that state to everybody else
                local tab = json.decode(string.sub(msg, 13, #msg))
                local existingTab
                for i,t in pairs(server_info["world_state"]) do
                    if t.playerName == tab.playerName then
                        existingTab = t
                        -- We now edit the X and Y positions of the client state on server-side
                        existingTab.playerX = tab.playerX
                        existingTab.playerY = tab.playerY
                        local uid = {ip = ip, port = port}
                        for i,c in pairs(server_info["clients"]) do
                            client:sendto("client state"..json.encode(existingTab), c.ip, c.port)
                        end
                    end
                end
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result == "valid confirmation" then
                -- We know that we successfully joined the room, and we can now receive messages from the server!
                print("Successfully joined the room!")
                joinedRoom = true
                current_time = nil
                parameter.clear()
                -- We now create a button that allows the player to leave the room
                local paramName = {"Leave Room"}
                local paramArgs = {function() leaveRoom() end}
                resetParameters(paramName, paramArgs)
                -- We now create a player for ourselves and send it to the server to be added to it's world state
                local playerTab = {playerName = "client", playerX = 50, playerY = 50}
                myPlayer = playerTab
                table.insert(client_info["world_state"], playerTab)
                client:send("client state"..json.encode(playerTab), client_info["serverIp"], "14285")
            elseif result and string.find(result, "server ip") then
                -- Now that we have the servers IP address, we can message them later if we need to
                local serverIp = string.sub(result, 10, #result)
                client_info["serverIp"] = serverIp
            elseif result and string.find(result, "player count") then
                -- We now know the current amount of players in our room, for now at least
                local count = string.sub(result, 13, #result)
                print("Player count: "..count)
            elseif result and string.find(result, "invalid confirmation") then
                -- We can now see the reason for our invalid confirmation and display it
                local reason = string.sub(result, 21, #result)
                print("Could not join the room: "..reason)
                current_time = nil
                -- We can now also remove the address and port set for our peer
                client:setpeername("*")
            elseif result == "notified of disconnection" then
                -- We know that we can now safely leave the server
                client:setpeername("*")
                myPlayer = nil
                print("Successfully left the room!")
                local paramNames = {"Create Room", "Join Room", "Find Room"}
                local paramArgs = {createRoom, function()
                        if other_ip then
                            joinRoom(other_ip, false)
                        else
                            parameter.text("IP", "", function(t)
                                other_ip = t
                            end)
                        end
                    end, findRoom}
                resetParameters(paramNames, paramArgs)
            elseif result and string.find(result, "client state") then
                -- We can now accurately modify the client's world state, and update them if needed in realtime
                local tab = json.decode(string.sub(result, 13, #result))
                for i,t in pairs(client_info["world_state"]) do
                    if t.playerName == tab.playerName then
                        t.playerX = tab.playerX
                        t.playerY = tab.playerY
                    end
                end
            elseif result and string.find(result, "create state") then
                -- The server has instructed us to create a client state for them, so we do just that
                local playerTab = json.decode(string.sub(result, 13, #result))
                table.insert(client_info["world_state"], playerTab)
            elseif result == nil then
                if current_time then
                    if finding_room == false then
                        if os.time() > current_time + 5 then
                            -- If we get no response back for 5 seconds or more, we know it didn't work
                            print("Failed to connect, please try again later")
                            current_time = nil
                        end
                    end
                end
            end
        else
            local result, ip, port = client:receivefrom()
            if result and string.find(result, "server info") then
                -- Now we can attempt to join the room with the given IP address
                local tab = json.decode(string.sub(result, 12, #result))
                if finding_room == true then
                    joinRoom(tab.ip, true)
                    finding_room = false
                end
            else
                if finding_room == true then
                    if current_time and os.time() > current_time + 15 then
                        -- If we get no response back for 15 seconds or more, we know we couldn't find a room
                        print("Failed to find a room!")
                        current_time = nil
                    end
                end
            end
        end
    end 
end

function updatePlayers()
    if myPlayer then
        myPlayer.playerX = CurrentTouch.x
        myPlayer.playerY = CurrentTouch.y
        local playerTab = {playerName = myPlayer.playerName, playerX = myPlayer.playerX, playerY = myPlayer.playerY}
    end
    if joinedRoom == true then
        client:send("state update"..json.encode(myPlayer), client_info["serverIp"], "14285")
    end
    if server == true then
        for i,c in pairs(server_info["clients"]) do
            client:sendto("client state"..json.encode(myPlayer), c.ip, c.port)
        end
    end
    for i,t in pairs(client_info["world_state"]) do
        ellipse(t.playerX, t.playerY, 30)
    end
    for i,e in pairs(server_info["world_state"]) do
        ellipse(e.playerX, e.playerY, 30)
    end
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    
    -- This sets the line thickness
    strokeWidth(5)
    
    -- Do your drawing here
    receiveData()
    updatePlayers()
    tick = tick + 1
end

@Creator27 When I ran this with 2 iPads, there were eventually 2 circles on each iPad. Moving my finger on ipad 2, caused the circle on iPad 1 move, but there was a slight delay. When I moved my finger on iPad 1, the circle wouldn’t move on iPad 2 until I moved my finger on iPad 2, but the was a long delay before the circle from ipad 1 would move. When I tried with 3 iPads, things didn’t work very well. Two circles were constantly blinking and there were large delays with the movement. One of the circles wouldn’t move.