Design of Simple Multiplayer Card Game

I had the idea to try and make a simple version of the Apples to Apples game and set it up as a webapp. My initial idea is that each game instance will essentially be a chat room. Multiple players (up to some arbitrary but small limit) connect to the game instance via websockets.
Each player will have some state that consists of a score and current cards.
The game will have Players, Cards, Topics, and Rounds. Each Round will have a Judge and a Winner.

Game {
    players: Vec<Player>,
    current_round: Round,
    winning_score: u32,
    winner: Option<Player>

Player {
    name: String
    cards: Vec<Card>
    score: u32

Round {
    judge: Player
    players: Vec<Player>
    topic: Topic
    cards: Vec<(Card, Player)>
    winner: Option<Player>

type Card = String;
type Topic = String;

Each round of play consists of all but one player submitting a card. The excepted player is the Judge for that round. Once all players have submitted a card (by checking Round::cards.len() and that Round::cards has only a single submission from each player), the Judge picks a winner. Once a winner is chosen the round is over and the next player in sequence becomes the Judge.

Before getting into the nuts and bolts of the websockets and so on, does this overall structure make sense? What organization would offer easier gameplay logic/algorithms?

I wish I could be of more help, but I'm pretty new to Rust. The overall structure looks good, but I get the feeling that your vectors for the players field in Round will probably need to be some sort of smart pointer like RefCell or something like that. I haven't had a real opportunity to use smart pointers in practice yet. Same with the Player field in the tuple for the Round struct's cards vector.

References might be the way to go so that you only have a single instance of each unique player in memory. If you don't reference players, then you'll have to make copies of these instances for each round.

For example: round 1 starts. If we go the copy route, then we'll need to copy/clone all participating players into that round. This might be fine, but if the player is allowed to change their screen name or other attributes at any time throughout the game, then the player instance will get updated in the Game struct, but not in the Round struct. If you want this exact behavior, then you're all set. If you don't, then smart pointers or even storing the IDs of each player could be easier too. If Round only stores each participating player's ID, then you could provide a method like get_player_by_id(id: u32) -> &Player, on the Game struct to look up players.

1 Like

Definitely a good point to not copy the whole Player to the Round but access it by ID. Thanks.

If you want users to have persistent identities -- usernames, win statistics, stuff like that -- I would recommend separating user data (stuff that follows a person from game to game) from player data (stuff that is only relevant to a game, such as hand and score). This also lets you do stuff like play multiple games at once as the same user.

1 Like

That's good advice. This is intended only for a small group of friends, though. I'm not planning to keep stats or have persistent storage at all really.

I'd suggest avoiding type and instead use a struct for player and card. It allows the compiler to give you a but more help in catching bugs, and also can ease refactoring later if you decide to change the representation of those types.

1 Like

I imagined that the persistent data would live in a database of some sort for the long-term data such as storing password hashes, and records between the player table and the games tables (assuming relational databases here). Then, when the user is actively using the service (playing the game in this case), their data would be loaded into memory which is where the Game struct comes in handy. The indefinite long-term stuff that's rarely modified could stored directly into the database which would be submitted directly by the server-side Rust binary.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.