keithgw / wingspan

A Wingspan AI that uses reinforcement learning to learn to play the wingspan board game.
2 stars 1 forks source link

Represent the game state as a feature vector #51

Closed keithgw closed 7 months ago

keithgw commented 7 months ago

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.

  1. Decide the format of the feature vector
  2. Implement a method that takes the current game state, and translates it into this feature vector.
  3. Implement a method that takes a feature vector and translates it back to a valid game state. Inherently, information about hidden information will not be perfectly translated back to the game state, but this will still be useful for simualting playouts from the current state.

This is a dependency for #45

keithgw commented 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.

Some things to note:

Other options considered:

keithgw commented 7 months ago

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.

keithgw commented 7 months ago

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)
keithgw commented 7 months ago

For the simplest representation, represent the deck as a count. Updating for known missing cards #58 #55 can be handled as an enhancement later.

keithgw commented 7 months ago

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.

keithgw commented 7 months ago

64