Local Multiplayer Game

@dave1707, I have programmed my code to have a UDP socket test how long it takes to get a message back from every server on the network and calculate all of the times into an average timeframe. So hopefully if this works, you now shouldn’t have to manually change the delay in the code to fit the iPad’s needs. If however the average is too small a time for your iPads to receive anything, you can also try multiplying the “send_average” variable by say 10, 100 or 1000. 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)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        packetTest()
    end)
    client = socket.udp()
    client:settimeout(0)
    -- We need a spare UDP socket to use for when we need to determine how long it takes to get a message back from every server on the network
    listener = socket.udp()
    listener:settimeout(0)
    listen_time = nil
    timeframes = {}
    -- Our send average variable will store the average amount of time it takes to get a message back from every server on your router
    send_average = 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)
    if server == false then
        print("Attempting to join the room...")
        -- The port for all the servers is the same, so we only need the IP address of the server
        player:setpeername(ip, "14285")
        player:send("connection confirmation")
        current_time = os.time()
    else
        print("You are already hosting a server!")
    end
end

function packetTest()
    if server == false then
        -- We need to send a broadcast message to every server on the network, but first we need to bind our socket to every address
        listener:setsockname("*", "14285")
        listener:setoption("broadcast", true)
        -- Now we can send a packet to every server using the broadcast address "255.255.255.255"
        listener:sendto("packet test", "255.255.255.255", "14285")
        listen_time = tick
    else
        print("You are already hosting a server!")
    end
end

function findRoom(count)
    if count == 1 then
        print("Attempting to find a room...")
        print("This can take anywhere from 5-50secs")
    end
    finding_room = true
    -- First, we need to find your IP
    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+)")
    client:setpeername(ip1..count, "14285")
    client:send("connection confirmation")
    current_time = tick
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 == "packet test" then
                client:sendto("packet test completed", ip, port)
            end
        end
    else
        local result = client:receive()
        if result == "valid confirmation" then
            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("*")
            -- If there is still another room available on the network and we haven't looped through to 255 yet, we can still continue the search
            current_room_count = current_room_count + 1
            if current_room_count < 256 then
                findRoom(current_room_count)
            end
        elseif result == nil then
            if current_time ~= nil 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
                else
                    -- If the time limit has passed, we can check the next address for a room
                    if tick > current_time + send_average then
                        current_room_count = current_room_count + 1
                        if current_room_count < 256 then
                            findRoom(current_room_count)
                        end
                    end
                end
            end
        end
        local data, ip, port = listener:receivefrom()
        if data == "packet test completed" then
            local result = (tick - listen_time)
            table.insert(timeframes, result)
            listen_time = tick
        else
            if listen_time and tick > listen_time + 15 then
                -- We can now assume that every server has sent the same message back to our listener, and now we can calculate an average
                local average = 0
                for i,t in pairs(timeframes) do
                    average = average + t
                end
                average = average / #timeframes
                send_average = average
                print("Average receive time: "..send_average)
                if finding_room == false then
                    findRoom(current_room_count)
                end
                listen_time = nil
            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.0
end

@Creator27 Your #timeframes is coming up 0 so the sendaverage is nan (not a number). Division by zero.

@Creator27 I had more time so I tried playing with this more and sometimes I got a value other than 0, but most of the time it was 0. I got values ranging from 4 to 17, but mostly around 5. Sometimes it would find a room at 5 and sometimes it wouldn’t. It seems the higher the value the more it was successful.

@dave1707, I think instead of trying to find the delay through sending packets, I should just set it to 20 or 25. Do you have more than 3 iPads to test with just to make sure that 25 is a good enough delay?

@Creator27 I can round up 3 iPads, but I could always run it on my iPhone too.

@dave1707, that’s fine, as long as you can test with it I’m happy.

@dave1707, have you finished testing with the iPads and phone yet? :slight_smile:

@Creator27 I thought you were going to make a new version that had a fixed value.

I created a program where I did some message testing. I would send 100 messages in a “for loop” to the router and read them back in a “repeat until” loop. It would read the 100 messages coming back in .0025 seconds. I called a new function from the draw function that did the repeat until calling a receiveMessage function. As long as it was receiving a message, it stayed in the repeat loop until it was done. That didn’t require any timing values and it always got all the messages even when I upped the send count from 100 to 1000.

@dave1707, that makes perfect sense! I’ll attempt to implement that into my project, so hopefully you won’t have to set the delay manually!

@dave1707, I tried but I couldn’t get it working. I just set the delay to 30 instead.

@dave1707, I have entirely rewritten the findRoom code. Instead of looping through every address on your router and checking each room along with trying to set a delay time for a response back, I have created a spare UDP socket that will broadcast a message to every server on the network, asking for the IP for their server. As soon as we get a reply back from any server, we call the joinRoom function with the IP that the server sent us. I also wrote the code for it to specifically stop listening for any messages if we haven’t got a reply from any server and 5 seconds has already passed. With this way of finding a room, not only is it a lot quicker but you don’t have to worry about setting a delay either!

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip then
            joinRoom(other_ip)
        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)
    if server == false then
        print("Attempting to join the room...")
        -- 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
        -- 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)
        listener:sendto("requesting server info", "255.255.255.255", "14285")
        -- Turn off broadcast messaging after
        listener:setoption("broadcast", false)
        current_time = os.time()
    else
        print("You are already hosting a server!")
    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
        local result = client:receive()
        if result == "valid confirmation" then
            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 == true 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
        local data, ip, port = listener:receivefrom()
        if data and string.find(data, "server info") then
            -- Now we can attempt to join the room with the given IP address
            local tab = json.decode(string.sub(data, 12, #data))
            if finding_room == true then
                joinRoom(ip)
                finding_room = false
            end
        else
            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

-- 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 get “Failed to find a room.” when I do FindRoom. I’ll look at it more later when I have more time to see what’s happening.

@dave1707, try getting rid of this chunk of code:

else
     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

Try the code again and see if anything is different.

@Creator27 Removed those lines, pressed FindRoom and it just printed

Attempting to join the room…

and nothing else after a long wait.

I put a print statement to see what result is

    local result = client:receive()
    print("result= ",result)

and it printed nil.

So result isn’t receiving anything.

@dave1707, that’s very odd. I’ll have a look into it later.

@dave1707, that’s really weird. I tested it on my iPad and it works just fine. Did you follow these instructions?

  1. For the first iPad, click “Create Room” and do nothing else.
  2. If you want to join a room, click “Join Room” on the second iPad.
  3. If you want to automatically find a room instead, click “Find Room” on the second iPad.

@Creator27 I start ipad1, then iPad2. I press CreateRoom on ipad1. It prints Room created and Connect to (an ip address…). I tap FindRoom on iPad2. It prints Attempting to find a room… then
Attempting to join the room… . After that it print nil for result.

@dave1707, try changing this line of code located in “listener:receivefrom()”:

joinRoom(ip)

And change it to:

joinRoom(tab.ip)

Main:142: attempt to index a nil value (local ‘tab’)
stack traceback:
Main:142: in function ‘receiveData’
Main:164: in function ‘draw’

@dave1707, try deleting the whole listen:receivefrom() chunk and replace it with this:

local data, ip, port = listener:receivefrom()
    if data and string.find(data, "server info") then
        -- Now we can attempt to join the room with the given IP address
        local tab = json.decode(string.sub(data, 12, #data))
        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