From dd2b7be1997e07871bf8b211a86b8abe0bd82bac Mon Sep 17 00:00:00 2001 From: Annalisa Xamin <82664061+annalisaxamin@users.noreply.github.com> Date: Sat, 4 Feb 2023 19:30:19 +0000 Subject: [PATCH 01/17] add cards definitions --- .../expansions/wild_west_show/card_events.py | 77 +++++++++++++++++++ .../expansions/wild_west_show/characters.py | 66 ++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 backend/bang/expansions/wild_west_show/card_events.py create mode 100644 backend/bang/expansions/wild_west_show/characters.py 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..4fe6a1d --- /dev/null +++ b/backend/bang/expansions/wild_west_show/card_events.py @@ -0,0 +1,77 @@ +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): + def __init__(self): + super().__init__("Camposanto", "⚰") + # 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. + +class DarlingValentine(CardEvent): + def __init__(self): + super().__init__("Darling Valentine", "💋") + # All'inizio del proprio turno, ogni giocatore scarta le carte in mano e ne pesca dal mazzo altrettante. + +class DorothyRage(CardEvent): + def __init__(self): + super().__init__("Dorothy Rage", "👩‍⚖️") + # Nel proprio turno, ogni giocatore può obbligarne un altro a giocare una carta. + +class HelenaZontero(CardEvent): + def __init__(self): + super().__init__("Helena Zontero", "💞") + # Quando Helena entra in gioco, "estrai!": se esce Cuori o Quadri, rimescola i ruoli attivi tranne lo Sceriffo, e ridistribuiscili a caso. + +class LadyRosaDelTexas(CardEvent): + def __init__(self): + super().__init__("Lady Rosa del Texas", "🩰") + # Nel proprio turno, ogni giocatore può scambiarsi di posto con quello alla sua destra, il quale salta il prossimo turno. + +class MissSusanna(CardEvent): + def __init__(self): + super().__init__("Miss Susanna", "👩‍🎤") + # Nel proprio turno ogni giocatore deve giocare almeno 3 carte. Se non lo fa, perde 1 punto vita. + +class RegolamentoDiConti(CardEvent): + def __init__(self): + super().__init__("Regolamento di conti", "🤠") + # Tutte le carte possono essere giocate come se fossero BANG!. Le carte BANG! come se fossero Mancato! + +class Sacagaway(CardEvent): + def __init__(self): + super().__init__("Sacagaway", "🏇") + # Tutti i giocatori giocano a carte scoperte (tranne il ruolo!). + +class WildWestShow(CardEvent): + def __init__(self): + super().__init__("Wild West Show", "🎪") + # L'obiettivo di ogni giocatore diventa: "Rimani l'ultimo in gioco!" + +def get_endgame_card(): + end_game = WildWestShow() + end_game.expansion = 'wild-west-show' + return end_game + +def get_all_events(rng=random): + cards = [ + Bavaglio(), + 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..2ec8e77 --- /dev/null +++ b/backend/bang/expansions/wild_west_show/characters.py @@ -0,0 +1,66 @@ +from typing import List +from bang.characters import Character + +class BigSpencer(Character): + def __init__(self): + super().__init__("Big Spencer", max_lives=9) + # Inizia con 5 carte. Non può giocare Mancato! + self.icon = '🫘' + +class FlintWestwood(Character): + def __init__(self): + super().__init__("Flint Westwood", max_lives=4) + # Nel suo turno può scambiare una carta dalla mano con 2 carte a caso dalla mano di un altro giocatore. + self.icon = '🔫' + +class GaryLooter(Character): + def __init__(self): + super().__init__("Gary Looter", max_lives=5) + # Pesca tutte le carte in eccesso scartate dagli altri giocatori a fine turno. + self.icon = '🥲' + +class GreygoryDeckard(Character): + def __init__(self): + super().__init__("Greygory Deckard", max_lives=4) + # All'inizio del suo turno può pescare 2 personaggi a caso. Ha tutte le abilità dei personaggi pescati. + self.icon = '👨‍🦳' + +class JohnPain(Character): + def __init__(self): + super().__init__("John Pain", max_lives=4) + # Se ha meno di 6 carte in mano, quando un giocatore "estrae!" John aggiunge alla mano la carta appena estratta. + self.icon = '🤕' + +class LeeVanKliff(Character): + def __init__(self): + super().__init__("Lee Van Kliff", max_lives=4) + # Nel suo turno, può scartare un BANG! per ripetere l'effetto di una carta a bordo marrone che ha appena giocato. + self.icon = '👨‍🦲' + +class TerenKill(Character): + def __init__(self): + super().__init__("Teren Kill", max_lives=3) + # Ogni volta che sarebbe eliminato "estrai!": se non è Picche, Teren resta a 1 punto vita e pesca 1 carta. + self.icon = '👨‍🦰' + +class YoulGrinner(Character): + def __init__(self): + super().__init__("Youl Grinner", max_lives=4) + # Prima di pescare, i giocatori con più carte in mano di lui devono dargli una carta a scelta. + 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 From 035fbb161da3a413e1ff7b7d9eff5fc680b1ef09 Mon Sep 17 00:00:00 2001 From: Annalisa Xamin <82664061+annalisaxamin@users.noreply.github.com> Date: Sat, 4 Feb 2023 20:19:37 +0000 Subject: [PATCH 02/17] register expansion --- backend/bang/characters.py | 2 ++ backend/bang/expansions/__init__.py | 5 +++++ 2 files changed, 7 insertions(+) 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/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 From d1bd07c717f7b75355e6aef8ed290e3e44cdd592 Mon Sep 17 00:00:00 2001 From: Annalisa Xamin <82664061+annalisaxamin@users.noreply.github.com> Date: Sat, 4 Feb 2023 20:19:44 +0000 Subject: [PATCH 03/17] add Flint Westwood --- .../expansions/wild_west_show/characters.py | 25 +++++++++++++------ backend/bang/players.py | 23 ++++++++++++++++- frontend/src/components/Player.vue | 1 + 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/backend/bang/expansions/wild_west_show/characters.py b/backend/bang/expansions/wild_west_show/characters.py index 2ec8e77..73c94a9 100644 --- a/backend/bang/expansions/wild_west_show/characters.py +++ b/backend/bang/expansions/wild_west_show/characters.py @@ -11,8 +11,19 @@ class FlintWestwood(Character): def __init__(self): super().__init__("Flint Westwood", max_lives=4) # 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. 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): def __init__(self): super().__init__("Gary Looter", max_lives=5) @@ -51,14 +62,14 @@ class YoulGrinner(Character): def all_characters() -> List[Character]: cards = [ - BigSpencer(), + # BigSpencer(), FlintWestwood(), - GaryLooter(), - GreygoryDeckard(), - JohnPain(), - LeeVanKliff(), - TerenKill(), - YoulGrinner(), + # GaryLooter(), + # GreygoryDeckard(), + # JohnPain(), + # LeeVanKliff(), + # TerenKill(), + # YoulGrinner(), ] for c in cards: c.expansion_icon = '🎪' diff --git a/backend/bang/players.py b/backend/bang/players.py index 8bcd78f..034be39 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 @@ -918,6 +918,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 diff --git a/frontend/src/components/Player.vue b/frontend/src/components/Player.vue index 685616e..c7ea4d7 100644 --- a/frontend/src/components/Player.vue +++ b/frontend/src/components/Player.vue @@ -36,6 +36,7 @@ +
{{$t('hand')}} From e68be77edc8d75a19c9fc1adffce5e16c20b42f1 Mon Sep 17 00:00:00 2001 From: Annalisa Xamin <82664061+annalisaxamin@users.noreply.github.com> Date: Sat, 4 Feb 2023 20:28:19 +0000 Subject: [PATCH 04/17] add choose_flint_special localization --- frontend/src/i18n/en.json | 1 + frontend/src/i18n/it.json | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 3bd627c..2cb0f2c 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -77,6 +77,7 @@ "choose_ricercato": "Choose who you will play Wanted against.", "choose_birra_function": "Choose between getting 1 gold nugget by discarding beer or if you want to play the beer.", "choose_play_as_bang": "Choose which card to play as Bang!", + "choose_flint_special": "Choose which card to swap.", "emporio_others": "{0} is choosing which card to get from the General Store", "you_died": "YOU DIED", "spectate": "SPECTATE", diff --git a/frontend/src/i18n/it.json b/frontend/src/i18n/it.json index afca9c4..4222422 100644 --- a/frontend/src/i18n/it.json +++ b/frontend/src/i18n/it.json @@ -77,6 +77,7 @@ "choose_ricercato": "Scegli il giocatore su cui vuoi giocare Ricercato", "choose_birra_function": "Scegli tra ottenere 1 pepita scartando la birra oppure giocare la birra.", "choose_play_as_bang": "Scegli che carta giocare come Bang!", + "choose_flint_special": "Scegli che carta scabiare.", "emporio_others": "{0} sta scegliendo che carta prendere dall'emporio", "you_died": "SEI MORTO", "spectate": "SPETTATORE", From d089cce48dd40ae03ad2cadfd564ee8a149eca0a Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 26 Feb 2023 16:14:56 +0000 Subject: [PATCH 05/17] add camposanto DarlingValentine --- backend/bang/deck.py | 4 ++++ .../expansions/wild_west_show/card_events.py | 1 - backend/bang/game.py | 21 ++++++++++++++----- backend/bang/players.py | 6 ++++++ backend/server.py | 3 +++ frontend/src/components/Card.vue | 6 +++++- frontend/src/components/Lobby.vue | 2 +- frontend/src/utils/emoji-map.js | 1 + frontend/src/utils/expansions-map.js | 6 ++++++ 9 files changed, 42 insertions(+), 8 deletions(-) diff --git a/backend/bang/deck.py b/backend/bang/deck.py index 5c49d6d..cf6aedf 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -3,6 +3,7 @@ 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.gold_rush.shop_cards as grc from globals import G @@ -33,6 +34,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] diff --git a/backend/bang/expansions/wild_west_show/card_events.py b/backend/bang/expansions/wild_west_show/card_events.py index 4fe6a1d..bc98b5c 100644 --- a/backend/bang/expansions/wild_west_show/card_events.py +++ b/backend/bang/expansions/wild_west_show/card_events.py @@ -61,7 +61,6 @@ def get_endgame_card(): def get_all_events(rng=random): cards = [ - Bavaglio(), Camposanto(), DarlingValentine(), DorothyRage(), diff --git a/backend/bang/game.py b/backend/bang/game.py index 82f006e..816add5 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: @@ -762,6 +769,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 +853,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() diff --git a/backend/bang/players.py b/backend/bang/players.py index 034be39..509edf2 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -11,6 +11,7 @@ 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.gold_rush.shop_cards as grc import bang.expansions.gold_rush.characters as grch import bang.expansions.the_valley_of_shadows.cards as tvosc @@ -453,6 +454,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)): 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/frontend/src/components/Card.vue b/frontend/src/components/Card.vue index 9fd488b..fc83483 100644 --- a/frontend/src/components/Card.vue +++ b/frontend/src/components/Card.vue @@ -118,6 +118,10 @@ export default { box-shadow: 0 0 0 3pt #6aa16e, 0 0 0 6pt var(--bg-color), 0 0 5pt 6pt #aaa } +.card.wild-west-show{ + box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa; + border: 2pt dotted #987e51; +} .card.high-noon{ box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa; border: 2pt dotted rgb(198 78 45); @@ -230,7 +234,7 @@ export default { .card.must-be-used { filter: drop-shadow(0 0 5px red); } -.fistful-of-cards .emoji, .high-noon .emoji, .exp-pack .emoji{ +.fistful-of-cards .emoji, .high-noon .emoji, .card.wild-west-show .emoji, .exp-pack .emoji{ top:auto !important; bottom:15% !important; } diff --git a/frontend/src/components/Lobby.vue b/frontend/src/components/Lobby.vue index b9e06b1..15fd7a7 100644 --- a/frontend/src/components/Lobby.vue +++ b/frontend/src/components/Lobby.vue @@ -159,7 +159,7 @@ Card password: '', togglable_expansions: [], expansions: [], - beta_expansions: ['the_valley_of_shadows'], + beta_expansions: ['the_valley_of_shadows', 'wild_west_show'], hasToSetUsername: false, is_competitive: false, disconnect_bot: false, diff --git a/frontend/src/utils/emoji-map.js b/frontend/src/utils/emoji-map.js index 7e61c32..ec5ec17 100644 --- a/frontend/src/utils/emoji-map.js +++ b/frontend/src/utils/emoji-map.js @@ -4,4 +4,5 @@ export const emojiMap = { 'high_noon': '🔥', 'fistful_of_cards': '🎴', 'the_valley_of_shadows': '👻', + 'wild_west_show': '🎪', } \ No newline at end of file diff --git a/frontend/src/utils/expansions-map.js b/frontend/src/utils/expansions-map.js index ae6a70e..b8b61d8 100644 --- a/frontend/src/utils/expansions-map.js +++ b/frontend/src/utils/expansions-map.js @@ -28,5 +28,11 @@ export const expansionsMap = { icon: '👻', back: true, expansion: 'the-valley-of-shadows', + }, + 'wild_west_show': { + name: 'Wild West Show', + icon: '🎪', + back: true, + expansion: 'wild-west-show' } } \ No newline at end of file From c4d23b6e379799336a684509536f241c931f361e Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 26 Feb 2023 16:39:36 +0000 Subject: [PATCH 06/17] add elena zontero --- .../expansions/wild_west_show/card_events.py | 46 +++++++++++++------ backend/bang/game.py | 9 ++++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/backend/bang/expansions/wild_west_show/card_events.py b/backend/bang/expansions/wild_west_show/card_events.py index bc98b5c..59009d8 100644 --- a/backend/bang/expansions/wild_west_show/card_events.py +++ b/backend/bang/expansions/wild_west_show/card_events.py @@ -3,56 +3,74 @@ 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 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", "⚰") - # 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. 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", "💋") - # All'inizio del proprio turno, ogni giocatore scarta le carte in mano e ne pesca dal mazzo altrettante. class DorothyRage(CardEvent): + """ + Nel proprio turno, ogni giocatore può obbligarne un altro a giocare una carta. + """ def __init__(self): super().__init__("Dorothy Rage", "👩‍⚖️") - # Nel proprio turno, ogni giocatore può obbligarne un altro a giocare una carta. 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", "💞") - # Quando Helena entra in gioco, "estrai!": se esce Cuori o Quadri, rimescola i ruoli attivi tranne lo Sceriffo, e ridistribuiscili a caso. 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", "🩰") - # Nel proprio turno, ogni giocatore può scambiarsi di posto con quello alla sua destra, il quale salta il prossimo turno. 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", "👩‍🎤") - # Nel proprio turno ogni giocatore deve giocare almeno 3 carte. Se non lo fa, perde 1 punto vita. 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", "🤠") - # Tutte le carte possono essere giocate come se fossero BANG!. Le carte BANG! come se fossero Mancato! class Sacagaway(CardEvent): + """ + Tutti i giocatori giocano a carte scoperte (tranne il ruolo!). + """ def __init__(self): super().__init__("Sacagaway", "🏇") - # Tutti i giocatori giocano a carte scoperte (tranne il ruolo!). class WildWestShow(CardEvent): + """ + L'obiettivo di ogni giocatore diventa: "Rimani l'ultimo in gioco!" + """ def __init__(self): super().__init__("Wild West Show", "🎪") - # L'obiettivo di ogni giocatore diventa: "Rimani l'ultimo in gioco!" def get_endgame_card(): end_game = WildWestShow() diff --git a/backend/bang/game.py b/backend/bang/game.py index 816add5..9032ddf 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -610,6 +610,15 @@ 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]): + 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()) if self.check_event(ce.DeadMan): self.did_resuscitate_deadman = False elif self.check_event(ce.RouletteRussa): From 5811821ccf3f45d657fbf9a26b81aabb434a8df8 Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 16:49:52 +0000 Subject: [PATCH 07/17] add tests --- .../expansions/wild_west_show/card_events.py | 2 +- backend/bang/game.py | 3 +- backend/tests/__init__.py | 38 +++++++++++- backend/tests/wild_west_show_events_test.py | 60 +++++++++++++++++++ 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 backend/tests/wild_west_show_events_test.py diff --git a/backend/bang/expansions/wild_west_show/card_events.py b/backend/bang/expansions/wild_west_show/card_events.py index 59009d8..016bdba 100644 --- a/backend/bang/expansions/wild_west_show/card_events.py +++ b/backend/bang/expansions/wild_west_show/card_events.py @@ -63,7 +63,7 @@ class Sacagaway(CardEvent): Tutti i giocatori giocano a carte scoperte (tranne il ruolo!). """ def __init__(self): - super().__init__("Sacagaway", "🏇") + super().__init__("Sacagaway", "🌄") class WildWestShow(CardEvent): """ diff --git a/backend/bang/game.py b/backend/bang/game.py index 9032ddf..452aa50 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -614,7 +614,8 @@ class Game: 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]): - pls = (p for p in self.players if not isinstance(p.role, roles.Sheriff)) + 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: diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py index e79239c..fe53a50 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): + 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('test_char', 4)] + 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 \ No newline at end of file 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..3a7e4ba --- /dev/null +++ b/backend/tests/wild_west_show_events_test.py @@ -0,0 +1,60 @@ + +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 + + +# 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'], 6) + set_events(g, [None, HelenaZontero()]) + roles = [p.role.name for p in g.players] + for i in range(5): + 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 + +# test MissSusanna + +# test RegolamentoDiConti + +# test Sacagaway + +# test WildWestShow \ No newline at end of file From 9598a6f81196b4b60baa519bdd080ec9ddb8218a Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 17:14:52 +0000 Subject: [PATCH 08/17] add Sacagaway --- backend/bang/deck.py | 7 ++++-- .../expansions/wild_west_show/card_events.py | 8 +++--- backend/bang/game.py | 2 ++ frontend/src/components/Lobby.vue | 4 +-- frontend/src/components/TinyHand.vue | 25 ++++++++++++++----- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/backend/bang/deck.py b/backend/bang/deck.py index cf6aedf..547d55c 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -1,4 +1,4 @@ -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 @@ -7,8 +7,10 @@ import bang.expansions.wild_west_show.card_events as cew 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] = [] @@ -58,6 +60,7 @@ class Deck: if len(self.event_cards) > 0 and not (isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte) or isinstance(self.event_cards[0], ceh.MezzogiornoDiFuoco)): 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)): diff --git a/backend/bang/expansions/wild_west_show/card_events.py b/backend/bang/expansions/wild_west_show/card_events.py index 016bdba..678bf08 100644 --- a/backend/bang/expansions/wild_west_show/card_events.py +++ b/backend/bang/expansions/wild_west_show/card_events.py @@ -81,11 +81,11 @@ def get_all_events(rng=random): cards = [ Camposanto(), DarlingValentine(), - DorothyRage(), + # DorothyRage(), HelenaZontero(), - LadyRosaDelTexas(), - MissSusanna(), - RegolamentoDiConti(), + # LadyRosaDelTexas(), + # MissSusanna(), + # RegolamentoDiConti(), Sacagaway(), ] rng.shuffle(cards) diff --git a/backend/bang/game.py b/backend/bang/game.py index 452aa50..a317370 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -895,9 +895,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/frontend/src/components/Lobby.vue b/frontend/src/components/Lobby.vue index 15fd7a7..230f7fb 100644 --- a/frontend/src/components/Lobby.vue +++ b/frontend/src/components/Lobby.vue @@ -38,7 +38,7 @@ - + {{getActionEmoji(p)}}
({ username: '', diff --git a/frontend/src/components/TinyHand.vue b/frontend/src/components/TinyHand.vue index f3c3c90..860f8f9 100644 --- a/frontend/src/components/TinyHand.vue +++ b/frontend/src/components/TinyHand.vue @@ -1,20 +1,30 @@ @@ -24,6 +34,9 @@ export default { animation-duration: 2s; animation-iteration-count: infinite; } +.zoomable:hover { + z-index: 1; +} @keyframes updown { 0% { top: 0; From 9f5d03743786d6665ea80b828f7f3195b4570f90 Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 17:31:23 +0000 Subject: [PATCH 09/17] add miss suzanna --- backend/bang/game.py | 2 +- backend/bang/players.py | 7 +++++++ backend/tests/wild_west_show_events_test.py | 20 ++++++++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/backend/bang/game.py b/backend/bang/game.py index a317370..deedb1c 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -619,7 +619,7 @@ class Game: newroles = [p.role for p in pls] random.shuffle(newroles) for p in pls: - p.set_role(newroles.pop()) + 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): diff --git a/backend/bang/players.py b/backend/bang/players.py index 509edf2..623f7eb 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -89,6 +89,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))] @@ -440,6 +441,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 @@ -762,6 +764,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) @@ -1563,6 +1567,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/tests/wild_west_show_events_test.py b/backend/tests/wild_west_show_events_test.py index 3a7e4ba..f4f0763 100644 --- a/backend/tests/wild_west_show_events_test.py +++ b/backend/tests/wild_west_show_events_test.py @@ -39,10 +39,10 @@ def test_darling_valentine(): # test HelenaZontero def test_helena_zontero(): - g = started_game(['wild_west_show'], 6) + 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(5): + 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() @@ -52,6 +52,22 @@ def test_helena_zontero(): # test LadyRosaDelTexas # 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 From 47a099d6248ab3d54ae0db7249f1b84aae272bff Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 17:46:26 +0000 Subject: [PATCH 10/17] add wild-west-show endgame --- backend/bang/deck.py | 2 +- backend/bang/game.py | 3 +++ backend/tests/wild_west_show_events_test.py | 10 +++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/bang/deck.py b/backend/bang/deck.py index 547d55c..2b77836 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -57,7 +57,7 @@ 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() diff --git a/backend/bang/game.py b/backend/bang/game.py index deedb1c..6c1d739 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -649,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) diff --git a/backend/tests/wild_west_show_events_test.py b/backend/tests/wild_west_show_events_test.py index f4f0763..6279d91 100644 --- a/backend/tests/wild_west_show_events_test.py +++ b/backend/tests/wild_west_show_events_test.py @@ -3,6 +3,7 @@ from tests import started_game, set_events, current_player, next_player, current from bang.expansions.wild_west_show.card_events import * from bang.cards import Card, Suit +import bang.roles as roles # test Camposanto @@ -73,4 +74,11 @@ def test_miss_suzanna(): # test Sacagaway -# test WildWestShow \ No newline at end of file +# test WildWestShow +def test_miss_suzanna(): + 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 From e33c2c3c48d72af2c404f4720802ed04472ee200 Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 17:57:31 +0000 Subject: [PATCH 11/17] add RegolamentoDiConti --- backend/bang/expansions/wild_west_show/card_events.py | 6 +++--- backend/bang/players.py | 6 ++++++ backend/tests/wild_west_show_events_test.py | 10 +++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/backend/bang/expansions/wild_west_show/card_events.py b/backend/bang/expansions/wild_west_show/card_events.py index 678bf08..6a72611 100644 --- a/backend/bang/expansions/wild_west_show/card_events.py +++ b/backend/bang/expansions/wild_west_show/card_events.py @@ -56,7 +56,7 @@ 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", "🤠") + super().__init__("Regolamento di Conti", "🤠") class Sacagaway(CardEvent): """ @@ -84,8 +84,8 @@ def get_all_events(rng=random): # DorothyRage(), HelenaZontero(), # LadyRosaDelTexas(), - # MissSusanna(), - # RegolamentoDiConti(), + MissSusanna(), + RegolamentoDiConti(), Sacagaway(), ] rng.shuffle(cards) diff --git a/backend/bang/players.py b/backend/bang/players.py index 623f7eb..3565a86 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -519,6 +519,12 @@ 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(ce.Rimbalzo) and any((c.name == cs.Bang(0,0).name for c in self.hand)): self.available_cards = [{ 'name': p.name, diff --git a/backend/tests/wild_west_show_events_test.py b/backend/tests/wild_west_show_events_test.py index 6279d91..a8813d1 100644 --- a/backend/tests/wild_west_show_events_test.py +++ b/backend/tests/wild_west_show_events_test.py @@ -4,6 +4,7 @@ from tests import started_game, set_events, current_player, next_player, current from bang.expansions.wild_west_show.card_events import * from bang.cards import Card, Suit import bang.roles as roles +from bang.players import PendingAction # test Camposanto @@ -52,6 +53,7 @@ def test_helena_zontero(): # test LadyRosaDelTexas + # test MissSusanna def test_miss_suzanna(): g = started_game(['wild_west_show'], 4) @@ -71,8 +73,14 @@ def test_miss_suzanna(): # test RegolamentoDiConti +def test_miss_suzanna(): + g = started_game(['wild_west_show'], 4) + set_events(g, [None, 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 Sacagaway # test WildWestShow def test_miss_suzanna(): From 3b568c987f9c27765038021f09161a2d0d46da2f Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 18:08:01 +0000 Subject: [PATCH 12/17] add Lady Rosa del Texas --- backend/bang/expansions/wild_west_show/card_events.py | 2 +- backend/bang/players.py | 7 +++++++ backend/tests/wild_west_show_events_test.py | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/bang/expansions/wild_west_show/card_events.py b/backend/bang/expansions/wild_west_show/card_events.py index 6a72611..666f1e1 100644 --- a/backend/bang/expansions/wild_west_show/card_events.py +++ b/backend/bang/expansions/wild_west_show/card_events.py @@ -83,7 +83,7 @@ def get_all_events(rng=random): DarlingValentine(), # DorothyRage(), HelenaZontero(), - # LadyRosaDelTexas(), + LadyRosaDelTexas(), MissSusanna(), RegolamentoDiConti(), Sacagaway(), diff --git a/backend/bang/players.py b/backend/bang/players.py index 3565a86..81c483d 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -525,6 +525,13 @@ class Player: 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, diff --git a/backend/tests/wild_west_show_events_test.py b/backend/tests/wild_west_show_events_test.py index a8813d1..505d460 100644 --- a/backend/tests/wild_west_show_events_test.py +++ b/backend/tests/wild_west_show_events_test.py @@ -52,7 +52,13 @@ def test_helena_zontero(): assert roles != roles2 # test LadyRosaDelTexas - +def test_miss_suzanna(): + g = started_game(['wild_west_show'], 4) + set_events(g, [None, LadyRosaDelTexas()]) + p = current_player_with_cards(g, [Card(0,'card',0)]*4) + t = g.turn + p.draw('event') + assert g.turn == t+1 # test MissSusanna def test_miss_suzanna(): From 8f645cf82aed48b2d258e29e65a5030db3753837 Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 18:22:15 +0000 Subject: [PATCH 13/17] add bigspencer --- .../expansions/wild_west_show/characters.py | 38 +++++++++++++------ backend/bang/players.py | 7 ++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/backend/bang/expansions/wild_west_show/characters.py b/backend/bang/expansions/wild_west_show/characters.py index 73c94a9..3f76558 100644 --- a/backend/bang/expansions/wild_west_show/characters.py +++ b/backend/bang/expansions/wild_west_show/characters.py @@ -2,16 +2,20 @@ 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) - # Inizia con 5 carte. Non può giocare Mancato! 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) - # 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. self.icon = '🔫' def special(self, player, data): @@ -25,44 +29,56 @@ class FlintWestwood(Character): 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) - # Pesca tutte le carte in eccesso scartate dagli altri giocatori a fine turno. 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) - # All'inizio del suo turno può pescare 2 personaggi a caso. Ha tutte le abilità dei personaggi pescati. - self.icon = '👨‍🦳' + 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) - # Se ha meno di 6 carte in mano, quando un giocatore "estrae!" John aggiunge alla mano la carta appena estratta. 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) - # Nel suo turno, può scartare un BANG! per ripetere l'effetto di una carta a bordo marrone che ha appena giocato. 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) - # Ogni volta che sarebbe eliminato "estrai!": se non è Picche, Teren resta a 1 punto vita e pesca 1 carta. 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) - # Prima di pescare, i giocatori con più carte in mano di lui devono dargli una carta a scelta. self.icon = '🤡' def all_characters() -> List[Character]: cards = [ - # BigSpencer(), + BigSpencer(), FlintWestwood(), # GaryLooter(), # GreygoryDeckard(), diff --git a/backend/bang/players.py b/backend/bang/players.py index 81c483d..d7ed5c9 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -12,6 +12,7 @@ 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 @@ -1132,6 +1133,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(chw.BigSpencer): + self.expected_response = [] self.on_failed_response_cb = self.take_damage_response self.notify_self() @@ -1162,6 +1165,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(chw.BigSpencer): + self.expected_response = [] self.on_failed_response_cb = self.take_no_damage_response self.notify_self() @@ -1258,6 +1263,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(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: From 499763ffca317c86de0a3d95e068d6253116e19d Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 18:30:05 +0000 Subject: [PATCH 14/17] add john pain --- backend/bang/deck.py | 12 +++++++++++- backend/bang/expansions/wild_west_show/characters.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/bang/deck.py b/backend/bang/deck.py index 2b77836..4dd4f2b 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -4,6 +4,7 @@ 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 @@ -83,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 isinstance(p, 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/wild_west_show/characters.py b/backend/bang/expansions/wild_west_show/characters.py index 3f76558..a7a1737 100644 --- a/backend/bang/expansions/wild_west_show/characters.py +++ b/backend/bang/expansions/wild_west_show/characters.py @@ -82,7 +82,7 @@ def all_characters() -> List[Character]: FlintWestwood(), # GaryLooter(), # GreygoryDeckard(), - # JohnPain(), + JohnPain(), # LeeVanKliff(), # TerenKill(), # YoulGrinner(), From e4f9cfc886e3733453a2e91734defe3d55e5cf26 Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 5 Mar 2023 19:49:34 +0000 Subject: [PATCH 15/17] fix johnpain, bigspencer and tests --- backend/bang/deck.py | 2 +- backend/bang/players.py | 6 +++--- backend/tests/wild_west_show_events_test.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/bang/deck.py b/backend/bang/deck.py index 4dd4f2b..0cd14ab 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -86,7 +86,7 @@ class Deck: card = self.cards.pop(0) jpain = None for p in self.game.players: - if isinstance(p, chw.JohnPain) and len(p.hand) < 6: + if p.character.check(self.game, chw.JohnPain) and len(p.hand) < 6: jpain = p break if jpain: diff --git a/backend/bang/players.py b/backend/bang/players.py index d7ed5c9..8907a58 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -1133,7 +1133,7 @@ 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(chw.BigSpencer): + if self.character.check(self.game, chw.BigSpencer): self.expected_response = [] self.on_failed_response_cb = self.take_damage_response self.notify_self() @@ -1165,7 +1165,7 @@ 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(chw.BigSpencer): + if self.character.check(self.game, chw.BigSpencer): self.expected_response = [] self.on_failed_response_cb = self.take_no_damage_response self.notify_self() @@ -1263,7 +1263,7 @@ 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(chw.BigSpencer): + 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) diff --git a/backend/tests/wild_west_show_events_test.py b/backend/tests/wild_west_show_events_test.py index 505d460..2f46b7d 100644 --- a/backend/tests/wild_west_show_events_test.py +++ b/backend/tests/wild_west_show_events_test.py @@ -52,9 +52,9 @@ def test_helena_zontero(): assert roles != roles2 # test LadyRosaDelTexas -def test_miss_suzanna(): +def test_LadyRosaDelTexas(): g = started_game(['wild_west_show'], 4) - set_events(g, [None, LadyRosaDelTexas()]) + set_events(g, [LadyRosaDelTexas()]) p = current_player_with_cards(g, [Card(0,'card',0)]*4) t = g.turn p.draw('event') @@ -79,9 +79,9 @@ def test_miss_suzanna(): # test RegolamentoDiConti -def test_miss_suzanna(): +def test_RegolamentoDiConti(): g = started_game(['wild_west_show'], 4) - set_events(g, [None, RegolamentoDiConti()]) + set_events(g, [RegolamentoDiConti()]) p = current_player_with_cards(g, [Card(0,'card',0)]*4) p.draw('event') assert p.pending_action == PendingAction.CHOOSE @@ -89,7 +89,7 @@ def test_miss_suzanna(): # test WildWestShow -def test_miss_suzanna(): +def test_WildWestShow(): g = started_game(['wild_west_show'], 8) set_events(g, [None, WildWestShow()]) for i in range(len(g.players)): From d3a056cc6aa7ae29a9695fece64400ebc14a5b02 Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sat, 11 Mar 2023 22:51:34 +0200 Subject: [PATCH 16/17] add terenkill and youlgrinner --- .../expansions/wild_west_show/characters.py | 4 +-- backend/bang/players.py | 12 +++++++ backend/tests/__init__.py | 4 +-- .../tests/wild_west_show_characters_test.py | 34 +++++++++++++++++++ backend/tests/wild_west_show_events_test.py | 2 +- 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 backend/tests/wild_west_show_characters_test.py diff --git a/backend/bang/expansions/wild_west_show/characters.py b/backend/bang/expansions/wild_west_show/characters.py index a7a1737..b8d28d1 100644 --- a/backend/bang/expansions/wild_west_show/characters.py +++ b/backend/bang/expansions/wild_west_show/characters.py @@ -84,8 +84,8 @@ def all_characters() -> List[Character]: # GreygoryDeckard(), JohnPain(), # LeeVanKliff(), - # TerenKill(), - # YoulGrinner(), + TerenKill(), + YoulGrinner(), ] for c in cards: c.expansion_icon = '🎪' diff --git a/backend/bang/players.py b/backend/bang/players.py index 8907a58..afd5a43 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -265,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 @@ -573,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 diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py index fe53a50..dfa24b0 100644 --- a/backend/tests/__init__.py +++ b/backend/tests/__init__.py @@ -7,7 +7,7 @@ from globals import G G.sio = DummySocket() -def started_game(expansions, players=4): +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)] @@ -15,7 +15,7 @@ def started_game(expansions, players=4): g.add_player(p) g.start_game() for p in ps: - p.available_characters = [Character('test_char', 4)] + p.available_characters = [character] p.set_character(p.available_characters[0].name) return g 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 index 2f46b7d..4a85cbc 100644 --- a/backend/tests/wild_west_show_events_test.py +++ b/backend/tests/wild_west_show_events_test.py @@ -58,7 +58,7 @@ def test_LadyRosaDelTexas(): p = current_player_with_cards(g, [Card(0,'card',0)]*4) t = g.turn p.draw('event') - assert g.turn == t+1 + assert g.turn == (t+1)%len(g.players) # test MissSusanna def test_miss_suzanna(): From 555e48a10a613072f4f240785c975422f50067dd Mon Sep 17 00:00:00 2001 From: Alberto Xamin Date: Sun, 12 Mar 2023 11:44:09 +0000 Subject: [PATCH 17/17] allow for alpha status --- backend/tests/__init__.py | 2 +- frontend/src/components/Card.vue | 450 ++++---- frontend/src/components/Lobby.vue | 1432 +++++++++++++++----------- frontend/src/utils/expansions-map.js | 8 +- 4 files changed, 1098 insertions(+), 794 deletions(-) diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py index dfa24b0..02a3aa6 100644 --- a/backend/tests/__init__.py +++ b/backend/tests/__init__.py @@ -36,4 +36,4 @@ def current_player_with_cards(g: Game, cards): p = current_player(g) p.draw('') p.hand = cards - return p \ No newline at end of file + return p diff --git a/frontend/src/components/Card.vue b/frontend/src/components/Card.vue index fc83483..a1540f7 100644 --- a/frontend/src/components/Card.vue +++ b/frontend/src/components/Card.vue @@ -1,271 +1,299 @@ \ No newline at end of file diff --git a/frontend/src/components/Lobby.vue b/frontend/src/components/Lobby.vue index 230f7fb..61eab6b 100644 --- a/frontend/src/components/Lobby.vue +++ b/frontend/src/components/Lobby.vue @@ -1,622 +1,896 @@ diff --git a/frontend/src/utils/expansions-map.js b/frontend/src/utils/expansions-map.js index b8b61d8..0b3ccc5 100644 --- a/frontend/src/utils/expansions-map.js +++ b/frontend/src/utils/expansions-map.js @@ -1,11 +1,11 @@ export const expansionsMap = { - 'dodge_city' : { + 'dodge_city': { name: 'Dodge City', icon: '🐄', back: true, expansion: 'dodge-city', }, - 'fistful_of_cards' : { + 'fistful_of_cards': { name: 'Fistful of Cards', icon: '🎴', back: true, @@ -28,11 +28,13 @@ export const expansionsMap = { icon: '👻', back: true, expansion: 'the-valley-of-shadows', + status: 'beta', }, 'wild_west_show': { name: 'Wild West Show', icon: '🎪', back: true, - expansion: 'wild-west-show' + expansion: 'wild-west-show', + status: 'alpha', } } \ No newline at end of file