From 2aa408e40e9f2201abad8e7fdb7af74d0da54b3c Mon Sep 17 00:00:00 2001 From: Dougal Scott Date: Thu, 2 Nov 2023 12:15:03 +1100 Subject: [PATCH] Randobot fixes and typing --- dominion/Card.py | 2 +- dominion/Player.py | 9 +++--- dominion/cards/Card_Cultist.py | 35 ++++++++++++----------- dominion/cards/Card_Giant.py | 1 + dominion/cards/Card_Guard_Dog.py | 18 ++++++------ dominion/cards/Card_Necromancer.py | 45 +++++++++++++++++++----------- dominion/cards/Card_Scryingpool.py | 2 +- dominion/cards/Card_Tiara.py | 26 ++++++++++------- dominion/cards/Card_Watchtower.py | 35 +++++++++++++---------- dominion/cards/old/Card_Tribute.py | 18 ++++++------ 10 files changed, 112 insertions(+), 79 deletions(-) diff --git a/dominion/Card.py b/dominion/Card.py index 8f46c64d8..4cceed278 100644 --- a/dominion/Card.py +++ b/dominion/Card.py @@ -114,7 +114,7 @@ def __init__(self) -> None: self.potion = 0 self.cards = 0 self.victory = 0 - self.required_cards: list[str] = [] + self.required_cards: list[str | tuple[str, str]] = [] self.image = None self.numcards = 10 self.retain_boon = False diff --git a/dominion/Player.py b/dominion/Player.py index 1191ba3f3..78c6c1721 100644 --- a/dominion/Player.py +++ b/dominion/Player.py @@ -552,10 +552,10 @@ def _spendable_selection(self) -> list[Option]: options = [] spendable = [_ for _ in self.piles[Piles.HAND] if _.isTreasure()] spendable.sort(key=lambda x: x.name) - totcoin = sum(self.hook_spend_value(_) for _ in spendable) + total_coin = sum(self.hook_spend_value(_) for _ in spendable) numpots = sum(1 for _ in spendable if _.name == "Potion") potstr = f", {numpots} potions" if numpots else "" - details = f"{totcoin} coin{potstr}" + details = f"{total_coin} coin{potstr}" if spendable: o = Option( selector="1", @@ -1427,7 +1427,8 @@ def gain_card( # trash: True - trash the new card # shuffle: True - shuffle the deck after gaining new card options: dict[str, Any] = {} - if not new_card: + if new_card is None: + assert card_name is not None if card_name == "Loot": pile = "Loot" else: @@ -1436,7 +1437,7 @@ def gain_card( pile = card_name new_card = self.game.get_card_from_pile(pile) - if not new_card: + if new_card is None: self.output(f"No more {card_name}") return None self.output(f"Gained a {new_card}") diff --git a/dominion/cards/Card_Cultist.py b/dominion/cards/Card_Cultist.py index e6caa08a4..85222c0a1 100755 --- a/dominion/cards/Card_Cultist.py +++ b/dominion/cards/Card_Cultist.py @@ -1,12 +1,14 @@ #!/usr/bin/env python import unittest -from dominion import Game, Card, Piles +from typing import Any + +from dominion import Game, Card, Piles, Player ############################################################################### class Card_Cultist(Card.Card): - def __init__(self): + def __init__(self) -> None: Card.Card.__init__(self) self.cardtype = [ Card.CardType.ACTION, @@ -20,36 +22,37 @@ def __init__(self): self.cost = 5 self.cards = 2 - def special(self, game, player): + def special(self, game: Game.Game, player: Player.Player) -> None: """Each other play gains a Ruins. You may play a Cultist from your hand.""" for plr in player.attack_victims(): plr.output(f"Gained a ruin from {player.name}'s Cultist") plr.gain_card("Ruins") - cultist = player.piles[Piles.HAND]["Cultist"] - if cultist: - ans = player.plr_choose_options( + if cultist := player.piles[Piles.HAND]["Cultist"]: + if player.plr_choose_options( "Play another cultist?", ("Don't play cultist", False), ("Play another cultist", True), - ) - if ans: + ): player.play_card(cultist, cost_action=False) - def hook_trash_this_card(self, game, player): + def hook_trash_this_card( + self, game: Game.Game, player: Player.Player + ) -> dict[str, Any]: """When you trash this, +3 cards""" player.pickup_cards(3) + return {} ############################################################################### class TestCultist(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.g = Game.TestGame(numplayers=2, initcards=["Cultist", "Moat"]) self.g.start_game() self.plr, self.victim = self.g.player_list() self.card = self.g.get_card_from_pile("Cultist") - def test_play(self): + def test_play(self) -> None: """Play a cultists - should give 2 cards""" self.plr.add_card(self.card, Piles.HAND) self.plr.play_card(self.card) @@ -57,7 +60,7 @@ def test_play(self): self.assertEqual(self.victim.piles[Piles.DISCARD].size(), 1) self.assertTrue(self.victim.piles[Piles.DISCARD][0].isRuin()) - def test_defense(self): + def test_defense(self) -> None: """Make sure moats work against cultists""" self.plr.add_card(self.card, Piles.HAND) moat = self.g.get_card_from_pile("Moat") @@ -66,7 +69,7 @@ def test_defense(self): self.assertEqual(self.plr.piles[Piles.HAND].size(), 7) self.assertTrue(self.victim.piles[Piles.DISCARD].is_empty()) - def test_no_other(self): + def test_no_other(self) -> None: """Don't ask to play another cultist if it doesn't exist""" self.plr.piles[Piles.HAND].set("Estate", "Estate", "Estate") self.plr.add_card(self.card, Piles.HAND) @@ -74,7 +77,7 @@ def test_no_other(self): self.plr.play_card(self.card) self.assertEqual(self.plr.test_input, ["0"]) - def test_another_cultist_no(self): + def test_another_cultist_no(self) -> None: """Don't play the other cultist""" self.plr.piles[Piles.HAND].set("Cultist", "Estate", "Estate") self.plr.add_card(self.card, Piles.HAND) @@ -82,7 +85,7 @@ def test_another_cultist_no(self): self.plr.play_card(self.card) self.assertEqual(self.plr.piles[Piles.PLAYED].size(), 1) - def test_another_cultist_yes(self): + def test_another_cultist_yes(self) -> None: """Another cultist can be played for free""" self.plr.piles[Piles.HAND].set("Cultist", "Estate", "Estate") self.plr.add_card(self.card, Piles.HAND) @@ -96,7 +99,7 @@ def test_another_cultist_yes(self): for c in self.victim.piles[Piles.DISCARD]: self.assertTrue(c.isRuin()) - def test_trash(self): + def test_trash(self) -> None: """Trashing a cultist should give 3 more cards""" self.plr.add_card(self.card, Piles.HAND) self.plr.trash_card(self.card) diff --git a/dominion/cards/Card_Giant.py b/dominion/cards/Card_Giant.py index 55f4aec74..cb43f3ef0 100755 --- a/dominion/cards/Card_Giant.py +++ b/dominion/cards/Card_Giant.py @@ -42,6 +42,7 @@ def giant_attack(victim: "Player.Player", player: "Player.Player") -> None: player.output(f"Trashed {victim.name}'s {card}") else: victim.output(f"{player.name}'s Giant discarded your {card} and cursed you") + victim.move_card(card, Piles.HAND) victim.discard_card(card) victim.gain_card("Curse") diff --git a/dominion/cards/Card_Guard_Dog.py b/dominion/cards/Card_Guard_Dog.py index f02c704e3..a8e2fbee7 100755 --- a/dominion/cards/Card_Guard_Dog.py +++ b/dominion/cards/Card_Guard_Dog.py @@ -2,14 +2,14 @@ """ http://wiki.dominionstrategy.com/index.php/Guard_Dog """ import unittest -from dominion import Card, Game, Piles +from dominion import Card, Game, Piles, Player ############################################################################### class Card_Guard_Dog(Card.Card): """Guard Dog""" - def __init__(self): + def __init__(self) -> None: Card.Card.__init__(self) self.cardtype = [Card.CardType.ACTION, Card.CardType.REACTION] self.base = Card.CardExpansion.HINTERLANDS @@ -18,12 +18,14 @@ def __init__(self): self.name = "Guard Dog" self.cost = 3 - def special(self, game, player): + def special(self, game: Game.Game, player: Player.Player) -> None: player.pickup_cards(2) if player.piles[Piles.HAND].size() <= 5: player.pickup_cards(2) - def hook_under_attack(self, game, player, attacker): + def hook_under_attack( + self, game: Game.Game, player: Player.Player, attacker: Player.Player + ) -> None: player.output(f"Playing Guard Dog as under attack by {attacker.name}") player.play_card(self, cost_action=False) @@ -32,27 +34,27 @@ def hook_under_attack(self, game, player, attacker): class TestGuardDog(unittest.TestCase): """Test Guard Dog""" - def setUp(self): + def setUp(self) -> None: self.g = Game.TestGame(numplayers=2, initcards=["Guard Dog", "Witch"]) self.g.start_game() self.plr, self.att = self.g.player_list() self.card = self.g.get_card_from_pile("Guard Dog") - def test_play_small_hand(self): + def test_play_small_hand(self) -> None: """Play a card - gain twice""" self.plr.piles[Piles.HAND].set("Copper") self.plr.add_card(self.card, Piles.HAND) self.plr.play_card(self.card) self.assertEqual(self.plr.piles[Piles.HAND].size(), 5) - def test_play_big_hand(self): + def test_play_big_hand(self) -> None: """Play a card - gain once""" self.plr.piles[Piles.HAND].set("Copper", "Silver", "Gold", "Duchy") self.plr.add_card(self.card, Piles.HAND) self.plr.play_card(self.card) self.assertEqual(self.plr.piles[Piles.HAND].size(), 6) - def test_attack(self): + def test_attack(self) -> None: """Test under attack""" self.plr.piles[Piles.HAND].set("Copper", "Guard Dog") witch = self.g.get_card_from_pile("Witch") diff --git a/dominion/cards/Card_Necromancer.py b/dominion/cards/Card_Necromancer.py index 6112b9ddd..e09f51c3a 100755 --- a/dominion/cards/Card_Necromancer.py +++ b/dominion/cards/Card_Necromancer.py @@ -1,14 +1,15 @@ #!/usr/bin/env python +""" https://wiki.dominionstrategy.com/index.php/Necromancer""" import unittest -from dominion import Card -from dominion import PlayArea -from dominion import Game, Piles +from dominion import Card, PlayArea, Game, Piles, Player + +NECROMANCER = "necromancer" ############################################################################### class Card_Necromancer(Card.Card): - def __init__(self): + def __init__(self) -> None: Card.Card.__init__(self) self.cardtype = [Card.CardType.ACTION] self.base = Card.CardExpansion.NOCTURNE @@ -21,29 +22,41 @@ def __init__(self): ("Card", "Zombie Spy"), ] - def special(self, game, player): - act = [_ for _ in game.trash_pile if _.isAction() and not _.isDuration() and _ not in game._necromancer] - card = player.card_sel(cardsrc=act, prompt="Select Action card from Trash") - game._necromancer.add(card[0]) - player.play_card(card[0], discard=False, cost_action=False) + def special(self, game: Game.Game, player: Player.Player) -> None: + """Play a non-Duration Action card from the trash, leaving it there.""" + action_cards = [ + _ + for _ in game.trash_pile + if _.isAction() + and not _.isDuration() + and _ not in game.specials[NECROMANCER] + ] + if card := player.card_sel( + cardsrc=action_cards, prompt="Select Action card from Trash" + ): + assert card[0] is not None + game.specials[NECROMANCER].add(card[0]) + player.play_card(card[0], discard=False, cost_action=False) - def setup(self, game): - game._necromancer = PlayArea.PlayArea() + def setup(self, game: Game.Game) -> None: + """Use a play area to keep track of what has been played by Necromancer this turn""" + game.specials[NECROMANCER] = PlayArea.PlayArea() - def hook_cleanup(self, game, player): - game._necromancer = PlayArea.PlayArea() + def hook_cleanup(self, game: Game.Game, player: Player.Player) -> None: + """Reset what has been played by Necromancer""" + game.specials[NECROMANCER].empty() ############################################################################### -class Test_Necromancer(unittest.TestCase): - def setUp(self): +class TestNecromancer(unittest.TestCase): + def setUp(self) -> None: self.g = Game.TestGame(numplayers=1, initcards=["Necromancer", "Moat"]) self.g.start_game() self.plr = self.g.player_list()[0] self.card = self.g.get_card_from_pile("Necromancer") self.plr.add_card(self.card, Piles.HAND) - def test_play(self): + def test_play(self) -> None: """Play a Necromancer""" self.plr.piles[Piles.DECK].set("Gold", "Silver") self.plr.test_input = ["Zombie Spy", "Keep"] diff --git a/dominion/cards/Card_Scryingpool.py b/dominion/cards/Card_Scryingpool.py index 915ffac93..afa808fa7 100755 --- a/dominion/cards/Card_Scryingpool.py +++ b/dominion/cards/Card_Scryingpool.py @@ -33,7 +33,7 @@ def special(self, game: "Game.Game", player: "Player.Player") -> None: if not top_card.isAction(): break for card in revealed: - player.add_card(card, Piles.HAND) + player.move_card(card, Piles.HAND) ############################################################################### diff --git a/dominion/cards/Card_Tiara.py b/dominion/cards/Card_Tiara.py index 07b510f8d..2bbe0e113 100755 --- a/dominion/cards/Card_Tiara.py +++ b/dominion/cards/Card_Tiara.py @@ -1,14 +1,16 @@ #!/usr/bin/env python """https://wiki.dominionstrategy.com/index.php/Tiara """ import unittest -from dominion import Game, Card, Piles +from typing import Any + +from dominion import Game, Card, Piles, Player ############################################################################### class Card_Tiara(Card.Card): """Tiara""" - def __init__(self): + def __init__(self) -> None: Card.Card.__init__(self) self.cardtype = Card.CardType.TREASURE self.base = Card.CardExpansion.PROSPERITY @@ -18,38 +20,42 @@ def __init__(self): self.cost = 4 self.buys = 1 - def special(self, game, player): + def special(self, game: Game.Game, player: Player.Player) -> None: """Play a treasure from your hand twice""" treasures = [_ for _ in player.piles[Piles.HAND] if _.isTreasure()] if not treasures: return player.output("Select treasure that Tiara will let you play twice") + player.move_card(self, Piles.PLAYED) if treasure := player.card_sel(cardsrc=treasures): for _ in range(2): player.play_card(treasure[0], discard=False, cost_action=False) - player.move_card(treasure[0], Piles.DISCARD) + player.move_card(treasure[0], Piles.PLAYED) - def hook_gain_card(self, game, player, card): + def hook_gain_card( + self, game: Game.Game, player: Player.Player, card: Card.Card + ) -> dict[str, Any]: """when you gain a card, you may put it onto your deck.""" - if top_deck := player.plr_choose_options( + if player.plr_choose_options( f"Tiara lets you put {card} on top of your deck.", (f"Put {card} on top of your deck?", True), (f"Discard {card} as per normal?", False), ): return {"destination": "topdeck"} + return {} ############################################################################### class TestTiara(unittest.TestCase): """Test Tiara""" - def setUp(self): + def setUp(self) -> None: self.g = Game.TestGame(numplayers=1, initcards=["Tiara"]) self.g.start_game() self.plr = self.g.player_list()[0] self.card = self.g.get_card_from_pile("Tiara") - def test_play_deck(self): + def test_play_deck(self) -> None: """Play a Tiara and put gained cards on to the deck""" self.plr.piles[Piles.HAND].empty() self.plr.add_card(self.card, Piles.HAND) @@ -60,7 +66,7 @@ def test_play_deck(self): self.plr.gain_card("Gold") self.assertIn("Gold", self.plr.piles[Piles.DECK]) - def test_discard(self): + def test_discard(self) -> None: """Play a tiara and discard gained cards""" self.plr.piles[Piles.HAND].empty() self.plr.add_card(self.card, Piles.HAND) @@ -70,7 +76,7 @@ def test_discard(self): self.assertNotIn("Gold", self.plr.piles[Piles.DECK]) self.assertIn("Gold", self.plr.piles[Piles.DISCARD]) - def test_treasure(self): + def test_treasure(self) -> None: """Play a tiara and play a treasure twice""" self.plr.piles[Piles.HAND].set("Copper", "Duchy", "Province") self.plr.add_card(self.card, Piles.HAND) diff --git a/dominion/cards/Card_Watchtower.py b/dominion/cards/Card_Watchtower.py index 8f6b93ade..28ad90184 100755 --- a/dominion/cards/Card_Watchtower.py +++ b/dominion/cards/Card_Watchtower.py @@ -1,14 +1,16 @@ #!/usr/bin/env python - +""" https://wiki.dominionstrategy.com/index.php/Watchtower""" import unittest -from dominion import Card, Game, Piles +from typing import Any + +from dominion import Card, Game, Piles, Player ############################################################################### class Card_Watchtower(Card.Card): """Watchtower""" - def __init__(self): + def __init__(self) -> None: Card.Card.__init__(self) self.cardtype = [Card.CardType.ACTION, Card.CardType.REACTION] self.desc = """Draw until you have 6 cards in hand. @@ -18,26 +20,27 @@ def __init__(self): self.name = "Watchtower" self.cost = 3 - def special(self, game, player): + def special(self, game: Game.Game, player: Player.Player) -> None: """Draw until you have 6 cards in hand.""" player.pick_up_hand(6) - def hook_gain_card(self, game, player, card): + def hook_gain_card( + self, game: Game.Game, player: Player.Player, card: Card.Card + ) -> dict[str, Any]: """When you gain a card, you may reveal this from your hand. If you do, either trash that card, or put it on top of your deck""" + options: dict[str, Any] = {} act = player.plr_choose_options( "What to do with Watchtower?", ("Do nothing", "nothing"), - (f"Trash {card.name}", "trash"), - (f"Put {card.name} on top of deck", "topdeck"), + (f"Trash {card}", "trash"), + (f"Put {card} on top of deck", "topdeck"), ) if act == "trash": options = {"trash": True} elif act == "topdeck": options = {"destination": "topdeck"} - else: - options = {} return options @@ -45,20 +48,22 @@ def hook_gain_card(self, game, player, card): class Test_Watchtower(unittest.TestCase): """Test Watchtower""" - def setUp(self): - self.g = Game.TestGame(numplayers=1, initcards=["Watchtower"], badcards=["Necromancer"]) + def setUp(self) -> None: + self.g = Game.TestGame( + numplayers=1, initcards=["Watchtower"], badcards=["Necromancer"] + ) self.g.start_game() self.plr = self.g.player_list()[0] self.card = self.g.get_card_from_pile("Watchtower") - def test_play(self): + def test_play(self) -> None: """Play a watch tower""" self.plr.piles[Piles.HAND].set("Gold") self.plr.add_card(self.card, Piles.HAND) self.plr.play_card(self.card) self.assertEqual(self.plr.piles[Piles.HAND].size(), 6) - def test_react_nothing(self): + def test_react_nothing(self) -> None: """React to gaining a card - but do nothing""" self.plr.piles[Piles.HAND].set("Gold") self.plr.add_card(self.card, Piles.HAND) @@ -68,7 +73,7 @@ def test_react_nothing(self): self.assertEqual(self.plr.piles[Piles.DISCARD].size(), 1) self.assertEqual(self.plr.piles[Piles.HAND].size(), 2) - def test_react_trash(self): + def test_react_trash(self) -> None: """React to gaining a card - discard card""" tsize = self.g.trash_pile.size() try: @@ -84,7 +89,7 @@ def test_react_trash(self): self.g.print_state() raise - def test_react_topdeck(self): + def test_react_top_deck(self) -> None: """React to gaining a card - put card on deck""" tsize = self.g.trash_pile.size() self.plr.test_input = ["top"] diff --git a/dominion/cards/old/Card_Tribute.py b/dominion/cards/old/Card_Tribute.py index 62b7acdd2..2dc47c380 100755 --- a/dominion/cards/old/Card_Tribute.py +++ b/dominion/cards/old/Card_Tribute.py @@ -2,14 +2,14 @@ """http://wiki.dominionstrategy.com/index.php/Tribute """ import unittest -from dominion import Card, Game, Piles +from dominion import Card, Game, Piles, Player ############################################################################### class Card_Tribute(Card.Card): """Tribute""" - def __init__(self): + def __init__(self) -> None: Card.Card.__init__(self) self.cardtype = Card.CardType.ACTION self.base = Card.CardExpansion.INTRIGUE @@ -20,18 +20,20 @@ def __init__(self): self.name = "Tribute" self.cost = 5 - def special(self, game, player): + def special(self, game: Game.Game, player: Player.Player) -> None: """Tribute Special""" victim = game.player_to_left(player) cards = [] for _ in range(2): card = victim.next_card() + if card is None: + continue victim.reveal_card(card) cards.append(card) card_name = None for card in cards: - player.output(f"Looking at {card.name} from {victim.name}") - victim.output(f"{player.name}'s Tribute discarded {card.name}") + player.output(f"Looking at {card} from {victim.name}") + victim.output(f"{player.name}'s Tribute discarded {card}") victim.add_card(card, "discard") if card.name == card_name: player.output("Duplicate - no extra") @@ -52,14 +54,14 @@ def special(self, game, player): class TestTribute(unittest.TestCase): """Test Tribute""" - def setUp(self): + def setUp(self) -> None: self.g = Game.TestGame(numplayers=2, oldcards=True, initcards=["Tribute"]) self.g.start_game() self.plr, self.victim = self.g.player_list() self.card = self.g.get_card_from_pile("Tribute") self.plr.add_card(self.card, Piles.HAND) - def test_play(self): + def test_play(self) -> None: """Play a tribute""" self.victim.piles[Piles.DECK].set("Copper", "Estate") self.plr.play_card(self.card) @@ -67,7 +69,7 @@ def test_play(self): self.assertEqual(self.plr.piles[Piles.HAND].size(), 7) self.assertEqual(self.victim.piles[Piles.DISCARD].size(), 2) - def test_same(self): + def test_same(self) -> None: """Victim has the same cards for Tribute""" self.victim.piles[Piles.DECK].set("Tribute", "Tribute") self.plr.play_card(self.card)