Trouble with structure for multiplayer rock, paper, scissors

I'm having a bit of trouble trying to come up with an optimal structure for a multiplayer rock, paper, scissors game. In the game you would see a list of everyone who is online, you can select a person and attack them with R, P, or S. The other player will get a notification that they were attacked and respond with R, P, or S. The winner will be notified once both people have responded. I also want to be able to fetch a list of all your matches that have been responded to, and all challenges that you need to respond to still.

The problem is I'm trying to offload as much functionality as possible to cloud functions as to avoid any client side cheating and what not. Ideally, nothing more than the choice of R, P, or S will be trusted from the client.

So far, using Firestore and Cloud Functions, my thought process is when a player selects an opponent and chooses an attack it will create an entry under "matches" that has no read privilege only write privelage to the client. The attacking player's attack as well as both players' ids are stored as well, like so:

matches : {
   match123: {
      players: [userA, userB],
      attacks: [{
         id: userA
         attack: "R" 
      }
   }
}

Then I will also create an entry in the opponent ("userB" in this case) "challenges" collection, this will show on screen that they have a match that the need to respond to without giving away userA's choice already inside the matches collection, like so:

users : {
   userB: {
      name: "Bob",
      challenges: [{
        match123 : {
           players: [userA, userB],
           challengeStarted: *timesstamp*
        }
      }]
   }
}

Then when userB makes a choice I add it to the matches > match123 > attacks list. The cloud function is watching for updates to that match so it sees that both people have submitted attacks, determines the winner, then stores that in a separate readable collection (that I haven't created yet) that the client subscribes to in order to list finished matches.

Now in theory (and in limited practice) this works fine and dandy. But it doesn't seem right. I don't really want to have to add an entry to the matches collection straight from the client, do some checking and logic in the cloud function on the attack that was already added, and then write the attacks and result to a separate table to show the players. The more practical solution would be for the user to get challenged, submit their attack straight to the cloud function, the cloud function does it's validation and logic and adds the finished result straight to some table. But I can't think of a good way to handle this since I would need userA's results stored somewhere anyway for the comparison to determine the winner, and it seems you have to have cloud functions watching database changes not being invoked directly. And I can't just have userA's choice stored under the challenges collection under userA's profile data and fetch it from there, because then when I fetch the list of users to show who's online their choice will be in the fetched data and a determined enough player could determine their choice before making their own.

How would you structure this functionality?

Posted Dec 6, 2018, 1:10 AM UTC · edited

31
Loading comments…