-
Notifications
You must be signed in to change notification settings - Fork 4
Representing spaces
An exploration, kicked off from whlle thinking through issue #550.
For games with a board component, you have spaces that player's tokens can be in.
The simplest set-up is when only a single component is ever legal at a given time. The way to model that is a single Stack on GameState.
If multiple players' components can be in a single space at the same time, then instead you have multiple stacks, one for each player state, for that type of component. (If a space allows more than 1, but has other criteria for when more are allowed; e.g. "never more than 2", then that logic has to be represented in the Move's legal method). Conceptually the first case laid out (only a single component ever in a space) can actually be modeled as each player having a Spaces stack, and then the MoveToken move simply referring to move a token into a space index that is occupied in any player's stack. But obviously modelling it that way is silly and unnecessary overhead--this is just to say that they're theoretically equivalent semantically.
Note that this set-up only works for cases where a single component per player exists that goes into the board. If the same player has multiple components that are all possibly going in those slots, then you conceptually need a separate stack for each component in general. (Note that the "ownership" of a given component is implied likely in an enum, of something like TokenColor.) If you have a single stack per component, then it's also possible to represent as a DynamicValues on the token that is the index of space it exists within, but obviously that doesn't make it easy to check to see if a given space is "occupied".
Another way to think about board spaces is as piles of stacks. In the Pass game, there's n+1 piles of tokens, where n is number of players. Each of those piles is a growable stack. Conceptually you could imagine a board to be represented as a SizedStack where each stack is actually a GrowableStack (possibly with a max).
Perhaps that implies that a Spaces is a special type of Stack with GrowableStacks inside.
2-D regions are an orthogonal thing to think through with all of this. As long as there's a finite number of spaces, then they can be represented as a SizedStack, where there's some implied mapping function from some dimensional space down into the single dimension. That mapping function can be encapsulated, especially for 2D boards, with a RangedEnum, which happens to capture the number of dimensions. This is convenient, ecause RangedEnums work with Graphs well, and also because they can convert to and from that function. This implies the solution to issue #602 where stacks can have an Enum associated with them to get that mapping function.
Boards need to do a few things:
- Make it easy to print out in a way that positions tokens clienside
- Make it easy to check the space index that a given player's token is in to see if it conflicts with other tokens
- Make it easy to index into to check e.g. connectedness of various types between different spaces (e.g. in chutes and ladders, or simply checkers)
- Make it easy to check if a token may be moved to a space (e.g. model "only one item" or "only two items", "no more than one item of each color", "if empty any token, but if not empty then up to 5 tokens of the same color")
One (bad) way to do this would be to just have an int property on each component, encoding its logical space. For example, some games like Mysterium use a physical component moving along a dial to represent a counter, which in practice is often easier to model simply as an integer counter and that's it.
One of the inescapable things is that often the index of the space the component is in is most relevant, to index into a Graph for e.g. connectedness, or to compare to another component's index in perhaps a different logical stack. But other times you want to be able to check all of the items at one logical space "what are the components already in this space" or "list all of the components that are no more than one space away from <SPACE_INDEX> given this connectedness graph".
One desirable property is that the various predicates/constraints (e.g. "only one token max" and "only tokens of unique colors") should be easy to express and combine in normal configurations.
If you have only one board, then when it comes time to fetch the current player's component to move it, you have to know where to look for it to find it.
In games like Monroe in the Games private repo, where multiple different player's components may be in any given space, we have each player have their own private stack and then have a computed property that returns the index of the space that the item is currently in, which is really the property that's used most often.
The maximally complex version is a Board where each player has multiple tokens, GameState also has some tokens, and each space can have more than 1 token in it according to some predicate logic.
It seems like the answer is something about having a Board with a predicate logic (perhaps configured on game delegate, and configured via struct tags) that runs as a final step in the "Legal" logic and can error before that. And then create some helpers that make it easy to write ComputedPlayerProperties for the index of the player's token in a given board.
See issue #736