Closed keithgw closed 7 months ago
Leveraging a combination of dictionary, frozenset, tuple, and base data types, the state can be represented in a way that is useful for a transposition table, for node representation, still human readable, is compact, and can be translated back to a valid and possible game state.
phase: str
tuple(vp: int, cost: int)
frozenset({(bird: tuple(int, int), count: int)})
frozenset({bird: tuple(int, int), bird, bird)})
the tuple (0, 0)
will stand for the empty slotfrozenset({bird: tuple(int, int), bird, ..., bird})
of size 5 and the tuple (0, 0)
will stand for the empty slotcount: int
count: int
frozenset({('food_supply', count: int), ('cards_in_hand', count: int), ('game_board': frozenset({bird: tuple(int, int), bird, ..., bird}, ('turns_remaining', count: int)})
Some things to note:
Other options considered:
Having a deck return its representation exactly is inappropriate, since some of this information is hidden to the player. The player can only know with certainty:
This suggests the deck and player hand should be represented differently. The same format frozenset({(bird: tuple(int, int), count: int)})
can still be used, but this should instead represent cards known to be missing from the deck, and in addition, it should have a count for cards in deck.
Here is an outline for the conversion back and forth between representations and game states:
def to_representation(self):
"""Returns a representation of the game state."""
state_dict = {
'num_turns': self.num_turns,
'num_players': self.num_players,
'game_turn': self.game_turn,
'phase': self.phase,
'bird_deck': self.bird_deck.to_representation(),
'discard_pile': self.discard_pile.to_representation(),
'tray': self.tray.to_representation(),
'bird_feeder': self.bird_feeder.to_representation(),
'players': [player.to_representation() for player in self.players]
}
return frozenset(state_dict.items())
@classmethod
def from_representation(cls, representation):
"""Reconstructs the game state from a representation."""
state_dict = dict(representation)
bird_deck = BirdDeck.from_representation(state_dict['bird_deck'])
discard_pile = DiscardPile.from_representation(state_dict['discard_pile'])
tray = Tray.from_representation(state_dict['tray'])
bird_feeder = BirdFeeder.from_representation(state_dict['bird_feeder'])
players = [Player.from_representation(player_repr) for player_repr in state_dict['players']]
return cls(state_dict['num_turns'], state_dict['num_players'], state_dict['game_turn'], state_dict['phase'], bird_deck, discard_pile, tray, bird_feeder, players)
For the simplest representation, represent the deck as a count. Updating for known missing cards #58 #55 can be handled as an enhancement later.
Having a game state specific to MCTS that can construct a valid game state is creating a circular import loop. It probably makes sense to create an MCTSGameState that inherits from GameState and adds the methods necessary to translate between representation. This also begs the question if all other classes that have a to_representation method should be a subclass with the additional representation method.
As part of the summarization and table transpositon strategies of #50 , the state of the game should be able to represented as a feature vector.
This is a dependency for #45