diff --git a/backend/bang/cards.py b/backend/bang/cards.py index 3406a0e..22eb09a 100644 --- a/backend/bang/cards.py +++ b/backend/bang/cards.py @@ -20,6 +20,7 @@ class Suit(IntEnum): HEARTS = 2 # ♥ SPADES = 3 # ♠ GOLD = 4 # 🤑 + TRAIN = 5 # 🚂 class Card(ABC): diff --git a/backend/bang/deck.py b/backend/bang/deck.py index 7bd7fda..52354c8 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -1,11 +1,11 @@ from typing import List, Set, Dict, Tuple, Optional, TYPE_CHECKING -import random import bang.cards as cs import bang.expansions.fistful_of_cards.card_events as ce import bang.expansions.high_noon.card_events as ceh import bang.expansions.wild_west_show.card_events as cew import bang.expansions.wild_west_show.characters as chw import bang.expansions.gold_rush.shop_cards as grc +import bang.expansions.train_robbery.stations as trs from globals import G if TYPE_CHECKING: @@ -35,6 +35,7 @@ class Deck: self.game = game self.event_cards: List[ce.CardEvent] = [] self.event_cards_wildwestshow: List[ce.CardEvent] = [] + self.stations: List[trs.StationCard] = [] endgame_cards: List[ce.CardEvent] = [] if "fistful_of_cards" in game.expansions: self.event_cards.extend(ce.get_all_events(game.rng)) @@ -47,6 +48,8 @@ class Deck: game.rng.shuffle(self.event_cards_wildwestshow) self.event_cards_wildwestshow.insert(0, None) self.event_cards_wildwestshow.append(cew.get_endgame_card()) + if "train_robbery" in game.expansions: + self.stations = game.rng.sample(trs.get_all_stations(), len(game.players)) if len(self.event_cards) > 0: game.rng.shuffle(self.event_cards) self.event_cards = self.event_cards[:12] diff --git a/backend/bang/expansions/train_robbery/cards.py b/backend/bang/expansions/train_robbery/cards.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/bang/expansions/train_robbery/characters.py b/backend/bang/expansions/train_robbery/characters.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/bang/expansions/train_robbery/stations.py b/backend/bang/expansions/train_robbery/stations.py new file mode 100644 index 0000000..ed774af --- /dev/null +++ b/backend/bang/expansions/train_robbery/stations.py @@ -0,0 +1,173 @@ +import bang.cards as cs + + +class StationCard: + def __init__(self, name: str): + self.name = name + self.expansion = "train_robbery" + self.price: list[dict] = [] + + def card_check(self, card: cs.Card): + """Check if the card can be used to rob the train""" + + +class BoomTown(StationCard): + """Discard a Bang! to rob the train""" + + def __init__(self): + super().__init__("Boom Town") + self.price = [cs.Bang(0, 0).__dict__] + + def card_check(self, card: cs.Card): + return isinstance(card, cs.Bang) + + +class Caticor(StationCard): + """Discard a Cat Balou or Panico to rob the train""" + + def __init__(self): + 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) + + +class CreepyCreek(StationCard): + """Discard a card of spades to rob the train""" + + def __init__(self): + super().__init__("Creepy Creek") + self.price = [{"icon": "♠️"}] + + def card_check(self, card: cs.Card): + return card.suit == cs.Suit.SPADES + + +class CrownsHole(StationCard): + """Discard a beer to rob the train""" + + def __init__(self): + super().__init__("Crown's Hole") + self.price = [cs.Birra(0, 0).__dict__] + + def card_check(self, card: cs.Card): + return isinstance(card, cs.Birra) + + +class Deadwood(StationCard): + """Discard an equipment card to rob the train""" + + def __init__(self): + super().__init__("Deadwood") + self.price = [{"is_equipment": True}] + + def card_check(self, card: cs.Card): + return card.is_equipment + + +class Dodgeville(StationCard): + """Discard a Missed! to rob the train""" + + def __init__(self): + super().__init__("Dodgeville") + self.price = [cs.Mancato(0, 0).__dict__] + + def card_check(self, card: cs.Card): + return isinstance(card, cs.Mancato) + + +class FortWorth(StationCard): + """Discard a card with number 10, J, Q, K, A to rob the train""" + + def __init__(self): + 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} + + +class Frisco(StationCard): + """Discard a card of clubs to rob the train""" + + def __init__(self): + super().__init__("Frisco") + self.price = [{"icon": "♣️"}] + + def card_check(self, card: cs.Card): + return card.suit == cs.Suit.CLUBS + + +class MinersOath(StationCard): + """Discard a card of diamonds to rob the train""" + + def __init__(self): + super().__init__("Miner's Oath") + self.price = [{"icon": "♦️"}] + + def card_check(self, card: cs.Card): + return card.suit == cs.Suit.DIAMONDS + + +class SanTafe(StationCard): + """Discard a card of hearts to rob the train""" + + def __init__(self): + super().__init__("San Tafe") + self.price = [{"icon": "♥️"}] + + def card_check(self, card: cs.Card): + return card.suit == cs.Suit.HEARTS + + +class Tombrock(StationCard): + """Lose 1 life point to rob the train""" + + def __init__(self): + super().__init__("Tombrock") + self.price = [{"icon": "💔"}] + + def card_check(self, card: cs.Card): + return True + + +class Yooma(StationCard): + """Discard a card with number between 2 and 9 to rob the train""" + + def __init__(self): + super().__init__("Yooma") + self.price = [{"icon": "2-9"}] + + def card_check(self, card: cs.Card): + return 2 <= card.number <= 9 + + +class VirginiaTown(StationCard): + """Discard two cards to rob the train""" + + def __init__(self): + super().__init__("Virginia Town") + self.price = [{}, {}] + + def card_check(self, card: cs.Card): + return True + + +def get_all_stations(): + """Return a list of all the station cards""" + return [ + BoomTown(), + Caticor(), + CreepyCreek(), + CrownsHole(), + Deadwood(), + Dodgeville(), + FortWorth(), + Frisco(), + MinersOath(), + SanTafe(), + Tombrock(), + Yooma(), + VirginiaTown(), + ] diff --git a/backend/bang/expansions/train_robbery/trains.py b/backend/bang/expansions/train_robbery/trains.py new file mode 100644 index 0000000..53d93db --- /dev/null +++ b/backend/bang/expansions/train_robbery/trains.py @@ -0,0 +1,101 @@ +from bang.cards import Card + + +class TrainCard(Card): + def __init__(self, name: str, is_locomotive: bool = False): + super().__init__(suit=5, number="", name=name) + self.expansion_icon = "🚂" + self.is_equipment = True + self.is_locomotive = is_locomotive + self.expansion = "train_robbery" + + +# Circus Wagon: gli altri giocatori +# scartano una carta, in senso orario, a +# partire dal giocatore alla tua sinistra. +# Express Car: non puoi svolgere +# un altro turno extra dopo quello +# ottenuto da questo effetto, anche se +# riesci a giocare di nuovo Express Car. + +# Ghost Car: giocabile su un +# qualsiasi giocatore, anche se già +# eliminato, te compreso. Non può +# essere giocato sullo Sceriffo. +# Se quel giocatore è/viene eliminato, +# invece ritorna/resta in gioco, senza +# punti vita. Non può guadagnare né +# perdere punti vita, e viene considerato +# un personaggio in gioco per tutti gli +# effetti (condizioni di vittoria, distanza +# tra giocatori, abilità dei personaggi, +# ecc.). Non avendo punti vita, deve +# scartare la sua intera mano alla fine +# del turno, ma può tenere qualsiasi +# carta in gioco di fronte a sé, incluso +# Ghost Car. Tuttavia, è eliminato +# dal gioco non appena Ghost Car +# viene scartato: nessuna ricompensa +# viene assegnata in questo caso se il +# giocatore è un Fuorilegge, e le abilità +# dei personaggi (ad es. Vulture Sam) +# non si attivano + +# Lounge Car: i vagoni che peschi +# non contano per il normale limite di +# acquisizione di 1 vagone per turno. Se +# sei lo Sceriffo e peschi Ghost Car, devi +# darlo a un altro giocatore. + +# Lumber Flatcar: gioca su un +# qualsiasi giocatore (compreso te). +# Finché questa carta è in gioco, quel +# giocatore vede gli altri giocatori a +# distanza aumentata di 1. + +# Private Car: questo effetto non +# ti protegge da Gatling, Knife Revolver, +# l’abilità di Evan Babbit, e così via. +# Sleeper Car: puoi anche usare +# l’effetto una volta per turno con +# Indiani!, Duello, ecc. + + +class Ironhorse(TrainCard): + """LOCOMOTIVA: + Ogni giocatore, incluso colui che ha attivato l'effetto, è bersaglio di un BANG! + Nessun giocatore è responsabile dell'eventuale perdita di punti vita. + Se tutti i giocatori vengono eliminati allo stesso tempo, i Fuorilegge vincono. + """ + + def __init__(self): + super().__init__("Ironhorse", is_locomotive=True) + self.icon = "🚂" + + def play_card(self, player, against=None, _with=None) -> bool: + player.game.attack(player, player.name, card_name=self.name) + player.game.attack_others(player, card_name=self.name) + return True + + +class Leland(TrainCard): + """ + LOCOMOTIVA: svolgi l’effetto dell’Emporio, cominciando dal giocatore di turno e procedendo in senso orario. + """ + + def __init__(self): + super().__init__("Leland", is_locomotive=True) + self.icon = "🚂" + + def play_card(self, player, against=None, _with=None) -> bool: + player.game.emporio(player) + return True + + +class CircusWagon(TrainCard): + def __init__(self): + super().__init__("Circus Wagon", is_locomotive=True) + self.icon = "🚋🎪" + + def play_card(self, player, against=None, _with=None) -> bool: + return True diff --git a/backend/bang/game.py b/backend/bang/game.py index c0bad29..dcc4894 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -93,6 +93,7 @@ class Game: "gold_rush", "the_valley_of_shadows", "wild_west_show", + "train_robbery", ] self.shutting_down = False self.is_competitive = False @@ -354,6 +355,7 @@ class Game: roles_str += f"|{role}|{str(current_roles.count(role))}" G.sio.emit("chat_message", room=self.name, data=f"_allroles{roles_str}") self.play_turn() + self.notify_stations() def choose_characters(self): n = self.characters_to_distribute @@ -917,6 +919,15 @@ class Game: data=json.dumps(self.deck.shop_cards, default=lambda o: o.__dict__), ) + def notify_stations(self, sid=None): + if "train_robbery" in self.expansions: + room = self.name if sid is None else sid + G.sio.emit( + "stations", + room=room, + data=json.dumps(self.deck.stations, default=lambda o: o.__dict__), + ) + def notify_scrap_pile(self, sid=None): print(f"{self.name}: scrap") room = self.name if sid is None else sid diff --git a/backend/server.py b/backend/server.py index 3208915..ba45db7 100644 --- a/backend/server.py +++ b/backend/server.py @@ -346,6 +346,7 @@ def get_me(sid, data): room.notify_scrap_pile(sid) room.notify_all() room.notify_gold_rush_shop() + room.notify_stations() room.notify_event_card() room.notify_event_card_wildwestshow(sid) else: diff --git a/frontend/src/components/Deck.vue b/frontend/src/components/Deck.vue index f34e6a0..157b20f 100644 --- a/frontend/src/components/Deck.vue +++ b/frontend/src/components/Deck.vue @@ -11,15 +11,13 @@ -
- 0" class="deck" :style="`position:relative;border: 2px dashed #6a6a6a42;border-radius:8pt;align-items: flex-end;`" > + {{ emoji }}
{{ card.alt_text }}
- +
+ +
@@ -17,7 +19,7 @@ export default { name: "StationCard", props: { card: Object, - price: Object, + price: Array, trainPiece: Object, donotlocalize: Boolean, }, @@ -89,8 +91,8 @@ export default { position: absolute; text-align: center; width: 100%; - top: -10pt; - font-size: 12pt; + top: -15pt; + font-size: 10pt; } .alt_text { right: 3pt; @@ -100,11 +102,17 @@ export default { bottom: 20pt; left: 3pt; } -.stationcard .card { - position: absolute; - transform: scale(0.3) rotate(14deg); - left: -12pt; - top: -22pt; +.stationcard .price { + justify-content: center; + /* left: -12pt; */ + margin-top: -20pt; + /* left: 0; */ + display: flex; + width: 60pt; + transform: scale(0.3); +} +.price .card { + transform: rotate(14deg); } .train-piece { margin: 6pt; diff --git a/frontend/src/utils/expansions-map.js b/frontend/src/utils/expansions-map.js index 0b3ccc5..925ac96 100644 --- a/frontend/src/utils/expansions-map.js +++ b/frontend/src/utils/expansions-map.js @@ -36,5 +36,12 @@ export const expansionsMap = { back: true, expansion: 'wild-west-show', status: 'alpha', + }, + 'train_robbery': { + name: 'The Great Train Robbery', + icon: '🚂', + back: true, + expansion: 'train-roobbery', + status: 'alpha', } } \ No newline at end of file