Repository navigation
Create a parameterized view? #5059
|
I'm trying to create a card game. Players should know their cards but not the one of the other players. Players can play multiple games at the same time. How best to modelize this? In a classic server/rest approach I would have a { cards: [['8♠', '8♥', 'T♥'], [null, null, null]] } // Player0
{ cards: [[null, null, null], ['1♠', '1♥', 'K♥']] } // Player1So Player0 has hidden card for Player1. But player1 still knows Player0 has 3 cards and can see his owns cards. But with spacetimedb:
Currently, what I'm doing is that I have a single ongoing game and hence my view returns the (filtered) current ongoing game: export const gameCurrent = spacetimedb.view(
{name: 'game_current', public: true},
t.option(t.object("current", {
id: t.uuid(),
name: t.string(),
cards: t.array(t.array(t.u8())),
playersNames: t.array(t.string()),
currentPlayerIndex: t.u8(),
myPlayerIndex: t.u8(),
})),
(ctx) => {
// Filter by one current game for me
const user = ctx.db.user.identity.find(ctx.sender);
if (! user) throw new Error('User not found');
const games = Array.from(ctx.db.game.state.filter(STATE_RUNNING));
const game = games.find(g => g.playersIds.some(pid => pid.compareTo(user.id) === 0));
if (! game) return undefined
const playerIndex = game.playersIds.findIndex(pid => pid.compareTo(user.id) === 0);
const anonymizedCards = game.playersCards.map((cards, idx) =>
idx === playerIndex ? cards : cards.map(() => null)
);
return {
id: game.id,
name: game.name,
cards: anonymizedCards,
playersNames: game.playersIds.map(pid => ctx.db.user.id.find(pid)!.name),
currentPlayerIndex: game.currentPlayerIndex,
myPlayerIndex: playerIndex,
}
}
)Another issue with my current approach: as soon as the game finished it is no longer an ongoing game, and hence displaying post game results is harder than if I could follow the game session with just the id. Note that from what I can see in BitCraft there is a bunch of tables which are declared |
Replies: 1 comment
|
I think the key point here is that a parameterized view is probably not the right abstraction for this use case. Instead, I would keep the authoritative game state private and expose the information that a player is allowed to see through a separate public table (or tables), keyed by game_id and player identity. For example, you could have something along the lines of: game_state
game_player_view
The reducer that modifies the game state would also update game_player_view for the affected players. The client can then subscribe/query by game_id, while the sensitive information never becomes part of a public table containing the complete game state. For the cards specifically, you could have one row per player/game, with that player's cards stored there. Then Player 0 only receives the row corresponding to (game_id, Player 0), while still being able to see the other players' public information (number of cards, names, etc.) through another public table. This also solves the "game has finished" problem: the game doesn't have to disappear from the public representation when its state changes from RUNNING to FINISHED. You can keep the game/session row around and expose the appropriate post-game state. The important distinction, IMO, is: private tables = authoritative/server-only state public tables = client-visible projection of that state subscriptions = how the client selects which rows it wants A parameterized view would certainly make this more convenient, but I don't think you should need one to model a multiplayer game securely. The BitCraft example is also interesting here: public tables don't necessarily mean that all information in the game has to be public. You can have public/projection tables containing only the information that clients are allowed to know, while keeping the authoritative state private. |
I think the key point here is that a parameterized view is probably not the right abstraction for this use case.
Instead, I would keep the authoritative game state private and expose the information that a player is allowed to see through a separate public table (or tables), keyed by game_id and player identity.
For example, you could have something along the lines of:
game_state
game_player_view
The reducer that modifies the game state would also update game_player_view for the affected players. The client can then subscribe/query by game_id, while the sensitive information never becomes part of a public table containing t…