Skip to content

Challenging Game Designs

Alex Komoroske edited this page Feb 6, 2017 · 12 revisions

This page collects examples of interesting challenges in various real-world game designs. The idea is that when considering a change to the design of the system, we verify that these interesting use cases are all satisfied.

Computational Complexity in Ticket To Ride

Ticket to ride has a number of cases where you need to do really expensive calculations, like longest train, or very complex decisions of if a move is legal or not. These are good stress tests for any ideas about representing IsLegal as a declarative tree of statements, for example.

Secret Hitler

Problem: Fascists know the other fascists, including Hitler. But Hitler doesn't know who the fascists are. This requires a very weird visibility state that knows quite a bit about the game to know who to reveal it to.

A VisibilityState of VisibleToUsers that is a list of user IDs might be the only way to do it?

Likely the easiest way is to create a notion of a UserGroup that lists a set of IDs of users who are in the group. This can calculated at any point by running a given ExtractUserGroups function, which is given the total state and sets which users are in which user group. This allows teams, including fascists, to know the state. The population in this would have to be allowed to be ACL'd, too, to satisfy the SH case--you can only see the items in this list if you are in it. Then there's a VisibleToGroup ACL that takes a UserGroup.

Dark Moon

Dark Moon has the notion of various dice (which are components), which can be either in the general pool, or hidden behind the player screen. This is an example of a component that has dynamic state (which side is showing) that needs to be hidden to other players.

Clue's board configuration

Many games have only light configuration, or configuration that can be easily modeled as stacks. The Clue board, however, has many bits of information to encode: the logical extent of rooms, where the doors are, which spaces are not in rooms, which rooms have a secret passage. Some of this could be modeled with a really big Stack for all spaces (where many are not legal) and a UseSecretPassage move that only works for a characters if they're in one of two rooms. Still, it's a lot of state to encode.

Ticket To Ride Contract Rejection Phase

This is not particular to Ticket to Ride, but the use case demonstrates it. Much of Ticket To Ride proceeds in orderly turns, where only one user may make a move at a time. However, at the very beginning of the game, users are given three contract cards, and they may reject up to two of them. The order in which they do this is not set; they may do it in any order, before signaling they are OK to proceed.

This example illustrates the need for more than just a "single player is current player" mode.

Cards Against Humanity

In most cases, the list of components is fixed across all games of that type. However, in some examples, like Cards Against Humanity, the players can customize a card for that game. A similar thing applies to Pandemic Legacy, but in those cases the possible modifications are easy to model in advance.

This use case gets at that components are not always fully fixed in their "Static" state.

Mysterium Ghost

In Mysterium, one (or possibly two) players are the ghost, and all others are the guessers. Their rules and types of state are fundamentally different across those two roles.

This gets at the fact that different users may have different structs representing their game states. The roles are very different, but are fixed for the life of the game.

Mysterium Timer

In mysterium, after all of the clues are given by the ghost in a round, a timer is started. When that timer is up (or all non-ghost players assert that they are done), we advance to the next round.

This is challenging because it's a move that should be auto-applied at some point in the future.

Related: in that case does the countdown timer get included in state? Or does every client count, too? And if the number of seconds remaining is part of the state, how do we ensure that we only tick it once a second?

House on Haunted Hill

In House on Haunted Hill, all players start off with the same basic rules. However, at some point in the middle of the game, one of the characters will turn into a demon, at which point their rules will drastically change compared to other users. (That game also has ~30 different demon rulesets that could be triggered, but that's not challenging to handle.)

The challenge is that at a key point in the game, a given "normal" user switches roles in a fundamental way that is likely hard to model with only one UserState object schema used by all users

#Evolution: Climate

In Evolution: Climate, players can have a large number of species, and each one that is added can be either on the far left or far right of the already-added ones. One way to model it is to have a large number of slots, such that the starting species is in the middle and if you added all species boards to left and right there would still be enough slots to put them in.

The way to do this is to have the SpeciesBoards be in a stack, where you can add new ones at the front or end. But there is still a need for a Stack that has empty slots in it. For example, in TtR you have 5 train cards that can be drawn. When one is drawn, before it is refilled, we wouldn't want the cards to slide around--there's still a slot there. One way would be to have each slot be a stack, but then you can't index them easily (without a new notion of a type of state to have). So the answer is probably a "hold" in a spot of a stack. Some stacks don't have holds; some leave holds in place when cards are removed. That's down to how they are defined. Perhaps another property, on a stack, including its length.

We'll use a normal Stack for species boards in Evolution. However, those variable slots all need to store 0-4 trait cards. I guess those are a DynamicProperty that is a Stack on the Species Card component.

#Innovation Melding

Innovation has the notion of "melding" a stack of played Innovation cards, left, right, up (and down?). The direction of meld is semantically very important. It doesn't apply to a Component (i.e. DynamicProperty) so much as it applies to a Stack (which don't have such properties). One benefit is that there are only 5 possible stacks per user and they are all known ahead of time, so we could cheat and have a purpleStackMeld = <'color'>. But it implies that we'd need another array state of properties so we could have one per stack. It's probably just an IntSlice, so maybe we're already set.

#Kill Dr. Lucky Turn Order

The turn order of Kill Dr. Lucky is very erratic and depends upon which room Dr. Lucky moves to. Any logic that simply goes around the players round robin style would be defeated, although a pretty easy FixUp move is to advance Dr Lucky, then another FixUp to see who should be set to currentPLayer next.

#Concept

Concept may have two people giving clues, and they can use all of the tokens and place them--not just in a square, but semantically on top of a precise part of the square (or really anywhere on the map). Also, the text proposals of the answer from guessers are part of the state. The clue-givers can say "Yes" to specific proposals to show they're on the right track. And presumably the matching whether the end-user got the proposal right factors out punctuation etc when deciding if it's a match

#Hanabi

In Hanabi, for cards in a player's hand that are hidden to the group, some semantically relevant state is what each other player currently thinks that card is. At a certain point, the rest of the group will come to be nearly certain about what card it is, but even then technically they don't know.

This seems kind of like Species cards with Trait cards, where people conceptually put a "vote" token on the card that is hidden to them.

#Evolution:Climate trait viewability

When you play a trait card on a species, the trait card is hidden until after that part of the round is over. That implies an ACL for that trait that is different for different cards in that stack, and also changes depending on the round.

The easiest way to model it is to have a TraitCardsToPlay stack that is moved over to PlayedTraitCards after that part of the round is over.