diff --git a/backend/bang/characters.py b/backend/bang/characters.py
index 620599a..5117b81 100644
--- a/backend/bang/characters.py
+++ b/backend/bang/characters.py
@@ -177,4 +177,6 @@ def all_characters(expansions: List[str]):
base_chars.extend(GoldRush.get_characters())
if 'the_valley_of_shadows' in expansions:
base_chars.extend(TheValleyOfShadows.get_characters())
+ if 'wild_west_show' in expansions:
+ base_chars.extend(WildWestShow.get_characters())
return base_chars
\ No newline at end of file
diff --git a/backend/bang/deck.py b/backend/bang/deck.py
index 5c49d6d..0cd14ab 100644
--- a/backend/bang/deck.py
+++ b/backend/bang/deck.py
@@ -1,13 +1,17 @@
-from typing import List, Set, Dict, Tuple, Optional
+from typing import List, Set, Dict, Tuple, Optional, TYPE_CHECKING
import random
import bang.cards as cs
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
+import bang.expansions.wild_west_show.card_events as cew
+import bang.expansions.wild_west_show.characters as chw
import bang.expansions.gold_rush.shop_cards as grc
from globals import G
+if TYPE_CHECKING:
+ from bang.game import Game
class Deck:
- def __init__(self, game):
+ def __init__(self, game: 'Game'):
super().__init__()
self.cards: List[cs.Card] = cs.get_starting_deck(game.expansions)
self.mancato_cards: List[str] = []
@@ -33,6 +37,9 @@ class Deck:
if 'high_noon' in game.expansions:
self.event_cards.extend(ceh.get_all_events(game.rng))
endgame_cards.append(ceh.get_endgame_card())
+ if 'wild_west_show' in game.expansions:
+ self.event_cards.extend(cew.get_all_events(game.rng))
+ endgame_cards.append(cew.get_endgame_card())
if len(self.event_cards) > 0:
game.rng.shuffle(self.event_cards)
self.event_cards = self.event_cards[:12]
@@ -51,9 +58,10 @@ class Deck:
print(f'Deck initialized with {len(self.cards)} cards')
def flip_event(self):
- if len(self.event_cards) > 0 and not (isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte) or isinstance(self.event_cards[0], ceh.MezzogiornoDiFuoco)):
+ if len(self.event_cards) > 0 and not (isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte) or isinstance(self.event_cards[0], ceh.MezzogiornoDiFuoco) or isinstance(self.event_cards[0], cew.WildWestShow)):
self.event_cards.append(self.event_cards.pop(0))
self.game.notify_event_card()
+ self.game.notify_all()
def fill_gold_rush_shop(self):
if not any((c is None for c in self.shop_cards)):
@@ -76,7 +84,16 @@ class Deck:
def pick_and_scrap(self) -> cs.Card:
card = self.cards.pop(0)
- self.scrap_pile.append(card)
+ jpain = None
+ for p in self.game.players:
+ if p.character.check(self.game, chw.JohnPain) and len(p.hand) < 6:
+ jpain = p
+ break
+ if jpain:
+ jpain.hand.append(card)
+ jpain.notify_self()
+ else:
+ self.scrap_pile.append(card)
if len(self.cards) == 0:
self.reshuffle()
self.game.notify_scrap_pile()
diff --git a/backend/bang/expansions/__init__.py b/backend/bang/expansions/__init__.py
index ebae2dd..220866e 100644
--- a/backend/bang/expansions/__init__.py
+++ b/backend/bang/expansions/__init__.py
@@ -22,3 +22,8 @@ class TheValleyOfShadows():
def get_cards():
from bang.expansions.the_valley_of_shadows import cards
return cards.get_starting_deck()
+
+class WildWestShow():
+ def get_characters():
+ from bang.expansions.wild_west_show import characters
+ return characters.all_characters()
\ No newline at end of file
diff --git a/backend/bang/expansions/wild_west_show/card_events.py b/backend/bang/expansions/wild_west_show/card_events.py
new file mode 100644
index 0000000..666f1e1
--- /dev/null
+++ b/backend/bang/expansions/wild_west_show/card_events.py
@@ -0,0 +1,94 @@
+import random
+
+from bang.expansions.fistful_of_cards.card_events import CardEvent
+
+
+# class Bavaglio(CardEvent):
+# def __init__(self):
+# super().__init__("Bavaglio", "π€")
+# # I giocatori non possono parlare (ma possono gesticolare, mugugnare...). Chi parla perde 1 punto vita.
+# # NOT IMPLEMENTED
+
+class Camposanto(CardEvent):
+ """
+ All'inizio del proprio turno, ogni giocatore eliminato torna in gioco con 1 punto vita. Pesca il ruolo a caso fra quelli dei giocatori eliminati.
+ """
+ def __init__(self):
+ super().__init__("Camposanto", "β°")
+
+class DarlingValentine(CardEvent):
+ """
+ All'inizio del proprio turno, ogni giocatore scarta le carte in mano e ne pesca dal mazzo altrettante.
+ """
+ def __init__(self):
+ super().__init__("Darling Valentine", "π")
+
+class DorothyRage(CardEvent):
+ """
+ Nel proprio turno, ogni giocatore puΓ² obbligarne un altro a giocare una carta.
+ """
+ def __init__(self):
+ super().__init__("Dorothy Rage", "π©ββοΈ")
+
+class HelenaZontero(CardEvent):
+ """
+ Quando Helena entra in gioco, "estrai!": se esce Cuori o Quadri, rimescola i ruoli attivi tranne lo Sceriffo, e ridistribuiscili a caso.
+ """
+ def __init__(self):
+ super().__init__("Helena Zontero", "π")
+
+class LadyRosaDelTexas(CardEvent):
+ """
+ Nel proprio turno, ogni giocatore puΓ² scambiarsi di posto con quello alla sua destra, il quale salta il prossimo turno.
+ """
+ def __init__(self):
+ super().__init__("Lady Rosa del Texas", "π©°")
+
+class MissSusanna(CardEvent):
+ """
+ Nel proprio turno ogni giocatore deve giocare almeno 3 carte. Se non lo fa, perde 1 punto vita.
+ """
+ def __init__(self):
+ super().__init__("Miss Susanna", "π©βπ€")
+
+class RegolamentoDiConti(CardEvent):
+ """
+ Tutte le carte possono essere giocate come se fossero BANG!. Le carte BANG! come se fossero Mancato!
+ """
+ def __init__(self):
+ super().__init__("Regolamento di Conti", "π€ ")
+
+class Sacagaway(CardEvent):
+ """
+ Tutti i giocatori giocano a carte scoperte (tranne il ruolo!).
+ """
+ def __init__(self):
+ super().__init__("Sacagaway", "π")
+
+class WildWestShow(CardEvent):
+ """
+ L'obiettivo di ogni giocatore diventa: "Rimani l'ultimo in gioco!"
+ """
+ def __init__(self):
+ super().__init__("Wild West Show", "πͺ")
+
+def get_endgame_card():
+ end_game = WildWestShow()
+ end_game.expansion = 'wild-west-show'
+ return end_game
+
+def get_all_events(rng=random):
+ cards = [
+ Camposanto(),
+ DarlingValentine(),
+ # DorothyRage(),
+ HelenaZontero(),
+ LadyRosaDelTexas(),
+ MissSusanna(),
+ RegolamentoDiConti(),
+ Sacagaway(),
+ ]
+ rng.shuffle(cards)
+ for c in cards:
+ c.expansion = 'wild-west-show'
+ return cards
\ No newline at end of file
diff --git a/backend/bang/expansions/wild_west_show/characters.py b/backend/bang/expansions/wild_west_show/characters.py
new file mode 100644
index 0000000..b8d28d1
--- /dev/null
+++ b/backend/bang/expansions/wild_west_show/characters.py
@@ -0,0 +1,93 @@
+from typing import List
+from bang.characters import Character
+
+class BigSpencer(Character):
+ """
+ Inizia con 5 carte. Non puΓ² giocare Mancato!
+ """
+ def __init__(self):
+ super().__init__("Big Spencer", max_lives=9)
+ self.icon = 'π«'
+
+class FlintWestwood(Character):
+ """
+ Nel suo turno puΓ² scambiare una carta dalla mano con 2 carte a caso dalla mano di un altro giocatore.
+ > NOTE: La carta dalla tua mano Γ¨ a scelta, non a caso. Se il giocatore bersaglio ha una sola carta, ne ricevi solo una.
+ """
+ def __init__(self):
+ super().__init__("Flint Westwood", max_lives=4)
+ self.icon = 'π«'
+
+ def special(self, player, data):
+ if not player.is_my_turn or not any((len(p.hand) > 0 for p in player.game.get_alive_players())) or not super().special(player, data):
+ return False
+ from bang.players import PendingAction
+ player.available_cards = player.hand.copy()
+ player.choose_text = 'choose_flint_special'
+ player.pending_action = PendingAction.CHOOSE
+ player.special_use_count += 1
+ player.notify_self()
+
+class GaryLooter(Character):
+ """
+ Pesca tutte le carte in eccesso scartate dagli altri giocatori a fine turno.
+ """
+ def __init__(self):
+ super().__init__("Gary Looter", max_lives=5)
+ self.icon = 'π₯²'
+
+class GreygoryDeckard(Character):
+ """
+ All'inizio del suo turno puΓ² pescare 2 personaggi a caso. Ha tutte le abilitΓ dei personaggi pescati.
+ """
+ def __init__(self):
+ super().__init__("Greygory Deckard", max_lives=4)
+ self.icon = 'π¨βπ¦³'
+
+class JohnPain(Character):
+ """
+ Se ha meno di 6 carte in mano, quando un giocatore "estrae!" John aggiunge alla mano la carta appena estratta.
+ """
+ def __init__(self):
+ super().__init__("John Pain", max_lives=4)
+ self.icon = 'π€'
+
+class LeeVanKliff(Character):
+ """
+ Nel suo turno, puΓ² scartare un BANG! per ripetere l'effetto di una carta a bordo marrone che ha appena giocato.
+ """
+ def __init__(self):
+ super().__init__("Lee Van Kliff", max_lives=4)
+ self.icon = 'π¨βπ¦²'
+
+class TerenKill(Character):
+ """
+ Ogni volta che sarebbe eliminato "estrai!": se non Γ¨ Picche, Teren resta a 1 punto vita e pesca 1 carta.
+ """
+ def __init__(self):
+ super().__init__("Teren Kill", max_lives=3)
+ self.icon = 'π¨βπ¦°'
+
+class YoulGrinner(Character):
+ """
+ Prima di pescare, i giocatori con piΓΉ carte in mano di lui devono dargli una carta a scelta.
+ """
+ def __init__(self):
+ super().__init__("Youl Grinner", max_lives=4)
+ self.icon = 'π€‘'
+
+def all_characters() -> List[Character]:
+ cards = [
+ BigSpencer(),
+ FlintWestwood(),
+ # GaryLooter(),
+ # GreygoryDeckard(),
+ JohnPain(),
+ # LeeVanKliff(),
+ TerenKill(),
+ YoulGrinner(),
+ ]
+ for c in cards:
+ c.expansion_icon = 'πͺ'
+ c.expansion = 'wild_west_show'
+ return cards
diff --git a/backend/bang/game.py b/backend/bang/game.py
index 82f006e..6c1d739 100644
--- a/backend/bang/game.py
+++ b/backend/bang/game.py
@@ -12,6 +12,7 @@ from bang.deck import Deck
import bang.roles as roles
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
+import bang.expansions.wild_west_show.card_events as cew
import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch
import bang.expansions.the_valley_of_shadows.cards as tvosc
@@ -61,7 +62,7 @@ class Game:
self.initial_players = 0
self.password = ''
self.expansions: List[str] = []
- self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon', 'gold_rush', 'the_valley_of_shadows']
+ self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon', 'gold_rush', 'the_valley_of_shadows', 'wild_west_show']
self.shutting_down = False
self.is_competitive = False
self.disconnect_bot = True
@@ -78,6 +79,7 @@ class Game:
self.attack_in_progress = False
self.characters_to_distribute = 2 # personaggi da dare a inizio partita
self.debug = self.name == 'debug'
+ self.dead_roles: List[roles.Role] = []
self.is_changing_pwd = False
self.is_hidden = False
self.rng = random.Random()
@@ -113,6 +115,7 @@ class Game:
self.incremental_turn = 0
self.turn = 0
self.pending_winners = []
+ self.dead_roles: List[roles.Role] = []
for p in self.players:
p.reset()
p.notify_self()
@@ -579,12 +582,16 @@ class Game:
Metrics.send_metric('incremental_turn', points=[self.incremental_turn], tags=[f'game:{self.SEED}'])
if self.players[self.turn].is_dead:
pl = sorted(self.get_dead_players(), key=lambda x:x.death_turn)[0]
- if self.check_event(ce.DeadMan) and not self.did_resuscitate_deadman and pl == self.players[self.turn]:
+ if self.check_event([ce.DeadMan, cew.Camposanto]) and not self.did_resuscitate_deadman and pl == self.players[self.turn]:
print(f'{self.name}: {self.players[self.turn]} is dead, revive')
- self.did_resuscitate_deadman = True
+ if self.check_event(ce.DeadMan):
+ self.did_resuscitate_deadman = True
+ pl.lives = 2
+ elif self.check_event(cew.Camposanto):
+ pl.lives = 1
+ pl.set_role = self.dead_roles.pop(random.randint(0, len(self.dead_roles)-1))
pl.is_dead = False
pl.is_ghost = False
- pl.lives = 2
self.deck.draw(player=pl)
self.deck.draw(player=pl)
if (ghost := next((c for c in pl.equipment if isinstance(c, tvosc.Fantasma)), None)) is not None:
@@ -603,6 +610,16 @@ class Game:
if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] is not None:
print(f'{self.name}: flip new event {self.deck.event_cards[0].name}')
G.sio.emit('chat_message', room=self.name, data={'color': f'orange','text':f'_flip_event|{self.deck.event_cards[0].name}'})
+ if self.check_event(cew.HelenaZontero):
+ c = self.deck.pick_and_scrap()
+ G.sio.emit('chat_message', room=self.name, data=f'_flipped|Helena Zontero|{c.name}|{c.num_suit()}')
+ if c.check_suit(self, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]):
+ G.sio.emit('chat_message', room=self.name, data=f'_swapped_roles|Helena Zontero|{c.name}|{c.num_suit()}')
+ pls = [p for p in self.players if not isinstance(p.role, roles.Sheriff)]
+ newroles = [p.role for p in pls]
+ random.shuffle(newroles)
+ for p in pls:
+ p.set_role(newroles.pop(random.randint(0, len(newroles)-1)))
if self.check_event(ce.DeadMan):
self.did_resuscitate_deadman = False
elif self.check_event(ce.RouletteRussa):
@@ -632,6 +649,9 @@ class Game:
if self.waiting_for != 0:
return
self.dalton_on = False
+ elif self.check_event(cew.WildWestShow):
+ for p in self.players:
+ p.set_role(roles.Renegade())
if self.check_event(ce.PerUnPugnoDiCarte) and len(self.players[self.turn].hand) > 0:
self.player_bangs = len(self.players[self.turn].hand)
@@ -762,6 +782,7 @@ class Game:
# if not disconnected:
# self.dead_players.append(corpse)
self.notify_room()
+ self.dead_roles.append(player.role)
G.sio.emit('chat_message', room=self.name, data=f'_died|{player.name}')
for p in self.players:
if not p.is_bot:
@@ -845,7 +866,10 @@ class Game:
def check_event(self, ev):
if self.deck is None or len(self.deck.event_cards) == 0: return False
- return isinstance(self.deck.event_cards[0], ev)
+ if isinstance(ev, type):
+ return isinstance(self.deck.event_cards[0], ev)
+ else:
+ return any(isinstance(self.deck.event_cards[0], evc) for evc in ev)
def get_visible_players(self, player: pl.Player): # returns a dictionary because we need to add the distance
pls = self.get_alive_players()
@@ -874,9 +898,11 @@ class Game:
def notify_all(self):
if self.started and self.replay_speed > 0:
+ show_cards = self.check_event(cew.Sacagaway)
data = [{
'name': p.name,
'ncards': len(p.hand),
+ 'hand_cards': [c.__dict__ for c in p.hand] if show_cards else [],
'equipment': [e.__dict__ for e in p.equipment],
'gold_rush_equipment': [e.__dict__ for e in p.gold_rush_equipment],
'lives': p.lives,
diff --git a/backend/bang/players.py b/backend/bang/players.py
index 8bcd78f..afd5a43 100644
--- a/backend/bang/players.py
+++ b/backend/bang/players.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from enum import IntEnum
import json
-from random import random, randrange, sample, uniform
+from random import random, randrange, sample, uniform, randint
import socketio
import bang.deck as deck
import bang.roles as r
@@ -11,6 +11,8 @@ import bang.characters as chars
import bang.expansions.dodge_city.characters as chd
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
+import bang.expansions.wild_west_show.card_events as cew
+import bang.expansions.wild_west_show.characters as chw
import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch
import bang.expansions.the_valley_of_shadows.cards as tvosc
@@ -88,6 +90,7 @@ class Player:
self.is_bot = bot
self.discord_token = discord_token
self.discord_id = None
+ self.played_cards = 0
self.avatar = ''
if self.is_bot:
self.avatar = robot_pictures[randrange(len(robot_pictures))]
@@ -262,6 +265,12 @@ class Player:
if isinstance(self.gold_rush_equipment[i], grc.Zaino):
self.gold_rush_equipment[i].play_card(self, None)
return # play card will notify the player
+ if self.character.check(self.game, chw.TerenKill):
+ picked: cs.Card = self.game.deck.pick_and_scrap()
+ G.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
+ if not picked.check_suit(self.game, [cs.Suit.SPADES]):
+ self.lives = 1
+ self.game.deck.draw(True, player=self)
if self.character.check(self.game, chars.SidKetchum) and len(self.hand) > 1 and self.lives == 0:
if self.game.players[self.game.turn] != self:
self.game.players[self.game.turn].pending_action = PendingAction.WAIT
@@ -439,6 +448,7 @@ class Player:
return self.end_turn(forced=True)
self.scrapped_cards = 0
self.setaccio_count = 0
+ self.played_cards = 0
self.can_play_ranch = True
self.is_playing_ranch = False
self.can_play_vendetta = can_play_vendetta
@@ -453,6 +463,11 @@ class Player:
self.has_played_bang = False
self.special_use_count = 0
self.bang_used = 0
+ if self.game.check_event(cew.DarlingValentine):
+ hand = len(self.hand)
+ for _ in range(hand):
+ self.game.deck.scrap(self.hand.pop(0), True, player=self)
+ self.game.deck.draw(True, player=self)
if self.game.check_event(ceh.MezzogiornoDiFuoco):
self.lives -= 1
if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
@@ -511,6 +526,19 @@ class Player:
self.choose_text = 'choose_cecchino'
self.pending_action = PendingAction.CHOOSE
self.notify_self()
+ if self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(cew.RegolamentoDiConti) and len(self.hand) > 0:
+ if not self.has_played_bang and any((self.get_sight() >= p['dist'] for p in self.game.get_visible_players(self))):
+ self.available_cards = self.hand.copy()
+ self.pending_action = PendingAction.CHOOSE
+ self.choose_text = 'choose_play_as_bang'
+ self.notify_self()
+ elif self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(cew.LadyRosaDelTexas):
+ nextp = self.game.next_player()
+ i, j = self.game.players_map[self.name], self.game.players_map[nextp.name]
+ self.game.players[i], self.game.players[j] = nextp, self
+ self.game.players_map[self.name], self.game.players_map[nextp.name] = j, i
+ self.game.turn = j
+ self.game.notify_all()
elif self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Rimbalzo) and any((c.name == cs.Bang(0,0).name for c in self.hand)):
self.available_cards = [{
'name': p.name,
@@ -551,6 +579,12 @@ class Player:
self.notify_self()
else:
self.pending_action = PendingAction.PLAY
+ if self.character.check(self.game, chw.YoulGrinner):
+ hsize = len(self.hand)
+ for p in self.game.get_alive_players():
+ if p != self and len(p.hand) > hsize:
+ G.sio.emit('card_drawn', room=self.game.name, data={'player': self.name, 'pile': p.name})
+ self.hand.append(p.hand.pop(randint(0, len(p.hand)-1)))
num = 2 if not self.character.check(self.game, chd.BillNoface) else self.max_lives-self.lives+1
if self.character.check(self.game, chd.PixiePete): num += 1
if self.character.check(self.game, tvosch.TucoFranziskaner) and not any((True for c in self.equipment if not c.usable_next_turn)): num += 2
@@ -756,6 +790,8 @@ class Player:
if not self.game.is_replay:
Metrics.send_metric('play_card', points=[1], tags=[f'success:{did_play_card}', f'card:{card.name}', f'bot:{self.is_bot}', f'exp:{card.expansion if "expansion" in card.__dict__ else "vanilla"}'])
print("did play card:", did_play_card)
+ if did_play_card:
+ self.played_cards += 1
self.notify_self()
if self.is_bot:
return did_play_card or card.is_equipment or (card.usable_next_turn and not card.can_be_used_now)
@@ -918,6 +954,27 @@ class Player:
self.pending_action = PendingAction.WAIT
self.game.responders_did_respond_resume_turn()
self.notify_self()
+ elif 'choose_flint_special' == self.choose_text:
+ if card_index < len(self.hand):
+ self.available_cards = [{
+ 'name': p.name,
+ 'icon': p.role.icon if(self.game.initial_players == 3) else 'βοΈ' if p['is_sheriff'] else 'π€ ',
+ 'avatar': p.avatar,
+ 'is_character': True,
+ 'is_player': True
+ } for p in self.game.get_alive_players() if p.name != self.name and len(p.hand) > 0]
+ self.choose_text = f'choose_flint_special;{card_index}'
+ self.notify_self()
+ elif 'choose_flint_special' in self.choose_text:
+ if card_index < len(self.available_cards):
+ my_card = self.hand.pop(int(self.choose_text.split(';')[1]))
+ other_player = self.game.get_player_named(self.available_cards[card_index]['name'])
+ for i in range(min(2, len(other_player.hand))):
+ self.hand.append(other_player.hand.pop(randint(0, len(other_player.hand)-1)))
+ other_player.hand.append(my_card)
+ other_player.notify_self()
+ self.pending_action = PendingAction.PLAY
+ self.notify_self()
elif self.game.check_event(ceh.NuovaIdentita) and self.choose_text == 'choose_nuova_identita':
if card_index == 1: # the other character
self.character = self.not_chosen_character
@@ -1088,6 +1145,8 @@ class Player:
self.expected_response = self.game.deck.mancato_cards.copy()
if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response:
self.expected_response.append(cs.Bang(0, 0).name)
+ if self.character.check(self.game, chw.BigSpencer):
+ self.expected_response = []
self.on_failed_response_cb = self.take_damage_response
self.notify_self()
@@ -1118,6 +1177,8 @@ class Player:
self.expected_response = self.game.deck.mancato_cards.copy()
if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response:
self.expected_response.append(cs.Bang(0, 0).name)
+ if self.character.check(self.game, chw.BigSpencer):
+ self.expected_response = []
self.on_failed_response_cb = self.take_no_damage_response
self.notify_self()
@@ -1214,6 +1275,8 @@ class Player:
self.expected_response = self.game.deck.mancato_cards_not_green_or_blue.copy()
if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response:
self.expected_response.append(cs.Bang(0, 0).name)
+ if self.character.check(self.game, chw.BigSpencer):
+ self.expected_response = []
if self.can_escape(card_name, with_mancato=False):
self.expected_response.append(tvosc.Fuga(0, 0).name)
if not no_dmg:
@@ -1536,6 +1599,9 @@ class Player:
for i in range(len(self.equipment)):
self.game.deck.scrap(self.equipment.pop(), True)
self.is_my_turn = False
+ if self.played_cards < 3 and self.game.check_event(cew.MissSusanna):
+ self.lives -= 1
+ self.played_cards = 0
self.can_play_again_don_bell = True
self.committed_suit_manette = None
self.pending_action = PendingAction.WAIT
diff --git a/backend/server.py b/backend/server.py
index c42dda1..6a21aa8 100644
--- a/backend/server.py
+++ b/backend/server.py
@@ -631,11 +631,14 @@ def chat_message(sid, msg, pl=None):
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'π¨ {ses.name} is in debug mode and changed event'})
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
+ import bang.expansions.wild_west_show.card_events as cew
chs = []
chs.extend(ce.get_all_events())
chs.append(ce.get_endgame_card())
chs.extend(ceh.get_all_events())
chs.append(ceh.get_endgame_card())
+ chs.extend(cew.get_all_events())
+ chs.append(cew.get_endgame_card())
ses.game.deck.event_cards.insert(int(cmd[1]), [c for c in chs if c is not None and c.name == ' '.join(cmd[2:])][0])
ses.game.notify_event_card()
elif '/removecard' in msg:
diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py
index e79239c..02a3aa6 100644
--- a/backend/tests/__init__.py
+++ b/backend/tests/__init__.py
@@ -1,3 +1,39 @@
+import pytest
+from bang.characters import Character
+from bang.game import Game
+from bang.players import Player
from tests.dummy_socket import DummySocket
from globals import G
-G.sio = DummySocket()
\ No newline at end of file
+G.sio = DummySocket()
+
+
+def started_game(expansions, players=4, character=Character('test_char', 4)):
+ g = Game('test')
+ g.expansions = expansions
+ ps = [Player(f'p{i}', f'p{i}') for i in range(players)]
+ for p in ps:
+ g.add_player(p)
+ g.start_game()
+ for p in ps:
+ p.available_characters = [character]
+ p.set_character(p.available_characters[0].name)
+ return g
+
+
+def set_events(g: Game, event_cards):
+ g.deck.event_cards = event_cards
+
+
+def current_player(g: Game):
+ return g.players[g.turn]
+
+
+def next_player(g: Game):
+ return g.players[(g.turn + 1) % len(g.players)]
+
+
+def current_player_with_cards(g: Game, cards):
+ p = current_player(g)
+ p.draw('')
+ p.hand = cards
+ return p
diff --git a/backend/tests/wild_west_show_characters_test.py b/backend/tests/wild_west_show_characters_test.py
new file mode 100644
index 0000000..ca3ec32
--- /dev/null
+++ b/backend/tests/wild_west_show_characters_test.py
@@ -0,0 +1,34 @@
+
+from tests import started_game, set_events, current_player, next_player, current_player_with_cards
+
+from bang.expansions.wild_west_show.characters import *
+from bang.cards import Card, Suit
+import bang.roles as roles
+from bang.players import PendingAction
+
+
+# test TerenKill
+def test_TerenKill():
+ g = started_game(['wild_west_show'], 4, TerenKill())
+ p = current_player_with_cards(g, [])
+ p.lives = 0
+ g.deck.cards = [Card(Suit.HEARTS, 'card', 0), Card(Suit.HEARTS, 'card', 0)]
+ p.notify_self()
+ assert p.lives == 1
+ assert len(p.hand) == 1
+ p.lives = 0
+ g.deck.cards = [Card(Suit.SPADES, 'card', 0), Card(Suit.HEARTS, 'card', 0)]
+ p.notify_self()
+ assert p.lives == 0
+
+
+# test YoulGrinner
+def test_YoulGrinner():
+ g = started_game(['wild_west_show'], 4, YoulGrinner())
+ p = current_player(g)
+ p.hand = []
+ p.draw('')
+ assert len(p.hand) == 5
+ for pl in g.players:
+ if pl != p:
+ assert len(pl.hand) == 3
diff --git a/backend/tests/wild_west_show_events_test.py b/backend/tests/wild_west_show_events_test.py
new file mode 100644
index 0000000..4a85cbc
--- /dev/null
+++ b/backend/tests/wild_west_show_events_test.py
@@ -0,0 +1,98 @@
+
+from tests import started_game, set_events, current_player, next_player, current_player_with_cards
+
+from bang.expansions.wild_west_show.card_events import *
+from bang.cards import Card, Suit
+import bang.roles as roles
+from bang.players import PendingAction
+
+
+# test Camposanto
+def test_camposanto():
+ g = started_game(['wild_west_show'], 4)
+ set_events(g, [Camposanto()])
+ current_player_with_cards(g, []).end_turn()
+ p = current_player_with_cards(g, [])
+ p.lives = 0
+ p.notify_self()
+ p1 = current_player_with_cards(g, [])
+ p1.lives = 0
+ p1.notify_self()
+ current_player_with_cards(g, []).end_turn()
+ current_player_with_cards(g, []).end_turn()
+ assert p.is_my_turn
+ assert p.lives == 1
+ current_player_with_cards(g, []).end_turn()
+ assert p1.is_my_turn
+ assert p1.lives == 1
+
+
+# test DarlingValentine
+def test_darling_valentine():
+ g = started_game(['wild_west_show'], 4)
+ set_events(g, [DarlingValentine()])
+ p = next_player(g)
+ hand = p.hand.copy()
+ current_player_with_cards(g, []).end_turn()
+ assert hand != current_player(g).hand
+
+
+# test DorothyRage
+
+# test HelenaZontero
+def test_helena_zontero():
+ g = started_game(['wild_west_show'], 8)
+ set_events(g, [None, HelenaZontero()])
+ roles = [p.role.name for p in g.players]
+ for i in range(len(g.players)-1):
+ current_player_with_cards(g, []).end_turn()
+ g.deck.cards = [Card(Suit.HEARTS, 'card', 0)]*5
+ current_player_with_cards(g, []).end_turn()
+ roles2 = [p.role.name for p in g.players]
+ assert roles != roles2
+
+# test LadyRosaDelTexas
+def test_LadyRosaDelTexas():
+ g = started_game(['wild_west_show'], 4)
+ set_events(g, [LadyRosaDelTexas()])
+ p = current_player_with_cards(g, [Card(0,'card',0)]*4)
+ t = g.turn
+ p.draw('event')
+ assert g.turn == (t+1)%len(g.players)
+
+# test MissSusanna
+def test_miss_suzanna():
+ g = started_game(['wild_west_show'], 4)
+ set_events(g, [MissSusanna()])
+ p = current_player_with_cards(g, [])
+ p.end_turn()
+ assert p.lives == 4 # sceriffo 5-1
+ p = current_player_with_cards(g, [Card(0,'card',0)]*4)
+ p.play_card(0)
+ p.play_card(0)
+ p.play_card(0)
+ p.end_turn()
+ assert p.lives == 4
+ p = current_player_with_cards(g, [])
+ p.end_turn()
+ assert p.lives == 3
+
+
+# test RegolamentoDiConti
+def test_RegolamentoDiConti():
+ g = started_game(['wild_west_show'], 4)
+ set_events(g, [RegolamentoDiConti()])
+ p = current_player_with_cards(g, [Card(0,'card',0)]*4)
+ p.draw('event')
+ assert p.pending_action == PendingAction.CHOOSE
+ p.choose(0)
+
+
+# test WildWestShow
+def test_WildWestShow():
+ g = started_game(['wild_west_show'], 8)
+ set_events(g, [None, WildWestShow()])
+ for i in range(len(g.players)):
+ current_player_with_cards(g, []).end_turn()
+ for p in g.players:
+ assert isinstance(p.role, roles.Renegade)
\ No newline at end of file
diff --git a/frontend/src/components/Card.vue b/frontend/src/components/Card.vue
index 9fd488b..a1540f7 100644
--- a/frontend/src/components/Card.vue
+++ b/frontend/src/components/Card.vue
@@ -1,267 +1,299 @@
- DEBUG ON {{$t('minimum_players')}} {{$t('click_to_toggle')}}
+ DEBUG ON
+
+ {{ $t("minimum_players") }}
+ {{ $t("click_to_toggle") }}{{cardName}}
-
- {{ cardName }}
+
+ {{$t('room')}}{{ lobbyName }}
-
- {{$t('room_players', {username:username})}}
- {{$t("expansions")}}
- {{$t('mods')}}
- {{$t('bots')}}
- {this.$socket.emit('chat_message', '/addbot'); e.preventDefault()}"/>
- {this.$socket.emit('chat_message', '/removebot'); e.preventDefault()}"/>
-
-
- {{ $t("room") }}{{ lobbyName }}
+
+ {{ $t("room_players", { username: username }) }}
+ {{ $t("expansions") }}
+ {{ $t("mods") }}
+ {{ $t("bots") }}
+ {
+ this.$socket.emit('chat_message', '/addbot');
+ e.preventDefault();
+ }
+ "
+ />
+ {
+ this.$socket.emit('chat_message', '/removebot');
+ e.preventDefault();
+ }
+ "
+ />
+
+
+