diff --git a/backend/bang/cards.py b/backend/bang/cards.py index 22eb09a..df8ceec 100644 --- a/backend/bang/cards.py +++ b/backend/bang/cards.py @@ -371,14 +371,18 @@ class Birra(Card): player.game.deck.draw(True, player=p) p.notify_self() if "gold_rush" in player.game.expansions and self.number != 42: - from bang.players import PendingAction - - player.available_cards = [ - {"name": "Pepita", "icon": "💵️", "alt_text": "1", "noDesc": True}, - self, - ] - player.choose_text = "choose_birra_function" - player.pending_action = PendingAction.CHOOSE + player.set_choose_action( + "choose_birra_function", + [ + { + "name": "Pepita", + "icon": "💵️", + "alt_text": "1", + "noDesc": True, + }, + self, + ], + ) player.notify_self() return True if ( diff --git a/backend/bang/expansions/the_valley_of_shadows/cards.py b/backend/bang/expansions/the_valley_of_shadows/cards.py index b178257..53f02d8 100644 --- a/backend/bang/expansions/the_valley_of_shadows/cards.py +++ b/backend/bang/expansions/the_valley_of_shadows/cards.py @@ -60,11 +60,7 @@ class Lemat(Card): for p in player.game.get_visible_players(player) ) ): - from bang.players import PendingAction - - player.available_cards = player.hand.copy() - player.pending_action = PendingAction.CHOOSE - player.choose_text = "choose_play_as_bang" + player.set_choose_action("choose_play_as_bang", player.hand.copy()) player.notify_self() return False diff --git a/backend/bang/expansions/the_valley_of_shadows/characters.py b/backend/bang/expansions/the_valley_of_shadows/characters.py index cdea867..af771a8 100644 --- a/backend/bang/expansions/the_valley_of_shadows/characters.py +++ b/backend/bang/expansions/the_valley_of_shadows/characters.py @@ -21,12 +21,11 @@ class BlackFlower(Character): for p in player.game.get_visible_players(player) ) ) and super().special(player, data): - from bang.players import PendingAction - - player.available_cards = [c for c in player.hand if c.suit == cs.Suit.CLUBS] player.special_use_count += 1 - player.pending_action = PendingAction.CHOOSE - player.choose_text = "choose_play_as_bang" + player.set_choose_action( + "choose_play_as_bang", + [c for c in player.hand if c.suit == cs.Suit.CLUBS], + ) player.notify_self() diff --git a/backend/bang/expansions/train_robbery/stations.py b/backend/bang/expansions/train_robbery/stations.py index ed774af..e9c50ef 100644 --- a/backend/bang/expansions/train_robbery/stations.py +++ b/backend/bang/expansions/train_robbery/stations.py @@ -1,5 +1,9 @@ +from typing import TYPE_CHECKING import bang.cards as cs +if TYPE_CHECKING: + from bang.players import Player + class StationCard: def __init__(self, name: str): @@ -7,8 +11,9 @@ class StationCard: self.expansion = "train_robbery" self.price: list[dict] = [] - def card_check(self, card: cs.Card): + def check_price(self, player: "Player") -> bool: """Check if the card can be used to rob the train""" + return len(player.hand) > 0 class BoomTown(StationCard): @@ -18,8 +23,11 @@ class BoomTown(StationCard): super().__init__("Boom Town") self.price = [cs.Bang(0, 0).__dict__] - def card_check(self, card: cs.Card): - return isinstance(card, cs.Bang) + def check_price(self, player: "Player"): + if super().check_price(player) and all( + not isinstance(c, cs.Bang) for c in player.hand + ): + return False class Caticor(StationCard): @@ -29,8 +37,12 @@ class Caticor(StationCard): super().__init__("Caticor") self.price = [cs.CatBalou(0, 0).__dict__, cs.Panico(0, 0).__dict__] - def card_check(self, card: cs.Card): - return isinstance(card, cs.CatBalou) or isinstance(card, cs.Panico) + def check_price(self, player: "Player"): + if super().check_price(player) and all( + not (isinstance(card, cs.CatBalou) or isinstance(card, cs.Panico)) + for card in player.hand + ): + return False class CreepyCreek(StationCard): @@ -40,8 +52,11 @@ class CreepyCreek(StationCard): super().__init__("Creepy Creek") self.price = [{"icon": "♠️"}] - def card_check(self, card: cs.Card): - return card.suit == cs.Suit.SPADES + def check_price(self, player: "Player"): + if super().check_price(player) and all( + card.suit != cs.Suit.SPADES for card in player.hand + ): + return False class CrownsHole(StationCard): @@ -51,8 +66,11 @@ class CrownsHole(StationCard): super().__init__("Crown's Hole") self.price = [cs.Birra(0, 0).__dict__] - def card_check(self, card: cs.Card): - return isinstance(card, cs.Birra) + def check_price(self, player: "Player"): + if super().check_price(player) and all( + not isinstance(card, cs.Birra) for card in player.hand + ): + return False class Deadwood(StationCard): @@ -62,8 +80,11 @@ class Deadwood(StationCard): super().__init__("Deadwood") self.price = [{"is_equipment": True}] - def card_check(self, card: cs.Card): - return card.is_equipment + def check_price(self, player: "Player"): + if super().check_price(player) and all( + not card.is_equipment for card in player.hand + ): + return False class Dodgeville(StationCard): @@ -73,8 +94,11 @@ class Dodgeville(StationCard): super().__init__("Dodgeville") self.price = [cs.Mancato(0, 0).__dict__] - def card_check(self, card: cs.Card): - return isinstance(card, cs.Mancato) + def check_price(self, player: "Player"): + if super().check_price(player) and all( + not isinstance(card, cs.Mancato) for card in player.hand + ): + return False class FortWorth(StationCard): @@ -84,8 +108,11 @@ class FortWorth(StationCard): super().__init__("Fort Worth") self.price = [{"icon": "10\nJ\nQ\nK\nA"}] - def card_check(self, card: cs.Card): - return card.number in {1, 10, 11, 12, 13} + def check_price(self, player: "Player"): + if super().check_price(player) and all( + card.number not in {1, 10, 11, 12, 13} for card in player.hand + ): + return False class Frisco(StationCard): @@ -95,8 +122,11 @@ class Frisco(StationCard): super().__init__("Frisco") self.price = [{"icon": "♣️"}] - def card_check(self, card: cs.Card): - return card.suit == cs.Suit.CLUBS + def check_price(self, player: "Player"): + if super().check_price(player) and all( + card.suit != cs.Suit.CLUBS for card in player.hand + ): + return False class MinersOath(StationCard): @@ -106,8 +136,11 @@ class MinersOath(StationCard): super().__init__("Miner's Oath") self.price = [{"icon": "♦️"}] - def card_check(self, card: cs.Card): - return card.suit == cs.Suit.DIAMONDS + def check_price(self, player: "Player"): + if super().check_price(player) and all( + card.suit != cs.Suit.DIAMONDS for card in player.hand + ): + return False class SanTafe(StationCard): @@ -117,8 +150,11 @@ class SanTafe(StationCard): super().__init__("San Tafe") self.price = [{"icon": "♥️"}] - def card_check(self, card: cs.Card): - return card.suit == cs.Suit.HEARTS + def check_price(self, player: "Player"): + if super().check_price(player) and all( + card.suit != cs.Suit.HEARTS for card in player.hand + ): + return False class Tombrock(StationCard): @@ -128,8 +164,9 @@ class Tombrock(StationCard): super().__init__("Tombrock") self.price = [{"icon": "💔"}] - def card_check(self, card: cs.Card): - return True + def check_price(self, player: "Player"): + if player.lives <= 1: + return False class Yooma(StationCard): @@ -139,8 +176,11 @@ class Yooma(StationCard): super().__init__("Yooma") self.price = [{"icon": "2-9"}] - def card_check(self, card: cs.Card): - return 2 <= card.number <= 9 + def check_price(self, player: "Player"): + if super().check_price(player) and all( + not (2 <= card.number <= 9) for card in player.hand + ): + return False class VirginiaTown(StationCard): @@ -150,8 +190,9 @@ class VirginiaTown(StationCard): super().__init__("Virginia Town") self.price = [{}, {}] - def card_check(self, card: cs.Card): - return True + def check_price(self, player: "Player"): + if super().check_price(player) and len(player.hand < 2): + return False def get_all_stations(): diff --git a/backend/bang/players.py b/backend/bang/players.py index a4fc9aa..47230e1 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -17,6 +17,8 @@ import bang.expansions.gold_rush.shop_cards as grc import bang.expansions.gold_rush.characters as grch import bang.expansions.the_valley_of_shadows.cards as tvosc import bang.expansions.the_valley_of_shadows.characters as tvosch +import bang.expansions.train_robbery.stations as trs +import bang.expansions.train_robbery.trains as trt from typing import List, TYPE_CHECKING from metrics import Metrics from globals import G @@ -280,25 +282,25 @@ class Player: if self.pending_action == PendingAction.DRAW and self.game.check_event( ce.Peyote ): - self.available_cards = [ - {"icon": "🔴", "noDesc": True}, - {"icon": "⚫", "noDesc": True}, - ] self.is_drawing = True - self.choose_text = "choose_guess" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_guess", + [ + {"icon": "🔴", "noDesc": True}, + {"icon": "⚫", "noDesc": True}, + ], + ) elif ( self.can_play_ranch and self.pending_action == PendingAction.PLAY and self.game.check_event(ce.Ranch) ): self.can_play_ranch = False - self.available_cards = [c for c in self.hand] self.discarded_cards = [] - self.available_cards.append({"icon": "✅", "noDesc": True}) self.is_playing_ranch = True - self.choose_text = "choose_ranch" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_ranch", [c for c in self.hand] + [{"icon": "✅", "noDesc": True}] + ) elif ( self.character and self.character.check(self.game, chars.SuzyLafayette) @@ -338,9 +340,7 @@ class Player: self.game.players[self.game.turn].notify_self() self.scrapped_cards = 0 self.previous_pending_action = self.pending_action - self.pending_action = PendingAction.CHOOSE - self.choose_text = "choose_sid_scrap" - self.available_cards = self.hand + self.set_choose_action("choose_sid_scrap", self.hand) self.lives += 1 ser = self.__dict__.copy() @@ -782,35 +782,36 @@ class Player: for p in self.game.get_alive_players() ) ): - self.available_cards = [ - { - "name": p.name, - "icon": p.role.icon - if (self.game.initial_players == 3) - else "⭐️" - if isinstance(p.role, r.Sheriff) - else "🤠", - "alt_text": "".join(["❤️"] * p.lives) - + "".join(["💀"] * (p.max_lives - p.lives)), - "avatar": p.avatar, - "is_character": True, - "is_player": True, - } - for p in self.game.get_alive_players() - if p != self and p.lives < p.max_lives - ] - self.available_cards.append({"icon": "❌", "noDesc": True}) - self.choose_text = "choose_fratelli_di_sangue" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_fratelli_di_sangue", + [ + { + "name": p.name, + "icon": p.role.icon + if (self.game.initial_players == 3) + else "⭐️" + if isinstance(p.role, r.Sheriff) + else "🤠", + "alt_text": "".join(["❤️"] * p.lives) + + "".join(["💀"] * (p.max_lives - p.lives)), + "avatar": p.avatar, + "is_character": True, + "is_player": True, + } + for p in self.game.get_alive_players() + if p != self and p.lives < p.max_lives + ] + + [{"icon": "❌", "noDesc": True}], + ) self.is_giving_life = True elif ( self.game.check_event(ceh.NuovaIdentita) and self.not_chosen_character is not None and not again ): - self.available_cards = [self.character, self.not_chosen_character] - self.choose_text = "choose_nuova_identita" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_nuova_identita", [self.character, self.not_chosen_character] + ) elif not self.game.check_event(ce.Lazo) and any( ( isinstance(c, cs.Dinamite) @@ -840,25 +841,26 @@ class Player: and sum((c.name == cs.Bang(0, 0).name for c in self.hand)) >= 2 ): self.is_using_checchino = True - self.available_cards = [ - { - "name": p["name"], - "icon": p["role"].icon - if (self.game.initial_players == 3) - else "⭐️" - if p["is_sheriff"] - else "🤠", - "alt_text": "".join(["❤️"] * p["lives"]) - + "".join(["💀"] * (p["max_lives"] - p["lives"])), - "is_character": True, - "is_player": True, - } - for p in self.game.get_visible_players(self) - if p["dist"] <= self.get_sight() - ] - self.available_cards.append({"icon": "❌", "noDesc": True}) - self.choose_text = "choose_cecchino" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_cecchino", + [ + { + "name": p["name"], + "icon": p["role"].icon + if (self.game.initial_players == 3) + else "⭐️" + if p["is_sheriff"] + else "🤠", + "alt_text": "".join(["❤️"] * p["lives"]) + + "".join(["💀"] * (p["max_lives"] - p["lives"])), + "is_character": True, + "is_player": True, + } + for p in self.game.get_visible_players(self) + if p["dist"] <= self.get_sight() + ] + + [{"icon": "❌", "noDesc": True}], + ) self.notify_self() if ( self.is_my_turn @@ -884,15 +886,15 @@ class Player: self.notify_self() elif self.character.check(self.game, chars.KitCarlson) and not self.is_ghost: self.is_drawing = True - self.available_cards = [self.game.deck.draw() for i in range(3)] - self.choose_text = "choose_card_to_get" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_card_to_get", [self.game.deck.draw() for i in range(3)] + ) self.notify_self() elif self.character.check(self.game, grch.DutchWill) and not self.is_ghost: self.is_drawing = True - self.available_cards = [self.game.deck.draw() for i in range(2)] - self.choose_text = "choose_card_to_get" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_card_to_get", [self.game.deck.draw() for i in range(2)] + ) self.notify_self() elif ( self.character.check(self.game, chd.PatBrennan) @@ -902,10 +904,10 @@ class Player: and len(self.game.get_player_named(pile).equipment) > 0 ): self.is_drawing = True - self.available_cards = self.game.get_player_named(pile).equipment self.pat_target = pile - self.choose_text = "choose_card_to_get" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_card_to_get", self.game.get_player_named(pile).equipment + ) self.notify_self() else: self.pending_action = PendingAction.PLAY @@ -1004,12 +1006,13 @@ class Player: def manette(self): if self.game.check_event(ceh.Manette): - self.choose_text = "choose_manette" - self.available_cards = [ - {"name": "", "icon": "♦♣♥♠"[s], "alt_text": "", "noDesc": True} - for s in [0, 1, 2, 3] - ] - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "choose_manette", + [ + {"name": "", "icon": "♦♣♥♠"[s], "alt_text": "", "noDesc": True} + for s in [0, 1, 2, 3] + ], + ) def pick(self): if self.pending_action != PendingAction.PICK: @@ -2100,9 +2103,7 @@ class Player: if len(equipments) == 0: return False else: - self.choose_text = "choose_dalton" - self.pending_action = PendingAction.CHOOSE - self.available_cards = equipments + self.set_choose_action("choose_dalton", equipments) return True def get_indians(self, attacker): @@ -2501,26 +2502,29 @@ class Player: self.character.special(self, data) def gold_rush_discard(self): - self.available_cards = [ - { - "name": p.name, - "icon": p.role.icon - if (self.game.initial_players == 3) - else "⭐️" - if isinstance(p.role, r.Sheriff) - else "🤠", - "is_character": True, - "avatar": p.avatar, - "alt_text": "".join(["🎴️"] * len(p.gold_rush_equipment)), - "is_player": True, - } - for p in self.game.get_alive_players() - if p != self - and any((e.number + 1 <= self.gold_nuggets for e in p.gold_rush_equipment)) - ] - self.available_cards.append({"icon": "❌", "noDesc": True}) - self.choose_text = "gold_rush_discard" - self.pending_action = PendingAction.CHOOSE + self.set_choose_action( + "gold_rush_discard", + [ + { + "name": p.name, + "icon": p.role.icon + if (self.game.initial_players == 3) + else "⭐️" + if isinstance(p.role, r.Sheriff) + else "🤠", + "is_character": True, + "avatar": p.avatar, + "alt_text": "".join(["🎴️"] * len(p.gold_rush_equipment)), + "is_player": True, + } + for p in self.game.get_alive_players() + if p != self + and any( + (e.number + 1 <= self.gold_nuggets for e in p.gold_rush_equipment) + ) + ] + + [{"icon": "❌", "noDesc": True}], + ) self.notify_self() def buy_gold_rush_card(self, index): @@ -2550,6 +2554,28 @@ class Player: self.game.deck.fill_gold_rush_shop() self.notify_self() + def buy_train(self, index): + if self.pending_action != PendingAction.PLAY: + return + print( + f"{self.name} wants to buy train card on station index {index} in room {self.game.name}" + ) + station: trs.StationCard = self.game.deck.stations[index] + train_index = len(self.game.deck.current_train) - 5 - index + if train_index < 0 or train_index >= len(self.game.deck.current_train): + return + + train: trt.TrainCard = self.game.deck.current_train[train_index] + if train is not None and not train.is_locomotive: + if station.check_price(self): + print(f"{station=} {train=}") + self.notify_self() + + def set_choose_action(self, choose_text: str, available_cards: List): + self.pending_action = PendingAction.CHOOSE + self.choose_text = choose_text + self.available_cards = available_cards + def check_can_end_turn(self): must_be_used_cards = [c for c in self.hand if c.must_be_used] if self.game.check_event(ce.LeggeDelWest) and len(must_be_used_cards) > 0: diff --git a/backend/server.py b/backend/server.py index f7e7274..99dfc70 100644 --- a/backend/server.py +++ b/backend/server.py @@ -679,6 +679,14 @@ def buy_gold_rush_card(sid, data: int): ses.buy_gold_rush_card(data) +@sio.event +@bang_handler +def buy_train(sid, data: int): + ses: Player = sio.get_session(sid) + ses.game.rpc_log.append(f"{ses.name};buy_train;{data}") + ses.buy_train(data) + + @sio.event @bang_handler def chat_message(sid, msg, pl=None): diff --git a/frontend/src/components/Deck.vue b/frontend/src/components/Deck.vue index 0f9c771..996455c 100644 --- a/frontend/src/components/Deck.vue +++ b/frontend/src/components/Deck.vue @@ -12,7 +12,7 @@
- +
@@ -181,6 +181,9 @@ export default { return null; return this.currentTrain[index]; }, + buyTrain(i) { + this.$socket.emit('buy_train', i) + }, action(pile) { if (this.pending_action !== false && this.pending_action < 2) { // console.log('action')