Merge pull request #505 from albertoxamin/train-robbery
Add Train robbery expansion
This commit is contained in:
commit
5d0e46f205
@ -20,6 +20,7 @@ class Suit(IntEnum):
|
||||
HEARTS = 2 # ♥
|
||||
SPADES = 3 # ♠
|
||||
GOLD = 4 # 🤑
|
||||
TRAIN = 5 # 🚂
|
||||
|
||||
|
||||
class Card(ABC):
|
||||
@ -66,7 +67,7 @@ class Card(ABC):
|
||||
|
||||
def __str__(self) -> str:
|
||||
if str(self.suit).isnumeric():
|
||||
char = ["♦️", "♣️", "♥️", "♠️", "🤑"][int(self.suit)]
|
||||
char = ["♦️", "♣️", "♥️", "♠️", "🤑", "🚋"][int(self.suit)]
|
||||
else:
|
||||
char = self.suit
|
||||
return f"{self.name} {char}{self.number}"
|
||||
@ -370,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 (
|
||||
|
@ -1,15 +1,17 @@
|
||||
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
|
||||
import bang.expansions.train_robbery.trains as trt
|
||||
from globals import G
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bang.game import Game
|
||||
from bang.players import Player
|
||||
|
||||
|
||||
class Deck:
|
||||
@ -35,6 +37,9 @@ class Deck:
|
||||
self.game = game
|
||||
self.event_cards: List[ce.CardEvent] = []
|
||||
self.event_cards_wildwestshow: List[ce.CardEvent] = []
|
||||
self.stations: List[trs.StationCard] = []
|
||||
self.train_pile: List[trt.TrainCard] = []
|
||||
self.current_train: List[trt.TrainCard] = []
|
||||
endgame_cards: List[ce.CardEvent] = []
|
||||
if "fistful_of_cards" in game.expansions:
|
||||
self.event_cards.extend(ce.get_all_events(game.rng))
|
||||
@ -47,6 +52,12 @@ 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))
|
||||
self.train_pile = trt.get_all_cards(game.rng)
|
||||
self.current_train = [trt.get_locomotives(game.rng)[0]] + self.train_pile[
|
||||
:3
|
||||
]
|
||||
if len(self.event_cards) > 0:
|
||||
game.rng.shuffle(self.event_cards)
|
||||
self.event_cards = self.event_cards[:12]
|
||||
@ -106,12 +117,21 @@ class Deck:
|
||||
self.shop_cards[i].reset_card()
|
||||
self.game.notify_gold_rush_shop()
|
||||
|
||||
def move_train_forward(self):
|
||||
if len(self.stations) == 0:
|
||||
return
|
||||
if len(self.current_train) == len(self.stations) + 4:
|
||||
return
|
||||
if len(self.current_train) > 0:
|
||||
self.current_train.append(None)
|
||||
self.game.notify_stations()
|
||||
|
||||
def peek(self, n_cards: int) -> list:
|
||||
return self.cards[:n_cards]
|
||||
|
||||
def peek_scrap_pile(self) -> cs.Card:
|
||||
def peek_scrap_pile(self, n_cards: int=1) -> List[cs.Card]:
|
||||
if len(self.scrap_pile) > 0:
|
||||
return self.scrap_pile[-1]
|
||||
return self.scrap_pile[-n_cards:]
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -176,7 +196,7 @@ class Deck:
|
||||
else:
|
||||
return self.draw()
|
||||
|
||||
def scrap(self, card: cs.Card, ignore_event=False, player=None):
|
||||
def scrap(self, card: cs.Card, ignore_event:bool=False, player:'Player'=None):
|
||||
if card.number == 42:
|
||||
return
|
||||
card.reset_card()
|
||||
|
@ -1,5 +1,6 @@
|
||||
from typing import List
|
||||
from bang.characters import Character
|
||||
from globals import PendingAction
|
||||
|
||||
|
||||
class PixiePete(Character):
|
||||
@ -165,8 +166,6 @@ class DocHolyday(Character):
|
||||
|
||||
def special(self, player, data):
|
||||
if super().special(player, data):
|
||||
from bang.players import PendingAction
|
||||
|
||||
if (
|
||||
player.special_use_count < 1
|
||||
and player.pending_action == PendingAction.PLAY
|
||||
|
@ -1,7 +1,7 @@
|
||||
from bang.cards import *
|
||||
import bang.roles as r
|
||||
import bang.players as pl
|
||||
from globals import G
|
||||
from globals import G, PendingAction
|
||||
|
||||
class ShopCardKind(IntEnum):
|
||||
BROWN = 0 # Se l’equipaggiamento ha il bordo marrone, applicane subito l’effetto e poi scartalo.
|
||||
@ -47,7 +47,7 @@ class Bicchierino(ShopCard):
|
||||
'is_player': True
|
||||
} for p in player.game.get_alive_players()]
|
||||
player.choose_text = 'choose_bicchierino'
|
||||
player.pending_action = pl.PendingAction.CHOOSE
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.notify_self()
|
||||
return super().play_card(player, against, _with)
|
||||
|
||||
@ -64,7 +64,7 @@ class Bottiglia(ShopCard):
|
||||
for i in range(len(player.available_cards)):
|
||||
player.available_cards[i].must_be_used = True
|
||||
player.choose_text = 'choose_bottiglia'
|
||||
player.pending_action = pl.PendingAction.CHOOSE
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.notify_self()
|
||||
return super().play_card(player, against, _with)
|
||||
|
||||
@ -79,7 +79,7 @@ class Complice(ShopCard):
|
||||
for i in range(len(player.available_cards)):
|
||||
player.available_cards[i].must_be_used = True
|
||||
player.choose_text = 'choose_complice'
|
||||
player.pending_action = pl.PendingAction.CHOOSE
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.notify_self()
|
||||
return super().play_card(player, against, _with)
|
||||
|
||||
@ -175,7 +175,7 @@ class Ricercato(ShopCard):
|
||||
} for p in player.game.get_alive_players() if p != player and not isinstance(p.role, r.Sheriff)]
|
||||
player.available_cards.append({'name': player.name, 'number':0,'icon': 'you', 'is_character': True})
|
||||
player.choose_text = 'choose_ricercato'
|
||||
player.pending_action = pl.PendingAction.CHOOSE
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.notify_self()
|
||||
return True
|
||||
# la giochi su un altro giocatore, ricompensa di 2 carte e 1 pepita a chi lo uccide
|
||||
|
@ -3,7 +3,7 @@ import bang.roles as r
|
||||
import bang.players as pl
|
||||
from bang.cards import Card, Suit, Bang, Mancato
|
||||
import bang.expansions.fistful_of_cards.card_events as ce
|
||||
from globals import G
|
||||
from globals import G, PendingAction
|
||||
|
||||
|
||||
class Fantasma(Card):
|
||||
@ -15,7 +15,7 @@ class Fantasma(Card):
|
||||
if (player.game.check_event(ce.IlGiudice)) or not self.can_be_used_now:
|
||||
return False
|
||||
if len(player.game.get_dead_players(include_ghosts=False)) > 0:
|
||||
player.pending_action = pl.PendingAction.CHOOSE
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.choose_text = "choose_fantasma"
|
||||
player.available_cards = [
|
||||
{
|
||||
@ -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
|
||||
|
||||
@ -185,7 +181,7 @@ class Sventagliata(
|
||||
if p["name"] != player.name and p["name"] != t.name and p["dist"]
|
||||
]
|
||||
if len(player.available_cards) > 0:
|
||||
player.pending_action = pl.PendingAction.CHOOSE
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.choose_text = "choose_sventagliata"
|
||||
else:
|
||||
player.available_cards = []
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
0
backend/bang/expansions/train_robbery/cards.py
Normal file
0
backend/bang/expansions/train_robbery/cards.py
Normal file
0
backend/bang/expansions/train_robbery/characters.py
Normal file
0
backend/bang/expansions/train_robbery/characters.py
Normal file
315
backend/bang/expansions/train_robbery/stations.py
Normal file
315
backend/bang/expansions/train_robbery/stations.py
Normal file
@ -0,0 +1,315 @@
|
||||
from typing import TYPE_CHECKING
|
||||
import bang.cards as cs
|
||||
from globals import PendingAction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bang.players import Player
|
||||
|
||||
|
||||
class StationCard:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.expansion = "train_robbery"
|
||||
self.price: list[dict] = []
|
||||
self.attached_train = None
|
||||
|
||||
def discard_and_buy_train(self, player: "Player", card_index: int):
|
||||
"""Discard the card and buy the train"""
|
||||
if self.attached_train is None:
|
||||
return
|
||||
card = player.available_cards.pop(card_index)
|
||||
for i, card in enumerate(player.hand):
|
||||
if card == self:
|
||||
player.hand.pop(i)
|
||||
break
|
||||
else:
|
||||
player.lives -= 1
|
||||
card = player.hand.pop(card_index)
|
||||
player.game.deck.scrap(card, True, player=player)
|
||||
player.equipment.append(self.attached_train)
|
||||
self.attached_train = None
|
||||
player.pending_action = PendingAction.PLAY
|
||||
|
||||
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):
|
||||
"""Discard a Bang! to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Boom Town")
|
||||
self.price = [cs.Bang(0, 0).__dict__]
|
||||
|
||||
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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if isinstance(c, cs.Bang)],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
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 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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[
|
||||
c
|
||||
for c in player.hand
|
||||
if isinstance(c, cs.CatBalou) or isinstance(c, cs.Panico)
|
||||
],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class CreepyCreek(StationCard):
|
||||
"""Discard a card of spades to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Creepy Creek")
|
||||
self.price = [{"icon": "♠️"}]
|
||||
|
||||
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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if c.suit == cs.Suit.SPADES],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class CrownsHole(StationCard):
|
||||
"""Discard a beer to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Crowns Hole")
|
||||
self.price = [cs.Birra(0, 0).__dict__]
|
||||
|
||||
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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if isinstance(c, cs.Birra)],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class Deadwood(StationCard):
|
||||
"""Discard an equipment card to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Deadwood")
|
||||
self.price = [{"is_equipment": True}]
|
||||
|
||||
def check_price(self, player: "Player"):
|
||||
if super().check_price(player) and all(
|
||||
not card.is_equipment for card in player.hand
|
||||
):
|
||||
return False
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if c.is_equipment],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class Dodgeville(StationCard):
|
||||
"""Discard a Missed! to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Dodgeville")
|
||||
self.price = [cs.Mancato(0, 0).__dict__]
|
||||
|
||||
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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if isinstance(c, cs.Mancato)],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
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 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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if c.number in {1, 10, 11, 12, 13}],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class Frisco(StationCard):
|
||||
"""Discard a card of clubs to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Frisco")
|
||||
self.price = [{"icon": "♣️"}]
|
||||
|
||||
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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if c.suit == cs.Suit.CLUBS],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class MinersOath(StationCard):
|
||||
"""Discard a card of diamonds to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Miners Oath")
|
||||
self.price = [{"icon": "♦️"}]
|
||||
|
||||
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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if c.suit == cs.Suit.DIAMONDS],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class SanTafe(StationCard):
|
||||
"""Discard a card of hearts to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("San Tafe")
|
||||
self.price = [{"icon": "♥️"}]
|
||||
|
||||
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
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if c.suit == cs.Suit.HEARTS],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class Tombrock(StationCard):
|
||||
"""Lose 1 life point to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Tombrock")
|
||||
self.price = [{"icon": "💔"}]
|
||||
|
||||
def check_price(self, player: "Player"):
|
||||
if player.lives <= 1:
|
||||
return False
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[{"icon": "💔"}],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
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 check_price(self, player: "Player"):
|
||||
if super().check_price(player) and all(
|
||||
not (2 <= card.number <= 9) for card in player.hand
|
||||
):
|
||||
return False
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
[c for c in player.hand if 2 <= c.number <= 9],
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class VirginiaTown(StationCard):
|
||||
"""Discard two cards to rob the train"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Virginia Town")
|
||||
self.price = [{}, {}]
|
||||
|
||||
def check_price(self, player: "Player"):
|
||||
if super().check_price(player) and len(player.hand) < 2:
|
||||
return False
|
||||
player.set_choose_action(
|
||||
"choose_buy_train",
|
||||
player.hand.copy(),
|
||||
self.discard_and_buy_train,
|
||||
)
|
||||
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(),
|
||||
]
|
406
backend/bang/expansions/train_robbery/trains.py
Normal file
406
backend/bang/expansions/train_robbery/trains.py
Normal file
@ -0,0 +1,406 @@
|
||||
import random
|
||||
from bang.cards import Card, Bang, Panico, CatBalou, Mancato
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from globals import G, PendingAction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bang.players import Player
|
||||
|
||||
|
||||
class TrainCard(Card):
|
||||
def __init__(self, name: str, is_locomotive: bool = False):
|
||||
super().__init__(suit=5, number=0, name=name)
|
||||
self.expansion_icon = "🚂"
|
||||
self.is_equipment = True
|
||||
self.is_locomotive = is_locomotive
|
||||
self.expansion = "train_robbery"
|
||||
self.type = "train"
|
||||
self.implemented = True
|
||||
|
||||
|
||||
# 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 BaggageCar(TrainCard):
|
||||
"""Scartalo: ottieni l'effetto di un Mancato!, Panico!, Cat Balou o di un BANG! extra.
|
||||
Discard this for a Missed! Panic!, Cat Balou, or an extra BANG!"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Baggage Car")
|
||||
self.icon = "🚋🛄"
|
||||
|
||||
def choose_callback(self, player: 'Player', card_index):
|
||||
player.hand.append(player.available_cards[card_index])
|
||||
player.pending_action = PendingAction.PLAY
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
player.set_choose_action(
|
||||
"choose_baggage_car",
|
||||
[Bang(4, 42), Mancato(4, 42), CatBalou(4, 42), Panico(4, 42)],
|
||||
self.choose_callback,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class Caboose(TrainCard):
|
||||
"""Puoi scartare un altra tua carta bordo blu incuso un vagone come se fosse un Mancato!"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Caboose")
|
||||
self.icon = "🚋"
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class CattleTruck(TrainCard):
|
||||
"""Scartalo: guarda le 3 carte in cima agli scarti e pescane I"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Cattle Truck")
|
||||
self.icon = "🚋🐄"
|
||||
|
||||
def choose_card_callback(self, player: 'Player', card_index):
|
||||
chosen_card = player.available_cards.pop(card_index)
|
||||
player.game.deck.scrap_pile.pop(-card_index)
|
||||
player.hand.append(chosen_card)
|
||||
player.pending_action = PendingAction.PLAY
|
||||
player.notify_self()
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
drawn_cards = player.game.deck.peek_scrap_pile(n_cards=3)
|
||||
player.set_choose_action(
|
||||
"choose_cattle_truck",
|
||||
drawn_cards,
|
||||
self.choose_card_callback,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class CircusWagon(TrainCard):
|
||||
"""Scartalo: ogni altro giocatore deve scartare una carta che ha in gioco."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Circus Wagon", is_locomotive=True)
|
||||
self.icon = "🚋🎪"
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
player.game.discard_others(player, card_name=self.name)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def choose_circus_wagon(cls, player: 'Player', card_index):
|
||||
player.game.deck.scrap(player.hand.pop(card_index), player=player)
|
||||
player.pending_action = PendingAction.WAIT
|
||||
player.game.responders_did_respond_resume_turn()
|
||||
player.notify_self()
|
||||
|
||||
|
||||
|
||||
class CoalHopper(TrainCard):
|
||||
"""Scartalo: pesca una carta e scarta un vagone in gioco davanti a un giocatore a tua scelta."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Coal Hopper")
|
||||
self.icon = "🚋🔥"
|
||||
self.need_target = True
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
if against is not None and len(player.game.get_player_named(against).equipment) > 0:
|
||||
player.game.steal_discard(player, against, self)
|
||||
return True
|
||||
|
||||
|
||||
class DiningCar(TrainCard):
|
||||
"""A inizio turno, "estrai!": se è Cuori, recuperi I punto vita."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Dining Car")
|
||||
self.icon = "🚋🍽"
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class ExpressCar(TrainCard):
|
||||
"""Scarta tutte le carte in mano, poi gioca un altro turno"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Express Car")
|
||||
self.icon = "🚋⚡"
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
while len(player.hand) > 0:
|
||||
player.game.deck.scrap(player.hand.pop(0), player=player)
|
||||
player.notify_self()
|
||||
player.play_turn()
|
||||
return True
|
||||
|
||||
|
||||
class GhostCar(TrainCard):
|
||||
"""Giocalo su chiunque tranne lo Sceritfo. Se vieni eliminato, invece resta in gioco, ma non puo guadagnare ne perdere punti vita."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Ghost Car")
|
||||
self.icon = "🚋👻"
|
||||
self.implemented = False
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class LoungeCar(TrainCard):
|
||||
"""Scartalo: pesca 2 vagoni dal mazzo, mettine I in gioco di fronte a te e 1 di fronte a un altro giocatore."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Lounge Car")
|
||||
self.icon = "🚋🛋"
|
||||
self.implemented = False
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class LumberFlatcar(TrainCard):
|
||||
"""Giocalo su un qualsiasi giocatore (compreso te). Finché questa carta è in gioco, quel giocatore vede gli altri giocatori a distanza aumentata di 1."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Lumber Flatcar")
|
||||
self.icon = "🚋🪵"
|
||||
self.sight_mod = -1
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class MailCar(TrainCard):
|
||||
"""Scartalo: pesca 3 carte e dai 1 di esse a un altro giocatore a tua scelta."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Mail Car")
|
||||
self.icon = "🚋📮"
|
||||
|
||||
def choose_card_callback(self, player: 'Player', card_index):
|
||||
chosen_card = player.available_cards.pop(card_index)
|
||||
player.hand.extend(player.available_cards)
|
||||
player.set_choose_action(
|
||||
"choose_other_player",
|
||||
player.game.get_other_players(player),
|
||||
lambda p, other_player_index: self.choose_player_callback(p, other_player_index, chosen_card)
|
||||
)
|
||||
|
||||
def choose_player_callback(self, player: 'Player', other_player_index, chosen_card):
|
||||
pl_name = player.game.get_other_players(player)[other_player_index]["name"]
|
||||
other_player = player.game.get_player_named(pl_name)
|
||||
other_player.hand.append(chosen_card)
|
||||
G.sio.emit(
|
||||
"card_drawn",
|
||||
room=player.game.name,
|
||||
data={"player": pl_name, "pile": player.name},
|
||||
)
|
||||
other_player.notify_self()
|
||||
player.pending_action = PendingAction.PLAY
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
drawn_cards = [player.game.deck.draw(player=player) for _ in range(3)]
|
||||
player.set_choose_action(
|
||||
"choose_mail_car",
|
||||
drawn_cards,
|
||||
self.choose_card_callback,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class ObservationCar(TrainCard):
|
||||
"""Tu vedi gli altri a distanza -1. Gli altri a vedono a distanza +1."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Observation Car")
|
||||
self.icon = "🚋👀"
|
||||
self.sight_mod = 1
|
||||
self.vis_mod = 1
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class PassengerCar(TrainCard):
|
||||
"""Scartalo: pesca una carta (o in mano o in gioco) da un altro giocatore"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Passenger Car")
|
||||
self.icon = "🚋🚶"
|
||||
self.range = 99
|
||||
self.need_target = True
|
||||
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
if (
|
||||
against is not None
|
||||
and (len(player.equipment) > 0 or len(player.equipment) > 0)
|
||||
):
|
||||
player.game.steal_discard(player, against, self)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class PrisonerCar(TrainCard):
|
||||
"""Le carte Duello e Indiani! giocate dagli altri giocatori non hanno effetto su di te."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Prisoner Car")
|
||||
self.icon = "🚋👮🏻♂️"
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class PrivateCar(TrainCard):
|
||||
"""Se non hai carte in mano, non puoi essere bersaglio di carte BANG"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Private Car")
|
||||
self.icon = "🚋💁🏻"
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class SleeperCar(TrainCard):
|
||||
"""Una volta per turno, puoi scartare un'altra tua carta a bordo blu incluso."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Sleeper Car")
|
||||
self.icon = "🚋🛌"
|
||||
|
||||
def choose_card_callback(self, player: 'Player', card_index):
|
||||
player.game.deck.scrap(player.equipment.pop(card_index), player=player)
|
||||
player.pending_action = PendingAction.PLAY
|
||||
self.usable_next_turn = True
|
||||
self.can_be_used_now = False
|
||||
player.notify_self()
|
||||
|
||||
def play_card(self, player, against=None, _with=None) -> bool:
|
||||
if not self.can_be_used_now:
|
||||
return False
|
||||
player.set_choose_action(
|
||||
"choose_sleeper_car",
|
||||
player.equipment,
|
||||
self.choose_card_callback,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def get_all_cards(rng=random):
|
||||
"""Return a list of all train cards in the expansion"""
|
||||
cars = [
|
||||
BaggageCar(),
|
||||
Caboose(),
|
||||
CattleTruck(),
|
||||
CircusWagon(),
|
||||
CoalHopper(),
|
||||
DiningCar(),
|
||||
ExpressCar(),
|
||||
GhostCar(),
|
||||
LoungeCar(),
|
||||
LumberFlatcar(),
|
||||
MailCar(),
|
||||
ObservationCar(),
|
||||
PassengerCar(),
|
||||
PrisonerCar(),
|
||||
PrivateCar(),
|
||||
SleeperCar(),
|
||||
]
|
||||
cars = [c for c in cars if c.implemented]
|
||||
rng.shuffle(cars)
|
||||
return cars
|
||||
|
||||
|
||||
def get_locomotives(rng=random):
|
||||
"""Return a list of all locomotive cards in the expansion"""
|
||||
locs = [
|
||||
Ironhorse(),
|
||||
Leland(),
|
||||
]
|
||||
rng.shuffle(locs)
|
||||
return locs
|
@ -17,8 +17,9 @@ import bang.expansions.wild_west_show.card_events as cew
|
||||
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.train_robbery.trains as trt
|
||||
from metrics import Metrics
|
||||
from globals import G
|
||||
from globals import G, PendingAction
|
||||
|
||||
|
||||
debug_commands = [
|
||||
@ -57,8 +58,11 @@ debug_commands = [
|
||||
"help": "Remove a card from hand/equip - sample /removecard 0",
|
||||
},
|
||||
{"cmd": "/getcard", "help": "Get a brand new card - sample /getcard Birra"},
|
||||
{"cmd": "/equipcard", "help": "Equip a brand new card - sample /getcard Barile"},
|
||||
{"cmd": "/meinfo", "help": "Get player data"},
|
||||
{"cmd": "/gameinfo", "help": "Get game data"},
|
||||
{"cmd": "/deckinfo", "help": "Get deck data"},
|
||||
{"cmd": "/trainfw", "help": "move train forward"},
|
||||
{"cmd": "/playerinfo", "help": "Get player data - sample /playerinfo player"},
|
||||
{"cmd": "/cardinfo", "help": "Get card data - sample /cardinfo handindex"},
|
||||
{"cmd": "/mebot", "help": "Toggles bot mode"},
|
||||
@ -93,6 +97,7 @@ class Game:
|
||||
"gold_rush",
|
||||
"the_valley_of_shadows",
|
||||
"wild_west_show",
|
||||
"train_robbery",
|
||||
]
|
||||
self.shutting_down = False
|
||||
self.is_competitive = False
|
||||
@ -354,6 +359,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
|
||||
@ -459,7 +465,7 @@ class Game:
|
||||
|
||||
def discard_others(self, attacker: pl.Player, card_name: str = None):
|
||||
self.attack_in_progress = True
|
||||
attacker.pending_action = pl.PendingAction.WAIT
|
||||
attacker.pending_action = PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.waiting_for = 0
|
||||
self.ready_count = 0
|
||||
@ -469,7 +475,7 @@ class Game:
|
||||
self.waiting_for += 1
|
||||
p.notify_self()
|
||||
if self.waiting_for == 0:
|
||||
attacker.pending_action = pl.PendingAction.PLAY
|
||||
attacker.pending_action = PendingAction.PLAY
|
||||
attacker.notify_self()
|
||||
self.attack_in_progress = False
|
||||
elif card_name == "Poker":
|
||||
@ -477,7 +483,7 @@ class Game:
|
||||
|
||||
def attack_others(self, attacker: pl.Player, card_name: str = None):
|
||||
self.attack_in_progress = True
|
||||
attacker.pending_action = pl.PendingAction.WAIT
|
||||
attacker.pending_action = PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.waiting_for = 0
|
||||
self.ready_count = 0
|
||||
@ -487,7 +493,7 @@ class Game:
|
||||
self.waiting_for += 1
|
||||
p.notify_self()
|
||||
if self.waiting_for == 0:
|
||||
attacker.pending_action = pl.PendingAction.PLAY
|
||||
attacker.pending_action = PendingAction.PLAY
|
||||
attacker.notify_self()
|
||||
self.attack_in_progress = False
|
||||
if self.pending_winners and not self.someone_won:
|
||||
@ -495,7 +501,7 @@ class Game:
|
||||
|
||||
def indian_others(self, attacker: pl.Player):
|
||||
self.attack_in_progress = True
|
||||
attacker.pending_action = pl.PendingAction.WAIT
|
||||
attacker.pending_action = PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.waiting_for = 0
|
||||
self.ready_count = 0
|
||||
@ -505,7 +511,7 @@ class Game:
|
||||
self.waiting_for += 1
|
||||
p.notify_self()
|
||||
if self.waiting_for == 0:
|
||||
attacker.pending_action = pl.PendingAction.PLAY
|
||||
attacker.pending_action = PendingAction.PLAY
|
||||
attacker.notify_self()
|
||||
self.attack_in_progress = False
|
||||
if self.pending_winners and not self.someone_won:
|
||||
@ -542,26 +548,26 @@ class Game:
|
||||
self.attack_in_progress = True
|
||||
self.ready_count = 0
|
||||
self.waiting_for = 1
|
||||
attacker.pending_action = pl.PendingAction.WAIT
|
||||
attacker.pending_action = PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.get_player_named(target_username).notify_self()
|
||||
elif not attacker.is_my_turn or len(self.attack_queue) == 0:
|
||||
self.players[self.turn].pending_action = pl.PendingAction.PLAY
|
||||
self.players[self.turn].pending_action = PendingAction.PLAY
|
||||
|
||||
def steal_discard(self, attacker: pl.Player, target_username: str, card: cs.Card):
|
||||
p = self.get_player_named(target_username)
|
||||
if p != attacker and p.get_discarded(
|
||||
attacker,
|
||||
card_name=card.name,
|
||||
action="steal" if isinstance(card, cs.Panico) else "discard",
|
||||
action="steal" if (isinstance(card, cs.Panico) or isinstance(card, trt.PassengerCar)) else "discard",
|
||||
):
|
||||
self.ready_count = 0
|
||||
self.waiting_for = 1
|
||||
attacker.pending_action = pl.PendingAction.WAIT
|
||||
attacker.pending_action = PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.get_player_named(target_username).notify_self()
|
||||
else:
|
||||
attacker.pending_action = pl.PendingAction.CHOOSE
|
||||
attacker.pending_action = PendingAction.CHOOSE
|
||||
attacker.target_p = target_username
|
||||
if isinstance(card, cs.CatBalou):
|
||||
attacker.choose_action = "discard"
|
||||
@ -575,7 +581,7 @@ class Game:
|
||||
):
|
||||
self.ready_count = 0
|
||||
self.waiting_for = 1
|
||||
attacker.pending_action = pl.PendingAction.WAIT
|
||||
attacker.pending_action = PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.get_player_named(target_username).notify_self()
|
||||
|
||||
@ -583,14 +589,14 @@ class Game:
|
||||
if self.get_player_named(target_username).get_dueled(attacker=attacker):
|
||||
self.ready_count = 0
|
||||
self.waiting_for = 1
|
||||
attacker.pending_action = pl.PendingAction.WAIT
|
||||
attacker.pending_action = PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.get_player_named(target_username).notify_self()
|
||||
|
||||
def emporio(self):
|
||||
pls = self.get_alive_players()
|
||||
self.available_cards = [self.deck.draw(True) for i in range(len(pls))]
|
||||
self.players[self.turn].pending_action = pl.PendingAction.CHOOSE
|
||||
self.players[self.turn].pending_action = PendingAction.CHOOSE
|
||||
self.players[self.turn].choose_text = "choose_card_to_get"
|
||||
self.players[self.turn].available_cards = self.available_cards
|
||||
G.sio.emit(
|
||||
@ -612,7 +618,7 @@ class Game:
|
||||
)
|
||||
player.hand.append(card)
|
||||
player.available_cards = []
|
||||
player.pending_action = pl.PendingAction.WAIT
|
||||
player.pending_action = PendingAction.WAIT
|
||||
player.notify_self()
|
||||
pls = self.get_alive_players()
|
||||
next_player = pls[
|
||||
@ -631,14 +637,14 @@ class Game:
|
||||
next_player.hand.append(self.available_cards.pop())
|
||||
next_player.notify_self()
|
||||
G.sio.emit("emporio", room=self.name, data='{"name":"","cards":[]}')
|
||||
self.players[self.turn].pending_action = pl.PendingAction.PLAY
|
||||
self.players[self.turn].pending_action = PendingAction.PLAY
|
||||
self.players[self.turn].notify_self()
|
||||
elif next_player == self.players[self.turn]:
|
||||
G.sio.emit("emporio", room=self.name, data='{"name":"","cards":[]}')
|
||||
self.players[self.turn].pending_action = pl.PendingAction.PLAY
|
||||
self.players[self.turn].pending_action = PendingAction.PLAY
|
||||
self.players[self.turn].notify_self()
|
||||
else:
|
||||
next_player.pending_action = pl.PendingAction.CHOOSE
|
||||
next_player.pending_action = PendingAction.CHOOSE
|
||||
next_player.choose_text = "choose_card_to_get"
|
||||
next_player.available_cards = self.available_cards
|
||||
G.sio.emit(
|
||||
@ -724,7 +730,7 @@ class Game:
|
||||
elif self.poker_on and not any(
|
||||
c.number == 1 for c in self.deck.scrap_pile[-tmp:]
|
||||
):
|
||||
self.players[self.turn].pending_action = pl.PendingAction.CHOOSE
|
||||
self.players[self.turn].pending_action = PendingAction.CHOOSE
|
||||
self.players[
|
||||
self.turn
|
||||
].choose_text = f"choose_from_poker;{min(2, tmp)}"
|
||||
@ -735,10 +741,10 @@ class Game:
|
||||
print("attack completed, next attack")
|
||||
atk = self.attack_queue.pop(0)
|
||||
self.attack(atk[0], atk[1], atk[2], atk[3], skip_queue=True)
|
||||
elif self.players[self.turn].pending_action == pl.PendingAction.CHOOSE:
|
||||
elif self.players[self.turn].pending_action == PendingAction.CHOOSE:
|
||||
self.players[self.turn].notify_self()
|
||||
else:
|
||||
self.players[self.turn].pending_action = pl.PendingAction.PLAY
|
||||
self.players[self.turn].pending_action = PendingAction.PLAY
|
||||
self.poker_on = False
|
||||
self.players[self.turn].notify_self()
|
||||
|
||||
@ -842,6 +848,7 @@ class Game:
|
||||
)
|
||||
):
|
||||
self.deck.flip_event()
|
||||
self.deck.move_train_forward()
|
||||
if self.check_event(ce.RouletteRussa):
|
||||
self.is_russian_roulette_on = True
|
||||
if self.players[self.turn].get_banged(self.deck.event_cards[0]):
|
||||
@ -917,11 +924,26 @@ 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(
|
||||
{
|
||||
"stations": self.deck.stations,
|
||||
"current_train": self.deck.current_train,
|
||||
},
|
||||
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
|
||||
if self.deck.peek_scrap_pile():
|
||||
G.sio.emit("scrap", room=room, data=self.deck.peek_scrap_pile().__dict__)
|
||||
G.sio.emit("scrap", room=room, data=self.deck.peek_scrap_pile()[0].__dict__)
|
||||
else:
|
||||
G.sio.emit("scrap", room=room, data=None)
|
||||
|
||||
@ -1015,9 +1037,9 @@ class Game:
|
||||
self.deck.draw(True, player=player.attacker)
|
||||
player.attacker.notify_self()
|
||||
print(f"{self.name}: player {player.name} died")
|
||||
if self.waiting_for > 0 and player.pending_action == pl.PendingAction.RESPOND:
|
||||
if self.waiting_for > 0 and player.pending_action == PendingAction.RESPOND:
|
||||
self.responders_did_respond_resume_turn()
|
||||
player.pending_action = pl.PendingAction.WAIT
|
||||
player.pending_action = PendingAction.WAIT
|
||||
|
||||
if player.is_dead:
|
||||
return
|
||||
@ -1220,6 +1242,29 @@ class Game:
|
||||
def get_alive_players(self):
|
||||
return [p for p in self.players if not p.is_dead or p.is_ghost]
|
||||
|
||||
def get_other_players(self, player:pl.Player):
|
||||
return [{
|
||||
"name": p.name,
|
||||
"dist": 0,
|
||||
"lives": p.lives,
|
||||
"max_lives": p.max_lives,
|
||||
"is_sheriff": isinstance(p.role, roles.Sheriff),
|
||||
"cards": len(p.hand) + len(p.equipment),
|
||||
"is_ghost": p.is_ghost,
|
||||
"is_bot": p.is_bot,
|
||||
"icon": p.role.icon
|
||||
if (
|
||||
p.role is not None
|
||||
and (
|
||||
self.initial_players == 3
|
||||
or isinstance(p.role, roles.Sheriff)
|
||||
)
|
||||
)
|
||||
else "🤠",
|
||||
"avatar": p.avatar,
|
||||
"role": p.role,
|
||||
} for p in self.get_alive_players() if p != player]
|
||||
|
||||
def get_dead_players(self, include_ghosts=True):
|
||||
return [
|
||||
p for p in self.players if p.is_dead and (include_ghosts or not p.is_ghost)
|
||||
|
@ -1,9 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from enum import IntEnum
|
||||
import json
|
||||
from random import random, randrange, sample, uniform, randint
|
||||
import socketio
|
||||
import bang.deck as deck
|
||||
from random import randrange, sample, uniform, randint
|
||||
import bang.roles as r
|
||||
import bang.cards as cs
|
||||
import bang.expansions.dodge_city.cards as csd
|
||||
@ -17,9 +14,11 @@ 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
|
||||
from typing import List, TYPE_CHECKING
|
||||
import bang.expansions.train_robbery.stations as trs
|
||||
import bang.expansions.train_robbery.trains as trt
|
||||
from typing import List, TYPE_CHECKING, Callable
|
||||
from metrics import Metrics
|
||||
from globals import G
|
||||
from globals import G, PendingAction
|
||||
import sys
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -45,15 +44,6 @@ robot_pictures = [
|
||||
]
|
||||
|
||||
|
||||
class PendingAction(IntEnum):
|
||||
PICK = 0
|
||||
DRAW = 1
|
||||
PLAY = 2
|
||||
RESPOND = 3
|
||||
WAIT = 4
|
||||
CHOOSE = 5
|
||||
|
||||
|
||||
class Player:
|
||||
def is_admin(self):
|
||||
return self.discord_id in {"244893980960096266", "539795574019457034"}
|
||||
@ -102,6 +92,7 @@ class Player:
|
||||
self.is_bot = bot
|
||||
self.discord_token = discord_token
|
||||
self.discord_id = None
|
||||
self.did_choose_callback = None
|
||||
self.played_cards = 0
|
||||
self.avatar = ""
|
||||
self.last_played_card: cs.Card = None
|
||||
@ -280,25 +271,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 +329,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()
|
||||
@ -748,6 +737,9 @@ class Player:
|
||||
self.has_played_bang = False
|
||||
self.special_use_count = 0
|
||||
self.bang_used = 0
|
||||
if any((isinstance(c, trt.DiningCar) for c in self.equipment)):
|
||||
if self.game.deck.pick_and_scrap().suit == cs.Suit.HEARTS:
|
||||
self.lives = min(self.lives + 1, self.max_lives)
|
||||
if self.game.check_event(cew.DarlingValentine):
|
||||
hand = len(self.hand)
|
||||
for _ in range(hand):
|
||||
@ -782,35 +774,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 +833,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 +878,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 +896,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 +998,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:
|
||||
@ -1399,6 +1394,9 @@ class Player:
|
||||
self.target_p = self.rissa_targets.pop(0).name
|
||||
print(f"rissa targets: {self.rissa_targets}")
|
||||
self.notify_self()
|
||||
elif self.did_choose_callback is not None:
|
||||
self.did_choose_callback(self, card_index)
|
||||
self.notify_self()
|
||||
elif self.choose_text == "choose_ricercato":
|
||||
player = self.game.get_player_named(
|
||||
self.available_cards[card_index]["name"]
|
||||
@ -1871,6 +1869,8 @@ class Player:
|
||||
self.expected_response.append(cs.Bang(0, 0).name)
|
||||
if self.character.check(self.game, chw.BigSpencer):
|
||||
self.expected_response = []
|
||||
if any((isinstance(c, trt.Caboose) for c in self.equipment)):
|
||||
self.expected_response.append([c.name for c in self.equipment if not c.usable_next_turn])
|
||||
self.on_failed_response_cb = self.take_damage_response
|
||||
self.notify_self()
|
||||
|
||||
@ -1933,7 +1933,7 @@ class Player:
|
||||
self.notify_self()
|
||||
|
||||
def get_discarded(self, attacker=None, card_name=None, action=None):
|
||||
if card_name in {"Tornado", "Poker", "Bandidos"}:
|
||||
if card_name in {"Tornado", "Poker", "Bandidos", "Circus Wagon"}:
|
||||
self.pending_action = PendingAction.CHOOSE
|
||||
self.available_cards = self.hand.copy()
|
||||
if card_name == "Tornado":
|
||||
@ -1946,6 +1946,13 @@ class Player:
|
||||
self.available_cards.append(
|
||||
{"name": "-1hp", "icon": "💔", "noDesc": True}
|
||||
)
|
||||
if card_name == "Circus Wagon":
|
||||
from bang.expansions.train_robbery.trains import CircusWagon
|
||||
self.set_choose_action(
|
||||
"choose_circus_wagon",
|
||||
self.hand.copy(),
|
||||
CircusWagon.choose_circus_wagon,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
if self.can_escape(card_name) or self.character.check(
|
||||
@ -1995,6 +2002,17 @@ class Player:
|
||||
self.attacker = attacker
|
||||
self.attacking_card = card_name
|
||||
print(f"attacker -> {attacker}")
|
||||
# check for trt.PrivateCar
|
||||
if (card_name == "Bang!" and any(
|
||||
(isinstance(c, trt.PrivateCar) for c in self.equipment)
|
||||
) and len(self.hand) == 0):
|
||||
self.take_no_damage_response()
|
||||
G.sio.emit(
|
||||
"chat_message",
|
||||
room=self.game.name,
|
||||
data=f"_in_private_car|{self.name}|{attacker.name}",
|
||||
)
|
||||
return False
|
||||
if (
|
||||
isinstance(attacker, Player)
|
||||
and attacker.character.check(self.game, tvosch.ColoradoBill)
|
||||
@ -2100,9 +2118,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):
|
||||
@ -2112,6 +2128,11 @@ class Player:
|
||||
(isinstance(c, grc.Calumet) for c in self.gold_rush_equipment)
|
||||
):
|
||||
return False
|
||||
# check for trt.PrisonerCar
|
||||
if any(
|
||||
(isinstance(c, trt.PrisonerCar) for c in self.equipment)
|
||||
):
|
||||
return False
|
||||
if (
|
||||
not self.game.is_competitive
|
||||
and not any(
|
||||
@ -2145,6 +2166,11 @@ class Player:
|
||||
def get_dueled(self, attacker):
|
||||
self.attacker = attacker
|
||||
self.attacking_card = "Duello"
|
||||
if not self.is_my_turn and any(
|
||||
(isinstance(c, trt.PrisonerCar) for c in self.equipment)
|
||||
):
|
||||
self.take_no_damage_response()
|
||||
return False
|
||||
if (self.game.check_event(ceh.Sermone) and self.is_my_turn) or (
|
||||
not self.game.is_competitive
|
||||
and not any(
|
||||
@ -2501,26 +2527,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):
|
||||
@ -2548,8 +2577,59 @@ class Player:
|
||||
self.game.deck.shop_deck.append(card)
|
||||
self.game.deck.shop_cards[index] = None
|
||||
self.game.deck.fill_gold_rush_shop()
|
||||
G.sio.emit(
|
||||
"card_scrapped",
|
||||
room=self.game.name,
|
||||
data={"player": self.name, "pile": "gold_rush", "card": card.__dict__},
|
||||
)
|
||||
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=}")
|
||||
station.attached_train = train
|
||||
G.sio.emit(
|
||||
"chat_message",
|
||||
room=self.game.name,
|
||||
data=f"_bought_train|{self.name}|{station.name}|{train.name}",
|
||||
)
|
||||
G.sio.emit(
|
||||
"card_scrapped",
|
||||
room=self.game.name,
|
||||
data={"player": self.name, "pile": "train_robbery", "card": train.__dict__},
|
||||
)
|
||||
# shift train forward
|
||||
for i in range(train_index, len(self.game.deck.current_train) - 1):
|
||||
self.game.deck.current_train[i] = self.game.deck.current_train[
|
||||
i + 1
|
||||
]
|
||||
self.game.notify_stations()
|
||||
# self.game.deck.current_train[train_index] = None
|
||||
self.notify_self()
|
||||
|
||||
def set_choose_action(
|
||||
self,
|
||||
choose_text: str,
|
||||
available_cards: List,
|
||||
did_choose_callback: Callable[['Player', int], None] = None,
|
||||
):
|
||||
self.pending_action = PendingAction.CHOOSE
|
||||
self.choose_text = choose_text
|
||||
self.available_cards = available_cards
|
||||
self.did_choose_callback = did_choose_callback
|
||||
|
||||
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:
|
||||
@ -2659,6 +2739,8 @@ class Player:
|
||||
and not self.equipment[i].can_be_used_now
|
||||
):
|
||||
self.equipment[i].can_be_used_now = True
|
||||
if isinstance(self.equipment[i], trt.TrainCard):
|
||||
self.equipment[i].usable_next_turn = False
|
||||
for i in range(len(self.hand)):
|
||||
if self.hand[i].must_be_used:
|
||||
self.hand[i].must_be_used = False
|
||||
|
@ -1,6 +1,17 @@
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class G:
|
||||
sio = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class PendingAction(IntEnum):
|
||||
PICK = 0
|
||||
DRAW = 1
|
||||
PLAY = 2
|
||||
RESPOND = 3
|
||||
WAIT = 4
|
||||
CHOOSE = 5
|
||||
|
@ -16,8 +16,8 @@ import socketio
|
||||
from discord_webhook import DiscordWebhook
|
||||
|
||||
from bang.game import Game
|
||||
from bang.players import PendingAction, Player
|
||||
from globals import G
|
||||
from bang.players import Player
|
||||
from globals import G, PendingAction
|
||||
from metrics import Metrics
|
||||
|
||||
sys.setrecursionlimit(10**6) # this should prevents bots from stopping
|
||||
@ -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:
|
||||
@ -678,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):
|
||||
@ -1029,7 +1038,9 @@ def chat_message(sid, msg, pl=None):
|
||||
|
||||
cmd = msg.split()
|
||||
if len(cmd) >= 2:
|
||||
import bang.expansions.train_robbery.trains as trt
|
||||
cards = cs.get_starting_deck(ses.game.expansions)
|
||||
cards.extend(trt.get_all_cards())
|
||||
card_names = " ".join(cmd[1:]).split(",")
|
||||
for cn in card_names:
|
||||
ses.equipment.append(
|
||||
@ -1088,6 +1099,21 @@ def chat_message(sid, msg, pl=None):
|
||||
"type": "json",
|
||||
},
|
||||
)
|
||||
elif "/deckinfo" in msg:
|
||||
sio.emit(
|
||||
"chat_message",
|
||||
room=sid,
|
||||
data={
|
||||
"color": "",
|
||||
"text": json.dumps(
|
||||
ses.game.deck.__dict__,
|
||||
default=lambda o: f"<{o.__class__.__name__}() not serializable>",
|
||||
),
|
||||
"type": "json",
|
||||
},
|
||||
)
|
||||
elif "/trainfw" in msg:
|
||||
ses.game.deck.move_train_forward()
|
||||
elif "/status" in msg and ses.is_admin():
|
||||
sio.emit("mount_status", room=sid)
|
||||
elif "/meinfo" in msg:
|
||||
@ -1282,6 +1308,23 @@ def get_wildwestshowcards(sid):
|
||||
"wwscards_info", room=sid, data=json.dumps(chs, default=lambda o: o.__dict__)
|
||||
)
|
||||
|
||||
@sio.event
|
||||
@bang_handler
|
||||
def get_trainrobberycards(sid):
|
||||
print("get_trainrobberycards")
|
||||
import bang.expansions.train_robbery.cards as trc
|
||||
import bang.expansions.train_robbery.stations as trs
|
||||
import bang.expansions.train_robbery.trains as trt
|
||||
|
||||
chs = []
|
||||
chs.extend(trt.get_locomotives())
|
||||
chs.extend(trt.get_all_cards())
|
||||
sio.emit(
|
||||
"trainrobberycards_info", room=sid, data=json.dumps({
|
||||
"cards": chs,
|
||||
"stations": trs.get_all_stations()
|
||||
}, default=lambda o: o.__dict__)
|
||||
)
|
||||
|
||||
@sio.event
|
||||
@bang_handler
|
||||
|
@ -1,3 +1,4 @@
|
||||
from typing import Any, List
|
||||
import pytest
|
||||
from bang.characters import Character
|
||||
from bang.game import Game
|
||||
@ -8,7 +9,7 @@ from globals import G
|
||||
G.sio = DummySocket()
|
||||
|
||||
|
||||
def started_game(expansions, players=4, character=Character("test_char", 4)):
|
||||
def started_game(expansions=[], players=4, character=Character("test_char", 4)) -> Game:
|
||||
g = Game("test")
|
||||
g.expansions = expansions
|
||||
ps = [Player(f"p{i}", f"p{i}") for i in range(players)]
|
||||
@ -23,19 +24,19 @@ def started_game(expansions, players=4, character=Character("test_char", 4)):
|
||||
return g
|
||||
|
||||
|
||||
def set_events(g: Game, event_cards):
|
||||
def set_events(g: Game, event_cards) -> None:
|
||||
g.deck.event_cards = event_cards
|
||||
|
||||
|
||||
def current_player(g: Game):
|
||||
def current_player(g: Game) -> Player:
|
||||
return g.players[g.turn]
|
||||
|
||||
|
||||
def next_player(g: Game):
|
||||
def next_player(g: Game) -> Player:
|
||||
return g.players[(g.turn + 1) % len(g.players)]
|
||||
|
||||
|
||||
def current_player_with_cards(g: Game, cards):
|
||||
def current_player_with_cards(g: Game, cards: List[Any]) -> Player:
|
||||
p = current_player(g)
|
||||
p.draw("")
|
||||
p.hand = cards
|
||||
|
@ -3,7 +3,8 @@ from bang.characters import Character
|
||||
from bang.cards import *
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player, PendingAction
|
||||
from bang.players import Player
|
||||
from globals import PendingAction
|
||||
|
||||
# test card Barile
|
||||
def test_barile():
|
||||
|
@ -2,7 +2,8 @@ from random import randint
|
||||
from bang.characters import *
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player, PendingAction
|
||||
from bang.players import Player
|
||||
from globals import PendingAction
|
||||
from bang.cards import *
|
||||
|
||||
def test_bartcassidy():
|
||||
|
@ -3,7 +3,7 @@ from bang.characters import Character
|
||||
from bang.expansions.dodge_city.cards import *
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player, PendingAction
|
||||
from bang.players import Player
|
||||
import bang.cards as cs
|
||||
|
||||
# test Borraccia
|
||||
|
@ -1,8 +1,9 @@
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player, PendingAction
|
||||
from bang.players import Player
|
||||
from bang.roles import *
|
||||
from bang.cards import *
|
||||
from globals import PendingAction
|
||||
from tests import started_game
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
from bang.characters import Character
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player, PendingAction
|
||||
from bang.players import Player
|
||||
from globals import PendingAction
|
||||
from bang.roles import *
|
||||
from bang.cards import *
|
||||
|
||||
|
24
backend/tests/test_trains.py
Normal file
24
backend/tests/test_trains.py
Normal file
@ -0,0 +1,24 @@
|
||||
from random import randint
|
||||
from bang.characters import Character
|
||||
from bang.expansions.train_robbery.trains import *
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player
|
||||
import bang.cards as cs
|
||||
from globals import PendingAction
|
||||
|
||||
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
|
||||
|
||||
|
||||
def test_cattle_truck():
|
||||
g = started_game()
|
||||
|
||||
g.deck.scrap_pile = [cs.CatBalou(0,1), cs.CatBalou(0,2), cs.CatBalou(0,3)]
|
||||
p = current_player_with_cards(g, [CattleTruck()])
|
||||
p.play_card(0)
|
||||
|
||||
assert p.pending_action == PendingAction.CHOOSE
|
||||
p.choose(0)
|
||||
assert p.pending_action == PendingAction.PLAY
|
||||
assert len(p.hand) == 1
|
||||
assert len(g.deck.scrap_pile) == 2
|
@ -3,8 +3,9 @@ from bang.characters import Character
|
||||
from bang.expansions.the_valley_of_shadows.characters import *
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player, PendingAction
|
||||
from bang.players import Player
|
||||
import bang.cards as cs
|
||||
from globals import PendingAction
|
||||
|
||||
# test TucoFranziskaner
|
||||
def test_TucoFranziskaner():
|
||||
|
@ -3,8 +3,9 @@ from bang.characters import Character
|
||||
from bang.expansions.the_valley_of_shadows.cards import *
|
||||
from bang.deck import Deck
|
||||
from bang.game import Game
|
||||
from bang.players import Player, PendingAction
|
||||
from bang.players import Player
|
||||
import bang.cards as cs
|
||||
from globals import PendingAction
|
||||
|
||||
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
|
||||
|
||||
|
@ -4,7 +4,7 @@ from tests import started_game, set_events, current_player, next_player, current
|
||||
from bang.expansions.wild_west_show.characters import *
|
||||
from bang.cards import Card, Suit
|
||||
import bang.roles as roles
|
||||
from bang.players import PendingAction
|
||||
from globals import PendingAction
|
||||
|
||||
|
||||
# test TerenKill
|
||||
|
@ -4,7 +4,7 @@ from tests import started_game, set_events, current_player, next_player, current
|
||||
from bang.expansions.wild_west_show.card_events import *
|
||||
from bang.cards import Card, Suit
|
||||
import bang.roles as roles
|
||||
from bang.players import PendingAction
|
||||
from globals import PendingAction
|
||||
|
||||
|
||||
# test Camposanto
|
||||
|
@ -11,6 +11,7 @@
|
||||
'gold-rush': card.expansion === 'gold_rush',
|
||||
brown: card.kind === 0,
|
||||
black: card.kind === 1,
|
||||
'train-piece': card.type && card.type === 'train',
|
||||
}"
|
||||
>
|
||||
<h4>{{ cardName }}</h4>
|
||||
@ -64,7 +65,7 @@ export default {
|
||||
},
|
||||
suit() {
|
||||
if (this.card && !isNaN(this.card.suit)) {
|
||||
let x = ["♦️", "♣️", "♥️", "♠️", "🤑"];
|
||||
let x = ["♦️", "♣️", "♥️", "♠️", "🤑", "🚂"];
|
||||
return x[this.card.suit];
|
||||
} else if (this.card.suit) {
|
||||
return this.card.suit;
|
||||
@ -115,7 +116,7 @@ export default {
|
||||
#816b45 10px
|
||||
);
|
||||
}
|
||||
.card:not(.back, .fistful-of-cards, .high-noon, .gold-rush):before {
|
||||
.card:not(.back, .fistful-of-cards, .high-noon, .gold-rush, .train-piece):before {
|
||||
content: "";
|
||||
background-image: radial-gradient(var(--bg-color) 13%, #0000 5%),
|
||||
radial-gradient(var(--bg-color) 14%, transparent 5%),
|
||||
@ -231,6 +232,18 @@ export default {
|
||||
padding: 4pt;
|
||||
border-radius: 12pt;
|
||||
}
|
||||
.wip::after {
|
||||
content: "WIP";
|
||||
position: absolute;
|
||||
bottom: -12pt;
|
||||
right: -12pt;
|
||||
background: red;
|
||||
font-size: 10pt;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: bold;
|
||||
padding: 4pt;
|
||||
border-radius: 12pt;
|
||||
}
|
||||
.avatar {
|
||||
position: absolute;
|
||||
width: 36pt;
|
||||
@ -338,6 +351,7 @@ export default {
|
||||
}
|
||||
.cant-play {
|
||||
filter: brightness(0.5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.expansion {
|
||||
position: absolute;
|
||||
@ -347,4 +361,24 @@ export default {
|
||||
border-radius: 100%;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
.train-piece {
|
||||
background: linear-gradient(180deg, rgba(218,101,64,1) 0%, rgba(217,197,184,1) 13%, rgba(217,197,184,1) 53%, rgba(235,169,95,1) 61%, rgba(158,81,55,1) 91%, rgba(158,81,55,1) 100%);
|
||||
box-shadow: 0 0 0pt 2pt var(--font-color), 0 0 5pt 2pt #aaa;
|
||||
}
|
||||
.train-piece .emoji {
|
||||
transform: scaleX(-1);
|
||||
/* filter: grayscale(1); */
|
||||
}
|
||||
.train-piece .suit, .train-piece .expansion {
|
||||
display: none;
|
||||
}
|
||||
.train-piece h4 {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
bottom: -10pt;
|
||||
top: unset;
|
||||
font-size: 11pt;
|
||||
color: #FFE27E;
|
||||
}
|
||||
</style>
|
@ -21,7 +21,8 @@
|
||||
@click="fillCmd(msg.cmd)">{{msg.cmd}} <i class="std-text" style="font-size:8pt;">{{msg.help}}</i></p>
|
||||
</div>
|
||||
<form @submit="sendChatMessage" id="msg-form">
|
||||
<input id="my-msg" autocomplete="off" v-model="text" style="flex-grow:2;"/>
|
||||
<input id="my-msg" autocomplete="off" v-model="text" style="flex-grow:2;"
|
||||
@keydown.tab.prevent="tabComplete($event.target.value)"/>
|
||||
<input id="submit-message" type="submit" class="btn" :value="$t('submit')"/>
|
||||
</form>
|
||||
</div>
|
||||
@ -164,6 +165,12 @@ export default {
|
||||
this.text = cmd;
|
||||
document.getElementById('my-msg').focus();
|
||||
},
|
||||
tabComplete() {
|
||||
if (this.commandSuggestion.length > 0) {
|
||||
let cmd = this.commandSuggestion[0].cmd;
|
||||
this.text = cmd + ' ';
|
||||
}
|
||||
},
|
||||
playEffects(path) {
|
||||
const promise = (new Audio(path)).play();
|
||||
if(promise !== undefined){
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div >
|
||||
<div class="deck">
|
||||
<div class="deck" :style="`position:relative;${goldRushShopOpen?'border: 2px dashed #6a6a6a42;border-radius:8pt':''}`" v-if="goldRushCards.length > 0" >
|
||||
<div class="deck" id="gold-rush-deck" :style="`position:relative;${goldRushShopOpen?'border: 2px dashed #6a6a6a42;border-radius:8pt':''}`" v-if="goldRushCards.length > 0" >
|
||||
<card @pointerenter.native="()=>{setGoldRushDesc(goldRushCards[0])}" @pointerleave.native="goldRushDesc=null" :style="goldRushShopOpen?``:`position:absolute; top:0; right:0; transform: rotate(95deg) translate(30px, -40px) scale(0.6)`" v-if="goldRushCards.length > 0" :key="goldRushCards[0].name" :card="goldRushCards[0]" :class="{'shop-open':goldRushShopOpen, 'cant-play': pending_action !==2 || gold_nuggets < goldRushCards[0].number - gold_rush_discount}" @click.native="() => {buy_gold_rush_card(0)}"/>
|
||||
<card @pointerenter.native="()=>{setGoldRushDesc(goldRushCards[1])}" @pointerleave.native="goldRushDesc=null" :style="goldRushShopOpen?``:`position:absolute; top:0; right:0; transform: rotate(90deg) translate(0, -40px) scale(0.6)`" v-if="goldRushCards.length > 1" :key="goldRushCards[1].name" :card="goldRushCards[1]" :class="{'shop-open':goldRushShopOpen, 'cant-play': pending_action !==2 || gold_nuggets < goldRushCards[1].number - gold_rush_discount}" @click.native="() => {buy_gold_rush_card(1)}"/>
|
||||
<card @pointerenter.native="()=>{setGoldRushDesc(goldRushCards[2])}" @pointerleave.native="goldRushDesc=null" :style="goldRushShopOpen?``:`position:absolute; top:0; right:0; transform: rotate(85deg) translate(-30px, -40px) scale(0.6)`" v-if="goldRushCards.length > 2" :key="goldRushCards[2].name" :card="goldRushCards[2]" :class="{'shop-open':goldRushShopOpen, 'cant-play': pending_action !==2 || gold_nuggets < goldRushCards[2].number - gold_rush_discount}" @click.native="() => {buy_gold_rush_card(2)}"/>
|
||||
@ -11,6 +11,10 @@
|
||||
<card :card="goldRushCardBack" :donotlocalize="true" class="gold-rush back last-event" @click.native="goldRushShopOpen = !goldRushShopOpen"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="currentStations.length > 0" id="train-robbery-deck" class="deck" :style="`position:relative;border: 2px dashed #6a6a6a42;border-radius:8pt;align-items: flex-end;flex-direction:row;`" >
|
||||
<station-card @click.native="()=>{buyTrain(i)}" v-for="station, i in currentStations" :key="station.name" :card="station" :price="station.price" :trainPiece="trainPieceForStation(i)"
|
||||
@pointerenter.native="()=>{setStationDesc(i)}" @pointerleave.native="stationDesc = null"/>
|
||||
</div>
|
||||
<div v-if="eventCard" style="position:relative">
|
||||
<div class="card fistful-of-cards" style="position:relative; bottom:-3pt;right:-3pt;"/>
|
||||
<div class="card fistful-of-cards" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
|
||||
@ -38,15 +42,14 @@
|
||||
</div>
|
||||
<transition name="list">
|
||||
<p v-if="eventCard" class="center-stuff"><b>{{eventDesc}}</b></p>
|
||||
</transition>
|
||||
<transition name="list">
|
||||
<p v-if="eventCardWildWestShow && !eventCardWildWestShow.back" class="center-stuff">🎪 <b>{{eventDescWildWestShow}}</b> 🎪</p>
|
||||
</transition>
|
||||
<transition name="list">
|
||||
<div v-if="goldRushDesc">
|
||||
<p class="center-stuff">🤑️ <i>{{$t(`cards.${goldRushDesc.name}.desc`)}}</i> 🤑️</p>
|
||||
<p class="center-stuff">🤑️ <b>{{goldRushDesc.number - gold_rush_discount}} 💵️</b> 🤑️</p>
|
||||
</div>
|
||||
<div v-if="stationDesc">
|
||||
<p class="center-stuff"><i>{{stationDesc}}</i></p>
|
||||
</div>
|
||||
</transition>
|
||||
<div style="margin-bottom:6pt;margin-bottom: 6pt;display: flex;flex-direction: column;">
|
||||
<button class="btn" v-if="pending_action == 2 && can_gold_rush_discard" @click="$socket.emit('gold_rush_discard')">{{$t('gold_rush_discard')}}</button>
|
||||
@ -59,6 +62,7 @@
|
||||
|
||||
<script>
|
||||
import Card from '@/components/Card.vue'
|
||||
import StationCard from '@/components/StationCard.vue'
|
||||
|
||||
export default {
|
||||
name: 'Deck',
|
||||
@ -67,6 +71,7 @@ export default {
|
||||
},
|
||||
components: {
|
||||
Card,
|
||||
StationCard
|
||||
},
|
||||
data: () => ({
|
||||
card: {
|
||||
@ -88,8 +93,11 @@ export default {
|
||||
goldRushCards: [],
|
||||
gold_nuggets: 0,
|
||||
goldRushDesc: null,
|
||||
stationDesc: null,
|
||||
can_gold_rush_discard: false,
|
||||
gold_rush_discount: 0,
|
||||
currentStations: [],
|
||||
currentTrain: [],
|
||||
}),
|
||||
sockets: {
|
||||
self(self){
|
||||
@ -123,6 +131,11 @@ export default {
|
||||
console.log('GOLD RUSH:'+ cards)
|
||||
this.goldRushCards = JSON.parse(cards)
|
||||
},
|
||||
stations(stations) {
|
||||
let msg = JSON.parse(stations)
|
||||
this.currentStations = msg.stations
|
||||
this.currentTrain = msg.current_train
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
endTurnCard() {
|
||||
@ -163,6 +176,15 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
trainPieceForStation(i) {
|
||||
let index = this.currentTrain.length-5-i;
|
||||
if (index < 0 || index >= this.currentTrain.length)
|
||||
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')
|
||||
@ -189,6 +211,14 @@ export default {
|
||||
setGoldRushDesc(card) {
|
||||
this.goldRushDesc = card
|
||||
},
|
||||
setStationDesc(index) {
|
||||
console.log('setStationDesc', index)
|
||||
this.stationDesc = this.$t(`cards.${this.currentStations[index].name}.desc`)
|
||||
const trainPiece = this.trainPieceForStation(index)
|
||||
if (trainPiece) {
|
||||
this.stationDesc += '\n\n🚂' + this.$t(`cards.${trainPiece.name}.desc`)
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (window.innerWidth < 1000) {
|
||||
|
@ -127,15 +127,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="trainrobberycards">{{$t('help.trainrobberycards')}}</h2>
|
||||
<div class="flexy-cards-wrapper">
|
||||
<div v-for="(c, i) in trainrobberycards" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
|
||||
<Card :card="c" class="train-robbery" @pointerenter.native="''" @pointerleave.native="''"/>
|
||||
<div style="margin-left:6pt;">
|
||||
<p>{{$t(`cards.${c.name}.desc`)}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(c, i) in trainrobberystations" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
|
||||
<StationCard :card="c" class="train-robbery" @pointerenter.native="''" @pointerleave.native="''" :price="c.price"/>
|
||||
<div style="margin-left:6pt;">
|
||||
<p>{{$t(`cards.${c.name}.desc`)}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Card from '@/components/Card.vue'
|
||||
import StationCard from './StationCard.vue'
|
||||
|
||||
export default {
|
||||
name: 'Help',
|
||||
components: {
|
||||
Card,
|
||||
StationCard,
|
||||
},
|
||||
props: {
|
||||
inGame: Boolean
|
||||
@ -152,6 +169,8 @@ export default {
|
||||
goldrushcards: [],
|
||||
valleyofshadowscards: [],
|
||||
wildwestshowcards: [],
|
||||
trainrobberycards: [],
|
||||
trainrobberystations: [],
|
||||
}),
|
||||
computed: {
|
||||
endTurnCard() {
|
||||
@ -196,6 +215,14 @@ export default {
|
||||
...x,
|
||||
}))
|
||||
},
|
||||
trainrobberycards_info(cardsJson) {
|
||||
this.trainrobberycards = JSON.parse(cardsJson).cards.map(x=>({
|
||||
...x,
|
||||
}))
|
||||
this.trainrobberystations = JSON.parse(cardsJson).stations.map(x=>({
|
||||
...x,
|
||||
}))
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$socket.emit('get_cards')
|
||||
@ -205,6 +232,7 @@ export default {
|
||||
this.$socket.emit('get_goldrushcards')
|
||||
this.$socket.emit('get_valleyofshadowscards')
|
||||
this.$socket.emit('get_wildwestshowcards')
|
||||
this.$socket.emit('get_trainrobberycards')
|
||||
document.getElementById('help').scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
@ -646,8 +646,15 @@ export default {
|
||||
console.log("card_scrapped no deck");
|
||||
return;
|
||||
}
|
||||
let decelOffset = cumulativeOffset(decel);
|
||||
let phand = document.getElementById(`${data.player}-hand`);
|
||||
if (data.pile == "train_robbery") {
|
||||
decel = phand
|
||||
phand = document.getElementById("train-robbery-deck");
|
||||
} else if (data.pile == "gold_rush") {
|
||||
decel = phand
|
||||
phand = document.getElementById("gold-rush-deck");
|
||||
}
|
||||
let decelOffset = cumulativeOffset(decel);
|
||||
if (!phand) {
|
||||
console.log("card_scrapped no phand");
|
||||
return;
|
||||
|
126
frontend/src/components/StationCard.vue
Normal file
126
frontend/src/components/StationCard.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="{stationcard:true, 'cant-play':!trainPiece || trainPiece.is_locomotive}">
|
||||
<h4>{{ cardName }}</h4>
|
||||
<div :class="{ emoji: true, bottomed: card.avatar }">{{ emoji }}</div>
|
||||
<div class="alt_text">{{ card.alt_text }}</div>
|
||||
<div class="price">
|
||||
<card v-for="c, i in price" :key="i" :card="c"/>
|
||||
</div>
|
||||
</div>
|
||||
<card v-if="trainPiece" :card="trainPiece" :class="{'cant-play':trainPiece.is_locomotive}"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Card from '@/components/Card.vue'
|
||||
|
||||
export default {
|
||||
name: "StationCard",
|
||||
props: {
|
||||
card: Object,
|
||||
price: Array,
|
||||
trainPiece: Object,
|
||||
donotlocalize: Boolean,
|
||||
},
|
||||
components: {
|
||||
Card
|
||||
},
|
||||
computed: {
|
||||
cardName() {
|
||||
if (
|
||||
!this.donotlocalize &&
|
||||
this.$t(`cards.${this.card.name}.name`) !==
|
||||
`cards.${this.card.name}.name`
|
||||
) {
|
||||
return this.$t(`cards.${this.card.name}.name`);
|
||||
}
|
||||
if (this.card.name == "you") {
|
||||
return this.$t("you");
|
||||
}
|
||||
return this.card.name;
|
||||
},
|
||||
emoji() {
|
||||
return this.card.icon != "you" ? this.card.icon : this.$t("you");
|
||||
},
|
||||
suit() {
|
||||
if (this.card && !isNaN(this.card.suit)) {
|
||||
let x = ["♦️", "♣️", "♥️", "♠️", "🤑", "🚂"];
|
||||
return x[this.card.suit];
|
||||
} else if (this.card.suit) {
|
||||
return this.card.suit;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
number() {
|
||||
if (isNaN(this.card.suit)) return this.card.number;
|
||||
if (this.card.number === 1) return "A";
|
||||
else if (this.card.number === 11) return "J";
|
||||
else if (this.card.number === 12) return "Q";
|
||||
else if (this.card.number === 13) return "K";
|
||||
else return this.card.number;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stationcard {
|
||||
cursor: pointer;
|
||||
width: 60pt;
|
||||
min-width: 60pt;
|
||||
height: 60pt;
|
||||
margin: 6pt;
|
||||
border-radius: 16pt 16pt 2pt 2pt;
|
||||
position: relative;
|
||||
transition: all 0.5s ease-in-out;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: normal;
|
||||
color: white;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
rgb(198 78 45),
|
||||
rgb(198 78 45) 5px,
|
||||
rgb(178 58 25) 5px,
|
||||
rgb(178 58 25) 10px
|
||||
);
|
||||
border: 2pt solid rgb(198 78 45);
|
||||
box-shadow: 0 0 0pt 2pt var(--font-color), 0 0 5pt 2pt #aaa;
|
||||
}
|
||||
.stationcard h4 {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
top: -15pt;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.alt_text {
|
||||
right: 3pt;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: small;
|
||||
bottom: 20pt;
|
||||
left: 3pt;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.cant-play {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.stationcard .card {
|
||||
cursor: unset;
|
||||
}
|
||||
</style>
|
@ -100,6 +100,11 @@
|
||||
"copy": "Copy invite",
|
||||
"no_players_in_range": "You can't see the other players, equip a weapon or a scope!",
|
||||
"cantplaycard": "You can't play this card like that!",
|
||||
"choose_baggage_car": "Discard this for a Missed! Panic!, Cat Balou, or an extra BANG!",
|
||||
"choose_mail_car": "Choose which card to give to another player",
|
||||
"choose_other_player": "Choose the player to give the card to",
|
||||
"choose_sleeper_car": "Choose a card to discard for Sleeper Car",
|
||||
"choose_buy_train": "Discard a card to rob the train, and get the carriage",
|
||||
"chat": {
|
||||
"spectators": " | A spectator is watching the game | {n} spectators are watching the game",
|
||||
"chat": "Chat",
|
||||
@ -153,7 +158,9 @@
|
||||
"choose_emporio": ";{0}; has chosen ;{1}; from General Store.",
|
||||
"shotgun_scrap": "When the shotgun hit ;{0}; a card flew away from his hand (;{1};)",
|
||||
"taglia_reward": "💰 ;{1}; got a card from the bounty on ;{0};",
|
||||
"snake_bit": "🐍 ;{0}; was bitten by the Rattle Snake."
|
||||
"snake_bit": "🐍 ;{0}; was bitten by the Rattle Snake.",
|
||||
"in_private_car": "🚋💁🏻 ;{0}; is in a private car and couldn't get Bang!ed by ;{1};",
|
||||
"bought_train": "🚂 ;{0}; robbed the train at ;{1}; and got the ;{2};"
|
||||
},
|
||||
"foc": {
|
||||
"leggedelwest": "He must play this card on this turn if possible."
|
||||
@ -855,6 +862,130 @@
|
||||
"Wild West Show": {
|
||||
"name": "Wild West Show",
|
||||
"desc": "The goal for each player becomes: \"Be the last one standing!\""
|
||||
},
|
||||
"Boom Town": {
|
||||
"name": "Boom Town",
|
||||
"desc": "Discard a Bang! to rob the train"
|
||||
},
|
||||
"Caticor": {
|
||||
"name": "Caticor",
|
||||
"desc": "Discard a Cat Balou or Panico to rob the train"
|
||||
},
|
||||
"Creepy Creek": {
|
||||
"name": "Creepy Creek",
|
||||
"desc": "Discard a card of spades to rob the train"
|
||||
},
|
||||
"Crowns Hole": {
|
||||
"name": "Crown's Hole",
|
||||
"desc": "Discard a beer to rob the train"
|
||||
},
|
||||
"Deadwood": {
|
||||
"name": "Deadwood",
|
||||
"desc": "Discard an equipment card to rob the train"
|
||||
},
|
||||
"Dodgeville": {
|
||||
"name": "Dodgeville",
|
||||
"desc": "Discard a Missed! to rob the train"
|
||||
},
|
||||
"Fort Worth": {
|
||||
"name": "Fort Worth",
|
||||
"desc": "Discard a card with number 10, J, Q, K, A to rob the train"
|
||||
},
|
||||
"Frisco": {
|
||||
"name": "Frisco",
|
||||
"desc": "Discard a card of clubs to rob the train"
|
||||
},
|
||||
"Miners Oath": {
|
||||
"name": "Miner's Oath",
|
||||
"desc": "Discard a card of diamonds to rob the train"
|
||||
},
|
||||
"San Tafe": {
|
||||
"name": "San Tafe",
|
||||
"desc": "Discard a card of hearts to rob the train"
|
||||
},
|
||||
"Tombrock": {
|
||||
"name": "Tombrock",
|
||||
"desc": "Lose 1 life point to rob the train"
|
||||
},
|
||||
"Yooma": {
|
||||
"name": "Yooma",
|
||||
"desc": "Discard a card with number between 2 and 9 to rob the train"
|
||||
},
|
||||
"Virginia Town": {
|
||||
"name": "Virginia Town",
|
||||
"desc": "Discard two cards to rob the train"
|
||||
},
|
||||
"Ironhorse": {
|
||||
"name": "Ironhorse",
|
||||
"desc": "Each player, including the one who activated the effect, is targeted by a BANG!\nNo player is responsible for any loss of life points.\nIf all players are eliminated at the same time, the Outlaws win."
|
||||
},
|
||||
"Leland": {
|
||||
"name": "Leland",
|
||||
"desc": "LOCOMOTIVE: perform the effect of the Emporium, starting with the current player and proceeding clockwise."
|
||||
},
|
||||
"Baggage Car": {
|
||||
"name": "Baggage Car",
|
||||
"desc": "Discard this card to gain the effect of a Missed!, Panic!, Cat Balou, or an extra BANG! card."
|
||||
},
|
||||
"Caboose": {
|
||||
"name": "Caboose",
|
||||
"desc": "To discard one of your blue-bordered cards, include a car as if it were a Missed!"
|
||||
},
|
||||
"Cattle Truck": {
|
||||
"name": "Cattle Truck",
|
||||
"desc": "Discard this card to look at the top 3 cards of the discard pile and choose 1 to add to your hand."
|
||||
},
|
||||
"Circus Wagon": {
|
||||
"name": "Circus Wagon",
|
||||
"desc": "Discard this card. Each other player must discard a card that he has in play."
|
||||
},
|
||||
"Coal Hopper": {
|
||||
"name": "Coal Hopper",
|
||||
"desc": "Discard this card to draw a card and discard a car in play in front of any player of your choice."
|
||||
},
|
||||
"Dining Car": {
|
||||
"name": "Dining Car",
|
||||
"desc": "At the beginning of your turn, \"pick!\": if it's Hearts, regain 1 life point."
|
||||
},
|
||||
"Express Car": {
|
||||
"name": "Express Car",
|
||||
"desc": "Discard all your hand cards and take another turn."
|
||||
},
|
||||
"Ghost Car": {
|
||||
"name": "Ghost Car",
|
||||
"desc": "Play it on any player except the Sheriff. If you are eliminated, you remain in play, but cannot look at or lose any life points."
|
||||
},
|
||||
"Lounge Car": {
|
||||
"name": "Lounge Car",
|
||||
"desc": "Discard this card to draw 2 cards from the deck and put 1 of them in play in front of you and 1 in front of another player."
|
||||
},
|
||||
"Lumber Flatcar": {
|
||||
"name": "Lumber Flatcar",
|
||||
"desc": "Play this card on any player (including yourself). While this card is in play, that player sees all other players at a distance increased by 1."
|
||||
},
|
||||
"Mail Car": {
|
||||
"name": "Mail Car",
|
||||
"desc": "Discard this card to draw 3 cards and give 1 of them to another player of your choice."
|
||||
},
|
||||
"Observation Car": {
|
||||
"name": "Observation Car",
|
||||
"desc": "You see other players at a distance reduced by 1. Other players see you at a distance increased by 1."
|
||||
},
|
||||
"Passenger Car": {
|
||||
"name": "Passenger Car",
|
||||
"desc": "Discard this card to draw a card (from hand or in play) from another player."
|
||||
},
|
||||
"Prisoner Car": {
|
||||
"name": "Prisoner Car",
|
||||
"desc": "Duel and Indians! cards played by other players have no effect on you."
|
||||
},
|
||||
"Private Car": {
|
||||
"name": "Private Car",
|
||||
"desc": "If you have no cards in hand, you cannot be targeted by BANG! cards."
|
||||
},
|
||||
"Sleeper Car": {
|
||||
"name": "Sleeper Car",
|
||||
"desc": "Once per turn, you may discard another of your blue-bordered cards in play."
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
@ -906,7 +1037,9 @@
|
||||
"highnooncards": "High Noon - Event Cards",
|
||||
"foccards": "Fistful of Cards - Event Cards",
|
||||
"goldrushcards": "Gold Rush Cards",
|
||||
"valleyofshadowscards": "The Valley of Shadows Cards"
|
||||
"valleyofshadowscards": "The Valley of Shadows Cards",
|
||||
"wildwestshowcards": "Wild West Show Cards",
|
||||
"trainrobberycards": "Train Robbery Cards"
|
||||
},
|
||||
"theme": {
|
||||
"sepia": "Sepia",
|
||||
|
@ -153,7 +153,8 @@
|
||||
"choose_emporio": ";{0}; ha scelto ;{1}; da Emporio.",
|
||||
"shotgun_scrap": "Quando lo shotgun ha colpito ;{0}; gli ha tolto una carta (;{1};)",
|
||||
"taglia_reward": "💰 ;{1}; ha ottenuto ricompensa dalla taglia su ;{0};",
|
||||
"snake_bit": "🐍 ;{0}; è stato morso dal Serpente a Sonagli."
|
||||
"snake_bit": "🐍 ;{0}; è stato morso dal Serpente a Sonagli.",
|
||||
"in_private_car": "🚋💁🏻 ;{0}; è in una carrozza privata e non è stato attaccato da ;{1};"
|
||||
},
|
||||
"foc": {
|
||||
"leggedelwest": "Ed è obbligato a usarla nel suo turno, se possibile"
|
||||
@ -855,6 +856,130 @@
|
||||
"Wild West Show": {
|
||||
"name": "Wild West Show",
|
||||
"desc": "L'obiettivo di ogni giocatore diventa: \"Rimani l'ultimo in gioco!\""
|
||||
},
|
||||
"Boom Town": {
|
||||
"name": "Boom Town",
|
||||
"desc": "Scarta una Bang! per derubare il treno"
|
||||
},
|
||||
"Caticor": {
|
||||
"name": "Caticor",
|
||||
"desc": "Scarta una Cat Balou o un Panico per derubare il treno"
|
||||
},
|
||||
"Creepy Creek": {
|
||||
"name": "Creepy Creek",
|
||||
"desc": "Scarta una carta di picche per derubare il treno"
|
||||
},
|
||||
"Crowns Hole": {
|
||||
"name": "Crown's Hole",
|
||||
"desc": "Scarta una birra per derubare il treno"
|
||||
},
|
||||
"Deadwood": {
|
||||
"name": "Deadwood",
|
||||
"desc": "Scarta una carta equipaggiamento per derubare il treno"
|
||||
},
|
||||
"Dodgeville": {
|
||||
"name": "Dodgeville",
|
||||
"desc": "Scarta un Mancato! per derubare il treno"
|
||||
},
|
||||
"Fort Worth": {
|
||||
"name": "Fort Worth",
|
||||
"desc": "Scarta una carta con il numero 10, J, Q, K, A per derubare il treno"
|
||||
},
|
||||
"Frisco": {
|
||||
"name": "Frisco",
|
||||
"desc": "Scarta una carta di fiori per derubare il treno"
|
||||
},
|
||||
"Miners Oath": {
|
||||
"name": "Miner's Oath",
|
||||
"desc": "Scarta una carta di quadri per derubare il treno"
|
||||
},
|
||||
"San Tafe": {
|
||||
"name": "San Tafe",
|
||||
"desc": "Scarta una carta di cuori per derubare il treno"
|
||||
},
|
||||
"Tombrock": {
|
||||
"name": "Tombrock",
|
||||
"desc": "Per derubare il treno, perdi 1 punto vita"
|
||||
},
|
||||
"Yooma": {
|
||||
"name": "Yooma",
|
||||
"desc": "Scarta una carta con un numero compreso tra 2 e 9 per derubare il treno"
|
||||
},
|
||||
"Virginia Town": {
|
||||
"name": "Virginia Town",
|
||||
"desc": "Scarta due carte per derubare il treno"
|
||||
},
|
||||
"Ironhorse": {
|
||||
"name": "Ironhorse",
|
||||
"desc": "LOCOMOTIVA: Ogni giocatore, incluso colui che ha attivato l'effetto, è bersaglio di un BANG!\nNessun giocatore è responsabile dell'eventuale perdita di punti vita.\nSe tutti i giocatori vengono eliminati allo stesso tempo, i Fuorilegge vincono."
|
||||
},
|
||||
"Leland": {
|
||||
"name": "Leland",
|
||||
"desc": "LOCOMOTIVA: svolgi l'effetto dell'Emporio, cominciando dal giocatore di turno e procedendo in senso orario."
|
||||
},
|
||||
"Baggage Car": {
|
||||
"name": "Baggage Car",
|
||||
"desc": "Scartalo: ottieni l'effetto di un Mancato!, Panico!, Cat Balou o di un BANG! extra.\nDiscard this for a Missed!Panic!, Cat Balou, or an extra BANG!"
|
||||
},
|
||||
"Caboose": {
|
||||
"name": "Caboose",
|
||||
"desc": "Pro scartare un aura tua carta bordo bin incuso un vagone come se fosse un Mancato!"
|
||||
},
|
||||
"Cattle Truck": {
|
||||
"name": "Cattle Truck",
|
||||
"desc": "Scartalo: guarda le 3 carte in cima agli scarti e pescane I"
|
||||
},
|
||||
"Circus Wagon": {
|
||||
"name": "Circus Wagon",
|
||||
"desc": "Scartalo: ogni altro giocatore deve scartare una carta che ha in gioco."
|
||||
},
|
||||
"Coal Hopper": {
|
||||
"name": "Coal Hopper",
|
||||
"desc": "Scartalo: pesca una carta e scarta un vagone in gioco davanti a un giocatore a ma scelta."
|
||||
},
|
||||
"Dining Car": {
|
||||
"name": "Dining Car",
|
||||
"desc": "A inizio turno, \"estrai!\": se è Cuori, recuperi I punto vita."
|
||||
},
|
||||
"Express Car": {
|
||||
"name": "Express Car",
|
||||
"desc": "Scarta tutte le carte in mano, poi gioca un altro turno"
|
||||
},
|
||||
"Ghost Car": {
|
||||
"name": "Ghost Car",
|
||||
"desc": "Giocalo su chiunque tranne lo Sceritfo. Se vieni eliminato, invece resti in gioco, ma non puol guada nare ne perdere punk vita."
|
||||
},
|
||||
"Lounge Car": {
|
||||
"name": "Lounge Car",
|
||||
"desc": "Scartalo: pesca 2 vagoni dal mazzo, mettine I in gioco di fronte a te e 1 di fronte a un altro giocatore."
|
||||
},
|
||||
"Lumber Flatcar": {
|
||||
"name": "Lumber Flatcar",
|
||||
"desc": "Giocalo su un qualsiasi giocatore (compreso te). Finché questa carta è in gioco, quel giocatore vede gli altri giocatori a distanza aumentata di 1."
|
||||
},
|
||||
"Mail Car": {
|
||||
"name": "Mail Car",
|
||||
"desc": "Scartalo: pesca 3 carte e dai 1 di esse a un altro giocatore a tua scelta."
|
||||
},
|
||||
"Observation Car": {
|
||||
"name": "Observation Car",
|
||||
"desc": "Tu vedi gli altri a distanza -1. Gli altri a vedono a distanza +1."
|
||||
},
|
||||
"Passenger Car": {
|
||||
"name": "Passenger Car",
|
||||
"desc": "Scartalo: pesca una carta (in mano o in gioco) da un altro giocatore"
|
||||
},
|
||||
"Prisoner Car": {
|
||||
"name": "Prisoner Car",
|
||||
"desc": "Le carte Duello e Indiani! giocate dagli altri giocatori non hanno effetto su di te."
|
||||
},
|
||||
"Private Car": {
|
||||
"name": "Private Car",
|
||||
"desc": "se non hai carte in mano. non puoi essere bersaelio di carte BANG"
|
||||
},
|
||||
"Sleeper Car": {
|
||||
"name": "Sleeper Car",
|
||||
"desc": "Una volta per turno, puoi scartare un'altra tua carta a bordo blu incluso."
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
|
@ -5,4 +5,5 @@ export const emojiMap = {
|
||||
'fistful_of_cards': '🎴',
|
||||
'the_valley_of_shadows': '👻',
|
||||
'wild_west_show': '🎪',
|
||||
'train_robbery': '🚂',
|
||||
}
|
@ -28,13 +28,19 @@ export const expansionsMap = {
|
||||
icon: '👻',
|
||||
back: true,
|
||||
expansion: 'the-valley-of-shadows',
|
||||
status: 'beta',
|
||||
},
|
||||
'wild_west_show': {
|
||||
name: 'Wild West Show',
|
||||
icon: '🎪',
|
||||
back: true,
|
||||
expansion: 'wild-west-show',
|
||||
status: 'alpha',
|
||||
status: 'beta',
|
||||
},
|
||||
'train_robbery': {
|
||||
name: 'The Great Train Robbery',
|
||||
icon: '🚂',
|
||||
back: true,
|
||||
expansion: 'train-roobbery',
|
||||
status: 'wip',
|
||||
}
|
||||
}
|
1257
frontend/yarn.lock
1257
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user