card event refactoring and moved wild-west-show to its own pile

This commit is contained in:
Alberto Xamin 2023-04-02 13:47:08 +01:00
parent f56120d44e
commit e3b168ff57
No known key found for this signature in database
GPG Key ID: 5ABFCD8A22EA6F5D
12 changed files with 3772 additions and 1452 deletions

View File

@ -11,6 +11,7 @@ if TYPE_CHECKING:
from bang.players import Player
from bang.game import Game
class Suit(IntEnum):
DIAMONDS = 0 # ♦
CLUBS = 1 # ♣
@ -20,14 +21,20 @@ class Suit(IntEnum):
class Card(ABC):
sym = {
'A': 1,
'J': 11,
'Q': 12,
'K': 13
}
sym = {"A": 1, "J": 11, "Q": 12, "K": 13}
def __init__(self, suit: Suit, name: str, number, is_equipment: bool = False, is_weapon: bool = False, vis_mod: int = 0, sight_mod: int = 0, range: int = 99, desc: str = ''):
def __init__(
self,
suit: Suit,
name: str,
number,
is_equipment: bool = False,
is_weapon: bool = False,
vis_mod: int = 0,
sight_mod: int = 0,
range: int = 99,
desc: str = "",
):
super().__init__()
self.name = name
self.suit = suit
@ -41,22 +48,24 @@ class Card(ABC):
self.sight_mod = sight_mod
self.range = range
if self.range != 0 and self.range != 99:
self.alt_text = f'{self.range} 🔍'
self.alt_text = f"{self.range} 🔍"
self.desc = desc # deprecated, has been replaced by the card's description in the localization files (see i18n folder)
self.need_target = False # Cards that need a target like Bang
self.can_target_self = False # for example Panico and CatBalou
self.can_be_used_now = True # to check wether the green card can be used now
self.usable_next_turn = False # it will be True for Green Cards
self.need_with = False # it will be true for cards that require a card to be discarded with
self.need_with_only = '' # names of the cards allowed to be discarded with
self.need_with = (
False # it will be true for cards that require a card to be discarded with
)
self.need_with_only = "" # names of the cards allowed to be discarded with
self.must_be_used = False # used by LeggeDelWest
def __str__(self):
if str(self.suit).isnumeric():
char = ['♦️', '♣️', '♥️', '♠️', '🤑'][int(self.suit)]
char = ["♦️", "♣️", "♥️", "♠️", "🤑"][int(self.suit)]
else:
char = self.suit
return f'{self.name} {char}{self.number}'
return f"{self.name} {char}{self.number}"
return super().__str__()
def num_suit(self):
@ -70,8 +79,14 @@ class Card(ABC):
if self.must_be_used:
self.must_be_used = False
def play_card(self, player:Player, against:str=None, _with:int=None):#self --> carta
if (player.game.check_event(ce.IlGiudice)) and self.usable_next_turn and not self.can_be_used_now:
def play_card(
self, player: Player, against: str = None, _with: int = None
): # self --> carta
if (
(player.game.check_event(ce.IlGiudice))
and self.usable_next_turn
and not self.can_be_used_now
):
return False
if self.is_equipment:
if (player.game.check_event(ce.IlGiudice)) or not self.can_be_used_now:
@ -86,38 +101,53 @@ class Card(ABC):
break
if not has_weapon:
player.equipment.append(self)
elif self.name in [c.name for c in player.equipment if not isinstance(c, Dinamite)]:
elif self.name in [
c.name for c in player.equipment if not isinstance(c, Dinamite)
]:
return False
else:
player.equipment.append(self)
self.must_be_used = False
self.can_be_used_now = False
if against:
G.sio.emit('card_against', room=player.game.name, data={'player': player.name, 'target': against, 'card': self.__dict__})
G.sio.emit('chat_message', room=player.game.name,
data=f'_play_card_against|{player.name}|{self.name}|{against}')
G.sio.emit(
"card_against",
room=player.game.name,
data={"player": player.name, "target": against, "card": self.__dict__},
)
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_against|{player.name}|{self.name}|{against}",
)
else:
G.sio.emit('chat_message', room=player.game.name,
data=f'_play_card|{player.name}|{self.name}')
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card|{player.name}|{self.name}",
)
return True
def use_card(self, player):
pass
def is_duplicate_card(self, player:Player):
return any(c.name==self.name for c in player.equipment) or any(c.name==self.name for c in player.gold_rush_equipment)
def is_duplicate_card(self, player: Player):
return any(c.name == self.name for c in player.equipment) or any(
c.name == self.name for c in player.gold_rush_equipment
)
def check_suit(self, game:Game, accepted:List[Suit]):
def check_suit(self, game: Game, accepted: List[Suit]):
if game.check_event(ceh.Benedizione):
return Suit.HEARTS in accepted
elif game.check_event(ceh.Maledizione):
return Suit.SPADES in accepted
return self.suit in accepted
class Barile(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Barile', number, is_equipment=True)
self.icon = '🛢'
super().__init__(suit, "Barile", number, is_equipment=True)
self.icon = "🛢"
self.alt_text = "♥️=😅"
# self.desc = "Quando sei bersagliato da un Bang puoi estrarre la prima carta dalla cima del mazzo, se la carta estratta è del seme Cuori allora vale come un Mancato"
# self.desc_eng = "When someone plays a Bang against you. You can flip the first card from the deck, if the suit is Hearts then it counts as a Missed card"
@ -125,8 +155,8 @@ class Barile(Card):
class Dinamite(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Dinamite', number, is_equipment=True)
self.icon = '🧨'
super().__init__(suit, "Dinamite", number, is_equipment=True)
self.icon = "🧨"
self.alt_text = "2-9♠ = 🤯"
# self.desc = "Giocando la Dinamite, posizionala davanti a te, resterà innocua per un intero giro. All'inizio del prossimo turno prima di pescare e prima di una eventuale estrazione (es. Prigione), estrai una carta dalla cima del mazzo. Se esce una carta tra il 2 il 9 di picche (compresi) allora la dinamite esplode: perdi 3 vite e scarta la carta, altrimenti passa la dinamite al giocatore successivo, il quale estrarà a sua volta dopo che tu avrai passato il tuo turno"
# self.desc_eng = "When playing Dynamite, place it in front of you, it will remain harmless for a whole round. At the beginning of the next turn before drawing and before any card flip (eg Prison), flip a card from the top of the deck. If a card is between 2 and 9 of spades (inclusive) then the dynamite explodes: you lose 3 lives and discard the card, otherwise pass the dynamite to the next player, who will draw in turn after you have ended your turn"
@ -134,8 +164,8 @@ class Dinamite(Card):
class Mirino(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Mirino', number, is_equipment=True, sight_mod=1)
self.icon = '🔎'
super().__init__(suit, "Mirino", number, is_equipment=True, sight_mod=1)
self.icon = "🔎"
self.alt_text = "-1"
# self.desc = "Tu vedi gli altri giocatori a distanza -1"
# self.desc_eng = "You see the other players at distance -1"
@ -143,8 +173,8 @@ class Mirino(Card):
class Mustang(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Mustang', number, is_equipment=True, vis_mod=1)
self.icon = '🐎'
super().__init__(suit, "Mustang", number, is_equipment=True, vis_mod=1)
self.icon = "🐎"
self.alt_text = "+1"
# self.desc = "Gli altri giocatori ti vedono a distanza +1"
# self.desc_eng = "The other players see you at distance +1"
@ -152,100 +182,130 @@ class Mustang(Card):
class Prigione(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Prigione', number, is_equipment=True)
self.icon = ''
super().__init__(suit, "Prigione", number, is_equipment=True)
self.icon = ""
# self.desc = "Equipaggia questa carta a un altro giocatore, tranne lo Sceriffo. Il giocatore scelto all'inizio del suo turno, prima di pescare dovrà estrarre: se esce Cuori scarta questa carta e gioca normalmente il turno, altrimenti scarta questa carta e salta il turno"
# self.desc_eng = "Equip this card to another player, except the Sheriff. The player chosen at the beginning of his turn, must flip a card before drawing: if it's Hearts, discard this card and play the turn normally, otherwise discard this card and skip the turn"
self.need_target = True
self.alt_text = "♥️= 🆓"
def play_card(self, player, against, _with=None):
if (player.game.check_event(ce.IlGiudice)):
if player.game.check_event(ce.IlGiudice):
return False
if against is not None and not isinstance(player.game.get_player_named(against).role, r.Sheriff):
if against is not None and not isinstance(
player.game.get_player_named(against).role, r.Sheriff
):
self.can_be_used_now = False
G.sio.emit('chat_message', room=player.game.name,
data=f'_play_card_against|{player.name}|{self.name}|{against}')
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_against|{player.name}|{self.name}|{against}",
)
player.game.get_player_named(against).equipment.append(self)
player.game.get_player_named(against).notify_self()
return True
return False
class Remington(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Remington', number,
is_equipment=True, is_weapon=True, range=3)
self.icon = '🔫'
super().__init__(
suit, "Remington", number, is_equipment=True, is_weapon=True, range=3
)
self.icon = "🔫"
# self.desc = "Puoi sparare a un giocatore che sia distante 3 o meno"
# self.desc_eng = "You can shoot another player at distance 3 or less"
class RevCarabine(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Rev Carabine', number,
is_equipment=True, is_weapon=True, range=4)
self.icon = '🔫'
super().__init__(
suit, "Rev Carabine", number, is_equipment=True, is_weapon=True, range=4
)
self.icon = "🔫"
# self.desc = "Puoi sparare a un giocatore che sia distante 4 o meno"
# self.desc_eng = "You can shoot another player at distance 4 or less"
class Schofield(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Schofield', number,
is_equipment=True, is_weapon=True, range=2)
self.icon = '🔫'
super().__init__(
suit, "Schofield", number, is_equipment=True, is_weapon=True, range=2
)
self.icon = "🔫"
# self.desc = "Puoi sparare a un giocatore che sia distante 2 o meno"
# self.desc_eng = "You can shoot another player at distance 2 or less"
class Volcanic(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Volcanic', number,
is_equipment=True, is_weapon=True, range=1)
self.icon = '🔫'
super().__init__(
suit, "Volcanic", number, is_equipment=True, is_weapon=True, range=1
)
self.icon = "🔫"
# self.desc = "Puoi sparare a un giocatore che sia distante 1 o meno, tuttavia puoi giocare quanti bang vuoi"
# self.desc_eng = "You can shoot another player at distance 1 or less, however you no longer have the limit of 1 Bang"
class Winchester(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Winchester', number,
is_equipment=True, is_weapon=True, range=5)
self.icon = '🔫'
super().__init__(
suit, "Winchester", number, is_equipment=True, is_weapon=True, range=5
)
self.icon = "🔫"
# self.desc = "Puoi sparare a un giocatore che sia distante 5 o meno"
# self.desc_eng = "You can shoot another player at distance 5 or less"
class Bang(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Bang!', number)
self.icon = '💥'
super().__init__(suit, "Bang!", number)
self.icon = "💥"
# self.desc = "Spara a un giocatore a distanza raggiungibile. Se non hai armi la distanza di default è 1"
# self.desc_eng = "Shoot a player in sight. If you do not have weapons, your is sight is 1"
self.need_target = True
def play_card(self, player, against, _with=None):
if player.game.check_event(ceh.Sermone) and not self.number == 42: # 42 gold rush
if (
player.game.check_event(ceh.Sermone) and not self.number == 42
): # 42 gold rush
return False
if ((player.has_played_bang and not self.number == 42) and (not any((isinstance(c, Volcanic) for c in player.equipment)) or player.game.check_event(ce.Lazo)) and against is not None): # 42 gold rush:
if (
(player.has_played_bang and not self.number == 42)
and (
not any((isinstance(c, Volcanic) for c in player.equipment))
or player.game.check_event(ce.Lazo)
)
and against is not None
): # 42 gold rush:
return False
elif against is not None:
import bang.characters as chars
super().play_card(player, against=against)
if not (self.number == 42 and self.suit == Suit.GOLD): # 42 gold rush
player.bang_used += 1
player.has_played_bang = True if not player.game.check_event(ceh.Sparatoria) else player.bang_used > 1
player.has_played_bang = (
True
if not player.game.check_event(ceh.Sparatoria)
else player.bang_used > 1
)
if player.character.check(player.game, chars.WillyTheKid):
player.has_played_bang = False
player.game.attack(player, against, double=player.character.check(player.game, chars.SlabTheKiller), card_name=self.name)
player.game.attack(
player,
against,
double=player.character.check(player.game, chars.SlabTheKiller),
card_name=self.name,
)
return True
return False
class Birra(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Birra', number)
self.icon = '🍺'
super().__init__(suit, "Birra", number)
self.icon = "🍺"
# self.desc = "Gioca questa carta per recuperare un punto vita. Non puoi andare oltre al limite massimo del tuo personaggio. Se stai per perdere l'ultimo punto vita puoi giocare questa carta anche nel turno dell'avversario. La birra non ha più effetto se ci sono solo due giocatori"
# self.desc_eng = "Play this card to regain a life point. You cannot heal more than your character's maximum limit. If you are about to lose your last life point, you can also play this card on your opponent's turn. Beer no longer takes effect if there are only two players"
@ -254,47 +314,68 @@ class Birra(Card):
return False
if not skipChecks:
import bang.expansions.gold_rush.characters as grch
madamYto = [p for p in player.game.get_alive_players() if p.character.check(player.game, grch.MadamYto) and self.number != 42]
madamYto = [
p
for p in player.game.get_alive_players()
if p.character.check(player.game, grch.MadamYto) and self.number != 42
]
for p in madamYto:
player.game.deck.draw(True, player=p)
p.notify_self()
if 'gold_rush' in player.game.expansions and self.number != 42:
if "gold_rush" in player.game.expansions and self.number != 42:
from bang.players import PendingAction
player.available_cards = [{
'name': 'Pepita',
'icon': '💵️',
'alt_text': '1',
'noDesc': True
}, self]
player.choose_text = 'choose_birra_function'
player.available_cards = [
{"name": "Pepita", "icon": "💵️", "alt_text": "1", "noDesc": True},
self,
]
player.choose_text = "choose_birra_function"
player.pending_action = PendingAction.CHOOSE
player.notify_self()
return True
if (len(player.game.get_alive_players()) != 2 or self.number == 42) and player.lives < player.max_lives:
if (
len(player.game.get_alive_players()) != 2 or self.number == 42
) and player.lives < player.max_lives:
super().play_card(player, against=against)
player.lives = min(player.lives+1, player.max_lives)
player.lives = min(player.lives + 1, player.max_lives)
import bang.expansions.dodge_city.characters as chd
if player.character.check(player.game, chd.TequilaJoe):
player.lives = min(player.lives+1, player.max_lives)
player.lives = min(player.lives + 1, player.max_lives)
return True
elif len(player.game.get_alive_players()) == 2 or player.lives == player.max_lives:
G.sio.emit('chat_message', room=player.game.name,
data=f'_spilled_beer|{player.name}|{self.name}')
elif (
len(player.game.get_alive_players()) == 2
or player.lives == player.max_lives
):
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_spilled_beer|{player.name}|{self.name}",
)
return True
return False
class CatBalou(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Cat Balou', number)
self.icon = '💃'
super().__init__(suit, "Cat Balou", number)
self.icon = "💃"
# self.desc = "Fai scartare una carta a un qualsiasi giocatore, scegli a caso dalla mano, oppure fra quelle che ha in gioco"
# self.desc_eng = "Choose and discard a card from any other player."
self.need_target = True
self.can_target_self = True
def play_card(self, player, against, _with=None):
if against is not None and (len(player.game.get_player_named(against).hand) + len(player.game.get_player_named(against).equipment)) > 0 and (player.name != against or len(player.equipment) > 0):
if (
against is not None
and (
len(player.game.get_player_named(against).hand)
+ len(player.game.get_player_named(against).equipment)
)
> 0
and (player.name != against or len(player.equipment) > 0)
):
super().play_card(player, against=against)
player.game.steal_discard(player, against, self)
return True
@ -303,25 +384,29 @@ class CatBalou(Card):
class Diligenza(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Diligenza', number)
self.icon = '🚡'
super().__init__(suit, "Diligenza", number)
self.icon = "🚡"
self.alt_text = "🎴🎴"
# self.desc = "Pesca 2 carte dalla cima del mazzo"
# self.desc_eng = "Draw 2 cards from the deck."
def play_card(self, player, against, _with=None):
G.sio.emit('chat_message', room=player.game.name,
data=f'_diligenza|{player.name}|{self.name}')
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_diligenza|{player.name}|{self.name}",
)
for i in range(2):
player.game.deck.draw(True, player)
player.game.deck.flip_wildwestshow()
return True
class Duello(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Duello', number)
super().__init__(suit, "Duello", number)
self.need_target = True
self.icon = '⚔️'
self.icon = "⚔️"
# self.desc = "Gioca questa carta contro un qualsiasi giocatore. A turno, cominciando dal tuo avversario, potete scartare una carta Bang!, il primo giocatore che non lo fa perde 1 vita"
# self.desc_eng = "Play this card against any player. In turn, starting with your opponent, you can discard a Bang! Card, the first player who does not do so loses 1 life."
@ -335,8 +420,8 @@ class Duello(Card):
class Emporio(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Emporio', number)
self.icon = '🏪'
super().__init__(suit, "Emporio", number)
self.icon = "🏪"
# self.desc = "Scopri dal mazzo tante carte quanto il numero di giocatori vivi, a turno, partendo da te, scegliete una carta e aggiungetela alla vostra mano"
# self.desc_eng = "Put on the table N cards from the deck, where N is the number of alive players, in turn, starting with you, choose a card and add it to your hand"
@ -348,8 +433,8 @@ class Emporio(Card):
class Gatling(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Gatling', number)
self.icon = '🛰'
super().__init__(suit, "Gatling", number)
self.icon = "🛰"
# self.desc = "Spara a tutti gli altri giocatori"
# self.desc_eng = "Shoot all the other players"
self.alt_text = "👥💥"
@ -362,8 +447,8 @@ class Gatling(Card):
class Indiani(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Indiani!', number)
self.icon = '🏹'
super().__init__(suit, "Indiani!", number)
self.icon = "🏹"
# self.desc = "Tutti gli altri giocatori devono scartare un Bang! o perdere una vita"
# self.desc_eng = "All the other players must discard a Bang! or lose 1 Health Point"
@ -375,22 +460,35 @@ class Indiani(Card):
class Mancato(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Mancato!', number)
self.icon = '😅'
super().__init__(suit, "Mancato!", number)
self.icon = "😅"
# self.desc = "Usa questa carta per annullare un bang"
# self.desc_eng = "Use this card to cancel the effect of a bang"
def play_card(self, player, against, _with=None):
import bang.characters as chars
if against is not None and player.character.check(player.game, chars.CalamityJanet):
if player.has_played_bang and (not any((isinstance(c, Volcanic) for c in player.equipment)) or player.game.check_event(ce.Lazo)):
if against is not None and player.character.check(
player.game, chars.CalamityJanet
):
if player.has_played_bang and (
not any((isinstance(c, Volcanic) for c in player.equipment))
or player.game.check_event(ce.Lazo)
):
return False
if player.game.check_event(ceh.Sermone):
return False
G.sio.emit('chat_message', room=player.game.name,
data=f'_special_calamity|{player.name}|{self.name}|{against}')
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_special_calamity|{player.name}|{self.name}|{against}",
)
player.bang_used += 1
player.has_played_bang = True if not player.game.check_event(ceh.Sparatoria) else player.bang_used > 1
player.has_played_bang = (
True
if not player.game.check_event(ceh.Sparatoria)
else player.bang_used > 1
)
player.game.attack(player, against, card_name=self.name)
return True
return False
@ -398,15 +496,23 @@ class Mancato(Card):
class Panico(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Panico!', number, range=1)
self.icon = '😱'
super().__init__(suit, "Panico!", number, range=1)
self.icon = "😱"
self.need_target = True
self.can_target_self = True
# self.desc = "Pesca una carta da un giocatore a distanza 1, scegli a caso dalla mano, oppure fra quelle che ha in gioco"
# self.desc_eng = "Steal a card from a player at distance 1"
def play_card(self, player, against, _with=None):
if against is not None and (len(player.game.get_player_named(against).hand) + len(player.game.get_player_named(against).equipment)) > 0 and (player.name != against or len(player.equipment) > 0):
if (
against is not None
and (
len(player.game.get_player_named(against).hand)
+ len(player.game.get_player_named(against).equipment)
)
> 0
and (player.name != against or len(player.equipment) > 0)
):
super().play_card(player, against=against)
player.game.steal_discard(player, against, self)
return True
@ -415,58 +521,66 @@ class Panico(Card):
class Saloon(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Saloon', number)
super().__init__(suit, "Saloon", number)
# self.desc = "Tutti i giocatori recuperano un punto vita compreso chi gioca la carta"
# self.desc_eng = "Everyone heals 1 Health point"
self.icon = '🍻'
self.icon = "🍻"
self.alt_text = "👥🍺"
def play_card(self, player, against, _with=None):
G.sio.emit('chat_message', room=player.game.name,
data=f'_saloon|{player.name}|{self.name}')
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_saloon|{player.name}|{self.name}",
)
for p in player.game.get_alive_players():
p.lives = min(p.lives+1, p.max_lives)
p.lives = min(p.lives + 1, p.max_lives)
p.notify_self()
return True
class WellsFargo(Card):
def __init__(self, suit, number):
super().__init__(suit, 'WellsFargo', number)
super().__init__(suit, "WellsFargo", number)
# self.desc = "Pesca 3 carte dalla cima del mazzo"
# self.desc_eng = "Draw 3 cards from the deck"
self.icon = '💸'
self.icon = "💸"
self.alt_text = "🎴🎴🎴"
def play_card(self, player, against, _with=None):
G.sio.emit('chat_message', room=player.game.name,
data=f'_wellsfargo|{player.name}|{self.name}')
for i in range(3):
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_wellsfargo|{player.name}|{self.name}",
)
for _ in range(3):
player.game.deck.draw(True, player)
player.game.deck.flip_wildwestshow()
return True
def get_starting_deck(expansions:List[str]) -> List[Card]:
def get_starting_deck(expansions: List[str]) -> List[Card]:
from bang.expansions import DodgeCity, TheValleyOfShadows
base_cards = [
Barile(Suit.SPADES, 'Q'),
Barile(Suit.SPADES, 'K'),
Barile(Suit.SPADES, "Q"),
Barile(Suit.SPADES, "K"),
Dinamite(Suit.HEARTS, 2),
Mirino(Suit.SPADES, 'A'),
Mirino(Suit.SPADES, "A"),
Mustang(Suit.HEARTS, 8),
Mustang(Suit.HEARTS, 9),
Prigione(Suit.SPADES, 'J'),
Prigione(Suit.SPADES, "J"),
Prigione(Suit.HEARTS, 4),
Prigione(Suit.SPADES, 10),
Remington(Suit.CLUBS, 'K'),
RevCarabine(Suit.CLUBS, 'A'),
Schofield(Suit.CLUBS, 'J'),
Schofield(Suit.CLUBS, 'Q'),
Schofield(Suit.SPADES, 'K'),
Remington(Suit.CLUBS, "K"),
RevCarabine(Suit.CLUBS, "A"),
Schofield(Suit.CLUBS, "J"),
Schofield(Suit.CLUBS, "Q"),
Schofield(Suit.SPADES, "K"),
Volcanic(Suit.SPADES, 10),
Volcanic(Suit.CLUBS, 10),
Winchester(Suit.SPADES, 8),
Bang(Suit.SPADES, 'A'),
Bang(Suit.SPADES, "A"),
Bang(Suit.DIAMONDS, 2),
Bang(Suit.DIAMONDS, 3),
Bang(Suit.DIAMONDS, 4),
@ -476,10 +590,10 @@ def get_starting_deck(expansions:List[str]) -> List[Card]:
Bang(Suit.DIAMONDS, 8),
Bang(Suit.DIAMONDS, 9),
Bang(Suit.DIAMONDS, 10),
Bang(Suit.DIAMONDS, 'J'),
Bang(Suit.DIAMONDS, 'Q'),
Bang(Suit.DIAMONDS, 'K'),
Bang(Suit.DIAMONDS, 'A'),
Bang(Suit.DIAMONDS, "J"),
Bang(Suit.DIAMONDS, "Q"),
Bang(Suit.DIAMONDS, "K"),
Bang(Suit.DIAMONDS, "A"),
Bang(Suit.CLUBS, 2),
Bang(Suit.CLUBS, 3),
Bang(Suit.CLUBS, 4),
@ -488,34 +602,34 @@ def get_starting_deck(expansions:List[str]) -> List[Card]:
Bang(Suit.CLUBS, 7),
Bang(Suit.CLUBS, 8),
Bang(Suit.CLUBS, 9),
Bang(Suit.HEARTS, 'Q'),
Bang(Suit.HEARTS, 'K'),
Bang(Suit.HEARTS, 'A'),
Bang(Suit.HEARTS, "Q"),
Bang(Suit.HEARTS, "K"),
Bang(Suit.HEARTS, "A"),
Birra(Suit.HEARTS, 6),
Birra(Suit.HEARTS, 7),
Birra(Suit.HEARTS, 8),
Birra(Suit.HEARTS, 9),
Birra(Suit.HEARTS, 10),
Birra(Suit.HEARTS, 'J'),
CatBalou(Suit.HEARTS, 'K'),
Birra(Suit.HEARTS, "J"),
CatBalou(Suit.HEARTS, "K"),
CatBalou(Suit.DIAMONDS, 9),
CatBalou(Suit.DIAMONDS, 10),
CatBalou(Suit.DIAMONDS, 'J'),
CatBalou(Suit.DIAMONDS, "J"),
Diligenza(Suit.SPADES, 9),
Diligenza(Suit.SPADES, 9),
Duello(Suit.DIAMONDS, 'Q'),
Duello(Suit.SPADES, 'J'),
Duello(Suit.DIAMONDS, "Q"),
Duello(Suit.SPADES, "J"),
Duello(Suit.CLUBS, 8),
Emporio(Suit.CLUBS, 9),
Emporio(Suit.SPADES, 'Q'),
Emporio(Suit.SPADES, "Q"),
Gatling(Suit.HEARTS, 10),
Indiani(Suit.DIAMONDS, 'K'),
Indiani(Suit.DIAMONDS, 'A'),
Indiani(Suit.DIAMONDS, "K"),
Indiani(Suit.DIAMONDS, "A"),
Mancato(Suit.CLUBS, 10),
Mancato(Suit.CLUBS, 'J'),
Mancato(Suit.CLUBS, 'Q'),
Mancato(Suit.CLUBS, 'K'),
Mancato(Suit.CLUBS, 'A'),
Mancato(Suit.CLUBS, "J"),
Mancato(Suit.CLUBS, "Q"),
Mancato(Suit.CLUBS, "K"),
Mancato(Suit.CLUBS, "A"),
Mancato(Suit.SPADES, 2),
Mancato(Suit.SPADES, 3),
Mancato(Suit.SPADES, 4),
@ -523,16 +637,15 @@ def get_starting_deck(expansions:List[str]) -> List[Card]:
Mancato(Suit.SPADES, 6),
Mancato(Suit.SPADES, 7),
Mancato(Suit.SPADES, 8),
Panico(Suit.HEARTS, 'J'),
Panico(Suit.HEARTS, 'Q'),
Panico(Suit.HEARTS, 'A'),
Panico(Suit.HEARTS, "J"),
Panico(Suit.HEARTS, "Q"),
Panico(Suit.HEARTS, "A"),
Panico(Suit.DIAMONDS, 8),
Saloon(Suit.HEARTS, 5),
WellsFargo(Suit.HEARTS, 3),
]
if 'dodge_city' in expansions:
if "dodge_city" in expansions:
base_cards.extend(DodgeCity.get_cards())
if 'the_valley_of_shadows' in expansions:
if "the_valley_of_shadows" in expansions:
base_cards.extend(TheValleyOfShadows.get_cards())
return base_cards

View File

@ -10,8 +10,12 @@ from globals import G
if TYPE_CHECKING:
from bang.game import Game
class Deck:
def __init__(self, game: 'Game'):
"""Class that handles all deck dealing information"""
def __init__(self, game: "Game"):
super().__init__()
self.cards: List[cs.Card] = cs.get_starting_deck(game.expansions)
self.mancato_cards: List[str] = []
@ -30,45 +34,74 @@ class Deck:
self.all_cards_str.append(c.name)
self.game = game
self.event_cards: List[ce.CardEvent] = []
self.event_cards_wildwestshow: List[ce.CardEvent] = []
endgame_cards: List[ce.CardEvent] = []
if 'fistful_of_cards' in game.expansions:
if "fistful_of_cards" in game.expansions:
self.event_cards.extend(ce.get_all_events(game.rng))
endgame_cards.append(ce.get_endgame_card())
if 'high_noon' in game.expansions:
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 "wild_west_show" in game.expansions:
self.event_cards_wildwestshow.extend(cew.get_all_events(game.rng))
game.rng.shuffle(self.event_cards_wildwestshow)
self.event_cards_wildwestshow.insert(0, None)
self.event_cards_wildwestshow.append(cew.get_endgame_card())
if len(self.event_cards) > 0:
game.rng.shuffle(self.event_cards)
self.event_cards = self.event_cards[:12]
self.event_cards.insert(0, None)
self.event_cards.insert(0, None) # 2 perchè iniziale, e primo flip dallo sceriffo
self.event_cards.insert(
0, None
) # 2 perchè iniziale, e primo flip dallo sceriffo
self.event_cards.append(game.rng.choice(endgame_cards))
game.rng.shuffle(self.cards)
self.shop_deck: List[grc.ShopCard] = []
self.shop_cards: List[grc.ShopCard] = []
if 'gold_rush' in game.expansions:
if "gold_rush" in game.expansions:
self.shop_cards = [None, None, None]
self.shop_deck = grc.get_cards()
game.rng.shuffle(self.shop_deck)
self.fill_gold_rush_shop()
self.scrap_pile: List[cs.Card] = []
print(f'Deck initialized with {len(self.cards)} cards')
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) or isinstance(self.event_cards[0], cew.WildWestShow)):
"""Flip event for regular Sheriff turn (High Noon, Fistful of Cards)"""
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))
if len(self.event_cards) > 0 and self.event_cards[0] is not None:
self.event_cards[0].on_flipped(self.game)
self.game.notify_event_card()
self.game.notify_all()
def flip_wildwestshow(self):
"""Flip event for Wild West Show only"""
if len(self.event_cards_wildwestshow) > 0 and not isinstance(
self.event_cards_wildwestshow[0], cew.WildWestShow
):
self.event_cards_wildwestshow.append(self.event_cards_wildwestshow.pop(0))
if (
len(self.event_cards_wildwestshow) > 0
and self.event_cards_wildwestshow[0] is not None
):
self.event_cards_wildwestshow[0].on_flipped(self.game)
self.game.notify_event_card_wildwestshow()
self.game.notify_all()
def fill_gold_rush_shop(self):
"""
As gold_rush shop cards are stored in a fixed 3 space array,
this function replaces the None values with new cards.
"""
if not any((c is None for c in self.shop_cards)):
return
for i in range(3):
if self.shop_cards[i] is None:
print(f'replacing gr-card {i}')
print(f"replacing gr-card {i}")
self.shop_cards[i] = self.shop_deck.pop(0)
self.shop_cards[i].reset_card()
self.game.notify_gold_rush_shop()
@ -102,14 +135,22 @@ class Deck:
def put_on_top(self, card: cs.Card):
self.cards.insert(0, card)
def draw(self, ignore_event = False, player=None) -> cs.Card:
if self.game.check_event(ce.MinieraAbbandonata) and len(self.scrap_pile) > 0 and not ignore_event:
def draw(self, ignore_event=False, player=None) -> cs.Card:
if (
self.game.check_event(ce.MinieraAbbandonata)
and len(self.scrap_pile) > 0
and not ignore_event
):
return self.draw_from_scrap_pile()
card = self.cards.pop(0)
if len(self.cards) == 0:
self.reshuffle()
if player is not None and self.game.replay_speed > 0:
G.sio.emit('card_drawn', room=self.game.name, data={'player': player.name, 'pile': 'deck'})
G.sio.emit(
"card_drawn",
room=self.game.name,
data={"player": player.name, "pile": "deck"},
)
player.hand.append(card)
return card
@ -127,15 +168,24 @@ class Deck:
else:
return self.draw()
def scrap(self, card: cs.Card, ignore_event = False, player=None):
if card.number == 42: return
def scrap(self, card: cs.Card, ignore_event=False, player=None):
if card.number == 42:
return
card.reset_card()
if self.game.check_event(ce.MinieraAbbandonata) and not ignore_event:
self.put_on_top(card)
else:
self.scrap_pile.append(card)
if player is not None and self.game.replay_speed > 0:
G.sio.emit('card_scrapped', room=self.game.name, data={'player': player.name, 'card':card.__dict__, 'pile': 'scrap'})
G.sio.emit(
"card_scrapped",
room=self.game.name,
data={
"player": player.name,
"card": card.__dict__,
"pile": "scrap",
},
)
G.sio.sleep(0.6)
self.game.notify_scrap_pile()
else:

View File

@ -1,106 +1,198 @@
from abc import ABC, abstractmethod
import random
import bang.players as players
import bang.roles as r
import bang.cards as cs
from globals import G
class CardEvent(ABC):
"""Base class for all event cards"""
def __init__(self, name, icon):
self.name = name
self.icon = icon
def on_flipped(self, game):
"""Default on flipped event
Args:
game (Game): the game object
"""
print(f"{game.name}: flip new event {self.name}")
G.sio.emit(
"chat_message",
room=game.name,
data={
"color": "orange",
"text": f"_flip_event|{self.name}",
},
)
return
def on_clicked(self, game, player):
"""Default on clicked event
Args:
game (Game): the game object
player (Player): the player that clicked the card
"""
print(f"{game.name}: {player.name} clicked event {self.name}")
return
class Agguato(CardEvent):
"""La distanza base tra 2 qualsiasi giocatori è 1
The distance between 2 players is always 1"""
def __init__(self):
super().__init__("Agguato", "🛁")
#self.desc = "La distanza base tra 2 qualsiasi giocatori è 1"
#self.desc_eng = "The base distance from any 2 players is 1"
class Cecchino(CardEvent):
"""Nel proprio turno i giocatori possono
scartare 2 Bang assieme per sparare un bang che necessita 2 mancato (clicca la carta)
In your turn you can discard 2 Bang together to shoot a bang that needs 2 miss (click the card)
"""
def __init__(self):
super().__init__("Cecchino", "👁")
#self.desc = "Nel proprio turno i giocatori possono scartare 2 Bang assieme per sparare un bang che necessita 2 mancato (clicca la carta)"
#self.desc_eng = "During their turn, players can discard 2 Bang! to shoot a bang that requires 2 missed (click the card)"
class DeadMan(CardEvent):
"""Al proprio turno il giocatore che è morto per primo torna in vita con 2 vite e 2 carte
The first player that died returns back to life with 2 hp and 2 cards"""
def __init__(self):
super().__init__("Dead Man", "⚰️")
#self.desc = "Al proprio turno il giocatore che è morto per primo torna in vita con 2 vite e 2 carte"
#self.desc_eng = "The first player that died returns back to life with 2 hp and 2 cards"
def on_flipped(self, game):
game.did_resuscitate_deadman = False
return super().on_flipped(game)
class FratelliDiSangue(CardEvent):
"""All'inizio del proprio turno, i giocatori possono perdere 1 vita (tranne l'ultimo) per darla a un altro giocatore"""
def __init__(self):
super().__init__("Fratelli Di Sangue", "💉")
#self.desc = "All'inizio del proprio turno, i giocatori possono perdere 1 vita (tranne l'ultimo) per darla a un altro giocatore"
#self.desc_eng = "At the begin of their turn, payers can lose 1 hp (except the last one) to give it to another player"
class IlGiudice(CardEvent):
"""Non si possono equipaggiare carte a se stessi o agli altri"""
def __init__(self):
super().__init__("Il Giudice", "👨‍⚖️")
#self.desc = "Non si possono equipaggiare carte a se stessi o agli altri"
#self.desc_eng = "You can't equip cards on your or other players"
class Lazo(CardEvent):
"""Le carte equipaggiate non hanno effetto"""
def __init__(self):
super().__init__("Lazo", "📿")
#self.desc = "Le carte equipaggiate non hanno effetto"
#self.desc_eng = "Cards in the equipment slot do not work"
class LeggeDelWest(CardEvent):
"""I giocatori mostrano la seconda carta che pescano e sono obbligati a usarla in quel turno (se possibile)"""
def __init__(self):
super().__init__("Legge Del West", "⚖️")
#self.desc = "I giocatori mostrano la seconda carta che pescano e sono obbligati a usarla in quel turno (se possibile)"
#self.desc_eng = "Every player shows the second card that they draw and must use it in that round (if it is possible)"
class LiquoreForte(CardEvent):
"""I giocatori possono evitare di pescare per recuperare 1 vita (clicca sulla carta evento per farlo)"""
def __init__(self):
super().__init__("Liquore Forte", "🥃")
#self.desc = "I giocatori possono evitare di pescare per recuperare 1 vita (clicca sulla carta evento per farlo)"
#self.desc_eng = "Players can skip drawing to regain 1 HP (click on the event card to use)"
class MinieraAbbandonata(CardEvent):
"""I giocatori pescano dagli scarti nella loro fase 1 e scartano in cima al mazzo nella loro fase 3 (se gli scarti finiscono, è necessario pescare e scartare in cima al mazzo)"""
def __init__(self):
super().__init__("Miniera Abbandonata", "")
#self.desc = "I giocatori pescano dagli scarti nella loro fase 1 e scartano in cima al mazzo nella loro fase 3 (se gli scarti finiscono, è necessario pescare e scartare in cima al mazzo)"
#self.desc_eng = "Players draw from the discarded pile in their phase 1 and discard to the top of the deck during phase 3 (if the discaded pile runs out, they must draw and discard on top of the deck)"
class PerUnPugnoDiCarte(CardEvent):
"""All'inizio del proprio turno, il giocatore subisce tanti bang quante carte ha in mano"""
def __init__(self):
super().__init__("Per Un Pugno Di Carte", "🎴")
#self.desc = "All'inizio del proprio turno, il giocatore subisce tanti bang quante carte ha in mano"
#self.desc_eng = "On the beginning of his turn, the player is target of as many Bang as how many cards he has in his hand"
class Peyote(CardEvent):
"""Invece che pescare il giocatore prova a indovinare il colore del seme, se lo indovina aggiunge la carta alla mano e continua provando ad indovinare la carta successiva"""
def __init__(self):
super().__init__("Peyote", "🌵")
#self.desc = "Invece che pescare il giocatore prova a indovinare il colore del seme, se lo indovina aggiunge la carta alla mano e continua provando ad indovinare la carta successiva"
#self.desc_eng = "Instead of drawing, the player tries to guess the color of the suit, if he's right he adds the card to the hand and continues trying to guess the next card"
class Ranch(CardEvent):
"""Dopo aver pescato il giocatore può scartare quante carte vuole dalla mano e pescarne altrettante dal mazzo"""
def __init__(self):
super().__init__("Ranch", "🐮")
#self.desc = "Dopo aver pescato il giocatore può scartare quante carte vuole dalla mano e pescarne altrettante dal mazzo"
#self.desc_eng = "After drawing, the player can discard as many cards as he wants from his hand and draw as many from the deck"
class Rimbalzo(CardEvent):
"""Il giocatore di turno può giocare bang contro le carte equipaggiate dagli altri giocatori, se non giocano mancato vengono scartate (clicca la carta evento)"""
def __init__(self):
super().__init__("Rimbalzo", "")
#self.desc = "Il giocatore di turno può giocare bang contro le carte equipaggiate dagli altri giocatori, se non giocano mancato vengono scartate (clicca la carta evento)"
#self.desc_eng = "The player can play bang against the cards equipped by the other players, if they do not play miss they are discarded (click the event card)"
def on_clicked(self, game, player):
super().on_clicked(game, player)
if any((c.name == cs.Bang(0, 0).name for c in player.hand)):
player.available_cards = [
{
"name": p.name,
"icon": p.role.icon
if (game.initial_players == 3)
else "⭐️"
if isinstance(p.role, r.Sheriff)
else "🤠",
"is_character": True,
"avatar": p.avatar,
"is_player": True,
}
for p in game.get_alive_players()
if len(p.equipment) > 0 and p != player
]
player.available_cards.append({"icon": "", "noDesc": True})
player.choose_text = "choose_rimbalzo_player"
player.pending_action = players.PendingAction.CHOOSE
player.using_rimbalzo = 1
player.notify_self()
class RouletteRussa(CardEvent):
"""A partire dallo sceriffo, ogni giocatore scarta 1 mancato, il primo che non lo fa perde 2 vite"""
def __init__(self):
super().__init__("Roulette Russa", "🇷🇺")
#self.desc = "A partire dallo sceriffo, ogni giocatore scarta 1 mancato, il primo che non lo fa perde 2 vite"
#self.desc_eng = "Starting from the sheriff, every player discards 1 missed, the first one that doesn't loses 2 HP"
# self.desc_eng = "Starting from the sheriff, every player discards 1 missed, the first one that doesn't loses 2 HP"
class Vendetta(CardEvent):
"""Alla fine del proprio turno il giocatore estrae dal mazzo, se esce ♥️ gioca un altro turno (ma non estrae di nuovo)"""
def __init__(self):
super().__init__("Vendetta", "😤")
#self.desc = "Alla fine del proprio turno il giocatore estrae dal mazzo, se esce ♥️ gioca un altro turno (ma non estrae di nuovo)"
#self.desc_eng = "When ending the turn, the player flips a card from the deck, if it's ♥️ he plays another turn (but he does not flip another card)"
# self.desc_eng = "When ending the turn, the player flips a card from the deck, if it's ♥️ he plays another turn (but he does not flip another card)"
def get_endgame_card():
end_game = PerUnPugnoDiCarte()
end_game.expansion = 'fistful-of-cards'
end_game.expansion = ( # pylint: disable=attribute-defined-outside-init
"fistful-of-cards"
)
return end_game
def get_all_events(rng=random):
cards = [
Agguato(),
@ -119,6 +211,8 @@ def get_all_events(rng=random):
Vendetta(),
]
rng.shuffle(cards)
for c in cards:
c.expansion = 'fistful-of-cards'
for card in cards:
card.expansion = ( # pylint: disable=attribute-defined-outside-init
"fistful-of-cards"
)
return cards

View File

@ -1,101 +1,166 @@
import random
from globals import G
from bang.expansions.fistful_of_cards.card_events import CardEvent
class Benedizione(CardEvent):
"""Tutte le carte sono considerate di cuori ♥️"""
def __init__(self):
super().__init__("Benedizione", "🙏")
#self.desc = "Tutte le carte sono considerate di cuori ♥️"
#self.desc_eng = "All cards are of hearts ♥️"
# self.desc_eng = "All cards are of hearts ♥️"
class Maledizione(CardEvent):
"""Tutte le carte sono considerate di picche ♠"""
def __init__(self):
super().__init__("Maledizione", "🤬")
#self.desc = "Tutte le carte sono considerate di picche ♠"
#self.desc_eng = "All cards are of spades ♠"
# self.desc_eng = "All cards are of spades ♠"
class Sbornia(CardEvent):
"""I personaggi perdono le loro abilità speciali"""
def __init__(self):
super().__init__("Sbornia", "🥴")
#self.desc = "I personaggi perdono le loro abilità speciali"
#self.desc_eng = "The characters lose their special abilities"
# self.desc_eng = "The characters lose their special abilities"
class Sete(CardEvent):
"""I giocatori pescano 1 carta in meno nella loro fase 1"""
def __init__(self):
super().__init__("Sete", "🥵")
#self.desc = "I giocatori pescano 1 carta in meno nella loro fase 1"
#self.desc_eng = "Players only draw 1 card at the start of their turn"
# self.desc_eng = "Players only draw 1 card at the start of their turn"
class IlTreno(CardEvent):
"""I giocatori pescano 1 carta extra nella loro fase 1"""
def __init__(self):
super().__init__("Il Treno", "🚂")
#self.desc = "I giocatori pescano 1 carta extra nella loro fase 1"
#self.desc_eng = "Players draw 1 extra card"
# self.desc_eng = "Players draw 1 extra card"
class IlReverendo(CardEvent):
"""Non si possono giocare le carte Birra"""
def __init__(self):
super().__init__("Il Reverendo", "⛪️")
#self.desc = "Non si possono giocare le carte Birra"
#self.desc_eng = "Beers can't be played"
# self.desc_eng = "Beers can't be played"
class IlDottore(CardEvent):
"""Il/i giocatore/i con meno vite ne recupera/no una"""
def __init__(self):
super().__init__("Il Dottore", "👨‍⚕️")
#self.desc = "Il/i giocatore/i con meno vite ne recupera/no una"
#self.desc_eng = "The player with the least amount of HP gets healed 1"
# self.desc_eng = "The player with the least amount of HP gets healed 1"
def on_flipped(self, game):
super().on_flipped(game)
most_hurt = [
p.lives for p in game.players if p.lives > 0 and p.max_lives > p.lives
]
if len(most_hurt) > 0:
hurt_players = [p for p in game.players if p.lives == min(most_hurt)]
for p in hurt_players:
if p.lives != p.max_lives:
p.lives += 1
G.sio.emit(
"chat_message",
room=game.name,
data=f"_doctor_heal|{p.name}",
)
p.notify_self()
return
class Sermone(CardEvent):
"""I giocatori non possono giocare Bang! durante il loro turno"""
def __init__(self):
super().__init__("Sermone", "✝️")
#self.desc = "I giocatori non possono giocare Bang! durante il loro turno"
#self.desc_eng = "Players can't play Bang! during their turn"
# self.desc_eng = "Players can't play Bang! during their turn"
class Sparatoria(CardEvent):
"""Il limite di Bang! per turno è 2 invece che 1"""
def __init__(self):
super().__init__("Sparatoria", "🔫🔫")
#self.desc = "Il limite di Bang! per turno è 2 invece che 1"
#self.desc_eng = "The turn Bang! limit is 2"
# self.desc_eng = "The turn Bang! limit is 2"
class CorsaAllOro(CardEvent):
"""Si gioca per un intero giro in senso antiorario, tuttavia gli effetti delle carte rimangono invariati"""
def __init__(self):
super().__init__("Corsa All Oro", "🌟")
#self.desc = "Si gioca per un intero giro in senso antiorario, tuttavia gli effetti delle carte rimangono invariati"
#self.desc_eng = "Turns are played counter clockwise"
# self.desc_eng = "Turns are played counter clockwise"
class IDalton(CardEvent):
"""Chi ha carte blu in gioco ne scarta 1 a sua scelta"""
def __init__(self):
super().__init__("I Dalton", "🙇‍♂️")
#self.desc = "Chi ha carte blu in gioco ne scarta 1 a sua scelta"
#self.desc_eng = "Players that have blue cards equipped, discard 1 of those card of their choice"
# self.desc_eng = "Players that have blue cards equipped, discard 1 of those card of their choice"
def on_flipped(self, game):
game.waiting_for = 0
game.ready_count = 0
game.dalton_on = True
for p in game.players:
if p.get_dalton():
game.waiting_for += 1
p.notify_self()
if game.waiting_for != 0:
return
game.dalton_on = False
return super().on_flipped(game)
class Manette(CardEvent):
"""Dopo aver pescato in fase 1, il giocatore di turno dichiara un seme: potrà usare solamente carte di quel seme nel suo turno"""
def __init__(self):
super().__init__("Manette", "🔗")
#self.desc = "Dopo aver pescato in fase 1, il giocatore di turno dichiara un seme: potrà usare solamente carte di quel seme nel suo turno"
#self.desc_eng = "After drawing in phase 1, the player declares a suit. He will be able to use only cards of that suit for that turn"
# self.desc_eng = "After drawing in phase 1, the player declares a suit. He will be able to use only cards of that suit for that turn"
class NuovaIdentita(CardEvent):
"""All'inizio del proprio turno, ogni giocatore potrà decidere se sostituire il suo personaggio attuale con quello era stato proposto ad inizio partita, se lo fa riparte con 2 punti vita"""
def __init__(self):
super().__init__("Nuova Identita", "🕶")
#self.desc = "All'inizio del proprio turno, ogni giocatore potrà decidere se sostituire il suo personaggio attuale con quello era stato proposto ad inizio partita, se lo fa riparte con 2 punti vita"
#self.desc_eng = "At the beginning of their turn, each player can choose to change its character with the other shown at the game start. If he does so he restarts from 2 HP."
# self.desc_eng = "At the beginning of their turn, each player can choose to change its character with the other shown at the game start. If he does so he restarts from 2 HP."
class CittaFantasma(CardEvent):
"""Tutti i giocatori morti tornano in vita al proprio turno, non possono morire e pescano 3 carte invece che 2. Quando terminano il turno tornano morti."""
def __init__(self):
super().__init__("Città Fantasma", "👻")
#self.desc = "Tutti i giocatori morti tornano in vita al proprio turno, non possono morire e pescano 3 carte invece che 2. Quando terminano il turno tornano morti."
#self.desc_eng = "All dead players come back to life in their turn, they can't die and draw 3 cards instead of 2. When they end their turn the die."
# self.desc_eng = "All dead players come back to life in their turn, they can't die and draw 3 cards instead of 2. When they end their turn the die."
class MezzogiornoDiFuoco(CardEvent):
"""Ogni giocatore perde 1 punto vita all'inizio del turno"""
def __init__(self):
super().__init__("Mezzogiorno di Fuoco", "🔥")
#self.desc = "Ogni giocatore perde 1 punto vita all'inizio del turno"
#self.desc_eng = "Every player loses 1 HP when their turn starts"
# self.desc_eng = "Every player loses 1 HP when their turn starts"
def get_endgame_card():
end_game = MezzogiornoDiFuoco()
end_game.expansion = 'high-noon'
end_game.expansion = "high-noon" # pylint: disable=attribute-defined-outside-init
return end_game
def get_all_events(rng=random):
cards = [
Benedizione(),
@ -115,5 +180,5 @@ def get_all_events(rng=random):
]
rng.shuffle(cards)
for c in cards:
c.expansion = 'high-noon'
c.expansion = "high-noon" # pylint: disable=attribute-defined-outside-init
return cards

View File

@ -1,4 +1,8 @@
import random
import bang.cards as cs
import bang.roles as roles
import bang.players as players
from globals import G
from bang.expansions.fistful_of_cards.card_events import CardEvent
@ -9,74 +13,143 @@ from bang.expansions.fistful_of_cards.card_events import CardEvent
# # 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", "💞")
def on_flipped(self, game):
c = game.deck.pick_and_scrap()
G.sio.emit(
"chat_message",
room=game.name,
data=f"_flipped|Helena Zontero|{c.name}|{c.num_suit()}",
)
if c.check_suit(game, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]):
G.sio.emit(
"chat_message",
room=game.name,
data=f"_swapped_roles|Helena Zontero|{c.name}|{c.num_suit()}",
)
pls = [p for p in game.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)))
return super().on_flipped(game)
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", "🩰")
def on_clicked(self, game, player):
super().on_clicked(game, player)
nextp = game.next_player()
i, j = game.players_map[player.name], game.players_map[nextp.name]
game.players[i], game.players[j] = nextp, player
game.players_map[player.name], game.players_map[nextp.name] = j, i
game.turn = j
game.notify_all()
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", "🤠")
def on_clicked(self, game, player):
super().on_clicked(game, player)
if len(player.hand) > 0:
if not player.has_played_bang and any(
(
player.get_sight() >= p["dist"]
for p in game.get_visible_players(player)
)
):
player.available_cards = player.hand.copy()
player.pending_action = players.PendingAction.CHOOSE
player.choose_text = "choose_play_as_bang"
player.notify_self()
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 on_flipped(self, game):
for player in game.players:
player.set_role(roles.Renegade())
return super().on_flipped(game)
def get_endgame_card():
end_game = WildWestShow()
end_game.expansion = 'wild-west-show'
end_game.expansion = "wild-west-show"
return end_game
def get_all_events(rng=random):
cards = [
Camposanto(),
@ -90,5 +163,5 @@ def get_all_events(rng=random):
]
rng.shuffle(cards)
for c in cards:
c.expansion = 'wild-west-show'
c.expansion = "wild-west-show"
return cards

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,18 +4,21 @@ from bang.game import Game
from bang.players import Player
from tests.dummy_socket import DummySocket
from globals import G
G.sio = DummySocket()
def started_game(expansions, players=4, character=Character('test_char', 4)):
g = Game('test')
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)]
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]
if "high_noon" in expansions:
p.available_characters.append(Character("test_char2", 4))
p.set_character(p.available_characters[0].name)
return g
@ -34,6 +37,6 @@ def next_player(g: Game):
def current_player_with_cards(g: Game, cards):
p = current_player(g)
p.draw('')
p.draw("")
p.hand = cards
return p

View File

@ -3,16 +3,17 @@ from bang.game import Game
from bang.players import Player, PendingAction
from bang.roles import *
from bang.cards import *
from tests import started_game
# test that game can start
def test_game_start():
g = Game('test')
p1 = Player('p1', 'p1')
g = Game("test")
p1 = Player("p1", "p1")
g.add_player(p1)
p2 = Player('p2', 'p2')
p2 = Player("p2", "p2")
g.add_player(p2)
p3 = Player('p3', 'p3')
p3 = Player("p3", "p3")
g.add_player(p3)
assert p1.role is None
assert p2.role is None
@ -34,57 +35,68 @@ def test_game_start():
assert p3.character is not None
assert g.players[g.turn].pending_action == PendingAction.DRAW
# test that dodge_city is added to games with more than 8 players
def test_dodge_city():
g = Game('test')
g = Game("test")
for i in range(9):
p = Player(f'p{i}', f'p{i}')
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
assert 'dodge_city' in g.expansions
assert "dodge_city" in g.expansions
# test that a game with 2 players has only renegade as role
def test_renegade_only():
g = Game('test')
p1 = Player('p1', 'p1')
g = Game("test")
p1 = Player("p1", "p1")
g.add_player(p1)
p2 = Player('p2', 'p2')
p2 = Player("p2", "p2")
g.add_player(p2)
g.start_game()
assert isinstance(g.players[0].role, Renegade)
assert isinstance(g.players[1].role, Renegade)
# test that a game with 3 player has Renegade, Vice and Outlaw as roles
def test_renegade_vice_outlaw():
g = Game('test')
g = Game("test")
for i in range(3):
p = Player(f'p{i}', f'p{i}')
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
g.start_game()
roles = {p.role.name for p in g.players}
assert len(roles) == 3
# test that a game with 4 players has all roles except the deputy
def test_4_players_roles():
g = Game('test')
g = Game("test")
for i in range(4):
p = Player(f'p{i}', f'p{i}')
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
g.start_game()
roles = {p.role.name for p in g.players}
assert len(roles) == 3
# test that a game with 5 players has all roles
def test_5_players_roles():
g = Game('test')
g = Game("test")
for i in range(5):
p = Player(f'p{i}', f'p{i}')
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
g.start_game()
roles = {p.role.name for p in g.players}
assert len(roles) == 4
def test_expansions():
started_game(
[
"high_noon",
"dodge_city",
"gold_rush",
"the_valley_of_shadows",
"wild_west_show",
]
)

View File

@ -14,7 +14,12 @@
<div v-if="eventCard" style="position:relative">
<div class="card fistful-of-cards" style="position:relative; bottom:-3pt;right:-3pt;"/>
<div class="card fistful-of-cards" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
<card :card="eventCard" :key="eventCard.name" :class="eventClasses" @click.native="event"/>
<card :card="eventCard" :key="eventCard.name" :class="eventClasses" @click.native="() => event('event')"/>
</div>
<div v-if="eventCardWildWestShow" style="position:relative">
<div class="card wild-west-show back" style="position:relative; bottom:-3pt;right:-3pt;"/>
<div class="card wild-west-show back" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
<card :card="eventCardWildWestShow" :key="eventCardWildWestShow.name" :class="eventWwsClasses" @click.native="() => event('event_wildwestshow')"/>
</div>
<div style="position:relative" class="deck">
<div style="position:relative" id="actual-deck">
@ -34,6 +39,9 @@
<transition name="list">
<p v-if="eventCard" class="center-stuff"><b>{{eventDesc}}</b></p>
</transition>
<transition name="list">
<p v-if="eventCardWildWestShow && !eventCardWildWestShow.back" class="center-stuff">🎪 <b>{{eventDescWildWestShow}}</b> 🎪</p>
</transition>
<transition name="list">
<div v-if="goldRushDesc">
<p class="center-stuff">🤑 <i>{{$t(`cards.${goldRushDesc.name}.desc`)}}</i> 🤑</p>
@ -71,6 +79,7 @@ export default {
},
lastScrap: null,
eventCard: null,
eventCardWildWestShow: null,
previousScrap: null,
pending_action: false,
isPlaying: true,
@ -102,6 +111,14 @@ export default {
expansion: 'fistful-of-cards',
} : card
},
event_card_wildwestshow(card) {
this.eventCardWildWestShow = card == false ? {
name: 'Wild West Show',
icon: '🎪',
back: true,
expansion: 'wild-west-show',
} : card
},
gold_rush_shop(cards) {
console.log('GOLD RUSH:'+ cards)
this.goldRushCards = JSON.parse(cards)
@ -122,13 +139,28 @@ export default {
classes[this.eventCard.expansion] = true
return classes
},
eventWwsClasses() {
let classes = {
'last-event':true,
'back':this.eventCardWildWestShow.back,
'wild-west-show':true,
}
return classes
},
eventDesc() {
this.eventCard;
if (this.eventCard.name !== 'PewPew!'){
return this.$t(`cards.${this.eventCard.name}.desc`)
}
return ""
},
eventDescWildWestShow() {
this.eventCardWildWestShow;
if (this.eventCardWildWestShow.name !== 'PewPew!'){
return this.$t(`cards.${this.eventCardWildWestShow.name}.desc`)
}
return ""
},
},
methods: {
action(pile) {
@ -143,9 +175,9 @@ export default {
buy_gold_rush_card(index) {
this.$socket.emit('buy_gold_rush_card', index)
},
event() {
event(pile='event') {
if (this.pending_action !== false) {
this.$socket.emit('draw', 'event')
this.$socket.emit('draw', pile)
}
},
setdesc() {

View File

@ -66,6 +66,7 @@
:donotlocalize="true"
:card="startGameCard"
@click.native="startGame"
style="margin-top: 36pt;"
/>
<div
v-for="p in playersTable"
@ -126,7 +127,7 @@
@click.native="drawFromPlayer(p.name)"
:ismyturn="p.pending_action === 2"
/>
<span style="position: absolute; top: 10pt" class="center-stuff">{{
<span style="position: absolute; top: 3pt" class="center-stuff">{{
getActionEmoji(p)
}}</span>
<div class="tiny-equipment">
@ -186,6 +187,7 @@
:donotlocalize="true"
:card="shufflePlayersCard"
@click.native="shufflePlayers"
style="margin-top: 36pt;"
class="fistful-of-cards"
/>
</transition-group>
@ -803,13 +805,18 @@ export default {
right: -35pt;
transform: scale(0.45);
transform-origin: 50% 0%;
top: 10pt;
top: 4pt;
}
.tiny-health {
display: flex;
justify-content: space-evenly;
transform: scale(0.8);
margin-bottom: -4pt;
margin-top: -16pt;
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
}
.tiny-equipment .card {
transform: rotate(2deg);
@ -823,7 +830,7 @@ export default {
}
.tiny-character {
position: absolute;
transform: scale(0.5) translate(-80px, -40px);
transform: translate(-30pt, -30pt) scale(0.5);
top: 0;
}
.players-table {
@ -888,6 +895,7 @@ export default {
}
.player-in-table {
transition: all 0.2s ease-in-out;
margin-top: 26pt;
}
.player-in-table:hover {
transform: translateY(-5px) scale(1.05);