diff --git a/card/named/cellar.py b/card/named/cellar.py index da3d5d5..b58d106 100644 --- a/card/named/cellar.py +++ b/card/named/cellar.py @@ -23,5 +23,5 @@ class Cellar(Action): self._Card__owner.draw_cards(cards_discarded) def __get_index(self, message): - return self.__Card_owner.get_general_input(message, int) + return self.__Card_owner.take_input(message, int) diff --git a/card/named/merchant.py b/card/named/merchant.py index cf0b9b4..2e9fed4 100644 --- a/card/named/merchant.py +++ b/card/named/merchant.py @@ -13,5 +13,5 @@ class Merchant(Action): self._Card__owner.add_purchase_power(3) def __get_Merchant_input(self, message): - return self.__Card_owner.get_general_input("Player " + str(self._Card__owner.get_player_index()) + ", " + - message, str) + return self.__Card_owner.take_input("Player " + str(self._Card__owner.get_player_index()) + ", " + + message, str) diff --git a/card/named/militia.py b/card/named/militia.py index 6711e1b..00ae47c 100644 --- a/card/named/militia.py +++ b/card/named/militia.py @@ -11,9 +11,9 @@ class Militia(Action, Attack): def __force_discard(self, chances, player): if player.get_hand().get_remaining() > 3 and chances > 0: - hand_index = player.militia_input("\nPlease provide an index to identify a card from hand you would like to" - " discard (0 to " + str(player.get_hand().get_remaining() - 1) + "): " - , int) + hand_index = player.get_response_input("\nPlease provide an index to identify a card from hand you would " + "like to discard (0 to " + str(player.get_hand().get_remaining() - 1) + + "): ", int, self.__class__) self.__check_discard(hand_index, player, chances) elif self.get_owner().get_hand().get_remaining() > 3 and chances <= 0: print("You're out of chances to select a valid card to discard, randomly selecting for you.") diff --git a/card/named/moat.py b/card/named/moat.py index d15679b..204bd8b 100644 --- a/card/named/moat.py +++ b/card/named/moat.py @@ -5,6 +5,6 @@ from card.basic.card_reaction import Reaction class Moat(Action, Reaction): def react(self, what_attack): owner = self.get_owner() - return "Y" == owner.get_general_input("Player " + str(owner.get_player_index()) + ", enter 'Y' if you'd " + return "Y" == owner.take_input("Player " + str(owner.get_player_index()) + ", enter 'Y' if you'd " "like to reveal " + str(self) + " to block the " + str(what_attack) + " attack: ", str) diff --git a/card/special/card_gain.py b/card/special/card_gain.py index bf724c4..0238416 100644 --- a/card/special/card_gain.py +++ b/card/special/card_gain.py @@ -26,7 +26,7 @@ class CardGain(Card): chances = 0 def __get_gain_card(self): - return self.get_owner().get_general_input("\nPlease identify the index of which card you would like to " + return self.get_owner().take_input("\nPlease identify the index of which card you would like to " "obtain: ", int) def __get_gainable_cards(self, spending_limit): diff --git a/card/special/card_trash.py b/card/special/card_trash.py index 8925171..8684259 100644 --- a/card/special/card_trash.py +++ b/card/special/card_trash.py @@ -29,7 +29,7 @@ class CardTrash(Card): self.trash_card_get_cost() def __get_card_to_trash(self): - return self.get_owner().get_general_input("\nPlease identify the index of the desired card to trash: ", int) + return self.get_owner().take_input("\nPlease identify the index of the desired card to trash: ", int) def __get_trashable_cards(self): result = list() diff --git a/game.py b/game.py index 91c1d80..a47cb49 100644 --- a/game.py +++ b/game.py @@ -1,6 +1,8 @@ from table.table import Table from player.human import Human from player.bots.pure_big_money import Pure_Big_Money +from player.bots.smithy_big_money import Smithy_Big_Money +from player.bots.militia_big_money import Militia_Big_Money from card.basic.card_action import Action from card.basic.card_curse import Curse from card.basic.card_victory import Victory @@ -56,7 +58,12 @@ def setup_new_game(game_list, parameter, card_info): t.add_player(human) for i in range(bots): - bot = Pure_Big_Money(t) + if i % 3 == 0: + bot = Militia_Big_Money(t) + elif i % 3 == 1: + bot = Smithy_Big_Money(t) + else: + bot = Pure_Big_Money(t) bot.draw_deck(t, get_starting_deck()) bot.draw_hand() t.add_player(bot) @@ -66,7 +73,7 @@ def setup_new_game(game_list, parameter, card_info): def get_game_parameters(): # humans, bots, card #1, card #2, ... etc - return [2, 1, True, True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True] + return [0, 3, True, True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True] def get_card_info(): diff --git a/player/bot.py b/player/bot.py new file mode 100644 index 0000000..f7ef929 --- /dev/null +++ b/player/bot.py @@ -0,0 +1,105 @@ +from player.player import Player +from card.basic.card_action import Action +from card.basic.card_treasure import Treasure +from card.named.militia import Militia + + +class Bot(Player): + target_card = None + target_card_threshold = 0 + + def get_play_input(self, message, target_type, restriction): + if restriction == Treasure: + choice = self.get_subclass_input(Treasure) + elif restriction == Action: + choice = self.get_subclass_input(Action) + else: + choice = -1 + + print(message + str(choice)) + return choice + + def get_response_input(self, message, target_type, restriction): + if restriction == Militia: + choice = self.get_militia_input() + else: + choice = -1 + return choice + + def can_afford_on_avg(self, card_name): + card_cost = self.get_table().get_piles()[self.get_table().get_pile_index_of_card(card_name)].get_card_group()\ + .get_cost() + total_coin = 0 + hand = self.get_hand().get_supply() + deck = self.get_deck().get_supply() + discard = self.get_discard().get_supply() + supplies = hand + deck + discard + + for c in supplies: + total_coin += c.get_purchase_power() + return card_cost <= total_coin/(len(hand) + len(deck) + len(discard)) + + def has_how_many_of_card(self, card_name): + total = 0 + hand = self.get_hand().get_supply() + deck = self.get_deck().get_supply() + discard = self.get_discard().get_supply() + supplies = hand + deck + discard + + for c in supplies: + if c.get_name() == card_name: + total += 1 + return total + + #This will pick either the first or the first least effective purchasing card as this bot doesn't care about that + def get_militia_input(self): + choice = self.__get_first_non_Treasure() + min_coin = self.get_hand().get_supply()[choice].get_purchase_power() + + for c in self.get_hand().get_supply(): + # We want to do isinstance rather than not isinstance because we only want to evaluate this loop when we are + # evaluating an all treasure card hand as at that point the choice will be a treasure card, otherwise the + # choice will already be non-treasure and we don't need to check anything since this bot doesn't do action + if c.get_purchase_power() < min_coin and isinstance(c, Treasure): + min_coin = c.get_purchase_power() + choice = self.get_hand().get_supply().index(c) + return choice + + # This method will only be called when it is time to buy things, a very simple logic will decide its action. + def get_buy_input(self, message, target_type): + coin = self.get_coin() + choice = -1 + + if coin >= self.target_card_threshold and not self.get_table().pile_is_empty(self.target_card) and \ + self.has_how_many_of_card(self.target_card) < 3: + choice = self.get_table().get_pile_index_of_card(self.target_card) + elif coin >= 8 and not self.get_table().pile_is_empty("Province"): + choice = self.get_table().get_pile_index_of_card("Province") + elif coin >= 8 and not self.get_table().pile_is_empty("Dutchy"): + choice = self.get_table().get_pile_index_of_card("Dutchy") + elif coin >= 6 and not self.get_table().pile_is_empty("Gold") and not \ + self.get_table().pile_is_empty("Province"): + choice = self.get_table().get_pile_index_of_card("Gold") + elif coin >= 5 and not self.get_table().pile_is_empty("Dutchy") and \ + (self.can_afford_on_avg("Province") or self.get_table().pile_is_empty("Province")): + choice = self.get_table().get_pile_index_of_card("Dutchy") + elif coin >= 3 and not self.get_table().pile_is_empty("Silver"): + choice = self.get_table().get_pile_index_of_card("Silver") + + print(message + str(choice)) + return choice + + def get_subclass_input(self, subclass): + choice = -1 + hand = self.get_hand().get_supply() + + for c in hand: + if isinstance(c, subclass): + choice = hand.index(c) + return choice + + def __get_first_non_Treasure(self): + for c in self.get_hand().get_supply(): + if not isinstance(c, Treasure): + return self.get_hand().get_supply().index(c) + return 0 \ No newline at end of file diff --git a/player/bots/militia_big_money.py b/player/bots/militia_big_money.py new file mode 100644 index 0000000..0add0c2 --- /dev/null +++ b/player/bots/militia_big_money.py @@ -0,0 +1,9 @@ +from player.bot import Bot + + +class Militia_Big_Money(Bot): + target_card = "Militia" + target_card_threshold = 4 + + def __str__(self): + return "Player " + str(self.get_player_index()) + " (militia big money bot)" \ No newline at end of file diff --git a/player/bots/pure_big_money.py b/player/bots/pure_big_money.py index 8513dd3..c427cc7 100644 --- a/player/bots/pure_big_money.py +++ b/player/bots/pure_big_money.py @@ -1,59 +1,12 @@ -from player.player import Player -from card.basic.card_treasure import Treasure +from player.bot import Bot -class Pure_Big_Money(Player): +class Pure_Big_Money(Bot): + target_card = "Province" + target_card_threshold = 8 + def take_action(self): print("\nAs a BIG MONEY BOT, I'm skipping this unnecessary action phase. Beep-boop, bow to me humans!") - #This method will only be called for this bot when it is time to play treasures, it will play all of them always. - def get_play_input(self, message, target_type): - choice = -1 - hand = self.get_hand().get_supply() - - for c in hand: - if isinstance(c, Treasure): - choice = hand.index(c) - - print(message + str(choice)) - return choice - - #This method will only be called when it is time to buy things, a very simple logic will decide its action. - def get_buy_input(self, message, target_type): - coin = self.get_coin() - choice = -1 - - if coin >= 8: - choice = self.get_table().get_pile_index_of_card("Province") - elif coin >= 6: - choice = self.get_table().get_pile_index_of_card("Gold") - elif coin >= 3: - choice = self.get_table().get_pile_index_of_card("Silver") - - print(message + str(choice)) - return choice - - #This will pick either the first or the first least effective purchasing card as this bot doesn't care about that - def militia_input(self, message, target_type): - choice = self.__get_first_non_Treasure() - min_coin = self.get_hand().get_supply()[choice].get_purchase_power() - - for c in self.get_hand().get_supply(): - # We want to do isinstance rather than not isinstance because we only want to evaluate this loop when we are - # evaluating an all treasure card hand as at that point the choice will be a treasure card, otherwise the - # choice will already be non-treasure and we don't need to check anything since this bot doesn't do action - if c.get_purchase_power() < min_coin and isinstance(c, Treasure): - min_coin = c.get_purchase_power() - choice = self.get_hand().get_supply().index(c) - - print(message + str(choice)) - return choice - - def __get_first_non_Treasure(self): - for c in self.get_hand().get_supply(): - if not isinstance(c, Treasure): - return self.get_hand().get_supply().index(c) - return 0 - def __str__(self): return "Player " + str(self.get_player_index()) + " (pure big money bot)" diff --git a/player/bots/smithy_big_money.py b/player/bots/smithy_big_money.py new file mode 100644 index 0000000..b73d175 --- /dev/null +++ b/player/bots/smithy_big_money.py @@ -0,0 +1,9 @@ +from player.bot import Bot + + +class Smithy_Big_Money(Bot): + target_card = "Smithy" + target_card_threshold = 5 + + def __str__(self): + return "Player " + str(self.get_player_index()) + " (smithy big money bot)" \ No newline at end of file diff --git a/player/hand.py b/player/hand.py index 87d1959..e946a28 100644 --- a/player/hand.py +++ b/player/hand.py @@ -22,7 +22,11 @@ class Hand(Supply): attack_blocked = False for c in self.get_supply(): attack_blocked |= c.react(what_attack) - return attack_blocked + if attack_blocked: + print(str(c.get_owner()) + " has " + str(c) + " as the " + str(self.get_supply().index(c)) + + ' and blocked the ' + what_attack + " attack.") + return True + return False def __get_unique_class_instances(self): unique_class_instances = list() diff --git a/player/human.py b/player/human.py index b06cb39..dd43e68 100644 --- a/player/human.py +++ b/player/human.py @@ -5,5 +5,3 @@ class Human(Player): def __str__(self): return "Player " + str(self.get_player_index()) + " (human)" - def militia_input(self, message, target_type): - return self.get_general_input(message, target_type) diff --git a/player/player.py b/player/player.py index 0c492e1..9bcb466 100644 --- a/player/player.py +++ b/player/player.py @@ -39,6 +39,9 @@ class Player: def get_discard(self): return self.__discard + def get_deck(self): + return self.__deck + def get_player_index(self): return self.__table.get_players().index(self) @@ -103,18 +106,30 @@ class Player: def discard_from_hand(self, n): self.__hand.transfer_card_by_index(n, self.__discard) - def play_card(self, acceptable_card_class, chances, counter): - if chances > 0 and self.__hand.contains_one_of(acceptable_card_class): - hand_index = self.get_play_input("\nPlease identify a card from hand to play by providing its index: ", int) - self.__check_play_card(hand_index, counter, acceptable_card_class, chances) - elif chances <= 0: - print("You have used up all of your chances to enter a valid integer; forfeiting remaining plays.") - if counter is not None: - counter.int = 0 - else: - print("There are no more acceptable card in hand, moving to next phase.") - if counter is not None: - counter.int = 0 + def claim_top_card(self, supply): + supply.get_top_card().set_owner(self) + + def print_hand(self): + print("\nPlayer " + str(self.__table.get_players().index(self)) + " Hand:") + self.__hand.print() + + def take_turn(self): + self.__turn_setup() + self.__print() + self.take_action() + self.take_buy() + self.discard_remaining_hand() + self.draw_hand() + + # The following two methods are identical under different names so they can be overridden by bot classes later + def get_play_input(self, message, target_type, card_restriction): + return self.__get_input(self.__std_chances, target_type, message) + + def get_buy_input(self, message, target_type): + return self.__get_input(self.__std_chances, target_type, message) + + def get_response_input(self, message, target_type, card_restriction): + return self.__get_input(self.__std_chances, target_type, message) def take_action(self): print("\nPlease play an Action card until you have no remaining actions.") @@ -130,6 +145,20 @@ class Player: self.play_card(Treasure, self.__std_chances, play_another) self.buy_card(self.__std_chances) + def play_card(self, acceptable_card_class, chances, counter): + if chances > 0 and self.__hand.contains_one_of(acceptable_card_class): + hand_index = self.get_play_input("\nPlease identify a card from hand to play by providing its index: ", int, + acceptable_card_class) + self.__check_play_card(hand_index, counter, acceptable_card_class, chances) + elif chances <= 0: + print("You have used up all of your chances to enter a valid integer; forfeiting remaining plays.") + if counter is not None: + counter.int = 0 + else: + print("There are no more acceptable card in hand, moving to next phase.") + if counter is not None: + counter.int = 0 + def buy_card(self, chances): self.__table.print() while self.__buys > 0 and not self.__table.are_there_three_empty_piles() and chances > 0: @@ -156,22 +185,6 @@ class Player: self.claim_top_card(self.__discard) chances = self.get_std_chances() - def take_turn(self): - self.__turn_setup() - self.__print() - self.take_action() - self.take_buy() - self.discard_remaining_hand() - self.draw_hand() - self.print_hand() - - def claim_top_card(self, supply): - supply.get_top_card().set_owner(self) - - def print_hand(self): - print("\nPlayer " + str(self.__table.get_players().index(self)) + " Hand:") - self.__hand.print() - def __check_play_card(self, hand_index, counter, acceptable_card_class, chances): if hand_index < 0: print("You have elected to forfeit any remaining plays.") @@ -192,19 +205,6 @@ class Player: print("Index in bounds but not an acceptable card type. Chance to get it right reduced.") self.play_card(acceptable_card_class, chances - 1, counter) - # The following two methods are identical under different names so they can be overridden by bot classes later - def get_play_input(self, message, target_type): - return self.get_general_input(message, target_type) - - def get_buy_input(self, message, target_type): - return self.get_general_input(message, target_type) - - def militia_input(self, message, target_type): - return self.get_general_input(message, target_type) - - def get_general_input(self, message, target_type): - return self.__get_input(self.__std_chances, target_type, message) - def __get_input(self, chances, target_type, message): value = input(message) if chances > 0: diff --git a/table/supply.py b/table/supply.py index 6157e7a..67c1cc7 100644 --- a/table/supply.py +++ b/table/supply.py @@ -9,6 +9,9 @@ class Supply: for i in range(n): self.add_card(card) + def is_empty(self): + return len(self.__card) <= 0 + def get_supply(self): return self.__card diff --git a/table/table.py b/table/table.py index 2adb2dd..1ed4d0d 100644 --- a/table/table.py +++ b/table/table.py @@ -46,6 +46,9 @@ class Table: result = self.__pile.index(p) return result + def pile_is_empty(self, card_name): + return self.__pile[self.get_pile_index_of_card(card_name)].is_empty() + def are_there_three_empty_piles(self): count = 0 for p in self.__pile: @@ -67,7 +70,7 @@ class Table: should_continue = True while should_continue: # game ends after - should_continue = not self.should_game_end() or player_turn % len(self.__player) != 0 + should_continue = not self.should_game_end() or player_turn % len(self.__player) != len(self.__player) - 1 self.print() self.__player[player_turn % len(self.__player)].take_turn() player_turn += 1