Local Multiplayer Game

@dave1707, I did think of another problem that could be causing this problem. Because all of the code is single-threaded, everything has to run in order so latency occurs. I’m going to try my best to make it multi-threaded instead.

@Creator27 I was playing around with the code I showed you around #41 of this discussion. I was running it on 2 iPad just to see what would happen. When I was moving my finger on 1 iPad with a circle moving on the other iPad, I noticed there was a slight delay. Also, sliding my finger up the screen fast, the circle would pause about halfway. I think the whole problem is the messages on the router. I don’t think my router can handle a lot of messages being sent when I’m moving my finger up the screen fast. Trying to do this with multiple iPads sending messages probably just causes more of a delay.

@dave1707, I’ve update the code again. I’ve also allowed the server to send player updates back to everyone connected so everyone is properly synced. 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)
    -- 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 = {}, puppet_states = {}, ids_taken = {}}
    -- 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
    myPlayerServer = nil
    myPlayerClient = nil
    sendNumber = 0
    sendInterval = 0
    playerTick = 0
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)
    client:setoption("dontroute", 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 ip ~= "*" 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("Address not allowed!")
        end
    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)
        print(success, err)
        if success == false then
            -- An error occured whether that be no internet connection or message wasn't received
            print("An error occured! Please check your internet connection, and try again later.")
            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()
    -- 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
                    -- We now create a random ID for the connecting peer so we can differentiate between players on the server
                    local playerID = math.random(10000, 99999)
                    if not server_info["ids_taken"][playerID] then
                        server_info["ids_taken"][playerID] = playerID
                    else
                        playerID = math.random(10000, 99999)
                    end
                    client:sendto("valid confirmation"..playerID, ip, port)
                    client:sendto("peer address info"..json.encode(uid), 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 and string.find(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
                local clientState
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                for i = 1, #server_info["world_state"] do
                    local state = server_info["world_state"][i]
                    if state["addrInfo"].ip == uid.ip and state["addrInfo"].port == uid.port then
                        clientState = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                table.remove(server_info["world_state"], clientState)
                print("server: "..json.encode(server_info["world_state"]))
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            elseif msg and string.find(msg, "client state") then
                -- A connected peer has sent their client state to us, so we update our world state accordingly
                local clientState = json.decode(string.sub(msg, 13, #msg))
                local hasClientState = false
                local existingState = nil
                for i,s in pairs(server_info["world_state"]) do
                    if s.playerName == clientState.playerName then
                        hasClientState = true
                        existingState = s
                        break
                    end
                end
                if hasClientState == true then
                    local uid = {ip = ip, port = port}
                    if clientState.sendNumber > existingState.sendNumber then
                        existingState.sendNumber = clientState.sendNumber
                        -- Previous state position
                        existingState.prevX = existingState.playerX
                        existingState.prevY = existingState.playerY
                        -- New state position
                        existingState.playerX = clientState.playerX
                        existingState.playerY = clientState.playerY
                        -- We now send every connected peer the new state position
                        if #server_info["world_state"] > 0 then
                            for i,c in pairs(server_info["world_state"]) do
                                if c["addrInfo"].ip == uid.ip and c["addrInfo"].port == uid.port then
                                    client:sendto("update client side"..json.encode(existingState), c["addrInfo"].ip, c["addrInfo"].port)
                                end
                            end
                        end
                    end
                else
                    clientState["prevX"] = clientState.playerX
                    clientState["prevY"] = clientState.playerY
                    if not server_info["world_state"][clientState] then
                        table.insert(server_info["world_state"], clientState)
                    end
                end
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result and string.find(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 set our player name to the ID that the server sent us
                local playerID = string.sub(result, 19, #result)
                local playerTab = {playerName = playerID, playerX = WIDTH/2, playerY = HEIGHT/2}
                myPlayerClient = playerTab
                if not client_info["world_state"][myPlayerClient] then
                    table.insert(client_info["world_state"], myPlayerClient)
                end
                client:setoption("dontroute", true)
            elseif result and string.find(result, "peer address info") then
                local addrInfo = json.decode(string.sub(result, 18, #result))
                if myPlayerClient then
                    myPlayerClient["addrInfo"] = addrInfo
                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("*")
                local removingState
                for i,s in pairs(client_info["world_state"]) do
                    if s["addrInfo"].ip == myPlayerClient.addrInfo.ip and s["addrInfo"].port == myPlayerClient.addrInfo.port then
                        removingState = i
                    end
                end
                table.remove(client_info["world_state"], removingState)
                print("client: "..json.encode(client_info["world_state"]))
                joinedRoom = false
                other_ip = 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, "update client side") then
                -- The server has sent us a update packet, so we fetch the state and update our world state
                local state = json.decode(string.sub(result, 19, #result))
                local oldState = nil
                local foundState = false
                for i,s in pairs(client_info["world_state"]) do
                    if s.playerName == state.playerName then
                        oldState = s
                        foundState = true
                        break
                    end
                end
                if foundState == true then
                    oldState.playerX = state.playerX
                    oldState.playerY = state.playerY
                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 touched(touch)
    if touch.state ~= ENDED and touch.state ~= CANCELLED then
        if touch.state == BEGAN or touch.state == CHANGED then
            if joinedRoom == true then
                if myPlayerClient then
                    myPlayerClient.playerX = CurrentTouch.x
                    myPlayerClient.playerY = CurrentTouch.y
                    sendNumber = sendNumber + 1
                end
            end
        end
    end
end

function updatePlayers()
    -- We now update the position of our player puppets, so it looks like the players are being updated in realtime
    if server == false then
        if joinedRoom == true then
            for i,cp in pairs(client_info["world_state"]) do
                fill(0, 255, 0)
                text("client side", cp.playerX, cp.playerY + 30)
                ellipse(cp.playerX, cp.playerY, 35)
            end
        end
    else
        for i,sp in pairs(server_info["world_state"]) do
            fill(255, 13, 0)
            text("server side", sp.playerX, sp.playerY + 30)
            ellipse(sp.playerX, sp.playerY, 35)
        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
    updatePlayers()
    receiveData()
    if myPlayerClient and joinedRoom == true and myPlayerClient.addrInfo ~= nil then
        local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, sendNumber = sendNumber, addrInfo = myPlayerClient.addrInfo}
        sendInterval = sendInterval + 1
        if sendInterval >= 3 then
            client:send("client state"..json.encode(playerTab))
            sendInterval = 0
        end
    end
end

@Creator27 An error occured! Please check your internet connection, and try again later.

I get the above error on the second iPad when I try to join the room.

@dave1707, I’ve added a print statement so you can see the error that occurs.

@Creator27

success= false
err= Main:77: attempt to index a nil value (global ‘player’)

Here’s what the errors are.

@dave1707, try the code now.

@Creator27 Tried with 3 iPads. On iPad 1, 2 circles showed up. As I moved my finger on iPad 2 & 3, the 2 circles moved on iPad 1. There wasn’t much of a delay as I moved my fingers, but the circles were very jerky as they moved to keep up. So they were skipping some of the positions in order to keep up with my finger positions on iPad 2 & 3.

@dave1707, yes that was the general idea I was going for. Were the circles being updated on iPad 2 & 3?

@dave1707, I’m trying a new method for interpolating between positions so the players on the server don’t look jerky. 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)
    -- 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 = {}, puppet_states = {}, ids_taken = {}}
    -- 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
    myPlayerServer = nil
    myPlayerClient = nil
    sendNumber = 0
    sendInterval = 0
    playerTick = 0
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)
    client:setoption("dontroute", 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 ip ~= "*" 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("Address not allowed!")
        end
    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)
        print(success, err)
        if success == false then
            -- An error occured whether that be no internet connection or message wasn't received
            print("An error occured! Please check your internet connection, and try again later.")
            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()
    -- 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
                    -- We now create a random ID for the connecting peer so we can differentiate between players on the server
                    local playerID = math.random(10000, 99999)
                    if not server_info["ids_taken"][playerID] then
                        server_info["ids_taken"][playerID] = playerID
                    else
                        playerID = math.random(10000, 99999)
                    end
                    client:sendto("valid confirmation"..playerID, ip, port)
                    client:sendto("peer address info"..json.encode(uid), 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 and string.find(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
                local clientState
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                for i = 1, #server_info["world_state"] do
                    local state = server_info["world_state"][i]
                    if state["addrInfo"].ip == uid.ip and state["addrInfo"].port == uid.port then
                        clientState = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                table.remove(server_info["world_state"], clientState)
                print("server: "..json.encode(server_info["world_state"]))
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            elseif msg and string.find(msg, "client state") then
                -- A connected peer has sent their client state to us, so we update our world state accordingly
                local clientState = json.decode(string.sub(msg, 13, #msg))
                local hasClientState = false
                local existingState = nil
                for i,s in pairs(server_info["world_state"]) do
                    if s.playerName == clientState.playerName then
                        hasClientState = true
                        existingState = s
                        break
                    end
                end
                if hasClientState == true then
                    local uid = {ip = ip, port = port}
                    if clientState.sendNumber > existingState.sendNumber then
                        existingState.sendNumber = clientState.sendNumber
                        -- Previous state position
                        existingState.prevX = existingState.playerX
                        existingState.prevY = existingState.playerY
                        -- We now also add a new waypoint for our puppet peer to follow
                        local waypoint = {x = clientState.playerX, y = clientState.playerY}
                        table.insert(existingState["waypoints"], waypoint)
                        -- We now interpolate to the new state position
                        if existingState["finishedWaypoint"] == true then
                            updatePositions(existingState)
                            existingState["finishedWaypoint"] = false
                        end
                        -- We now send every connected peer the new state position
                        if #server_info["world_state"] > 0 then
                            for i,c in pairs(server_info["world_state"]) do
                                if c["addrInfo"].ip == uid.ip and c["addrInfo"].port == uid.port then
                                    client:sendto("update client side"..json.encode(existingState), c["addrInfo"].ip, c["addrInfo"].port)
                                end
                            end
                        end
                    end
                else
                    clientState["prevX"] = clientState.playerX
                    clientState["prevY"] = clientState.playerY
                    clientState["waypoints"] = {{x = clientState.playerX, y = clientState.playerY}}
                    if not server_info["world_state"][clientState] then
                        table.insert(server_info["world_state"], clientState)
                    end
                end
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result and string.find(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 set our player name to the ID that the server sent us
                local playerID = string.sub(result, 19, #result)
                local playerTab = {playerName = playerID, playerX = WIDTH/2, playerY = HEIGHT/2}
                myPlayerClient = playerTab
                if not client_info["world_state"][myPlayerClient] then
                    table.insert(client_info["world_state"], myPlayerClient)
                end
                client:setoption("dontroute", true)
            elseif result and string.find(result, "peer address info") then
                local addrInfo = json.decode(string.sub(result, 18, #result))
                if myPlayerClient then
                    myPlayerClient["addrInfo"] = addrInfo
                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("*")
                local removingState
                for i,s in pairs(client_info["world_state"]) do
                    if s["addrInfo"].ip == myPlayerClient.addrInfo.ip and s["addrInfo"].port == myPlayerClient.addrInfo.port then
                        removingState = i
                    end
                end
                table.remove(client_info["world_state"], removingState)
                print("client: "..json.encode(client_info["world_state"]))
                joinedRoom = false
                other_ip = 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, "update client side") then
                -- The server has sent us a update packet, so we fetch the state and update our world state
                local state = json.decode(string.sub(result, 19, #result))
                local oldState = nil
                local foundState = false
                for i,s in pairs(client_info["world_state"]) do
                    if s.playerName == state.playerName then
                        oldState = s
                        foundState = true
                        break
                    end
                end
                if foundState == true then
                    oldState.playerX = state.playerX
                    oldState.playerY = state.playerY
                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 updatePositions(state)
    local waypoints = state["waypoints"]
    if #waypoints > 0 then
        local interpolation = tween(0.3, state, {playerX = waypoints[1].x, playerY = waypoints[1].y}, tween.easing.linear, function()
            table.remove(waypoints, 1)
            print("next waypoint")
            state["finishedWaypoint"] = true
        end)
    else
        return false
    end
end

function touched(touch)
    if touch.state ~= ENDED and touch.state ~= CANCELLED then
        if touch.state == BEGAN or touch.state == CHANGED then
            if joinedRoom == true then
                if myPlayerClient then
                    myPlayerClient.playerX = CurrentTouch.x
                    myPlayerClient.playerY = CurrentTouch.y
                    sendNumber = sendNumber + 1
                end
            end
        end
    end
end

function updatePlayers()
    -- We now update the position of our player puppets, so it looks like the players are being updated in realtime
    if server == false then
        if joinedRoom == true then
            for i,cp in pairs(client_info["world_state"]) do
                fill(0, 255, 0)
                text("client side", cp.playerX, cp.playerY + 30)
                ellipse(cp.playerX, cp.playerY, 35)
            end
        end
    else
        for i,sp in pairs(server_info["world_state"]) do
            fill(255, 13, 0)
            text("server side", sp.playerX, sp.playerY + 30)
            ellipse(sp.playerX, sp.playerY, 35)
        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
    updatePlayers()
    receiveData()
    if myPlayerClient and joinedRoom == true and myPlayerClient.addrInfo ~= nil then
        local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, sendNumber = sendNumber, addrInfo = myPlayerClient.addrInfo, finishedWaypoint = false}
        sendInterval = sendInterval + 1
        if sendInterval >= 3 then
            client:send("client state"..json.encode(playerTab))
            sendInterval = 0
        end
    end
end

@Creator27 When I move my finger on iPad2, the circle doesn’t move on ipad1.

@dave1707, I have successfully managed to interpolate between two positions even with less packets being sent to the server. Here’s the new 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)
    -- 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 = {}, puppet_states = {}, ids_taken = {}}
    -- 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
    myPlayerServer = nil
    myPlayerClient = nil
    sendNumber = 0
    sendInterval = 0
    playerTick = 0
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)
    client:setoption("dontroute", 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 ip ~= "*" 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("Address not allowed!")
        end
    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)
        print(success, err)
        if success == false then
            -- An error occured whether that be no internet connection or message wasn't received
            print("An error occured! Please check your internet connection, and try again later.")
            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()
    -- 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
                    -- We now create a random ID for the connecting peer so we can differentiate between players on the server
                    local playerID = math.random(10000, 99999)
                    if not server_info["ids_taken"][playerID] then
                        server_info["ids_taken"][playerID] = playerID
                    else
                        playerID = math.random(10000, 99999)
                    end
                    client:sendto("valid confirmation"..playerID, ip, port)
                    client:sendto("peer address info"..json.encode(uid), 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 and string.find(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
                local clientState
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                for i = 1, #server_info["world_state"] do
                    local state = server_info["world_state"][i]
                    if state["addrInfo"].ip == uid.ip and state["addrInfo"].port == uid.port then
                        clientState = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                table.remove(server_info["world_state"], clientState)
                print("server: "..json.encode(server_info["world_state"]))
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            elseif msg and string.find(msg, "client state") then
                -- A connected peer has sent their client state to us, so we update our world state accordingly
                local clientState = json.decode(string.sub(msg, 13, #msg))
                local hasClientState = false
                local existingState = nil
                for i,s in pairs(server_info["world_state"]) do
                    if s.playerName == clientState.playerName then
                        hasClientState = true
                        existingState = s
                        break
                    end
                end
                if hasClientState == true then
                    local uid = {ip = ip, port = port}
                    if clientState.sendNumber > existingState.sendNumber then
                        existingState.sendNumber = clientState.sendNumber
                        -- Previous state position
                        existingState.prevX = existingState.playerX
                        existingState.prevY = existingState.playerY
                        -- We now also add a new waypoint for our puppet peer to follow
                        local waypoint = {x = clientState.playerX, y = clientState.playerY}
                        -- We now interpolate to the new state position
                        updatePositions(existingState, waypoint)
                        -- We now send every connected peer the new state position
                        if #server_info["world_state"] > 0 then
                            for i,c in pairs(server_info["world_state"]) do
                                if c["addrInfo"].ip ~= uid.ip and c["addrInfo"].port ~= uid.port then
                                    client:sendto("update client side"..json.encode(existingState), c["addrInfo"].ip, c["addrInfo"].port)
                                end
                            end
                        end
                    end
                else
                    clientState["prevX"] = clientState.playerX
                    clientState["prevY"] = clientState.playerY
                    if not server_info["world_state"][clientState] then
                        table.insert(server_info["world_state"], clientState)
                    end
                end
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result and string.find(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 set our player name to the ID that the server sent us
                local playerID = string.sub(result, 19, #result)
                local playerTab = {playerName = playerID, playerX = WIDTH/2, playerY = HEIGHT/2}
                myPlayerClient = playerTab
                if not client_info["world_state"][myPlayerClient] then
                    table.insert(client_info["world_state"], myPlayerClient)
                end
                client:setoption("dontroute", true)
            elseif result and string.find(result, "peer address info") then
                local addrInfo = json.decode(string.sub(result, 18, #result))
                if myPlayerClient then
                    myPlayerClient["addrInfo"] = addrInfo
                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("*")
                local removingState
                for i,s in pairs(client_info["world_state"]) do
                    if s["addrInfo"].ip == myPlayerClient.addrInfo.ip and s["addrInfo"].port == myPlayerClient.addrInfo.port then
                        removingState = i
                    end
                end
                table.remove(client_info["world_state"], removingState)
                print("client: "..json.encode(client_info["world_state"]))
                joinedRoom = false
                other_ip = 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, "update client side") then
                -- The server has sent us a update packet, so we fetch the state and update our world state
                local state = json.decode(string.sub(result, 19, #result))
                local oldState = nil
                local foundState = false
                for i,s in pairs(client_info["world_state"]) do
                    if s.playerName == state.playerName then
                        oldState = s
                        foundState = true
                        break
                    end
                end
                if foundState == true then
                    oldState.playerX = state.playerX
                    oldState.playerY = state.playerY
                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 updatePositions(state, pos)
    if state["waypointId"] then
        tween.stop(state["waypointId"])
    end
    local interpolation = tween(0.075, state, {playerX = pos.x, playerY = pos.y})
    state["waypointId"] = interpolation
end

function touched(touch)
    if touch.state ~= ENDED and touch.state ~= CANCELLED then
        if touch.state == BEGAN or touch.state == CHANGED then
            if joinedRoom == true then
                if myPlayerClient then
                    myPlayerClient.playerX = CurrentTouch.x
                    myPlayerClient.playerY = CurrentTouch.y
                    sendNumber = sendNumber + 1
                end
            end
        end
    end
end

function updatePlayers()
    -- We now update the position of our player puppets, so it looks like the players are being updated in realtime
    if server == false then
        if joinedRoom == true then
            for i,cp in pairs(client_info["world_state"]) do
                fill(0, 255, 0)
                text("client side", cp.playerX, cp.playerY + 30)
                ellipse(cp.playerX, cp.playerY, 35)
            end
        end
    else
        for i,sp in pairs(server_info["world_state"]) do
            fill(255, 13, 0)
            text("server side", sp.playerX, sp.playerY + 30)
            ellipse(sp.playerX, sp.playerY, 35)
        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
    updatePlayers()
    receiveData()
    if myPlayerClient and joinedRoom == true and myPlayerClient.addrInfo ~= nil then
        local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, sendNumber = sendNumber, addrInfo = myPlayerClient.addrInfo, waypointId = nil}
        sendInterval = sendInterval + 1
        if sendInterval >= 3 then
            client:send("client state"..json.encode(playerTab))
            sendInterval = 0
        end
    end
end

@Creator27 Got this error when I tried to move my finger on iPad2.

Main:212: type ‘function’ is not supported by JSON.
stack traceback:
[C]: in function ‘error’
…DB2/Codea.app/Frameworks/RuntimeKit.framework/dkjson.lua:365: in function ‘dkjson.encode’
Main:212: in function ‘receiveData’
Main:392: in function ‘draw’

@dave1707, try it now.

@Creator27 The jerkyness is worse. It pauses then moves, pauses, moves.

@dave1707, I fixed something else. Try it now.

@Creator27 Same thing. Moves, stops, moves, stops.

@dave1707, I really don’t know if this will change anything, but I removed the code that involves the server sending the client position back to everyone else. Here’s that 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)
    -- 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 = {}, puppet_states = {}, ids_taken = {}}
    -- 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
    myPlayerServer = nil
    myPlayerClient = nil
    sendNumber = 0
    sendInterval = 0
    playerTick = 0
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)
    client:setoption("dontroute", 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 ip ~= "*" 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("Address not allowed!")
        end
    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
            -- An error occured whether that be no internet connection or message wasn't received
            print("An error occured! Please check your internet connection, and try again later.")
            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()
    -- We need to notify the server that we are leaving, so we send a packet to them
    player: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
                    -- We now create a random ID for the connecting peer so we can differentiate between players on the server
                    local playerID = math.random(10000, 99999)
                    if not server_info["ids_taken"][playerID] then
                        server_info["ids_taken"][playerID] = playerID
                    else
                        playerID = math.random(10000, 99999)
                    end
                    client:sendto("valid confirmation"..playerID, ip, port)
                    client:sendto("peer address info"..json.encode(uid), 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 and string.find(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
                local clientState
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                for i = 1, #server_info["world_state"] do
                    local state = server_info["world_state"][i]
                    if state["addrInfo"].ip == uid.ip and state["addrInfo"].port == uid.port then
                        clientState = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                table.remove(server_info["world_state"], clientState)
                print("server: "..json.encode(server_info["world_state"]))
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            elseif msg and string.find(msg, "client state") then
                -- A connected peer has sent their client state to us, so we update our world state accordingly
                local clientState = json.decode(string.sub(msg, 13, #msg))
                local hasClientState = false
                local existingState = nil
                for i,s in pairs(server_info["world_state"]) do
                    if s.playerName == clientState.playerName then
                        hasClientState = true
                        existingState = s
                        break
                    end
                end
                if hasClientState == true then
                    local uid = {ip = ip, port = port}
                    if clientState.sendNumber > existingState.sendNumber then
                        existingState.sendNumber = clientState.sendNumber
                        -- We now also add a new waypoint for our puppet peer to follow
                        local waypoint = {x = clientState.playerX, y = clientState.playerY}
                        -- We now interpolate to the new state position
                        updatePositions(existingState, waypoint)
                    end
                else
                    if not server_info["world_state"][clientState] then
                        table.insert(server_info["world_state"], clientState)
                    end
                end
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result and string.find(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 set our player name to the ID that the server sent us
                local playerID = string.sub(result, 19, #result)
                local playerTab = {playerName = playerID, playerX = WIDTH/2, playerY = HEIGHT/2}
                myPlayerClient = playerTab
                if not client_info["world_state"][myPlayerClient] then
                    table.insert(client_info["world_state"], myPlayerClient)
                end
                client:setoption("dontroute", true)
            elseif result and string.find(result, "peer address info") then
                local addrInfo = json.decode(string.sub(result, 18, #result))
                if myPlayerClient then
                    myPlayerClient["addrInfo"] = addrInfo
                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("*")
                local removingState
                for i,s in pairs(client_info["world_state"]) do
                    if s["addrInfo"].ip == myPlayerClient.addrInfo.ip and s["addrInfo"].port == myPlayerClient.addrInfo.port then
                        removingState = i
                    end
                end
                table.remove(client_info["world_state"], removingState)
                print("client: "..json.encode(client_info["world_state"]))
                joinedRoom = false
                other_ip = 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, "update client side") then
                -- The server has sent us a update packet, so we fetch the state and update our world state
                local state = json.decode(string.sub(result, 19, #result))
                local oldState = nil
                local foundState = false
                for i,s in pairs(client_info["world_state"]) do
                    if s.playerName == state.playerName then
                        oldState = s
                        foundState = true
                        break
                    end
                end
                if foundState == true then
                    oldState.playerX = state.playerX
                    oldState.playerY = state.playerY
                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 updatePositions(state, pos)
    if state["waypointId"] then
        tween.stop(state["waypointId"])
    end
    local interpolation = tween(0.075, state, {playerX = pos.x, playerY = pos.y})
    state["waypointId"] = interpolation
end

function touched(touch)
    if touch.state ~= ENDED and touch.state ~= CANCELLED then
        if touch.state == BEGAN or touch.state == CHANGED then
            if joinedRoom == true then
                if myPlayerClient then
                    myPlayerClient.playerX = CurrentTouch.x
                    myPlayerClient.playerY = CurrentTouch.y
                    sendNumber = sendNumber + 1
                end
            end
        end
    end
end

function updatePlayers()
    -- We now update the position of our player puppets, so it looks like the players are being updated in realtime
    if server == false then
        if joinedRoom == true then
            for i,cp in pairs(client_info["world_state"]) do
                fill(0, 255, 0)
                text("client side", cp.playerX, cp.playerY + 30)
                ellipse(cp.playerX, cp.playerY, 35)
            end
        end
    else
        for i,sp in pairs(server_info["world_state"]) do
            fill(255, 13, 0)
            text("server side", sp.playerX, sp.playerY + 30)
            ellipse(sp.playerX, sp.playerY, 35)
        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
    updatePlayers()
    receiveData()
    if myPlayerClient and joinedRoom == true and myPlayerClient.addrInfo ~= nil then
        local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, sendNumber = sendNumber, addrInfo = myPlayerClient.addrInfo}
        sendInterval = sendInterval + 1
        if sendInterval >= 3 then
            client:send("client state"..json.encode(playerTab))
            sendInterval = 0
        end
    end
end

@Creator27 Got this error.

An error occured! Please check your internet connection, and try again later.

@dave1707, try it now.