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 @@