jkomoros / boardgame

An in-progress framework in golang to easily build boardgame Progressive Web Apps
Apache License 2.0
31 stars 4 forks source link

Allow more complex team selection mechanics #752

Open jkomoros opened 4 years ago

jkomoros commented 4 years ago

Currently the engine assumes that every game has a fixed number of players, set at creation time. Those slots might be filled or they might not, but once the game is created, the number of players is fixed (and various caching strategies assume that is true, for example NUM_PLAYERS)

This works fine... ish... for simple games where every player is equivalent. One downside: if you think you'll have three other people playing but only two show up you can't say "screw it, just cut out that one extra player", which applies to every type of game.

But some games have teams where multiple players are on specific teams. Other games have specific roles (like Mysterium, or Codenames) where a specific player has special knowledge or rules. Some games have both. Basically, if a game has a bespoke GroupMembership (e.g. #491) then it cares about this even more. In those cases, who is in what group is really important--you want to potentially split up spouses onto different teams, for example, or let the more experienced players have the harder role. That's not possible today.

There are two general solutions to this: 1) Have the game engine take a lot more configuration in game-setup, to have a more official notion of a "lobby", where people can be assigned to roles. This has the downside that games can't support "new players join in after game has started", which some game types would be OK with. 2) Push all of the game logic into the custom game itself. In this case, NUM_PLAYERS becomes, really, just "number of slots of potential players the game engine knows about, any one of which might be actually unfilled." But in a given instantiation of the game, the logic has to keep track of the real notional number of players.

The latter is more flexible, but it also pushes more bespoke logic onto the game. That could be mitigated by conventions and helpers and behaviors. It also would require the custom game logic to be able to communicate to the engine: "OK, I know i said there were 7 slots but even though only 4 have players please don't allow anyone else to join". And then you'd conceptually change the game engine logic and naming convention to NUM_PLAYER_SLOTS, where any player slot might not actually be filled yet. Note that the core engine has no idea which player has a user associated yet; that's entirely in the webapp logical layer.

jkomoros commented 4 years ago

The other option is to have the game engine actually contain game / group logic (at the downside that late additions aren't supported, on a pretty fundamental level). Another downside: if you want to change roles for a second game, you have to create a new game to allow different players to be in different groups. (Does the group membership machinery allow players to change groups?)

You'd need to express the constraints and logics in terms of groups. But the types of group logic could get really hard to express in a general but succinct way. e.g. "groups 'red_team', 'blue_team', 'green_team', must have either 0 or 2+ people in them. At least two of 'red_team', 'blue_team', 'green_team' must have more than 2 people in them. Each team with more than 2 players must have one member also in 'special_role' group".

There's no way you could do that in e.g. configuration logic without getting really complex.

That implies that GameDelegate grows something like LegalGroupConfiguration(state ImmutableState, groupMembership map[int]map[int]) error, which can be passed a proposed group membership configuration,and then get a descriptive error if it fails. E.g. "Each team needs to have one clue giver, but green team is missing a clue giver."

Before the game is created, in the webapp, there's a dialog where player slots can be dumped into different roles, and every time the player slot is moved, we ask the GameDelegate if it's legal. If it is, then we allow the game to be started,and pass the group memberships into the creator. If not, we gray out the "start game" and can even show UI saying why you can't start yet.

Then later if you want to change dynamic group membership, you'd propose new membership sets, and the exact same logic could be run. (When you run the logic before the game is created, the exampleState is nil, but during the game running it's non-nil).

This set-up would require people to be able to create games before they're actually created,which is weird.

One way to do it is to have the logic be in the server layer. As far as the core engine is concerned,the game is a real one and it's running. But the server knows that it's in the Player Selection phase. (Maybe games communicate this by literally having a PlayerSelection phase that htey're in). So the core game engine allows people to be joined to slots, etc, and doesn't need to have special logic about LegalGroupMembership before the game, becuase the game is running. (One downside is that variants can't be changed after player selection. Alternatively consider having variants be handled in the same way, entirely at the server layer) . This still doesn't handle the "num player slots" vs "num actual players", but whatever.

For the dialog, it could be either fully custom (a new client renderer) per game. Later, you could allow configuration, similar to the VariantConfig machinery, that explains what roles need to be filled and etc and generates a default dialog for you. (That may be impossible to do well in general for all but the simplest ones).

One question: in most games, there are teams of size 1: e.g. red player, blue player. Selecting your player color can be important socially. Would that be done with the same machinery, or different?

jkomoros commented 4 years ago
jkomoros commented 4 years ago

Note: if Num Players decoheres from Num_Player_Slots, then PlayerIndex.Next() logic has to be different.

jkomoros commented 4 years ago

This should block on #755 being finished

jkomoros commented 4 years ago

See also #771 about administration.

In some cases you only want an administrator to be able to assign roles. In some cases you want anyone to be able to propose their OWN color, role, etc, and only allow an administrator to finalize it. (You might also want a move where an administrator makes it so that people can't change their own proposed color any more, then only the administrator can make final tweaks, and finally the administrator finalizes colors. (Sometimes, players can change their own proposed colors right up until the administrator locks it in).

So in general, the property would be e.g. ProposedColor, which when finalized would copy it over to Color. ... But then you'd need similar machinery / behaviors for Color, Role, etc. Or just have each one have a Foo/ProposedFoo property inside of it.