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 # ♥
|
HEARTS = 2 # ♥
|
||||||
SPADES = 3 # ♠
|
SPADES = 3 # ♠
|
||||||
GOLD = 4 # 🤑
|
GOLD = 4 # 🤑
|
||||||
|
TRAIN = 5 # 🚂
|
||||||
|
|
||||||
|
|
||||||
class Card(ABC):
|
class Card(ABC):
|
||||||
@ -66,7 +67,7 @@ class Card(ABC):
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if str(self.suit).isnumeric():
|
if str(self.suit).isnumeric():
|
||||||
char = ["♦️", "♣️", "♥️", "♠️", "🤑"][int(self.suit)]
|
char = ["♦️", "♣️", "♥️", "♠️", "🤑", "🚋"][int(self.suit)]
|
||||||
else:
|
else:
|
||||||
char = self.suit
|
char = self.suit
|
||||||
return f"{self.name} {char}{self.number}"
|
return f"{self.name} {char}{self.number}"
|
||||||
@ -370,14 +371,18 @@ class Birra(Card):
|
|||||||
player.game.deck.draw(True, player=p)
|
player.game.deck.draw(True, player=p)
|
||||||
p.notify_self()
|
p.notify_self()
|
||||||
if "gold_rush" in player.game.expansions and self.number != 42:
|
if "gold_rush" in player.game.expansions and self.number != 42:
|
||||||
from bang.players import PendingAction
|
player.set_choose_action(
|
||||||
|
"choose_birra_function",
|
||||||
player.available_cards = [
|
[
|
||||||
{"name": "Pepita", "icon": "💵️", "alt_text": "1", "noDesc": True},
|
{
|
||||||
self,
|
"name": "Pepita",
|
||||||
]
|
"icon": "💵️",
|
||||||
player.choose_text = "choose_birra_function"
|
"alt_text": "1",
|
||||||
player.pending_action = PendingAction.CHOOSE
|
"noDesc": True,
|
||||||
|
},
|
||||||
|
self,
|
||||||
|
],
|
||||||
|
)
|
||||||
player.notify_self()
|
player.notify_self()
|
||||||
return True
|
return True
|
||||||
if (
|
if (
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
from typing import List, Set, Dict, Tuple, Optional, TYPE_CHECKING
|
from typing import List, Set, Dict, Tuple, Optional, TYPE_CHECKING
|
||||||
import random
|
|
||||||
import bang.cards as cs
|
import bang.cards as cs
|
||||||
import bang.expansions.fistful_of_cards.card_events as ce
|
import bang.expansions.fistful_of_cards.card_events as ce
|
||||||
import bang.expansions.high_noon.card_events as ceh
|
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.card_events as cew
|
||||||
import bang.expansions.wild_west_show.characters as chw
|
import bang.expansions.wild_west_show.characters as chw
|
||||||
import bang.expansions.gold_rush.shop_cards as grc
|
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
|
from globals import G
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
|
from bang.players import Player
|
||||||
|
|
||||||
|
|
||||||
class Deck:
|
class Deck:
|
||||||
@ -35,6 +37,9 @@ class Deck:
|
|||||||
self.game = game
|
self.game = game
|
||||||
self.event_cards: List[ce.CardEvent] = []
|
self.event_cards: List[ce.CardEvent] = []
|
||||||
self.event_cards_wildwestshow: 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] = []
|
endgame_cards: List[ce.CardEvent] = []
|
||||||
if "fistful_of_cards" in game.expansions:
|
if "fistful_of_cards" in game.expansions:
|
||||||
self.event_cards.extend(ce.get_all_events(game.rng))
|
self.event_cards.extend(ce.get_all_events(game.rng))
|
||||||
@ -47,6 +52,12 @@ class Deck:
|
|||||||
game.rng.shuffle(self.event_cards_wildwestshow)
|
game.rng.shuffle(self.event_cards_wildwestshow)
|
||||||
self.event_cards_wildwestshow.insert(0, None)
|
self.event_cards_wildwestshow.insert(0, None)
|
||||||
self.event_cards_wildwestshow.append(cew.get_endgame_card())
|
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:
|
if len(self.event_cards) > 0:
|
||||||
game.rng.shuffle(self.event_cards)
|
game.rng.shuffle(self.event_cards)
|
||||||
self.event_cards = self.event_cards[:12]
|
self.event_cards = self.event_cards[:12]
|
||||||
@ -106,12 +117,21 @@ class Deck:
|
|||||||
self.shop_cards[i].reset_card()
|
self.shop_cards[i].reset_card()
|
||||||
self.game.notify_gold_rush_shop()
|
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:
|
def peek(self, n_cards: int) -> list:
|
||||||
return self.cards[:n_cards]
|
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:
|
if len(self.scrap_pile) > 0:
|
||||||
return self.scrap_pile[-1]
|
return self.scrap_pile[-n_cards:]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -176,7 +196,7 @@ class Deck:
|
|||||||
else:
|
else:
|
||||||
return self.draw()
|
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:
|
if card.number == 42:
|
||||||
return
|
return
|
||||||
card.reset_card()
|
card.reset_card()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
from bang.characters import Character
|
from bang.characters import Character
|
||||||
|
from globals import PendingAction
|
||||||
|
|
||||||
|
|
||||||
class PixiePete(Character):
|
class PixiePete(Character):
|
||||||
@ -165,8 +166,6 @@ class DocHolyday(Character):
|
|||||||
|
|
||||||
def special(self, player, data):
|
def special(self, player, data):
|
||||||
if super().special(player, data):
|
if super().special(player, data):
|
||||||
from bang.players import PendingAction
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
player.special_use_count < 1
|
player.special_use_count < 1
|
||||||
and player.pending_action == PendingAction.PLAY
|
and player.pending_action == PendingAction.PLAY
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from bang.cards import *
|
from bang.cards import *
|
||||||
import bang.roles as r
|
import bang.roles as r
|
||||||
import bang.players as pl
|
import bang.players as pl
|
||||||
from globals import G
|
from globals import G, PendingAction
|
||||||
|
|
||||||
class ShopCardKind(IntEnum):
|
class ShopCardKind(IntEnum):
|
||||||
BROWN = 0 # Se l’equipaggiamento ha il bordo marrone, applicane subito l’effetto e poi scartalo.
|
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
|
'is_player': True
|
||||||
} for p in player.game.get_alive_players()]
|
} for p in player.game.get_alive_players()]
|
||||||
player.choose_text = 'choose_bicchierino'
|
player.choose_text = 'choose_bicchierino'
|
||||||
player.pending_action = pl.PendingAction.CHOOSE
|
player.pending_action = PendingAction.CHOOSE
|
||||||
player.notify_self()
|
player.notify_self()
|
||||||
return super().play_card(player, against, _with)
|
return super().play_card(player, against, _with)
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ class Bottiglia(ShopCard):
|
|||||||
for i in range(len(player.available_cards)):
|
for i in range(len(player.available_cards)):
|
||||||
player.available_cards[i].must_be_used = True
|
player.available_cards[i].must_be_used = True
|
||||||
player.choose_text = 'choose_bottiglia'
|
player.choose_text = 'choose_bottiglia'
|
||||||
player.pending_action = pl.PendingAction.CHOOSE
|
player.pending_action = PendingAction.CHOOSE
|
||||||
player.notify_self()
|
player.notify_self()
|
||||||
return super().play_card(player, against, _with)
|
return super().play_card(player, against, _with)
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ class Complice(ShopCard):
|
|||||||
for i in range(len(player.available_cards)):
|
for i in range(len(player.available_cards)):
|
||||||
player.available_cards[i].must_be_used = True
|
player.available_cards[i].must_be_used = True
|
||||||
player.choose_text = 'choose_complice'
|
player.choose_text = 'choose_complice'
|
||||||
player.pending_action = pl.PendingAction.CHOOSE
|
player.pending_action = PendingAction.CHOOSE
|
||||||
player.notify_self()
|
player.notify_self()
|
||||||
return super().play_card(player, against, _with)
|
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)]
|
} 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.available_cards.append({'name': player.name, 'number':0,'icon': 'you', 'is_character': True})
|
||||||
player.choose_text = 'choose_ricercato'
|
player.choose_text = 'choose_ricercato'
|
||||||
player.pending_action = pl.PendingAction.CHOOSE
|
player.pending_action = PendingAction.CHOOSE
|
||||||
player.notify_self()
|
player.notify_self()
|
||||||
return True
|
return True
|
||||||
# la giochi su un altro giocatore, ricompensa di 2 carte e 1 pepita a chi lo uccide
|
# 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
|
import bang.players as pl
|
||||||
from bang.cards import Card, Suit, Bang, Mancato
|
from bang.cards import Card, Suit, Bang, Mancato
|
||||||
import bang.expansions.fistful_of_cards.card_events as ce
|
import bang.expansions.fistful_of_cards.card_events as ce
|
||||||
from globals import G
|
from globals import G, PendingAction
|
||||||
|
|
||||||
|
|
||||||
class Fantasma(Card):
|
class Fantasma(Card):
|
||||||
@ -15,7 +15,7 @@ class Fantasma(Card):
|
|||||||
if (player.game.check_event(ce.IlGiudice)) or not self.can_be_used_now:
|
if (player.game.check_event(ce.IlGiudice)) or not self.can_be_used_now:
|
||||||
return False
|
return False
|
||||||
if len(player.game.get_dead_players(include_ghosts=False)) > 0:
|
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.choose_text = "choose_fantasma"
|
||||||
player.available_cards = [
|
player.available_cards = [
|
||||||
{
|
{
|
||||||
@ -60,11 +60,7 @@ class Lemat(Card):
|
|||||||
for p in player.game.get_visible_players(player)
|
for p in player.game.get_visible_players(player)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
from bang.players import PendingAction
|
player.set_choose_action("choose_play_as_bang", player.hand.copy())
|
||||||
|
|
||||||
player.available_cards = player.hand.copy()
|
|
||||||
player.pending_action = PendingAction.CHOOSE
|
|
||||||
player.choose_text = "choose_play_as_bang"
|
|
||||||
player.notify_self()
|
player.notify_self()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -185,7 +181,7 @@ class Sventagliata(
|
|||||||
if p["name"] != player.name and p["name"] != t.name and p["dist"]
|
if p["name"] != player.name and p["name"] != t.name and p["dist"]
|
||||||
]
|
]
|
||||||
if len(player.available_cards) > 0:
|
if len(player.available_cards) > 0:
|
||||||
player.pending_action = pl.PendingAction.CHOOSE
|
player.pending_action = PendingAction.CHOOSE
|
||||||
player.choose_text = "choose_sventagliata"
|
player.choose_text = "choose_sventagliata"
|
||||||
else:
|
else:
|
||||||
player.available_cards = []
|
player.available_cards = []
|
||||||
|
@ -21,12 +21,11 @@ class BlackFlower(Character):
|
|||||||
for p in player.game.get_visible_players(player)
|
for p in player.game.get_visible_players(player)
|
||||||
)
|
)
|
||||||
) and super().special(player, data):
|
) 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.special_use_count += 1
|
||||||
player.pending_action = PendingAction.CHOOSE
|
player.set_choose_action(
|
||||||
player.choose_text = "choose_play_as_bang"
|
"choose_play_as_bang",
|
||||||
|
[c for c in player.hand if c.suit == cs.Suit.CLUBS],
|
||||||
|
)
|
||||||
player.notify_self()
|
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.shop_cards as grc
|
||||||
import bang.expansions.gold_rush.characters as grch
|
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.cards as tvosc
|
||||||
|
import bang.expansions.train_robbery.trains as trt
|
||||||
from metrics import Metrics
|
from metrics import Metrics
|
||||||
from globals import G
|
from globals import G, PendingAction
|
||||||
|
|
||||||
|
|
||||||
debug_commands = [
|
debug_commands = [
|
||||||
@ -57,8 +58,11 @@ debug_commands = [
|
|||||||
"help": "Remove a card from hand/equip - sample /removecard 0",
|
"help": "Remove a card from hand/equip - sample /removecard 0",
|
||||||
},
|
},
|
||||||
{"cmd": "/getcard", "help": "Get a brand new card - sample /getcard Birra"},
|
{"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": "/meinfo", "help": "Get player data"},
|
||||||
{"cmd": "/gameinfo", "help": "Get game 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": "/playerinfo", "help": "Get player data - sample /playerinfo player"},
|
||||||
{"cmd": "/cardinfo", "help": "Get card data - sample /cardinfo handindex"},
|
{"cmd": "/cardinfo", "help": "Get card data - sample /cardinfo handindex"},
|
||||||
{"cmd": "/mebot", "help": "Toggles bot mode"},
|
{"cmd": "/mebot", "help": "Toggles bot mode"},
|
||||||
@ -93,6 +97,7 @@ class Game:
|
|||||||
"gold_rush",
|
"gold_rush",
|
||||||
"the_valley_of_shadows",
|
"the_valley_of_shadows",
|
||||||
"wild_west_show",
|
"wild_west_show",
|
||||||
|
"train_robbery",
|
||||||
]
|
]
|
||||||
self.shutting_down = False
|
self.shutting_down = False
|
||||||
self.is_competitive = False
|
self.is_competitive = False
|
||||||
@ -354,6 +359,7 @@ class Game:
|
|||||||
roles_str += f"|{role}|{str(current_roles.count(role))}"
|
roles_str += f"|{role}|{str(current_roles.count(role))}"
|
||||||
G.sio.emit("chat_message", room=self.name, data=f"_allroles{roles_str}")
|
G.sio.emit("chat_message", room=self.name, data=f"_allroles{roles_str}")
|
||||||
self.play_turn()
|
self.play_turn()
|
||||||
|
self.notify_stations()
|
||||||
|
|
||||||
def choose_characters(self):
|
def choose_characters(self):
|
||||||
n = self.characters_to_distribute
|
n = self.characters_to_distribute
|
||||||
@ -459,7 +465,7 @@ class Game:
|
|||||||
|
|
||||||
def discard_others(self, attacker: pl.Player, card_name: str = None):
|
def discard_others(self, attacker: pl.Player, card_name: str = None):
|
||||||
self.attack_in_progress = True
|
self.attack_in_progress = True
|
||||||
attacker.pending_action = pl.PendingAction.WAIT
|
attacker.pending_action = PendingAction.WAIT
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.waiting_for = 0
|
self.waiting_for = 0
|
||||||
self.ready_count = 0
|
self.ready_count = 0
|
||||||
@ -469,7 +475,7 @@ class Game:
|
|||||||
self.waiting_for += 1
|
self.waiting_for += 1
|
||||||
p.notify_self()
|
p.notify_self()
|
||||||
if self.waiting_for == 0:
|
if self.waiting_for == 0:
|
||||||
attacker.pending_action = pl.PendingAction.PLAY
|
attacker.pending_action = PendingAction.PLAY
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.attack_in_progress = False
|
self.attack_in_progress = False
|
||||||
elif card_name == "Poker":
|
elif card_name == "Poker":
|
||||||
@ -477,7 +483,7 @@ class Game:
|
|||||||
|
|
||||||
def attack_others(self, attacker: pl.Player, card_name: str = None):
|
def attack_others(self, attacker: pl.Player, card_name: str = None):
|
||||||
self.attack_in_progress = True
|
self.attack_in_progress = True
|
||||||
attacker.pending_action = pl.PendingAction.WAIT
|
attacker.pending_action = PendingAction.WAIT
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.waiting_for = 0
|
self.waiting_for = 0
|
||||||
self.ready_count = 0
|
self.ready_count = 0
|
||||||
@ -487,7 +493,7 @@ class Game:
|
|||||||
self.waiting_for += 1
|
self.waiting_for += 1
|
||||||
p.notify_self()
|
p.notify_self()
|
||||||
if self.waiting_for == 0:
|
if self.waiting_for == 0:
|
||||||
attacker.pending_action = pl.PendingAction.PLAY
|
attacker.pending_action = PendingAction.PLAY
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.attack_in_progress = False
|
self.attack_in_progress = False
|
||||||
if self.pending_winners and not self.someone_won:
|
if self.pending_winners and not self.someone_won:
|
||||||
@ -495,7 +501,7 @@ class Game:
|
|||||||
|
|
||||||
def indian_others(self, attacker: pl.Player):
|
def indian_others(self, attacker: pl.Player):
|
||||||
self.attack_in_progress = True
|
self.attack_in_progress = True
|
||||||
attacker.pending_action = pl.PendingAction.WAIT
|
attacker.pending_action = PendingAction.WAIT
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.waiting_for = 0
|
self.waiting_for = 0
|
||||||
self.ready_count = 0
|
self.ready_count = 0
|
||||||
@ -505,7 +511,7 @@ class Game:
|
|||||||
self.waiting_for += 1
|
self.waiting_for += 1
|
||||||
p.notify_self()
|
p.notify_self()
|
||||||
if self.waiting_for == 0:
|
if self.waiting_for == 0:
|
||||||
attacker.pending_action = pl.PendingAction.PLAY
|
attacker.pending_action = PendingAction.PLAY
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.attack_in_progress = False
|
self.attack_in_progress = False
|
||||||
if self.pending_winners and not self.someone_won:
|
if self.pending_winners and not self.someone_won:
|
||||||
@ -542,26 +548,26 @@ class Game:
|
|||||||
self.attack_in_progress = True
|
self.attack_in_progress = True
|
||||||
self.ready_count = 0
|
self.ready_count = 0
|
||||||
self.waiting_for = 1
|
self.waiting_for = 1
|
||||||
attacker.pending_action = pl.PendingAction.WAIT
|
attacker.pending_action = PendingAction.WAIT
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.get_player_named(target_username).notify_self()
|
self.get_player_named(target_username).notify_self()
|
||||||
elif not attacker.is_my_turn or len(self.attack_queue) == 0:
|
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):
|
def steal_discard(self, attacker: pl.Player, target_username: str, card: cs.Card):
|
||||||
p = self.get_player_named(target_username)
|
p = self.get_player_named(target_username)
|
||||||
if p != attacker and p.get_discarded(
|
if p != attacker and p.get_discarded(
|
||||||
attacker,
|
attacker,
|
||||||
card_name=card.name,
|
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.ready_count = 0
|
||||||
self.waiting_for = 1
|
self.waiting_for = 1
|
||||||
attacker.pending_action = pl.PendingAction.WAIT
|
attacker.pending_action = PendingAction.WAIT
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.get_player_named(target_username).notify_self()
|
self.get_player_named(target_username).notify_self()
|
||||||
else:
|
else:
|
||||||
attacker.pending_action = pl.PendingAction.CHOOSE
|
attacker.pending_action = PendingAction.CHOOSE
|
||||||
attacker.target_p = target_username
|
attacker.target_p = target_username
|
||||||
if isinstance(card, cs.CatBalou):
|
if isinstance(card, cs.CatBalou):
|
||||||
attacker.choose_action = "discard"
|
attacker.choose_action = "discard"
|
||||||
@ -575,7 +581,7 @@ class Game:
|
|||||||
):
|
):
|
||||||
self.ready_count = 0
|
self.ready_count = 0
|
||||||
self.waiting_for = 1
|
self.waiting_for = 1
|
||||||
attacker.pending_action = pl.PendingAction.WAIT
|
attacker.pending_action = PendingAction.WAIT
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.get_player_named(target_username).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):
|
if self.get_player_named(target_username).get_dueled(attacker=attacker):
|
||||||
self.ready_count = 0
|
self.ready_count = 0
|
||||||
self.waiting_for = 1
|
self.waiting_for = 1
|
||||||
attacker.pending_action = pl.PendingAction.WAIT
|
attacker.pending_action = PendingAction.WAIT
|
||||||
attacker.notify_self()
|
attacker.notify_self()
|
||||||
self.get_player_named(target_username).notify_self()
|
self.get_player_named(target_username).notify_self()
|
||||||
|
|
||||||
def emporio(self):
|
def emporio(self):
|
||||||
pls = self.get_alive_players()
|
pls = self.get_alive_players()
|
||||||
self.available_cards = [self.deck.draw(True) for i in range(len(pls))]
|
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].choose_text = "choose_card_to_get"
|
||||||
self.players[self.turn].available_cards = self.available_cards
|
self.players[self.turn].available_cards = self.available_cards
|
||||||
G.sio.emit(
|
G.sio.emit(
|
||||||
@ -612,7 +618,7 @@ class Game:
|
|||||||
)
|
)
|
||||||
player.hand.append(card)
|
player.hand.append(card)
|
||||||
player.available_cards = []
|
player.available_cards = []
|
||||||
player.pending_action = pl.PendingAction.WAIT
|
player.pending_action = PendingAction.WAIT
|
||||||
player.notify_self()
|
player.notify_self()
|
||||||
pls = self.get_alive_players()
|
pls = self.get_alive_players()
|
||||||
next_player = pls[
|
next_player = pls[
|
||||||
@ -631,14 +637,14 @@ class Game:
|
|||||||
next_player.hand.append(self.available_cards.pop())
|
next_player.hand.append(self.available_cards.pop())
|
||||||
next_player.notify_self()
|
next_player.notify_self()
|
||||||
G.sio.emit("emporio", room=self.name, data='{"name":"","cards":[]}')
|
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()
|
self.players[self.turn].notify_self()
|
||||||
elif next_player == self.players[self.turn]:
|
elif next_player == self.players[self.turn]:
|
||||||
G.sio.emit("emporio", room=self.name, data='{"name":"","cards":[]}')
|
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()
|
self.players[self.turn].notify_self()
|
||||||
else:
|
else:
|
||||||
next_player.pending_action = pl.PendingAction.CHOOSE
|
next_player.pending_action = PendingAction.CHOOSE
|
||||||
next_player.choose_text = "choose_card_to_get"
|
next_player.choose_text = "choose_card_to_get"
|
||||||
next_player.available_cards = self.available_cards
|
next_player.available_cards = self.available_cards
|
||||||
G.sio.emit(
|
G.sio.emit(
|
||||||
@ -724,7 +730,7 @@ class Game:
|
|||||||
elif self.poker_on and not any(
|
elif self.poker_on and not any(
|
||||||
c.number == 1 for c in self.deck.scrap_pile[-tmp:]
|
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.players[
|
||||||
self.turn
|
self.turn
|
||||||
].choose_text = f"choose_from_poker;{min(2, tmp)}"
|
].choose_text = f"choose_from_poker;{min(2, tmp)}"
|
||||||
@ -735,10 +741,10 @@ class Game:
|
|||||||
print("attack completed, next attack")
|
print("attack completed, next attack")
|
||||||
atk = self.attack_queue.pop(0)
|
atk = self.attack_queue.pop(0)
|
||||||
self.attack(atk[0], atk[1], atk[2], atk[3], skip_queue=True)
|
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()
|
self.players[self.turn].notify_self()
|
||||||
else:
|
else:
|
||||||
self.players[self.turn].pending_action = pl.PendingAction.PLAY
|
self.players[self.turn].pending_action = PendingAction.PLAY
|
||||||
self.poker_on = False
|
self.poker_on = False
|
||||||
self.players[self.turn].notify_self()
|
self.players[self.turn].notify_self()
|
||||||
|
|
||||||
@ -842,6 +848,7 @@ class Game:
|
|||||||
)
|
)
|
||||||
):
|
):
|
||||||
self.deck.flip_event()
|
self.deck.flip_event()
|
||||||
|
self.deck.move_train_forward()
|
||||||
if self.check_event(ce.RouletteRussa):
|
if self.check_event(ce.RouletteRussa):
|
||||||
self.is_russian_roulette_on = True
|
self.is_russian_roulette_on = True
|
||||||
if self.players[self.turn].get_banged(self.deck.event_cards[0]):
|
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__),
|
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):
|
def notify_scrap_pile(self, sid=None):
|
||||||
print(f"{self.name}: scrap")
|
print(f"{self.name}: scrap")
|
||||||
room = self.name if sid is None else sid
|
room = self.name if sid is None else sid
|
||||||
if self.deck.peek_scrap_pile():
|
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:
|
else:
|
||||||
G.sio.emit("scrap", room=room, data=None)
|
G.sio.emit("scrap", room=room, data=None)
|
||||||
|
|
||||||
@ -1015,9 +1037,9 @@ class Game:
|
|||||||
self.deck.draw(True, player=player.attacker)
|
self.deck.draw(True, player=player.attacker)
|
||||||
player.attacker.notify_self()
|
player.attacker.notify_self()
|
||||||
print(f"{self.name}: player {player.name} died")
|
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()
|
self.responders_did_respond_resume_turn()
|
||||||
player.pending_action = pl.PendingAction.WAIT
|
player.pending_action = PendingAction.WAIT
|
||||||
|
|
||||||
if player.is_dead:
|
if player.is_dead:
|
||||||
return
|
return
|
||||||
@ -1220,6 +1242,29 @@ class Game:
|
|||||||
def get_alive_players(self):
|
def get_alive_players(self):
|
||||||
return [p for p in self.players if not p.is_dead or p.is_ghost]
|
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):
|
def get_dead_players(self, include_ghosts=True):
|
||||||
return [
|
return [
|
||||||
p for p in self.players if p.is_dead and (include_ghosts or not p.is_ghost)
|
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 __future__ import annotations
|
||||||
from enum import IntEnum
|
|
||||||
import json
|
import json
|
||||||
from random import random, randrange, sample, uniform, randint
|
from random import randrange, sample, uniform, randint
|
||||||
import socketio
|
|
||||||
import bang.deck as deck
|
|
||||||
import bang.roles as r
|
import bang.roles as r
|
||||||
import bang.cards as cs
|
import bang.cards as cs
|
||||||
import bang.expansions.dodge_city.cards as csd
|
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.gold_rush.characters as grch
|
||||||
import bang.expansions.the_valley_of_shadows.cards as tvosc
|
import bang.expansions.the_valley_of_shadows.cards as tvosc
|
||||||
import bang.expansions.the_valley_of_shadows.characters as tvosch
|
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 metrics import Metrics
|
||||||
from globals import G
|
from globals import G, PendingAction
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
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:
|
class Player:
|
||||||
def is_admin(self):
|
def is_admin(self):
|
||||||
return self.discord_id in {"244893980960096266", "539795574019457034"}
|
return self.discord_id in {"244893980960096266", "539795574019457034"}
|
||||||
@ -102,6 +92,7 @@ class Player:
|
|||||||
self.is_bot = bot
|
self.is_bot = bot
|
||||||
self.discord_token = discord_token
|
self.discord_token = discord_token
|
||||||
self.discord_id = None
|
self.discord_id = None
|
||||||
|
self.did_choose_callback = None
|
||||||
self.played_cards = 0
|
self.played_cards = 0
|
||||||
self.avatar = ""
|
self.avatar = ""
|
||||||
self.last_played_card: cs.Card = None
|
self.last_played_card: cs.Card = None
|
||||||
@ -280,25 +271,25 @@ class Player:
|
|||||||
if self.pending_action == PendingAction.DRAW and self.game.check_event(
|
if self.pending_action == PendingAction.DRAW and self.game.check_event(
|
||||||
ce.Peyote
|
ce.Peyote
|
||||||
):
|
):
|
||||||
self.available_cards = [
|
|
||||||
{"icon": "🔴", "noDesc": True},
|
|
||||||
{"icon": "⚫", "noDesc": True},
|
|
||||||
]
|
|
||||||
self.is_drawing = True
|
self.is_drawing = True
|
||||||
self.choose_text = "choose_guess"
|
self.set_choose_action(
|
||||||
self.pending_action = PendingAction.CHOOSE
|
"choose_guess",
|
||||||
|
[
|
||||||
|
{"icon": "🔴", "noDesc": True},
|
||||||
|
{"icon": "⚫", "noDesc": True},
|
||||||
|
],
|
||||||
|
)
|
||||||
elif (
|
elif (
|
||||||
self.can_play_ranch
|
self.can_play_ranch
|
||||||
and self.pending_action == PendingAction.PLAY
|
and self.pending_action == PendingAction.PLAY
|
||||||
and self.game.check_event(ce.Ranch)
|
and self.game.check_event(ce.Ranch)
|
||||||
):
|
):
|
||||||
self.can_play_ranch = False
|
self.can_play_ranch = False
|
||||||
self.available_cards = [c for c in self.hand]
|
|
||||||
self.discarded_cards = []
|
self.discarded_cards = []
|
||||||
self.available_cards.append({"icon": "✅", "noDesc": True})
|
|
||||||
self.is_playing_ranch = True
|
self.is_playing_ranch = True
|
||||||
self.choose_text = "choose_ranch"
|
self.set_choose_action(
|
||||||
self.pending_action = PendingAction.CHOOSE
|
"choose_ranch", [c for c in self.hand] + [{"icon": "✅", "noDesc": True}]
|
||||||
|
)
|
||||||
elif (
|
elif (
|
||||||
self.character
|
self.character
|
||||||
and self.character.check(self.game, chars.SuzyLafayette)
|
and self.character.check(self.game, chars.SuzyLafayette)
|
||||||
@ -338,9 +329,7 @@ class Player:
|
|||||||
self.game.players[self.game.turn].notify_self()
|
self.game.players[self.game.turn].notify_self()
|
||||||
self.scrapped_cards = 0
|
self.scrapped_cards = 0
|
||||||
self.previous_pending_action = self.pending_action
|
self.previous_pending_action = self.pending_action
|
||||||
self.pending_action = PendingAction.CHOOSE
|
self.set_choose_action("choose_sid_scrap", self.hand)
|
||||||
self.choose_text = "choose_sid_scrap"
|
|
||||||
self.available_cards = self.hand
|
|
||||||
self.lives += 1
|
self.lives += 1
|
||||||
|
|
||||||
ser = self.__dict__.copy()
|
ser = self.__dict__.copy()
|
||||||
@ -748,6 +737,9 @@ class Player:
|
|||||||
self.has_played_bang = False
|
self.has_played_bang = False
|
||||||
self.special_use_count = 0
|
self.special_use_count = 0
|
||||||
self.bang_used = 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):
|
if self.game.check_event(cew.DarlingValentine):
|
||||||
hand = len(self.hand)
|
hand = len(self.hand)
|
||||||
for _ in range(hand):
|
for _ in range(hand):
|
||||||
@ -782,35 +774,36 @@ class Player:
|
|||||||
for p in self.game.get_alive_players()
|
for p in self.game.get_alive_players()
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
self.available_cards = [
|
self.set_choose_action(
|
||||||
{
|
"choose_fratelli_di_sangue",
|
||||||
"name": p.name,
|
[
|
||||||
"icon": p.role.icon
|
{
|
||||||
if (self.game.initial_players == 3)
|
"name": p.name,
|
||||||
else "⭐️"
|
"icon": p.role.icon
|
||||||
if isinstance(p.role, r.Sheriff)
|
if (self.game.initial_players == 3)
|
||||||
else "🤠",
|
else "⭐️"
|
||||||
"alt_text": "".join(["❤️"] * p.lives)
|
if isinstance(p.role, r.Sheriff)
|
||||||
+ "".join(["💀"] * (p.max_lives - p.lives)),
|
else "🤠",
|
||||||
"avatar": p.avatar,
|
"alt_text": "".join(["❤️"] * p.lives)
|
||||||
"is_character": True,
|
+ "".join(["💀"] * (p.max_lives - p.lives)),
|
||||||
"is_player": True,
|
"avatar": p.avatar,
|
||||||
}
|
"is_character": True,
|
||||||
for p in self.game.get_alive_players()
|
"is_player": True,
|
||||||
if p != self and p.lives < p.max_lives
|
}
|
||||||
]
|
for p in self.game.get_alive_players()
|
||||||
self.available_cards.append({"icon": "❌", "noDesc": True})
|
if p != self and p.lives < p.max_lives
|
||||||
self.choose_text = "choose_fratelli_di_sangue"
|
]
|
||||||
self.pending_action = PendingAction.CHOOSE
|
+ [{"icon": "❌", "noDesc": True}],
|
||||||
|
)
|
||||||
self.is_giving_life = True
|
self.is_giving_life = True
|
||||||
elif (
|
elif (
|
||||||
self.game.check_event(ceh.NuovaIdentita)
|
self.game.check_event(ceh.NuovaIdentita)
|
||||||
and self.not_chosen_character is not None
|
and self.not_chosen_character is not None
|
||||||
and not again
|
and not again
|
||||||
):
|
):
|
||||||
self.available_cards = [self.character, self.not_chosen_character]
|
self.set_choose_action(
|
||||||
self.choose_text = "choose_nuova_identita"
|
"choose_nuova_identita", [self.character, self.not_chosen_character]
|
||||||
self.pending_action = PendingAction.CHOOSE
|
)
|
||||||
elif not self.game.check_event(ce.Lazo) and any(
|
elif not self.game.check_event(ce.Lazo) and any(
|
||||||
(
|
(
|
||||||
isinstance(c, cs.Dinamite)
|
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
|
and sum((c.name == cs.Bang(0, 0).name for c in self.hand)) >= 2
|
||||||
):
|
):
|
||||||
self.is_using_checchino = True
|
self.is_using_checchino = True
|
||||||
self.available_cards = [
|
self.set_choose_action(
|
||||||
{
|
"choose_cecchino",
|
||||||
"name": p["name"],
|
[
|
||||||
"icon": p["role"].icon
|
{
|
||||||
if (self.game.initial_players == 3)
|
"name": p["name"],
|
||||||
else "⭐️"
|
"icon": p["role"].icon
|
||||||
if p["is_sheriff"]
|
if (self.game.initial_players == 3)
|
||||||
else "🤠",
|
else "⭐️"
|
||||||
"alt_text": "".join(["❤️"] * p["lives"])
|
if p["is_sheriff"]
|
||||||
+ "".join(["💀"] * (p["max_lives"] - p["lives"])),
|
else "🤠",
|
||||||
"is_character": True,
|
"alt_text": "".join(["❤️"] * p["lives"])
|
||||||
"is_player": True,
|
+ "".join(["💀"] * (p["max_lives"] - p["lives"])),
|
||||||
}
|
"is_character": True,
|
||||||
for p in self.game.get_visible_players(self)
|
"is_player": True,
|
||||||
if p["dist"] <= self.get_sight()
|
}
|
||||||
]
|
for p in self.game.get_visible_players(self)
|
||||||
self.available_cards.append({"icon": "❌", "noDesc": True})
|
if p["dist"] <= self.get_sight()
|
||||||
self.choose_text = "choose_cecchino"
|
]
|
||||||
self.pending_action = PendingAction.CHOOSE
|
+ [{"icon": "❌", "noDesc": True}],
|
||||||
|
)
|
||||||
self.notify_self()
|
self.notify_self()
|
||||||
if (
|
if (
|
||||||
self.is_my_turn
|
self.is_my_turn
|
||||||
@ -884,15 +878,15 @@ class Player:
|
|||||||
self.notify_self()
|
self.notify_self()
|
||||||
elif self.character.check(self.game, chars.KitCarlson) and not self.is_ghost:
|
elif self.character.check(self.game, chars.KitCarlson) and not self.is_ghost:
|
||||||
self.is_drawing = True
|
self.is_drawing = True
|
||||||
self.available_cards = [self.game.deck.draw() for i in range(3)]
|
self.set_choose_action(
|
||||||
self.choose_text = "choose_card_to_get"
|
"choose_card_to_get", [self.game.deck.draw() for i in range(3)]
|
||||||
self.pending_action = PendingAction.CHOOSE
|
)
|
||||||
self.notify_self()
|
self.notify_self()
|
||||||
elif self.character.check(self.game, grch.DutchWill) and not self.is_ghost:
|
elif self.character.check(self.game, grch.DutchWill) and not self.is_ghost:
|
||||||
self.is_drawing = True
|
self.is_drawing = True
|
||||||
self.available_cards = [self.game.deck.draw() for i in range(2)]
|
self.set_choose_action(
|
||||||
self.choose_text = "choose_card_to_get"
|
"choose_card_to_get", [self.game.deck.draw() for i in range(2)]
|
||||||
self.pending_action = PendingAction.CHOOSE
|
)
|
||||||
self.notify_self()
|
self.notify_self()
|
||||||
elif (
|
elif (
|
||||||
self.character.check(self.game, chd.PatBrennan)
|
self.character.check(self.game, chd.PatBrennan)
|
||||||
@ -902,10 +896,10 @@ class Player:
|
|||||||
and len(self.game.get_player_named(pile).equipment) > 0
|
and len(self.game.get_player_named(pile).equipment) > 0
|
||||||
):
|
):
|
||||||
self.is_drawing = True
|
self.is_drawing = True
|
||||||
self.available_cards = self.game.get_player_named(pile).equipment
|
|
||||||
self.pat_target = pile
|
self.pat_target = pile
|
||||||
self.choose_text = "choose_card_to_get"
|
self.set_choose_action(
|
||||||
self.pending_action = PendingAction.CHOOSE
|
"choose_card_to_get", self.game.get_player_named(pile).equipment
|
||||||
|
)
|
||||||
self.notify_self()
|
self.notify_self()
|
||||||
else:
|
else:
|
||||||
self.pending_action = PendingAction.PLAY
|
self.pending_action = PendingAction.PLAY
|
||||||
@ -1004,12 +998,13 @@ class Player:
|
|||||||
|
|
||||||
def manette(self):
|
def manette(self):
|
||||||
if self.game.check_event(ceh.Manette):
|
if self.game.check_event(ceh.Manette):
|
||||||
self.choose_text = "choose_manette"
|
self.set_choose_action(
|
||||||
self.available_cards = [
|
"choose_manette",
|
||||||
{"name": "", "icon": "♦♣♥♠"[s], "alt_text": "", "noDesc": True}
|
[
|
||||||
for s in [0, 1, 2, 3]
|
{"name": "", "icon": "♦♣♥♠"[s], "alt_text": "", "noDesc": True}
|
||||||
]
|
for s in [0, 1, 2, 3]
|
||||||
self.pending_action = PendingAction.CHOOSE
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def pick(self):
|
def pick(self):
|
||||||
if self.pending_action != PendingAction.PICK:
|
if self.pending_action != PendingAction.PICK:
|
||||||
@ -1399,6 +1394,9 @@ class Player:
|
|||||||
self.target_p = self.rissa_targets.pop(0).name
|
self.target_p = self.rissa_targets.pop(0).name
|
||||||
print(f"rissa targets: {self.rissa_targets}")
|
print(f"rissa targets: {self.rissa_targets}")
|
||||||
self.notify_self()
|
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":
|
elif self.choose_text == "choose_ricercato":
|
||||||
player = self.game.get_player_named(
|
player = self.game.get_player_named(
|
||||||
self.available_cards[card_index]["name"]
|
self.available_cards[card_index]["name"]
|
||||||
@ -1871,6 +1869,8 @@ class Player:
|
|||||||
self.expected_response.append(cs.Bang(0, 0).name)
|
self.expected_response.append(cs.Bang(0, 0).name)
|
||||||
if self.character.check(self.game, chw.BigSpencer):
|
if self.character.check(self.game, chw.BigSpencer):
|
||||||
self.expected_response = []
|
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.on_failed_response_cb = self.take_damage_response
|
||||||
self.notify_self()
|
self.notify_self()
|
||||||
|
|
||||||
@ -1933,7 +1933,7 @@ class Player:
|
|||||||
self.notify_self()
|
self.notify_self()
|
||||||
|
|
||||||
def get_discarded(self, attacker=None, card_name=None, action=None):
|
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.pending_action = PendingAction.CHOOSE
|
||||||
self.available_cards = self.hand.copy()
|
self.available_cards = self.hand.copy()
|
||||||
if card_name == "Tornado":
|
if card_name == "Tornado":
|
||||||
@ -1946,6 +1946,13 @@ class Player:
|
|||||||
self.available_cards.append(
|
self.available_cards.append(
|
||||||
{"name": "-1hp", "icon": "💔", "noDesc": True}
|
{"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
|
return True
|
||||||
else:
|
else:
|
||||||
if self.can_escape(card_name) or self.character.check(
|
if self.can_escape(card_name) or self.character.check(
|
||||||
@ -1995,6 +2002,17 @@ class Player:
|
|||||||
self.attacker = attacker
|
self.attacker = attacker
|
||||||
self.attacking_card = card_name
|
self.attacking_card = card_name
|
||||||
print(f"attacker -> {attacker}")
|
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 (
|
if (
|
||||||
isinstance(attacker, Player)
|
isinstance(attacker, Player)
|
||||||
and attacker.character.check(self.game, tvosch.ColoradoBill)
|
and attacker.character.check(self.game, tvosch.ColoradoBill)
|
||||||
@ -2100,9 +2118,7 @@ class Player:
|
|||||||
if len(equipments) == 0:
|
if len(equipments) == 0:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
self.choose_text = "choose_dalton"
|
self.set_choose_action("choose_dalton", equipments)
|
||||||
self.pending_action = PendingAction.CHOOSE
|
|
||||||
self.available_cards = equipments
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_indians(self, attacker):
|
def get_indians(self, attacker):
|
||||||
@ -2112,6 +2128,11 @@ class Player:
|
|||||||
(isinstance(c, grc.Calumet) for c in self.gold_rush_equipment)
|
(isinstance(c, grc.Calumet) for c in self.gold_rush_equipment)
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
# check for trt.PrisonerCar
|
||||||
|
if any(
|
||||||
|
(isinstance(c, trt.PrisonerCar) for c in self.equipment)
|
||||||
|
):
|
||||||
|
return False
|
||||||
if (
|
if (
|
||||||
not self.game.is_competitive
|
not self.game.is_competitive
|
||||||
and not any(
|
and not any(
|
||||||
@ -2145,6 +2166,11 @@ class Player:
|
|||||||
def get_dueled(self, attacker):
|
def get_dueled(self, attacker):
|
||||||
self.attacker = attacker
|
self.attacker = attacker
|
||||||
self.attacking_card = "Duello"
|
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 (
|
if (self.game.check_event(ceh.Sermone) and self.is_my_turn) or (
|
||||||
not self.game.is_competitive
|
not self.game.is_competitive
|
||||||
and not any(
|
and not any(
|
||||||
@ -2501,26 +2527,29 @@ class Player:
|
|||||||
self.character.special(self, data)
|
self.character.special(self, data)
|
||||||
|
|
||||||
def gold_rush_discard(self):
|
def gold_rush_discard(self):
|
||||||
self.available_cards = [
|
self.set_choose_action(
|
||||||
{
|
"gold_rush_discard",
|
||||||
"name": p.name,
|
[
|
||||||
"icon": p.role.icon
|
{
|
||||||
if (self.game.initial_players == 3)
|
"name": p.name,
|
||||||
else "⭐️"
|
"icon": p.role.icon
|
||||||
if isinstance(p.role, r.Sheriff)
|
if (self.game.initial_players == 3)
|
||||||
else "🤠",
|
else "⭐️"
|
||||||
"is_character": True,
|
if isinstance(p.role, r.Sheriff)
|
||||||
"avatar": p.avatar,
|
else "🤠",
|
||||||
"alt_text": "".join(["🎴️"] * len(p.gold_rush_equipment)),
|
"is_character": True,
|
||||||
"is_player": True,
|
"avatar": p.avatar,
|
||||||
}
|
"alt_text": "".join(["🎴️"] * len(p.gold_rush_equipment)),
|
||||||
for p in self.game.get_alive_players()
|
"is_player": True,
|
||||||
if p != self
|
}
|
||||||
and any((e.number + 1 <= self.gold_nuggets for e in p.gold_rush_equipment))
|
for p in self.game.get_alive_players()
|
||||||
]
|
if p != self
|
||||||
self.available_cards.append({"icon": "❌", "noDesc": True})
|
and any(
|
||||||
self.choose_text = "gold_rush_discard"
|
(e.number + 1 <= self.gold_nuggets for e in p.gold_rush_equipment)
|
||||||
self.pending_action = PendingAction.CHOOSE
|
)
|
||||||
|
]
|
||||||
|
+ [{"icon": "❌", "noDesc": True}],
|
||||||
|
)
|
||||||
self.notify_self()
|
self.notify_self()
|
||||||
|
|
||||||
def buy_gold_rush_card(self, index):
|
def buy_gold_rush_card(self, index):
|
||||||
@ -2548,8 +2577,59 @@ class Player:
|
|||||||
self.game.deck.shop_deck.append(card)
|
self.game.deck.shop_deck.append(card)
|
||||||
self.game.deck.shop_cards[index] = None
|
self.game.deck.shop_cards[index] = None
|
||||||
self.game.deck.fill_gold_rush_shop()
|
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()
|
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):
|
def check_can_end_turn(self):
|
||||||
must_be_used_cards = [c for c in self.hand if c.must_be_used]
|
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:
|
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
|
and not self.equipment[i].can_be_used_now
|
||||||
):
|
):
|
||||||
self.equipment[i].can_be_used_now = True
|
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)):
|
for i in range(len(self.hand)):
|
||||||
if self.hand[i].must_be_used:
|
if self.hand[i].must_be_used:
|
||||||
self.hand[i].must_be_used = False
|
self.hand[i].must_be_used = False
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
class G:
|
class G:
|
||||||
sio = None
|
sio = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
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 discord_webhook import DiscordWebhook
|
||||||
|
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
from bang.players import PendingAction, Player
|
from bang.players import Player
|
||||||
from globals import G
|
from globals import G, PendingAction
|
||||||
from metrics import Metrics
|
from metrics import Metrics
|
||||||
|
|
||||||
sys.setrecursionlimit(10**6) # this should prevents bots from stopping
|
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_scrap_pile(sid)
|
||||||
room.notify_all()
|
room.notify_all()
|
||||||
room.notify_gold_rush_shop()
|
room.notify_gold_rush_shop()
|
||||||
|
room.notify_stations()
|
||||||
room.notify_event_card()
|
room.notify_event_card()
|
||||||
room.notify_event_card_wildwestshow(sid)
|
room.notify_event_card_wildwestshow(sid)
|
||||||
else:
|
else:
|
||||||
@ -678,6 +679,14 @@ def buy_gold_rush_card(sid, data: int):
|
|||||||
ses.buy_gold_rush_card(data)
|
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
|
@sio.event
|
||||||
@bang_handler
|
@bang_handler
|
||||||
def chat_message(sid, msg, pl=None):
|
def chat_message(sid, msg, pl=None):
|
||||||
@ -1029,7 +1038,9 @@ def chat_message(sid, msg, pl=None):
|
|||||||
|
|
||||||
cmd = msg.split()
|
cmd = msg.split()
|
||||||
if len(cmd) >= 2:
|
if len(cmd) >= 2:
|
||||||
|
import bang.expansions.train_robbery.trains as trt
|
||||||
cards = cs.get_starting_deck(ses.game.expansions)
|
cards = cs.get_starting_deck(ses.game.expansions)
|
||||||
|
cards.extend(trt.get_all_cards())
|
||||||
card_names = " ".join(cmd[1:]).split(",")
|
card_names = " ".join(cmd[1:]).split(",")
|
||||||
for cn in card_names:
|
for cn in card_names:
|
||||||
ses.equipment.append(
|
ses.equipment.append(
|
||||||
@ -1088,6 +1099,21 @@ def chat_message(sid, msg, pl=None):
|
|||||||
"type": "json",
|
"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():
|
elif "/status" in msg and ses.is_admin():
|
||||||
sio.emit("mount_status", room=sid)
|
sio.emit("mount_status", room=sid)
|
||||||
elif "/meinfo" in msg:
|
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__)
|
"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
|
@sio.event
|
||||||
@bang_handler
|
@bang_handler
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from typing import Any, List
|
||||||
import pytest
|
import pytest
|
||||||
from bang.characters import Character
|
from bang.characters import Character
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
@ -8,7 +9,7 @@ from globals import G
|
|||||||
G.sio = DummySocket()
|
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 = Game("test")
|
||||||
g.expansions = expansions
|
g.expansions = expansions
|
||||||
ps = [Player(f"p{i}", f"p{i}") for i in range(players)]
|
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
|
return g
|
||||||
|
|
||||||
|
|
||||||
def set_events(g: Game, event_cards):
|
def set_events(g: Game, event_cards) -> None:
|
||||||
g.deck.event_cards = event_cards
|
g.deck.event_cards = event_cards
|
||||||
|
|
||||||
|
|
||||||
def current_player(g: Game):
|
def current_player(g: Game) -> Player:
|
||||||
return g.players[g.turn]
|
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)]
|
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 = current_player(g)
|
||||||
p.draw("")
|
p.draw("")
|
||||||
p.hand = cards
|
p.hand = cards
|
||||||
|
@ -3,7 +3,8 @@ from bang.characters import Character
|
|||||||
from bang.cards import *
|
from bang.cards import *
|
||||||
from bang.deck import Deck
|
from bang.deck import Deck
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
from bang.players import Player, PendingAction
|
from bang.players import Player
|
||||||
|
from globals import PendingAction
|
||||||
|
|
||||||
# test card Barile
|
# test card Barile
|
||||||
def test_barile():
|
def test_barile():
|
||||||
|
@ -2,7 +2,8 @@ from random import randint
|
|||||||
from bang.characters import *
|
from bang.characters import *
|
||||||
from bang.deck import Deck
|
from bang.deck import Deck
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
from bang.players import Player, PendingAction
|
from bang.players import Player
|
||||||
|
from globals import PendingAction
|
||||||
from bang.cards import *
|
from bang.cards import *
|
||||||
|
|
||||||
def test_bartcassidy():
|
def test_bartcassidy():
|
||||||
|
@ -3,7 +3,7 @@ from bang.characters import Character
|
|||||||
from bang.expansions.dodge_city.cards import *
|
from bang.expansions.dodge_city.cards import *
|
||||||
from bang.deck import Deck
|
from bang.deck import Deck
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
from bang.players import Player, PendingAction
|
from bang.players import Player
|
||||||
import bang.cards as cs
|
import bang.cards as cs
|
||||||
|
|
||||||
# test Borraccia
|
# test Borraccia
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from bang.deck import Deck
|
from bang.deck import Deck
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
from bang.players import Player, PendingAction
|
from bang.players import Player
|
||||||
from bang.roles import *
|
from bang.roles import *
|
||||||
from bang.cards import *
|
from bang.cards import *
|
||||||
|
from globals import PendingAction
|
||||||
from tests import started_game
|
from tests import started_game
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
from bang.characters import Character
|
from bang.characters import Character
|
||||||
from bang.deck import Deck
|
from bang.deck import Deck
|
||||||
from bang.game import Game
|
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.roles import *
|
||||||
from bang.cards 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.expansions.the_valley_of_shadows.characters import *
|
||||||
from bang.deck import Deck
|
from bang.deck import Deck
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
from bang.players import Player, PendingAction
|
from bang.players import Player
|
||||||
import bang.cards as cs
|
import bang.cards as cs
|
||||||
|
from globals import PendingAction
|
||||||
|
|
||||||
# test TucoFranziskaner
|
# test TucoFranziskaner
|
||||||
def 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.expansions.the_valley_of_shadows.cards import *
|
||||||
from bang.deck import Deck
|
from bang.deck import Deck
|
||||||
from bang.game import Game
|
from bang.game import Game
|
||||||
from bang.players import Player, PendingAction
|
from bang.players import Player
|
||||||
import bang.cards as cs
|
import bang.cards as cs
|
||||||
|
from globals import PendingAction
|
||||||
|
|
||||||
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
|
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.expansions.wild_west_show.characters import *
|
||||||
from bang.cards import Card, Suit
|
from bang.cards import Card, Suit
|
||||||
import bang.roles as roles
|
import bang.roles as roles
|
||||||
from bang.players import PendingAction
|
from globals import PendingAction
|
||||||
|
|
||||||
|
|
||||||
# test TerenKill
|
# 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.expansions.wild_west_show.card_events import *
|
||||||
from bang.cards import Card, Suit
|
from bang.cards import Card, Suit
|
||||||
import bang.roles as roles
|
import bang.roles as roles
|
||||||
from bang.players import PendingAction
|
from globals import PendingAction
|
||||||
|
|
||||||
|
|
||||||
# test Camposanto
|
# test Camposanto
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
'gold-rush': card.expansion === 'gold_rush',
|
'gold-rush': card.expansion === 'gold_rush',
|
||||||
brown: card.kind === 0,
|
brown: card.kind === 0,
|
||||||
black: card.kind === 1,
|
black: card.kind === 1,
|
||||||
|
'train-piece': card.type && card.type === 'train',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<h4>{{ cardName }}</h4>
|
<h4>{{ cardName }}</h4>
|
||||||
@ -64,7 +65,7 @@ export default {
|
|||||||
},
|
},
|
||||||
suit() {
|
suit() {
|
||||||
if (this.card && !isNaN(this.card.suit)) {
|
if (this.card && !isNaN(this.card.suit)) {
|
||||||
let x = ["♦️", "♣️", "♥️", "♠️", "🤑"];
|
let x = ["♦️", "♣️", "♥️", "♠️", "🤑", "🚂"];
|
||||||
return x[this.card.suit];
|
return x[this.card.suit];
|
||||||
} else if (this.card.suit) {
|
} else if (this.card.suit) {
|
||||||
return this.card.suit;
|
return this.card.suit;
|
||||||
@ -115,7 +116,7 @@ export default {
|
|||||||
#816b45 10px
|
#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: "";
|
content: "";
|
||||||
background-image: radial-gradient(var(--bg-color) 13%, #0000 5%),
|
background-image: radial-gradient(var(--bg-color) 13%, #0000 5%),
|
||||||
radial-gradient(var(--bg-color) 14%, transparent 5%),
|
radial-gradient(var(--bg-color) 14%, transparent 5%),
|
||||||
@ -231,6 +232,18 @@ export default {
|
|||||||
padding: 4pt;
|
padding: 4pt;
|
||||||
border-radius: 12pt;
|
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 {
|
.avatar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 36pt;
|
width: 36pt;
|
||||||
@ -338,6 +351,7 @@ export default {
|
|||||||
}
|
}
|
||||||
.cant-play {
|
.cant-play {
|
||||||
filter: brightness(0.5);
|
filter: brightness(0.5);
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
.expansion {
|
.expansion {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -347,4 +361,24 @@ export default {
|
|||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
transform: scale(0.8);
|
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>
|
</style>
|
@ -21,7 +21,8 @@
|
|||||||
@click="fillCmd(msg.cmd)">{{msg.cmd}} <i class="std-text" style="font-size:8pt;">{{msg.help}}</i></p>
|
@click="fillCmd(msg.cmd)">{{msg.cmd}} <i class="std-text" style="font-size:8pt;">{{msg.help}}</i></p>
|
||||||
</div>
|
</div>
|
||||||
<form @submit="sendChatMessage" id="msg-form">
|
<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')"/>
|
<input id="submit-message" type="submit" class="btn" :value="$t('submit')"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -164,6 +165,12 @@ export default {
|
|||||||
this.text = cmd;
|
this.text = cmd;
|
||||||
document.getElementById('my-msg').focus();
|
document.getElementById('my-msg').focus();
|
||||||
},
|
},
|
||||||
|
tabComplete() {
|
||||||
|
if (this.commandSuggestion.length > 0) {
|
||||||
|
let cmd = this.commandSuggestion[0].cmd;
|
||||||
|
this.text = cmd + ' ';
|
||||||
|
}
|
||||||
|
},
|
||||||
playEffects(path) {
|
playEffects(path) {
|
||||||
const promise = (new Audio(path)).play();
|
const promise = (new Audio(path)).play();
|
||||||
if(promise !== undefined){
|
if(promise !== undefined){
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div >
|
<div >
|
||||||
<div class="deck">
|
<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[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[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)}"/>
|
<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"/>
|
<card :card="goldRushCardBack" :donotlocalize="true" class="gold-rush back last-event" @click.native="goldRushShopOpen = !goldRushShopOpen"/>
|
||||||
</div>
|
</div>
|
||||||
</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 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:relative; bottom:-3pt;right:-3pt;"/>
|
||||||
<div class="card fistful-of-cards" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
|
<div class="card fistful-of-cards" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
|
||||||
@ -38,15 +42,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<transition name="list">
|
<transition name="list">
|
||||||
<p v-if="eventCard" class="center-stuff"><b>{{eventDesc}}</b></p>
|
<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>
|
<p v-if="eventCardWildWestShow && !eventCardWildWestShow.back" class="center-stuff">🎪 <b>{{eventDescWildWestShow}}</b> 🎪</p>
|
||||||
</transition>
|
|
||||||
<transition name="list">
|
|
||||||
<div v-if="goldRushDesc">
|
<div v-if="goldRushDesc">
|
||||||
<p class="center-stuff">🤑️ <i>{{$t(`cards.${goldRushDesc.name}.desc`)}}</i> 🤑️</p>
|
<p class="center-stuff">🤑️ <i>{{$t(`cards.${goldRushDesc.name}.desc`)}}</i> 🤑️</p>
|
||||||
<p class="center-stuff">🤑️ <b>{{goldRushDesc.number - gold_rush_discount}} 💵️</b> 🤑️</p>
|
<p class="center-stuff">🤑️ <b>{{goldRushDesc.number - gold_rush_discount}} 💵️</b> 🤑️</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="stationDesc">
|
||||||
|
<p class="center-stuff"><i>{{stationDesc}}</i></p>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<div style="margin-bottom:6pt;margin-bottom: 6pt;display: flex;flex-direction: column;">
|
<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>
|
<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>
|
<script>
|
||||||
import Card from '@/components/Card.vue'
|
import Card from '@/components/Card.vue'
|
||||||
|
import StationCard from '@/components/StationCard.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Deck',
|
name: 'Deck',
|
||||||
@ -67,6 +71,7 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Card,
|
Card,
|
||||||
|
StationCard
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
card: {
|
card: {
|
||||||
@ -88,8 +93,11 @@ export default {
|
|||||||
goldRushCards: [],
|
goldRushCards: [],
|
||||||
gold_nuggets: 0,
|
gold_nuggets: 0,
|
||||||
goldRushDesc: null,
|
goldRushDesc: null,
|
||||||
|
stationDesc: null,
|
||||||
can_gold_rush_discard: false,
|
can_gold_rush_discard: false,
|
||||||
gold_rush_discount: 0,
|
gold_rush_discount: 0,
|
||||||
|
currentStations: [],
|
||||||
|
currentTrain: [],
|
||||||
}),
|
}),
|
||||||
sockets: {
|
sockets: {
|
||||||
self(self){
|
self(self){
|
||||||
@ -123,6 +131,11 @@ export default {
|
|||||||
console.log('GOLD RUSH:'+ cards)
|
console.log('GOLD RUSH:'+ cards)
|
||||||
this.goldRushCards = JSON.parse(cards)
|
this.goldRushCards = JSON.parse(cards)
|
||||||
},
|
},
|
||||||
|
stations(stations) {
|
||||||
|
let msg = JSON.parse(stations)
|
||||||
|
this.currentStations = msg.stations
|
||||||
|
this.currentTrain = msg.current_train
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
endTurnCard() {
|
endTurnCard() {
|
||||||
@ -163,6 +176,15 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
action(pile) {
|
||||||
if (this.pending_action !== false && this.pending_action < 2) {
|
if (this.pending_action !== false && this.pending_action < 2) {
|
||||||
// console.log('action')
|
// console.log('action')
|
||||||
@ -189,6 +211,14 @@ export default {
|
|||||||
setGoldRushDesc(card) {
|
setGoldRushDesc(card) {
|
||||||
this.goldRushDesc = 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() {
|
mounted() {
|
||||||
if (window.innerWidth < 1000) {
|
if (window.innerWidth < 1000) {
|
||||||
|
@ -127,15 +127,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Card from '@/components/Card.vue'
|
import Card from '@/components/Card.vue'
|
||||||
|
import StationCard from './StationCard.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Help',
|
name: 'Help',
|
||||||
components: {
|
components: {
|
||||||
Card,
|
Card,
|
||||||
|
StationCard,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
inGame: Boolean
|
inGame: Boolean
|
||||||
@ -152,6 +169,8 @@ export default {
|
|||||||
goldrushcards: [],
|
goldrushcards: [],
|
||||||
valleyofshadowscards: [],
|
valleyofshadowscards: [],
|
||||||
wildwestshowcards: [],
|
wildwestshowcards: [],
|
||||||
|
trainrobberycards: [],
|
||||||
|
trainrobberystations: [],
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
endTurnCard() {
|
endTurnCard() {
|
||||||
@ -196,6 +215,14 @@ export default {
|
|||||||
...x,
|
...x,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
trainrobberycards_info(cardsJson) {
|
||||||
|
this.trainrobberycards = JSON.parse(cardsJson).cards.map(x=>({
|
||||||
|
...x,
|
||||||
|
}))
|
||||||
|
this.trainrobberystations = JSON.parse(cardsJson).stations.map(x=>({
|
||||||
|
...x,
|
||||||
|
}))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$socket.emit('get_cards')
|
this.$socket.emit('get_cards')
|
||||||
@ -205,6 +232,7 @@ export default {
|
|||||||
this.$socket.emit('get_goldrushcards')
|
this.$socket.emit('get_goldrushcards')
|
||||||
this.$socket.emit('get_valleyofshadowscards')
|
this.$socket.emit('get_valleyofshadowscards')
|
||||||
this.$socket.emit('get_wildwestshowcards')
|
this.$socket.emit('get_wildwestshowcards')
|
||||||
|
this.$socket.emit('get_trainrobberycards')
|
||||||
document.getElementById('help').scrollIntoView();
|
document.getElementById('help').scrollIntoView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -646,8 +646,15 @@ export default {
|
|||||||
console.log("card_scrapped no deck");
|
console.log("card_scrapped no deck");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let decelOffset = cumulativeOffset(decel);
|
|
||||||
let phand = document.getElementById(`${data.player}-hand`);
|
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) {
|
if (!phand) {
|
||||||
console.log("card_scrapped no phand");
|
console.log("card_scrapped no phand");
|
||||||
return;
|
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",
|
"copy": "Copy invite",
|
||||||
"no_players_in_range": "You can't see the other players, equip a weapon or a scope!",
|
"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!",
|
"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": {
|
"chat": {
|
||||||
"spectators": " | A spectator is watching the game | {n} spectators are watching the game",
|
"spectators": " | A spectator is watching the game | {n} spectators are watching the game",
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
@ -153,7 +158,9 @@
|
|||||||
"choose_emporio": ";{0}; has chosen ;{1}; from General Store.",
|
"choose_emporio": ";{0}; has chosen ;{1}; from General Store.",
|
||||||
"shotgun_scrap": "When the shotgun hit ;{0}; a card flew away from his hand (;{1};)",
|
"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};",
|
"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": {
|
"foc": {
|
||||||
"leggedelwest": "He must play this card on this turn if possible."
|
"leggedelwest": "He must play this card on this turn if possible."
|
||||||
@ -855,6 +862,130 @@
|
|||||||
"Wild West Show": {
|
"Wild West Show": {
|
||||||
"name": "Wild West Show",
|
"name": "Wild West Show",
|
||||||
"desc": "The goal for each player becomes: \"Be the last one standing!\""
|
"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": {
|
"help": {
|
||||||
@ -906,7 +1037,9 @@
|
|||||||
"highnooncards": "High Noon - Event Cards",
|
"highnooncards": "High Noon - Event Cards",
|
||||||
"foccards": "Fistful of Cards - Event Cards",
|
"foccards": "Fistful of Cards - Event Cards",
|
||||||
"goldrushcards": "Gold Rush 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": {
|
"theme": {
|
||||||
"sepia": "Sepia",
|
"sepia": "Sepia",
|
||||||
|
@ -153,7 +153,8 @@
|
|||||||
"choose_emporio": ";{0}; ha scelto ;{1}; da Emporio.",
|
"choose_emporio": ";{0}; ha scelto ;{1}; da Emporio.",
|
||||||
"shotgun_scrap": "Quando lo shotgun ha colpito ;{0}; gli ha tolto una carta (;{1};)",
|
"shotgun_scrap": "Quando lo shotgun ha colpito ;{0}; gli ha tolto una carta (;{1};)",
|
||||||
"taglia_reward": "💰 ;{1}; ha ottenuto ricompensa dalla taglia su ;{0};",
|
"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": {
|
"foc": {
|
||||||
"leggedelwest": "Ed è obbligato a usarla nel suo turno, se possibile"
|
"leggedelwest": "Ed è obbligato a usarla nel suo turno, se possibile"
|
||||||
@ -855,6 +856,130 @@
|
|||||||
"Wild West Show": {
|
"Wild West Show": {
|
||||||
"name": "Wild West Show",
|
"name": "Wild West Show",
|
||||||
"desc": "L'obiettivo di ogni giocatore diventa: \"Rimani l'ultimo in gioco!\""
|
"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": {
|
"help": {
|
||||||
|
@ -5,4 +5,5 @@ export const emojiMap = {
|
|||||||
'fistful_of_cards': '🎴',
|
'fistful_of_cards': '🎴',
|
||||||
'the_valley_of_shadows': '👻',
|
'the_valley_of_shadows': '👻',
|
||||||
'wild_west_show': '🎪',
|
'wild_west_show': '🎪',
|
||||||
|
'train_robbery': '🚂',
|
||||||
}
|
}
|
@ -28,13 +28,19 @@ export const expansionsMap = {
|
|||||||
icon: '👻',
|
icon: '👻',
|
||||||
back: true,
|
back: true,
|
||||||
expansion: 'the-valley-of-shadows',
|
expansion: 'the-valley-of-shadows',
|
||||||
status: 'beta',
|
|
||||||
},
|
},
|
||||||
'wild_west_show': {
|
'wild_west_show': {
|
||||||
name: 'Wild West Show',
|
name: 'Wild West Show',
|
||||||
icon: '🎪',
|
icon: '🎪',
|
||||||
back: true,
|
back: true,
|
||||||
expansion: 'wild-west-show',
|
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