Sunday, May 10, 2020

php file locking for multiuser games

So I'm messing around with making quarantine-friendly version of the like-Cards-Against-Humanity-But-With-Comics game Joking Hazard. I'm thinking each game will just be a single .json file on the server, each client will be a SPA in the browser, the endpoints will be humble PHP scripts that update the file - the clients will poll and get an updated version of the whole game state, and then modify it according to the rules.

It's surprisingly tricky to teach a computer to referee even a medium complexity board or card game (It reminds me I need to go back and update Score-Matic for Dixit, another lovely boardgame, but it's casual-friendly creative/artsy mojo is marred by a fiddly scoring system).

I think I am basically making a state machine: not counting the games red cards (a slight fork) the core game loop is something like

vvv
[create-game(username)] /* put all cards in deck */
vvv
GAME_SIGNUP (add username)  <<< [join-game(username)] /* assign 7 cards from deck */
vvv
[start-game]
vvv
NEW_ROUND <<< next judge assigned, deck-picks-card
v
v
REG_ROUND 
vvv
[judge-picks-card(cardoff, judgepos)
vvv
REG_PLAYERS_PICK <<< [reg-player-pick(cardoff)
vvv
(all players_pick)
vvv
REG_JUDGE_PICK <<< [reg-judge-pick(player)]  /*shows all picks to everyone */
vvv
END_ROUND
vvv
[judge-ends-turn]
vvv

So one challenge will be making sure each client can update the gamestate without stomping on other people's play- PHP supports filelocking, though you have to start using syntax more complicated than good ol' file_put_contents() - since there's a lot of boilerplate, I'm going to use a callbacks to a shared function:

function updateGame($gamename, $function){
    if(! preg_match('/^[A-Z][A-Z][A-Z][A-Z]$/',$gamename)) {
        echo "shenanigans!";
        return;
    }
    $filename = "games/$gamename.json";
    //Open the File Stream
    $handle = fopen($filename,"r+");
    //Lock File, error if unable to lock
    if(flock($handle, LOCK_EX)) {
        $oldguts = fread($handle, filesize($filename)); //get current state
        $newguts = call_user_func($function,$oldguts); //update state
        ftruncate($handle, 0);    //Truncate the file to 0
        rewind($handle);           //Set write pointer to beginning of file
        fwrite($handle, $newguts);    //Write the new Hit Count
        flock($handle, LOCK_UN);    //Unlock File
} else {
    echo "Could not Lock File!";
}
//Close Stream
fclose($handle);    
}
Heh, this is extremely FP! Get a copy of the state, modify it, replace the old state.

So my test code for a call back for this was like:
function addSomething($oldraw){
    $content = json_decode($oldraw,true);
    $content["foo"] = array();
    $content["foo"]["bar"] = "baz2";
    $content["foo"]["bat"] = "bag2";
    return json_encode($content);
}
It feels a little odd to pass in the function as a string, but whatevs.


No comments:

Post a Comment