Multiplayer games using sockets.

I just got a second iPad and decided to try a multiplayer game using sockets. One problem I had was trying to keep the code in sync on both iPads as I kept making changes/fixes. At first, I would copy the Codea code, paste it in an email and email it to the other iPad. I would open the email there, copy the code and paste it into a project. Well, that got old real fast. So I set up this code to make it easier for me. I copy the code I want to send, run this code on both iPads, paste the copied code into the parameter message area, and press send message. On the other iPad, the sent code is printed in the print area. I then tap on the print area which copies the code to the pasteboard. I close this code, open the project where I want the new code and do a paste. It’s a little easier and I’m not messing around with email. To use this code, go to the Settings app, select Wi-Fi, tap your wi-fi name and get your IP address. Put that IP address in myIp. Get the IP address of the other iPad and put it in theirIp. Do the same on the other iPad. The myIp and theirIp will be flipped on both iPads. There’s code that will get the IP address automatically, but I didn’t include it. Once the ip addresses are set, I don’t think they change. At least they haven’t for me.

As for the multiplayer game, it’s a simple game similar to TRON. Not a lot is being put into the game because I want to show what it takes to use sockets for a multiplayer game. The game currently runs OK, but I’m still testing it. I’ll post it here a little later. One of the problems people were having was the games getting out of sync as you played. I added code that keeps both iPads in sync for this game.

function setup()    
    socket = require("socket")
    
    myIp="192.168.254.27"           -- this ipads IP address
    theirIp="192.168.254.15"        -- the other ipads ip address

    server = socket.udp()
    server:setsockname(myIp,5544)
    server:settimeout(0)

    client = socket.udp()
    client:setpeername(theirIp,5544)
    client:settimeout(0)

    parameter.text("msg", "")
    parameter.action("send message", sendMessage)
end

function sendMessage()
    client:send(msg)
end

function draw()
    background(0)
    local data, msgOrIp, portOrNil=server:receivefrom()
    if data~=nil then
        print(data)
    end
end

Here’s the simple multiplayer TRON type game. Copy this code to both iPads. See the discussion above for setting the 2 ip addresses. One other thing that needs to be done is to select the player color. One iPad needs to set the col variable to 1 (red) and the other iPad to 2 (green). The object of the game is to surround the other player and force them to either run into their own trail or your trail. When that happens I show the losing color. To change the player direction, tap one of the buttons. If your going up, don’t press the down button or you’ll run into your own tail. Same for the other directions, don’t press the opposite button. I don’t have any restart code, so when the game is over, press the arrow in the lower left corner to get back to the editor. Start both programs to play again.

EDIT: Corrected an error.

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    col=1   -- determine who wants to be 1 (red) and who wants to be 2 (green)
    cTab={color(255,0,0),color(0,255,0)}
    w=1020  -- size for a 9.7" ipad, multiple of size
    h=760   -- size for a 9.7" ipad, multiple of size
    size=10
    sr=0
    tabSr={}
    cnt=0
    if col==1 then
        x=200
    else
        x=w-200
    end
    y=100
    vx,vy=0,size
    tab={vec3(x,y,col)}

    myIp="192.168.254.27"           -- this ipads ip address
    theirIp="192.168.254.15"        -- the other ipads ip address

    socket = require("socket")
    server = socket.udp()
    server:setsockname(myIp,5544)
    server:settimeout(0)
    client = socket.udp()
    client:setpeername(theirIp,5544)
    client:settimeout(0)
end

function draw()
    background(0)
    fill(123, 200, 225, 255)
    rect(0,0,w,h)
    fill(cTab[col])
    rect(50,h/2,50,50)
    rect(w-100,h/2,50,50)
    rect(w/2,50,50,50)
    rect(w/2,h-100,50,50)
    receive()
    stroke(255)
    strokeWidth(2)
    -- draw both player positions
    for a,b in pairs(tab) do
        fill(cTab[b.z])
        rect(b.x,b.y,size,size)
    end
    cnt=cnt+1
    if cnt%5==0 then
        checkSr()
    end
    if cnt>15 and not over then -- move player every 1/2 second
        cnt=0
        x=x+vx
        y=y+vy
        if x<=0 or x>=w or y<=0 or y>=h then
            gameOver()
        else
            checkHit()
        end
        table.insert(tab,vec3(x,y,col))
        sr=sr+1
        tabSr[sr]=vec4(x,y,sr,0)
        send(sr..","..x..","..y..","..col)
    end
    if over then    -- display game over message
        fill(255)
        rect(w/2-200,h/2-50,400,100)
        fill(cTab[lost])    
        text("GAME OVER...THIS COLOR LOST",w/2,h/2)
    end
end

function checkHit()
    -- check if player ran into itself or the other player
    for z=1,#tab-1 do
        if x==tab[z].x and y==tab[z].y then
            gameOver()
        end
    end    
end

function gameOver()
    -- set gameover for this ipad
    over=true
    lost=col
    vx,vy=0,0
    send("GameOver"..col)   -- send gameover message to other ipad
end

function checkSr()
    -- check the sent/received table to see if player message was received
    -- if it wasnt, send the player info again
    for a,b in pairs(tabSr) do
        if b.w==0 then
            send(a..","..b.x..","..b.y..","..col)
            cnt=0
            text("checkSr "..a,WIDTH/2,HEIGHT-30)
        end
    end
end

function touched(t)
    -- check if a button was pressed
    if t.state==BEGAN and not over then
        if t.x>50 and t.x<100 and t.y>h/2 and t.y<h/2+50 then
            vy=0
            vx=-size
        elseif t.x>w-100 and t.x<w-50 and t.y>h/2 and t.y<h/2+50  then
            vy=0
            vx=size
        elseif t.x>w/2 and t.x<w/2+50 and t.y>50 and t.y<100 then
            vx=0
            vy=-size
        elseif t.x>w/2 and t.x<w/2+50 and t.y>h-100 and t.y<h-50 then
            vx=0
            vy=size
        end
    end
end

function send(msg)  -- send a message to the other ipad
    client:send(msg)
end

function receive()  -- check if a message was received
    local data=server:receivefrom()
    if data~=nil then
        -- check for gameover message from other ipad
        if string.sub(data,1,8)=="GameOver" then
            for a in string.gmatch(data,"GameOver(%d+)") do
                lost=tonumber(a)
            end
            over=true
            vx,vy=0,0
            return
        end
        -- if the sent message was received, update the table
        if string.sub(data,1,1)=="R" then
            for a in string.gmatch(data,"R(%d+)") do
                tabSr[a//1].w=1
            end
            return
        end
        -- get the other ipads player position
        for a,b,c,d in string.gmatch(data,"(%d+),(%d+.%d+),(%d+.%d+),(%d)") do
            table.insert(tab,vec3(b,c,d))
            code=a
        end
        send("R"..code) -- send message received code back to the other ipad
    end
end

@dave1707 I wrote some code based on yours and @JakAttak s library. It’d be nice if you could test it, since I don’t have another iPad. It’s basically what your code does, but it automatically takes code from the clipboard and sends it when you press the send message button. And when the other iPad receives the message, it automatically copies that to its clipboard.


Code removed, it didn't work properly. Original code at this link.

https://codea.io/talk/discussion/6344/luasocket-questions/p1

@Attila717 It gets my IP address OK, but then it displays the message Waiting for Connection not matter what else I do.

@dave1707 Huh, well I guess code from 2014 won’t always work. I’m not sure what to fix. Maybe it’s just the automatic IP finder not working.

@Attila717 That’s why I didn’t add any of that code to my program. It’s easier just to use the settings app to get the ip addresses.

@dave1707 Right. I’ll keep looking into finding that address automatically though. I’m thinking maybe a http request to a “find my ip” site would work.

Wicked! Im wondering if I might be able to set up OSC support using sockets, or wireless MIDI… side note but I am really hoping for the next Codea update to integrate more audio/music. I have found some good resources for this online simply googling LUA audio / LUA DSP / LUA MIDI etc…

@Attila717 Here’s something I wrote for another project to get the IP address of another device on the same network. This code needs to be running on both devices. Once it’s started, it will print your IP address. To get the IP address of the other device, press the parameter button get their ip. The IP address of the other device will be printed. I don’t have more than 2 iPads to run this on, but I believe it will print the IP address of all devices that’s running this code.

function setup()  
    socket=require("socket")
    getMyIp()
    setSocket()
    parameter.action("get their ip",sendMessageToAll)
end

function draw()
    background(0)
    receiveTheirMessage()
end

function setSocket()    
    server=socket.udp()
    server:setsockname(myIp,5544)
    server:settimeout(0)
    client=socket.udp()
    client:settimeout(0)
end

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

function sendMessageToAll()
    -- send a message to everyone on this network except to myself
    for z=1,255 do
        if z~=tonumber(ip2) then
            client:setpeername(ip1..z,5544)
            client:send("send")
        end
    end
end

function receiveTheirMessage()
    local data,msg,port=server:receivefrom()
    if data~=nil then        
        if data=="send" then
            client=socket.udp()
            client:settimeout(0)
            client:setpeername(msg,5544)
            client:send("recv"..string.sub(data,5,#data))
        elseif data=="recv" then
            theirIp=msg
            print("Their Ip  "..theirIp)
        end
    end
end

@AxiomCrux What are you referring to when to write OSC support.

@dave1707 A UDP broadcast would be a much cleaner way of implementing sendMessageToAll. Then it would only have to send out one packet to the network’s broadcast address / 255.255.255.255.

Client

socket = require("socket")
function setup()
    client = socket.udp()
    assert(client:setoption('broadcast',true))
    parameter.action("Send",function() 
        assert(client:sendto("Hello","255.255.255.255","12345")) 
        print("sent")
    end)
end

Server / Listener

socket = require("socket")
function setup()
    server = socket.udp()
    server:setsockname("*","12345")
    server:settimeout(0)
end

function draw()
    background(40, 40, 50)
    strokeWidth(5)

    local data = server:receivefrom()
    if data then
        print(data)
    end
end

@XanDDemoX Thanks. I didn’t know that could be done. I guess I’ll have to find and read the socket documentation more closely. I would have to add another variable to server:receivefrom() to get the IP address coming back and then ignore my own address since I’m looking for other iPad addresses. See my discussion Project transfer iPad to iPad a few discussions down where I use my above code to transfer projects to each other on my 2 iPads.

@XanDDemoX I guess I could also use your code to get my IP address instead of having a seperate routine to get it. I’ll have to play with your code more. The same code needs to run on different devices to use it in my Project transfer iPad to iPad.

@dave1707 The broadcast will send to all other devices on the network. The device that sends the broadcast shouldn’t also receive it. In a more practical setup you could send a broadcast and then listen for replies to discover devices. Then establish direct TCP connections using the IP addresses that you get back from receivefrom.

Here’s an updated version since it was brought up in another post. I made some changes that fixed some problems I found using different sized devices. Added code to display the IP address of the current device. Added comments to help set things up to get started.

PS. Just tap a button to change directions.

displayMode(FULLSCREEN)
-- play this in landscape orientation.
-- set the w and h variables in both programs to the width and height of the smaller device.
-- determine who wants to be red or green and set the col variable to 1 or 2 .
-- get the ip address of the other device. 
--    start the program and your ip will show in the upper left corner.
--    put that ip in the other devices theirIp variable.
-- game ends when you run over your trail or the other players trail. 
--    you might tap the wrong button and back up over your own tail. 
-- it's possible for both players to cross paths and occupy the same space at the same time.
--     if that happens then the game will continue. 
-- if both players run head first into each other, then both players might show as losing. 
-- once the game ends, press the restart icon on both devices at the same time to play again. 
--     if you don't, one of the devices might not show the beginning of the other players tail. 

function setup()
    theirIp="192.168.254.15"     -- the other devices ip address
    col=1   -- determine who wants to be 1 (red) and who wants to be 2 (green)
    cTab={color(255,0,0),color(0,255,0)}
    
    w=1112  -- width of the smaller of the two devices being used
    h=834  --  height of the smaller of the two devices device being used
    
    size=10
    w=w-w%size
    h=h-h%size
    sr=0
    tabSr={}
    cnt=0
    if col==1 then
        x=200
    else
        x=w-200
    end
    y=100
    vx,vy=0,size
    tab={vec3(x,y,col)}

    socket = require("socket")
    getMyIp()
    myIp=ip1..ip2
    print("my ip "..ip1,ip2)
    server = socket.udp()
    server:setsockname(myIp,5544)
    server:settimeout(0)
    client = socket.udp()
    client:setpeername(theirIp,5544)
    client:settimeout(0)
end

function draw()
    background(0)
    fill(123, 200, 225, 255)
    rect(0,0,w,h)
    fill(cTab[col])
    rect(50,h/2,50,50)
    rect(w-100,h/2,50,50)
    rect(w/2,50,50,50)
    rect(w/2,h-100,50,50)
    text(myIp,80,HEIGHT-25)
    receive()
    stroke(255)
    strokeWidth(2)
    -- draw both player positions
    for a,b in pairs(tab) do
        fill(cTab[b.z])
        rect(b.x,b.y,size,size)
    end
    cnt=cnt+1
    if cnt%5==0 then
        checkSr()
    end
    if cnt>15 and not over then -- move player every 1/2 second
        cnt=0
        x=x+vx
        y=y+vy
        if x<=0 or x>=w or y<=0 or y>=h then
            gameOver()
        else
            checkHit()
        end
        table.insert(tab,vec3(x,y,col))
        sr=sr+1
        tabSr[sr]=vec4(x,y,sr,0)
        send(sr..","..x..","..y..","..col)
    end
    if over then    -- display game over message
        fill(255)
        rect(w/2-200,h/2-50,400,100)
        fill(cTab[lost])    
        text("GAME OVER...THIS COLOR LOST",w/2,h/2+20)
        text("PRESS THE RESTART ICON TO PLAY AGAIN",w/2,h/2-20)
    end
end

function getMyIp()
    server=socket.udp()
    server:setpeername("1.1.1.1",80)
    myIp,myPort=server:getsockname()
    ip1,ip2=string.match(myIp,"(%d+.%d+.%d+.)(%d+)")
end

function checkHit()
    -- check if player ran into itself or the other player
    for z=1,#tab-1 do
        if x==tab[z].x and y==tab[z].y then
            gameOver()
        end
    end    
end

function gameOver()
    -- set gameover for this ipad
    over=true
    lost=col
    vx,vy=0,0
    send("GameOver"..col)   -- send gameover message to other ipad
end

function checkSr()
    -- check the sent/received table to see if player message was received
    -- if it wasnt, send the player info again
    for a,b in pairs(tabSr) do
        if b.w==0 then
            send(a..","..b.x..","..b.y..","..col)
            cnt=0
            --text("checkSr "..a,WIDTH/2,HEIGHT-30)
        end
    end
end

function touched(t)
    -- check if a button was pressed
    if t.state==BEGAN and not over then
        if t.x>50 and t.x<100 and t.y>h/2 and t.y<h/2+50 then
            vy=0
            vx=-size
        elseif t.x>w-100 and t.x<w-50 and t.y>h/2 and t.y<h/2+50  then
            vy=0
            vx=size
        elseif t.x>w/2 and t.x<w/2+50 and t.y>50 and t.y<100 then
            vx=0
            vy=-size
        elseif t.x>w/2 and t.x<w/2+50 and t.y>h-100 and t.y<h-50 then
            vx=0
            vy=size
        end
    end
end

function send(msg)  -- send a message to the other ipad
    client:send(msg)
end

function receive()  -- check if a message was received
    local data=server:receivefrom()
    if data~=nil then
        -- check for gameover message from other ipad
        if string.sub(data,1,8)=="GameOver" then
            for a in string.gmatch(data,"GameOver(%d+)") do
                lost=tonumber(a)
            end
            over=true
            vx,vy=0,0
            return
        end
        -- if the sent message was received, update the table
        if string.sub(data,1,1)=="R" then
            for a in string.gmatch(data,"R(%d+)") do
                tabSr[a//1].w=1
            end
            return
        end
        -- get the other ipads player position
        for a,b,c,d in string.gmatch(data,"(%d+),(%d+.%d+),(%d+.%d+),(%d)") do
            table.insert(tab,vec3(b,c,d))
            code=a
        end
        send("R"..code) -- send message received code back to the other ipad
    end
end