def test_move_handmaid(self): """Deploy the handmaid and survive attack""" game = Game.new(4, 2) action = PlayerAction(Card.handmaid, 0, Card.noCard, Card.noCard) game, _ = game.move(action) players = game.players() player = players[0] self.assertTrue(PlayerTools.is_playing(player)) self.assertFalse(PlayerActionTools.is_blank(player.actions[0])) self.assertEqual(player.actions[0], action) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action)) action_attack = PlayerAction(Card.guard, 0, Card.prince, Card.noCard) game, _ = game.move(action_attack) players = game.players() target = players[0] player = players[1] self.assertTrue(PlayerTools.is_playing(player)) self.assertTrue(PlayerTools.is_playing(target)) self.assertFalse(PlayerActionTools.is_blank(player.actions[0])) self.assertEqual(player.actions[0], action_attack) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action)) for action in target.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action))
def _move_princess(self, dealt_card, new_deck): """Handle a princess action into a new game state""" player = PlayerTools.force_discard(self.player(), dealt_card) player = PlayerTools.force_discard(player) current_players = Game._set_player( self._players, player, self.player_turn()) return Game(new_deck, current_players, self._turn_index + 1)
def _move_prince(self, current_players, action, deck_new): """Handle a prince action into a new game state""" player_before_discard = current_players[action.player_target] action_updated = action._replace(player=self.player_turn(), force_discarded=player_before_discard.hand_card, force_discarder=action.player_target) # if there are no more cards, this has no effect if len(deck_new) - 1 < 1: return Game(deck_new, current_players, self._turn_index + 1, [*self._action_log, action_updated]) if player_before_discard.hand_card == Card.princess: player_post_discard = PlayerTools.force_discard( player_before_discard) deck_final = deck_new else: player_post_discard = PlayerTools.force_discard( player_before_discard, deck_new[0]) deck_final = deck_new[1:] current_players = Game._set_player( current_players, player_post_discard, action.player_target) return Game(deck_final, current_players, self._turn_index + 1, [*self._action_log, action_updated])
def _to_str_player(self, idx, player): is_playing = " " if PlayerTools.is_playing(player) else "☠️" is_turn = "⭐" if self.player_turn() == idx else " " draw_card = self.draw_card() if self.active( ) and self.player_turn() == idx else Card.noCard draw_card_render = Card.render_card_number(draw_card) header = "Player {} {} {}".format(idx, is_turn, is_playing) state = " Current: {} {}".format( draw_card_render, PlayerTools.to_str(player)) return [header, state]
def is_winner(self, idx): """True iff that player has won the game""" if self.active(): return False player = self._players[idx] if not PlayerTools.is_playing(player): return False other_scores = [ p.hand_card > player.hand_card for p in self._players if PlayerTools.is_playing(p)] return sum(other_scores) == 0
def is_action_valid(self, action): """Tests if an action is valid given the current game state""" player = self.player() # if player is out, only valid action is no action if player.hand_card == Card.noCard: return PlayerActionTools.is_blank(action) target_player = self._players[action.player_target] player_hand = [player.hand_card, self._deck[0]] # cannot discard a card not in the hand if action.discard not in player_hand: return False new_hand_card = Game.new_hand_card(action.discard, player_hand) # countess must be discarded if the other card is king/prince if new_hand_card == Card.countess and \ (action.discard == Card.prince or action.discard == Card.king): return False # cannot target an invalid player if not self._is_valid_player_target(action.player_target): return False # cannot mis-target a card if self.player_turn() == action.player_target and action.discard in Card.only_other: # Check if self is the only valid target due to everybody else protected (or dead) other_players_invalid = [not PlayerTools.is_playing(p) or PlayerTools.is_defended(p) for p in self._players if p is not self.player()] if all(other_players_invalid): return True else: return False if self.player_turn() != action.player_target and action.discard in Card.only_self: return False if not PlayerTools.is_playing(target_player): return False # Check if target is defender (and not the current player) if PlayerTools.is_defended(target_player) and player != target_player: return False # Cannot guess guard or no card if action.discard == Card.guard and ( action.guess == Card.guard or action.guess == Card.noCard): return False return True
def _move_king(self, current_players, action, deck_new): """Handle a king action into a new game state""" player = current_players[self.player_turn()] target = current_players[action.player_target] player_new = PlayerTools.set_hand(player, target.hand_card) target_new = PlayerTools.set_hand(target, player.hand_card) current_players = Game._set_player( current_players, player_new, self.player_turn()) current_players = Game._set_player( current_players, target_new, action.player_target) return Game(deck_new, current_players, self._turn_index + 1)
def _move_guard(self, current_players, action, deck_new): """ Handle a guard action into a new game state Player makes a guess to try and eliminate the opponent """ if self._players[action.player_target].hand_card == action.guess and \ not PlayerTools.is_defended(self._players[action.player_target]): # then target player is out player_target = PlayerTools.force_discard( self._players[action.player_target]) current_players = Game._set_player( current_players, player_target, action.player_target) return Game(deck_new, current_players, self._turn_index + 1)
def test_move_guard_success(self): """Getting a guard move, with a right guess""" game = Game.new() action = PlayerAction(Card.guard, 3, Card.handmaid, 0) self.assertEqual(len(game.opponents()), 3) self.assertListEqual(game.opponent_turn(), [1, 2, 3]) game, _ = game.move(action) self.assertEqual(game.round(), 0) self.assertEqual(game.player_turn(), 1) self.assertEqual(game.cards_left(), 10) self.assertTrue(game.active()) self.assertFalse(game.over()) players = game.players() player = players[0] target = players[3] recent_action = player.actions[0] self.assertListEqual(game.opponent_turn(), [0, 2]) self.assertEqual(len(game.opponents()), 2) self.assertFalse(PlayerTools.is_playing(target)) self.assertEqual(recent_action, action) self.assertEqual(player.hand_card, Card.handmaid) self.assertFalse(PlayerActionTools.is_blank(recent_action)) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action))
def test_init(self): """Create a Player""" player = PlayerTools.blank(1) self.assertEqual(player.hand_card, 1) self.assertEqual(len(player.actions), 8) for action in player.actions: self.assertTrue(PlayerActionTools.is_blank(action))
def test_from_np(self): """Player from a numpy array""" player = Player(1, [PlayerAction(1, 3, 5, 0), PlayerAction(3, 0, 0, 2)]) arr = np.array([1, 1, 3, 5, 0, 3, 0, 0, 2], dtype=np.uint8) player_res = PlayerTools.from_np(arr) self.assertEqual(player_res, player)
def __init__(self, deck, players, turn_index): self._deck = deck self._players = players self._turn_index = turn_index total_playing = sum( [1 for player in players if PlayerTools.is_playing(player)]) self._game_active = total_playing > 1 and self.cards_left() > 0
def _move_priest(self, action, player_hand_new, deck_new): """ Handle a priest action into a new game state Action gains knowledge of other player's card """ player_targets_card = Card.noCard if \ PlayerTools.is_defended(self._players[action.player_target]) \ else self._players[action.player_target].hand_card action_updated = PlayerAction( action.discard, action.player_target, action.guess, player_targets_card) player = PlayerTools.move( self.player(), player_hand_new, action_updated) current_players = Game._set_player( self._players, player, self.player_turn()) return Game(deck_new, current_players, self._turn_index + 1)
def test_to_np(self): """Player to a numpy array""" player = Player(1, [PlayerAction(1, 3, 5, 0), PlayerAction(3, 0, 0, 0)]) arr = np.array([1, 1, 3, 5, 0, 3, 0, 0, 0], dtype=np.uint8) arr_res = PlayerTools.to_np(player) self.assertEqual(len(arr_res), len(arr)) self.assertTrue((arr_res == arr).all())
def test_move_baron_failure(self): """Getting a baron move, with a failure""" game = Game.new(4, 48) action = PlayerAction(Card.baron, 1, Card.noCard, Card.noCard) game, _ = game.move(action) players = game.players() player = players[0] target = players[1] self.assertFalse(PlayerTools.is_playing(player)) self.assertTrue(PlayerTools.is_playing(target)) self.assertFalse(PlayerActionTools.is_blank(player.actions[0])) self.assertFalse(PlayerActionTools.is_blank(player.actions[1])) for action in player.actions[2:]: self.assertTrue(PlayerActionTools.is_blank(action)) for action in target.actions: self.assertTrue(PlayerActionTools.is_blank(action))
def test_move_king(self): """Use king to swap hands with the target""" game = Game.new(4, 0) action = PlayerAction(Card.king, 1, Card.noCard, Card.noCard) game, _ = game.move(action) players = game.players() player = players[0] target = players[1] self.assertTrue(PlayerTools.is_playing(player)) self.assertFalse(PlayerActionTools.is_blank(player.actions[0])) self.assertEqual(player.actions[0], action) self.assertEqual(player.hand_card, Card.priest) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action)) self.assertTrue(PlayerTools.is_playing(target)) self.assertEqual(target.hand_card, Card.guard) for action in target.actions: self.assertTrue(PlayerActionTools.is_blank(action))
def _move(self, action, throw=False): """Current player makes an action. Returns (NewGame and Reward)<Game,int>""" if self.over() or not self.is_action_valid(action): return self._invalid_input(throw) # player is out, increment turn index if action.discard == Card.noCard: return Game(self.deck(), self.players(), self.turn_index() + 1, self._action_log) player = self.player() player_hand = [player.hand_card, self._deck[0]] player_hand_new = Game.new_hand_card(action.discard, player_hand) deck_new = self._deck[1:] # choosing to discard the princess ... is valid if action.discard == Card.princess: return self._move_princess(self._deck[0], action, deck_new) # priest requires modification of action (knowledge) if action.discard == Card.priest: return self._move_priest(action, player_hand_new, deck_new) # updated players for the next turn player = PlayerTools.move(self.player(), player_hand_new, action) current_players = Game._set_player( self._players, player, self.player_turn()) if action.discard == Card.baron: return self._move_baron(action, current_players, player_hand_new, deck_new) # No other logic for handmaids or countess if action.discard == Card.handmaid or \ action.discard == Card.countess: action_updated = action._replace(player=self.player_turn()) return Game(deck_new, current_players, self._turn_index + 1, [*self._action_log, action_updated]) if action.discard == Card.guard: return self._move_guard(current_players, action, deck_new) if action.discard == Card.prince: return self._move_prince(current_players, action, deck_new) if action.discard == Card.king: return self._move_king(current_players, action, deck_new) raise NotImplementedError("Missing game logic")
def test_move(self): """Player performs a move""" player = PlayerTools.blank(1) player_next = PlayerTools.move(player, 4, PlayerAction(3, 2, 0, 0)) self.assertEqual(player.hand_card, 1) self.assertEqual(len(player.actions), 8) for action in player.actions: self.assertTrue(PlayerActionTools.is_blank(action)) self.assertEqual(player_next.hand_card, 4) self.assertEqual(len(player_next.actions), 8) action = player_next.actions[0] self.assertEqual(action.discard, 3) self.assertEqual(action.player_target, 2) self.assertEqual(action.guess, 0) self.assertEqual(action.revealed_card, 0) self.assertEqual(PlayerActionTools.is_blank(action), False) for action in player_next.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action))
def test_move_baron_success(self): """Getting a baron move, with a success""" game = Game.new(4, 48) action = PlayerAction(Card.baron, 3, Card.noCard, Card.noCard) game, _ = game.move(action) players = game.players() player = players[0] target = players[3] recent_action = player.actions[0] self.assertTrue(PlayerTools.is_playing(player)) self.assertFalse(PlayerTools.is_playing(target)) self.assertEqual(recent_action, action) self.assertFalse(PlayerActionTools.is_blank(recent_action)) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action)) self.assertFalse(PlayerActionTools.is_blank(target.actions[0])) for action in target.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action))
def test_move_prince_other(self): """Use prince to force another to discard""" game = Game.new(4, 2) action = PlayerAction(Card.prince, 1, Card.noCard, Card.noCard) action_target = PlayerAction(Card.guard, 0, Card.noCard, Card.noCard) game, _ = game.move(action) players = game.players() player = players[0] target = players[1] self.assertTrue(PlayerTools.is_playing(player)) self.assertFalse(PlayerActionTools.is_blank(player.actions[0])) self.assertEqual(player.actions[0], action) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action)) self.assertTrue(PlayerTools.is_playing(target)) self.assertFalse(PlayerActionTools.is_blank(target.actions[0])) self.assertEqual(target.actions[0], action_target) for action in target.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action))
def _move_baron(self, action, current_players, player_hand_new, deck_new): """ Handle a baron action into a new game state Player and target compare hand cards. Player with lower hand card is eliminated """ card_target = self._players[action.player_target].hand_card if player_hand_new > card_target: if not PlayerTools.is_defended(self._players[action.player_target]): # target is eliminated player_target = PlayerTools.force_discard( self._players[action.player_target]) current_players = Game._set_player( current_players, player_target, action.player_target) action_updated = action._replace(player=self.player_turn(), force_discarded=card_target, force_discarder=action.player_target) else: action_updated = action._replace(player=self.player_turn()) elif player_hand_new == card_target: # Tie, nobody wins action_updated = action._replace(player=self.player_turn(), revealed_card=card_target) else: # player is eliminated player = PlayerTools.force_discard(self.player(), player_hand_new) player = PlayerTools.force_discard(player) current_players = Game._set_player( current_players, player, self.player_turn()) action_updated = action._replace(player=self.player_turn(), force_discarded=player_hand_new, force_discarder=action.player) return Game(deck_new, current_players, self._turn_index + 1, [*self._action_log, action_updated])
def _move_baron(self, action, current_players, player_hand_new, deck_new): """ Handle a baron action into a new game state Player and target compare hand cards. Player with lower hand card is eliminated """ card_target = self._players[action.player_target].hand_card if player_hand_new > card_target: if not PlayerTools.is_defended(self._players[action.player_target]): # target is eliminated player_target = PlayerTools.force_discard( self._players[action.player_target]) current_players = Game._set_player( current_players, player_target, action.player_target) else: # player is eliminated player = PlayerTools.force_discard(self.player(), player_hand_new) player = PlayerTools.force_discard(player) current_players = Game._set_player( current_players, player, self.player_turn()) return Game(deck_new, current_players, self._turn_index + 1)
def test_move_prince_self(self): """Use prince to force self discard""" game = Game.new(4, 2) action = PlayerAction(Card.prince, 0, Card.noCard, Card.noCard) action_other = PlayerAction(Card.handmaid, 0, Card.noCard, Card.noCard) game, _ = game.move(action) players = game.players() player = players[0] self.assertTrue(PlayerTools.is_playing(player)) self.assertFalse(PlayerActionTools.is_blank(player.actions[0])) self.assertFalse(PlayerActionTools.is_blank(player.actions[1])) self.assertEqual(player.actions[0], action) self.assertEqual(player.actions[1], action_other) for action in player.actions[2:]: self.assertTrue(PlayerActionTools.is_blank(action))
def test_move_princess(self): """Commit suicide by discarding the princess""" game = Game.new(4, 11) action = PlayerAction(Card.princess, 0, Card.noCard, Card.noCard) game, _ = game.move(action) players = game.players() player = players[0] self.assertFalse(PlayerTools.is_playing(player)) self.assertFalse(PlayerActionTools.is_blank(player.actions[0])) self.assertFalse(PlayerActionTools.is_blank(player.actions[1])) self.assertEqual(player.actions[0], PlayerAction(Card.baron, 0, Card.noCard, Card.noCard)) self.assertEqual(player.actions[1], action) for action in player.actions[2:]: self.assertTrue(PlayerActionTools.is_blank(action))
def is_action_valid(self, action): """Tests if an action is valid given the current game state""" player = self.player() # if player is out, only valid action is no action if player.hand_card == Card.noCard: return PlayerActionTools.is_blank(action) target_player = self._players[action.player_target] player_hand = [player.hand_card, self._deck[0]] # cannot discard a card not in the hand if action.discard not in player_hand: return False new_hand_card = Game.new_hand_card(action.discard, player_hand) # countess must be discarded if the other card is king/prince if new_hand_card == Card.countess and \ (action.discard == Card.prince or action.discard == Card.king): return False # cannot target an invalid player if not self._is_valid_player_target(action.player_target): return False # cannot mis-target a card if self.player_turn() == action.player_target and action.discard in Card.only_other: return False if self.player_turn() != action.player_target and action.discard in Card.only_self: return False if not PlayerTools.is_playing(target_player): return False # Cannot guess guard or no card if action.discard == Card.guard and ( action.guess == Card.guard or action.guess == Card.noCard): return False return True
def test_move_priest(self): """Getting a priest move""" game = Game.new(4, 5) action = PlayerAction(Card.priest, 1, Card.noCard, Card.noCard) action_expected = PlayerAction(Card.priest, 1, Card.noCard, Card.guard) game, _ = game.move(action) self.assertEqual(game.round(), 0) self.assertEqual(game.player_turn(), 1) self.assertEqual(game.cards_left(), 10) self.assertTrue(game.active()) self.assertFalse(game.over()) players = game.players() player = players[0] target = players[1] recent_action = player.actions[0] self.assertTrue(PlayerTools.is_playing(target)) self.assertEqual(recent_action, action_expected) self.assertFalse(PlayerActionTools.is_blank(recent_action)) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action))
def test_move_guard_failure(self): """Getting a guard move, with a wrong guess""" game = Game.new() action = PlayerAction(Card.guard, 1, Card.handmaid, 0) game, _ = game.move(action) self.assertEqual(game.round(), 0) self.assertEqual(game.player_turn(), 1) self.assertEqual(game.cards_left(), 10) self.assertTrue(game.active()) self.assertFalse(game.over()) players = game.players() player = players[0] target = players[1] recent_action = player.actions[0] self.assertTrue(PlayerTools.is_playing(target)) self.assertEqual(recent_action, action) self.assertEqual(player.hand_card, Card.handmaid) self.assertFalse(PlayerActionTools.is_blank(recent_action)) for action in player.actions[1:]: self.assertTrue(PlayerActionTools.is_blank(action))
def test_move_guard_guess_guard(self): """Getting a guard move and guessing guard""" game = Game.new() action = PlayerAction(Card.guard, 1, Card.guard, 0) game, _ = game.move(action) self.assertEqual(game.round(), 0) self.assertEqual(game.player_turn(), 0) self.assertEqual(game.cards_left(), 11) self.assertTrue(game.active()) self.assertFalse(game.over()) players = game.players() player = players[0] target = players[1] recent_action = player.actions[0] self.assertTrue(PlayerTools.is_playing(player)) self.assertEqual(player.hand_card, Card.handmaid) self.assertEqual(game.deck()[0], Card.guard) self.assertTrue(PlayerActionTools.is_blank(recent_action)) for action in player.actions: self.assertTrue(PlayerActionTools.is_blank(action))
def opponent_turn(self): """Returns the opposing players indices""" return [idx for idx, player in enumerate(self._players) if idx != self.player_turn() and PlayerTools.is_playing(player)]
def _is_valid_player_target(self, player_target): """True iff the player can be targeted by an action""" return PlayerTools.is_playing(self._players[player_target])