Local Multiplayer Game

@Creator27 I managed to get this message.

You are already hosting a server, so you can’t join another!

@dave1707, are you using two iPads to test this project or just one??

@Creator27 I’m running this on 2 iPads. I don’t know what I’m really supposed to be doing, so I try different things to see what happens.

@dave1707, I don’t know if this is what you have been doing, but these are the steps to test the project:

  1. On the first iPad, click “Create Room” so you get the print statement that shows what IP and port to connect to.
  2. On the second iPad, click “Join Room” and fill in the IP and port in their respective boxes.
  3. Click “Join Room” again, and if everything works correctly you should see “Successfully connected to the room” on the second iPad.
  4. If you wanted to find a random room instead, follow step one and then click “Find Room” on the second iPad. If everything works, you should see a message saying “Attempting to find a room” and “Successfully connected to the room” after a few seconds.

@Creator27 I followed your instructions and on the second iPad I got “Attempting to join the room…” and “ Successfully joined the room!”. This was keying in the ip and port. If I did just the find room on the second iPad I got “Attempting to find a room…” and nothing else.

@dave1707, when you just pressed “Join Room” on the second iPad and nothing happened, did you first press “Create Room” on the first iPad or did you literally do nothing on the first iPad and just press “Join Room” on the second iPad??

@Creator27 Give me the exact steps you want me to do.

For example: ipad1 do this, then this. iPad 2 do this. iPad 1 do this. iPad2 do this.

It’s hard to test someone else’s code when you don’t know what should be done. Giving me the exact order will help, not only me, but you too.

I pressed Create Room on the first iPad
, keyed in the ip and port, then pressed Join Room on the second iPad. That said Successfully joined. I started over, Create Room on the first iPad and Find Room on the second iPad. It didn’t join anything.

@dave1707, here is the exact order of what you should do:

  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.

Also I think I know why the “Find Room” doesn’t work. On the second iPad, scroll down in the code where it says “if tick > current_time + 0.001 then”, and change 0.001 to something like 0.5. If that doesn’t work, I have another method that could work.

@Creator27 The Join Room seems to work when the ip and port numbers are keyed in. The Find Room doesn’t work because you’re looping thru the code too fast. You’re not waiting long enough for a response. The if tick>current_time doesn’t seem to work at slowing the call. It’s calling the findRoom(…) function too fast. Sometimes it works, but most of the time it doesn’t. Also, when it doesn’t work the current_room_count just keeps incrementing beyond 256. You stop calling findRoom(…), but you keep incrementing.

When I changed the code to

                if tick > current_time + 15 then

it found the room every time that I did FindRoom. You should set the current_room_count to 0 when the FindRoom button is pressed so anytime it’s pressed it always starts at the beginning.

I don’t know if my router is just slow, but I know that when I send a message to it, it’s slow to reply back. If I send too many messages too fast, then responses get missed. That’s why I had to set the value to 15 in the if statement for it to work every time.

@dave1707, just so you don’t have to manually change the amount of seconds to fit your router’s internet speed, I will program a spare UDP socket that sends something to the server and check how long it took to receive a message back. I can then put the result into a variable and set “if tick > current_time + ??” to the amount of seconds it took to get something back.

@dave1707, It’s okay actually I think that 15 will be just fine. I thought for a second that the tick variable was in seconds, but I remembered I told it to plus 1 every frame, so 15 is very good.

@dave1707, I now know that sending messages to other devices from my project works like a charm, but last night I did integrate multicasting into my project just in case it didn’t work. It still functions the same, but I just wanted to see if it works too. Here’s the code:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip and other_port then
            joinRoom(other_ip, other_port)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
            parameter.text("Port", "", function(t)
                other_port = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        findRoom(current_room_count)
    end)
    client = socket.udp()
    client:settimeout(0)
    -- We make a new UDP socket here, so we can use it to receive data from another device
    listener = socket.udp()
    listener:settimeout(0)
    current_time = nil
    current_room_count = 0
    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.." : "..port2)
    server = true
end

function joinRoom(ip, port)
    if server == false then
        print("Attempting to join the room...")
        client:setpeername(ip, port)
        -- We need to use the listener UDP socket we made before to act as a listener for multicast messages
        listener:setsockname("224.0.0.0", "11111")
        local sock = listener:getsockname()
        if sock then
            listener:setoption("ip-add-membership", {multiaddr = "224.0.0.0", interface = ip})
        end
        client:send("connection confirmation")
        current_time = os.time()
    else
        print("You are already hosting a server!")
    end
end

function findRoom(count)
    if server == false then
        if count == 0 then
            print("Attempting to find a room...")
            print("This can take a while, so please be patient!")
        end
        finding_room = true
        -- We need to first 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+)")
        -- In order to receive a message from another device on the network, we need to enable multicasting
        -- We now need to use the spare UDP socket to make it listen to multicast messages
        listener:setsockname("224.0.0.0", "11111")
        local sock = listener:getsockname()
        -- If the device supports multicast, we can add the socket to the multicast group
        if sock then
            listener:setoption("ip-add-membership", {multiaddr = "224.0.0.0", interface = ip})
        end
        client:setpeername(ip1..count, "14285")
        client:send("connection confirmation")
        current_time = tick
    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
                local result, err = client:sendto("valid confirmation", "224.0.0.0", "11111")
            end
        end
    else
        local result = client:receive()
        if result == "valid confirmation" then
            print("Successfully joined the room!")
            current_time = nil
        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 + 15 then
                        current_room_count = current_room_count + 1
                        if current_room_count < 256 then
                            findRoom(current_room_count)
                        end
                    end
                end
            end
        end 
    end
end

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

@Creator27 Nothing worked. It didn’t work when I keyed the ip and port or when I tried FindRoom.

I got “Failed to connect, please try again later” keying the ip/port and “This can take a while, so please be patient!” with FindRoom. I put a print statement in and the current room count just kept increasing.

@dave1707, I now see that it in fact doesn’t work, so I have reverted to the old code but updated it to fix the “Find Room” function.

@dave1707, if this code works you should be able to see a live player count when you connect to a room. 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()
        findRoom(current_room_count)
    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 = {}}
    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 room!")
    end
end

function findRoom(count)
    if server == false then
        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
    else
        print("You are already hosting a room!")
    end
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                if server_info["players"] < 32 then
                    -- We can now add 1 more to our player count
                    server_info["players"] = server_info["players"] + 1
                    -- We now need to store the clients IP and port, so we can send them messages later
                    local uid = {ip = ip, port = port}
                    if not server_info["clients"][uid] then
                        table.insert(server_info["clients"], uid)
                    end
                    client:sendto("valid confirmation", ip, port)
                    -- We can now send everyone that has joined this room a packet telling them the amount of players in the room
                    for i,c in pairs(server_info["clients"]) do
                        client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
                    end
                else
                    -- We don't have enough room for another player, so we send the peer a message telling them to disconnect
                    local reason = "Max players inside the room!"
                    client:sendto("invalid confirmation"..reason, ip, port)
                end
            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 + 15 then
                        current_room_count = current_room_count + 1
                        if current_room_count < 256 then
                            findRoom(current_room_count)
                        end
                    end
                end
            end
        end
    end
end

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

@Creator27 I did JoinRoom, and it printed it will take 5-50 seconds. Then it printed Successfully joined the room. Then it printed Player count: 1 . So it looks like everything worked OK.

@Creator27 I tried the code on 3 iPads. On ipad #1 I did Create Room. On iPad #2 I did Find Room which it did successfully and printed Player count 1. On iPad #3 I did Find Room but it never said it found a room, but on iPad #2 it printed Player count 2.

@dave1707, my best guess is that iPad #3 didn’t get a message back fast enough from the server, so the code moved on. Try changing “if tick > current_time” from 15 to something a little higher. If you still can’t get it to work, I will attempt to program a spare UDP socket that sends a message to every server on the network and see how long it takes to get a message back from each and every server. I can then store the numbers and calculate an average timeframe.

@Creator27 On iPad #1 I selected Create Room. On iPad #2 I selected Find Room. It found the room and printed Player count 1. On iPad #3 I selected Find Room. It found the room and printed Player count2. It also printed Player count 2 on iPad #2.

Ps. This was with a delay of 20 instead of 15.

@dave1707, that’s great news! I will still try to program a spare UDP socket that I told you about before, just so you don’t have to manually change how long the delay should be on each iPad.