diff --git a/.gitignore b/.gitignore index 6e83984..566ac6e 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,5 @@ cython_debug/ frontend/package-lock.json bang-workspace.code-workspace + +.vscode/ diff --git a/backend/__init__.py b/backend/__init__.py index 82c5a01..b49947d 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -8,6 +8,9 @@ import socketio from bang.game import Game from bang.players import Player +import sys +sys.setrecursionlimit(10**6) # this should prevents bots from stopping + sio = socketio.Server(cors_allowed_origins="*") static_files={ '/': {'content_type': 'text/html', 'filename': 'index.html'}, @@ -205,6 +208,8 @@ def chat_message(sid, msg): ses.game.reset() elif '/startgame' in msg and not ses.game.started: ses.game.start_game() + elif '/setbotspeed' in msg: + ses.game.bot_speed = float(msg.split()[1]) elif '/addex' in msg and not ses.game.started: cmd = msg.split() if len(cmd) == 2: @@ -214,10 +219,39 @@ def chat_message(sid, msg): ses.game.notify_room() else: sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'}) + elif '/setcharacter' in msg: + sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and changed character'}) + import bang.characters as characters + cmd = msg.split() + if len(cmd) >= 2: + chs = characters.all_characters(ses.game.expansions) + ses.character = [c for c in chs if c.name == ' '.join(cmd[1:])][0] + ses.real_character = ses.character + ses.notify_self() + elif '/removecard' in msg: + sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and removed a card'}) + cmd = msg.split() + if len(cmd) == 2: + if len(ses.hand) > int(cmd[1]): + ses.hand.pop(int(cmd[1])) + else: + ses.hand.pop(int(cmd[1])-len(ses.hand)) + ses.notify_self() + elif '/getcard' in msg: + sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got a card'}) + import bang.cards as cs + cmd = msg.split() + if len(cmd) >= 2: + cards = cs.get_starting_deck(ses.game.expansions) + ses.hand.append([c for c in cards if c.name == ' '.join(cmd[1:])][0]) + ses.notify_self() elif '/gameinfo' in msg: sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.game.__dict__}'}) elif '/meinfo' in msg: sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.__dict__}'}) + elif '/mebot' in msg: + ses.is_bot = not ses.is_bot + ses.notify_self() else: sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} COMMAND NOT FOUND'}) else: diff --git a/backend/bang/cards.py b/backend/bang/cards.py index 91faeac..20816d4 100644 --- a/backend/bang/cards.py +++ b/backend/bang/cards.py @@ -58,12 +58,7 @@ class Card(ABC): if not has_weapon: player.equipment.append(self) elif self.name in [c.name for c in player.equipment if not isinstance(c, Dinamite)]: - for i in range(len(player.equipment)): - print('tipo',type(self)) - if type(player.equipment[i]) == type(self): - player.game.deck.scrap(player.equipment[i]) - player.equipment[i] = self - break + return False else: player.equipment.append(self) if against: @@ -77,6 +72,9 @@ class Card(ABC): def use_card(self, player): pass + def is_duplicate_card(self, player): + return self.name in [c.name for c in player.equipment] + class Barile(Card): def __init__(self, suit, number): @@ -124,6 +122,7 @@ class Prigione(Card): 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): diff --git a/backend/bang/deck.py b/backend/bang/deck.py index f484e8b..136127d 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -22,15 +22,16 @@ class Deck: self.event_cards: List[ce.CardEvent] = [] if 'fistful_of_cards' in game.expansions: self.event_cards.extend(ce.get_all_events()) - random.shuffle(self.event_cards) + self.event_cards.insert(0, None) + self.event_cards.insert(0, None) # 2 perchè iniziale, e primo flip dallo sceriffo random.shuffle(self.cards) self.scrap_pile: List[cs.Card] = [] print(f'Deck initialized with {len(self.cards)} cards') def flip_event(self): - if len(self.event_cards) > 0: + if len(self.event_cards) > 0 and not isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte): self.event_cards.append(self.event_cards.pop(0)) - self.game.notify_event_card() + self.game.notify_event_card() def peek(self, n_cards: int) -> list: return self.cards[:n_cards] @@ -52,7 +53,9 @@ class Deck: def put_on_top(self, card: cs.Card): self.cards.insert(0, card) - def draw(self) -> cs.Card: + def draw(self, ignore_event = False) -> 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() @@ -71,8 +74,11 @@ class Deck: else: return self.draw() - def scrap(self, card: cs.Card): + def scrap(self, card: cs.Card, ignore_event = False): if card.usable_next_turn: card.can_be_used_now = False - self.scrap_pile.append(card) - self.game.notify_scrap_pile() + if self.game.check_event(ce.MinieraAbbandonata) and not ignore_event: + self.put_on_top(card) + else: + self.scrap_pile.append(card) + self.game.notify_scrap_pile() diff --git a/backend/bang/expansions/dodge_city/cards.py b/backend/bang/expansions/dodge_city/cards.py index a431a58..051ada6 100644 --- a/backend/bang/expansions/dodge_city/cards.py +++ b/backend/bang/expansions/dodge_city/cards.py @@ -152,8 +152,11 @@ class Bibbia(Schivata): pass return False else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class Cappello(Mancato): def __init__(self, suit, number): @@ -168,8 +171,11 @@ class Cappello(Mancato): pass return False else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class PlaccaDiFerro(Cappello): def __init__(self, suit, number): @@ -195,8 +201,11 @@ class Pugnale(Pugno): if self.can_be_used_now: return super().play_card(player, against=against) else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class Derringer(Pugnale): def __init__(self, suit, number): @@ -212,8 +221,11 @@ class Derringer(Pugnale): player.hand.append(player.game.deck.draw()) return super().play_card(player, against=against) else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False def use_card(self, player): player.hand.append(player.game.deck.draw()) @@ -235,8 +247,11 @@ class Borraccia(Card): player.notify_self() return True else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class PonyExpress(WellsFargo): def __init__(self, suit, number): @@ -250,8 +265,11 @@ class PonyExpress(WellsFargo): if self.can_be_used_now: return super().play_card(player, against) else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class Howitzer(Gatling): def __init__(self, suit, number): @@ -265,8 +283,11 @@ class Howitzer(Gatling): if self.can_be_used_now: return super().play_card(player, against) else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class CanCan(CatBalou): def __init__(self, suit, number): @@ -280,8 +301,11 @@ class CanCan(CatBalou): if self.can_be_used_now: return super().play_card(player, against) else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class Conestoga(Panico): def __init__(self, suit, number): @@ -297,8 +321,11 @@ class Conestoga(Panico): if self.can_be_used_now: return super().play_card(player, against) else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class Pepperbox(Bang): def __init__(self, suit, number): @@ -316,8 +343,11 @@ class Pepperbox(Bang): return True return False else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False class FucileDaCaccia(Card): def __init__(self, suit, number): @@ -336,12 +366,14 @@ class FucileDaCaccia(Card): return True return False else: - player.equipment.append(self) - return True + if not self.is_duplicate_card(player): + player.equipment.append(self) + return True + else: + return False def get_starting_deck() -> List[Card]: return [ - #TODO: aggiungere anche le carte normalmente presenti https://bang.dvgiochi.com/cardslist.php?id=3 Barile(Suit.CLUBS, 'A'), Binocolo(Suit.DIAMONDS, 10), Dinamite(Suit.CLUBS, 10), diff --git a/backend/bang/expansions/fistful_of_cards/card_events.py b/backend/bang/expansions/fistful_of_cards/card_events.py index 528d3b3..ed14be4 100644 --- a/backend/bang/expansions/fistful_of_cards/card_events.py +++ b/backend/bang/expansions/fistful_of_cards/card_events.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +import random class CardEvent(ABC): def __init__(self, name, icon): @@ -7,109 +8,114 @@ class CardEvent(ABC): class Agguato(CardEvent): def __init__(self): - super().__init__('Agguato', '🛁') - self.desc = 'La distanza base di tra 2 qualsiasi giocatori è 1' - self.desc_eng = 'The base distance from any 2 players is 1' + super().__init__("Agguato", "🛁") + self.desc = "La distanza base di tra 2 qualsiasi giocatori è 1" + self.desc_eng = "The base distance from any 2 players is 1" class Cecchino(CardEvent): - def __init__(self): #TODO - super().__init__('Cecchino', '👁') - self.desc = 'Nel proprio turno i giocatori possono scartare 2 Bang assieme per sparare un bang a cui servono 2 mancato' - self.desc_eng = 'During their turn, players can discard 2 Bang! to shoot a bang that requires 2 missed' + 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): 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 return back to life with 2 hp and 2 cards' + 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 return back to life with 2 hp and 2 cards" class FratelliDiSangue(CardEvent): - def __init__(self):#TODO - super().__init__('Fratelli Di Sangue', '💉') - self.desc = 'All\'inizio del proprio turno i giocatori possono perdere 1 vita per darla a un altro giocatore' - self.desc_eng = 'At the begin of their turn, payers can lose 1 hp to give it to another player' + 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): 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' + 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): 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' + 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): 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' + 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): - def __init__(self):#TODO - super().__init__('Liquore Forte', '🥃') - self.desc = 'I giocatori possono evitare di pescare per recuperare 1 vita' - self.desc_eng = 'Players can skip drawing to regain 1 HP' + 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): - def __init__(self):#TODO - super().__init__('Miniera Abbandonata', '⛏') - self.desc = 'I giocatori pescano dagli scarti e scartano in cima al mazzo' - self.desc_eng = 'Players draw from the discarded pile and discard to the deck' + 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)" + #TODO: cambiare anche la descrizione inglese + self.desc_eng = "Players draw from the discarded pile and discard to the top of the deck (if the discards run out, they must draw and discard on top of the deck)" class PerUnPugnoDiCarte(CardEvent): - def __init__(self):#TODO - super().__init__('Per Un Pugno Di Carte', '🎴') - self.desc = 'Il giocatore subisce tanti bang quante carte ha in mano' - self.desc_eng = 'On his turn the player is target of as many Bang as how many cards he has in his hand' + 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): - def __init__(self):#TODO - super().__init__('Peyote', '🌵') - self.desc = 'Invece che pescare il giocatore prova a indovinare il colore del seme, se lo indovina continua' - self.desc_eng = 'Instead of drawing, the player tries to guess the color of the suit, if he\'s right he repeats' + 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): - def __init__(self):#TODO - 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' + 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): - def __init__(self):#TODO - 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' - 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' + 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)" class RouletteRussa(CardEvent): - def __init__(self):#TODO - 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' + 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" class Vendetta(CardEvent): def __init__(self): - super().__init__('Vendetta', '😤') - self.desc = 'Alla fine del proprio turno il giocatore estrae, se esce ♥️ gioca un altro turno' - self.desc_eng = 'When ending the turn, the player flips a card, if it\'s ♥️ he plays another turn' + 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)" def get_all_events(): - return [ + cards = [ Agguato(), - # Cecchino(), + Cecchino(), DeadMan(), - # FratelliDiSangue(), + FratelliDiSangue(), IlGiudice(), Lazo(), LeggeDelWest(), - # LiquoreForte(), - # MinieraAbbandonata(), - # PerUnPugnoDiCarte(), - # Peyote(), - # Ranch(), - # Rimbalzo(), - # RouletteRussa(), + LiquoreForte(), + MinieraAbbandonata(), + Peyote(), + Ranch(), + Rimbalzo(), + RouletteRussa(), Vendetta(), - ] \ No newline at end of file + ] + random.shuffle(cards) + cards.append(PerUnPugnoDiCarte()) + for c in cards: + c.expansion = 'fistful-of-cards' + return cards \ No newline at end of file diff --git a/backend/bang/game.py b/backend/bang/game.py index 6057456..b4a210b 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -2,7 +2,7 @@ from typing import List, Set, Dict, Tuple, Optional import random import socketio -import bang.players as players +import bang.players as pl import bang.characters as characters from bang.deck import Deck import bang.roles as roles @@ -14,8 +14,8 @@ class Game: super().__init__() self.sio = sio self.name = name - self.players: List[players.Player] = [] - self.dead_players: List[players.Player] = [] + self.players: List[pl.Player] = [] + self.dead_players: List[pl.Player] = [] self.deck: Deck = None self.started = False self.turn = 0 @@ -24,10 +24,13 @@ class Game: self.initial_players = 0 self.password = '' self.expansions = [] - self.available_expansions = ['dodge_city'] + self.available_expansions = ['dodge_city', 'fistful_of_cards'] self.shutting_down = False self.is_competitive = False self.disconnect_bot = True + self.player_bangs = 0 + self.is_russian_roulette_on = False + self.bot_speed = 1.5 def notify_room(self, sid=None): if len([p for p in self.players if p.character == None]) != 0 or sid: @@ -59,7 +62,7 @@ class Game: self.disconnect_bot = not self.disconnect_bot self.notify_room() - def add_player(self, player: players.Player): + def add_player(self, player: pl.Player): if player.is_bot and len(self.players) >= 8: return if player in self.players or len(self.players) >= 10: @@ -138,9 +141,10 @@ class Game: self.sio.emit('chat_message', room=self.name, data=f'_sheriff|{self.players[i].name}') self.turn = i self.players[i].notify_self() + self.notify_event_card() - def attack_others(self, attacker: players.Player): - attacker.pending_action = players.PendingAction.WAIT + def attack_others(self, attacker: pl.Player): + attacker.pending_action = pl.PendingAction.WAIT attacker.notify_self() self.waiting_for = 0 self.readyCount = 0 @@ -150,11 +154,11 @@ class Game: self.waiting_for += 1 p.notify_self() if self.waiting_for == 0: - attacker.pending_action = players.PendingAction.PLAY + attacker.pending_action = pl.PendingAction.PLAY attacker.notify_self() - def indian_others(self, attacker: players.Player): - attacker.pending_action = players.PendingAction.WAIT + def indian_others(self, attacker: pl.Player): + attacker.pending_action = pl.PendingAction.WAIT attacker.notify_self() self.waiting_for = 0 self.readyCount = 0 @@ -164,60 +168,99 @@ class Game: self.waiting_for += 1 p.notify_self() if self.waiting_for == 0: - attacker.pending_action = players.PendingAction.PLAY + attacker.pending_action = pl.PendingAction.PLAY attacker.notify_self() - def attack(self, attacker: players.Player, target_username:str, double:bool=False): + def attack(self, attacker: pl.Player, target_username:str, double:bool=False): if self.get_player_named(target_username).get_banged(attacker=attacker, double=double): self.readyCount = 0 self.waiting_for = 1 - attacker.pending_action = players.PendingAction.WAIT + attacker.pending_action = pl.PendingAction.WAIT attacker.notify_self() self.get_player_named(target_username).notify_self() - def duel(self, attacker: players.Player, target_username:str): + def rimbalzo(self, attacker: pl.Player, target_username:str, card_index:int): + if self.get_player_named(target_username).get_banged(attacker=attacker, no_dmg=True, card_index=card_index): + self.readyCount = 0 + self.waiting_for = 1 + attacker.pending_action = pl.PendingAction.WAIT + attacker.notify_self() + self.get_player_named(target_username).notify_self() + + def duel(self, attacker: pl.Player, target_username:str): if self.get_player_named(target_username).get_dueled(attacker=attacker): self.readyCount = 0 self.waiting_for = 1 - attacker.pending_action = players.PendingAction.WAIT + attacker.pending_action = pl.PendingAction.WAIT attacker.notify_self() self.get_player_named(target_username).notify_self() def emporio(self): - self.available_cards = [self.deck.draw() for i in range(len(self.players))] - self.players[self.turn].pending_action = players.PendingAction.CHOOSE + self.available_cards = [self.deck.draw(True) for i in range(len([p for p in self.players if p.lives > 0]))] + self.players[self.turn].pending_action = pl.PendingAction.CHOOSE + self.players[self.turn].choose_text = 'choose_card_to_get' self.players[self.turn].available_cards = self.available_cards self.players[self.turn].notify_self() def respond_emporio(self, player, i): player.hand.append(self.available_cards.pop(i)) player.available_cards = [] - player.pending_action = players.PendingAction.WAIT + player.pending_action = pl.PendingAction.WAIT player.notify_self() nextPlayer = self.players[(self.turn + (len(self.players)-len(self.available_cards))) % len(self.players)] if nextPlayer == self.players[self.turn]: - self.players[self.turn].pending_action = players.PendingAction.PLAY + self.players[self.turn].pending_action = pl.PendingAction.PLAY self.players[self.turn].notify_self() else: - nextPlayer.pending_action = players.PendingAction.CHOOSE + nextPlayer.pending_action = pl.PendingAction.CHOOSE + self.players[self.turn].choose_text = 'choose_card_to_get' nextPlayer.available_cards = self.available_cards nextPlayer.notify_self() def get_player_named(self, name:str): return self.players[self.players_map[name]] - def responders_did_respond_resume_turn(self): - self.readyCount += 1 - if self.readyCount == self.waiting_for: - self.waiting_for = 0 - self.readyCount = 0 - self.players[self.turn].pending_action = players.PendingAction.PLAY - self.players[self.turn].notify_self() + def responders_did_respond_resume_turn(self, did_lose=False): + print('did_lose', did_lose) + if self.player_bangs > 0 and self.check_event(ce.PerUnPugnoDiCarte): + self.player_bangs -= 1 + if self.player_bangs > 1: + print('bang again') + if self.players[self.turn].get_banged(self.deck.event_cards[0]): + self.players[self.turn].notify_self() + else: + self.responders_did_respond_resume_turn() + else: + print('ok play turn now') + self.player_bangs = 0 + self.players[self.turn].play_turn() + elif self.is_russian_roulette_on and self.check_event(ce.RouletteRussa): + if did_lose: + print('stop roulette') + self.players[(self.turn+self.player_bangs) % len(self.players)].lives -= 1 + self.players[(self.turn+self.player_bangs) % len(self.players)].notify_self() + self.is_russian_roulette_on = False + self.players[self.turn].play_turn() + else: + self.player_bangs += 1 + print(f'next in line {self.players[(self.turn+self.player_bangs) % len(self.players)].name}') + if self.players[(self.turn+self.player_bangs) % len(self.players)].get_banged(self.deck.event_cards[0]): + self.players[(self.turn+self.player_bangs) % len(self.players)].notify_self() + else: + self.responders_did_respond_resume_turn(did_lose=True) + else: + self.readyCount += 1 + if self.readyCount == self.waiting_for: + self.waiting_for = 0 + self.readyCount = 0 + self.players[self.turn].pending_action = pl.PendingAction.PLAY + self.players[self.turn].notify_self() def next_player(self): return self.players[(self.turn + 1) % len(self.players)] def play_turn(self): + self.player_bangs = 0 if isinstance(self.players[self.turn].role, roles.Sheriff): self.deck.flip_event() if self.check_event(ce.DeadMan) and len(self.dead_players) > 0: @@ -227,7 +270,21 @@ class Game: self.players[-1].hand.append(self.deck.draw()) self.players_map = {c.name: i for i, c in enumerate(self.players)} self.players[-1].notify_self() - self.players[self.turn].play_turn() + elif self.check_event(ce.RouletteRussa): + self.is_russian_roulette_on = True + if self.players[self.turn].get_banged(self.deck.event_cards[0]): + self.players[self.turn].notify_self() + else: + self.responders_did_respond_resume_turn(did_lose=True) + return + if self.check_event(ce.PerUnPugnoDiCarte) and len(self.players[self.turn].hand) > 0: + self.player_bangs = len(self.players[self.turn].hand) + if self.players[self.turn].get_banged(self.deck.event_cards[0]): + self.players[self.turn].notify_self() + else: + self.responders_did_respond_resume_turn() + else: + self.players[self.turn].play_turn() def next_turn(self): if self.shutting_down: return @@ -237,7 +294,10 @@ class Game: def notify_event_card(self): if len(self.deck.event_cards) > 0: - self.sio.emit('event_card', room=self.name, data=self.deck.event_cards[0].__dict__) + if self.deck.event_cards[0] != None: + self.sio.emit('event_card', room=self.name, data=self.deck.event_cards[0].__dict__) + else: + self.sio.emit('event_card', room=self.name, data=None) def notify_scrap_pile(self): print('scrap') @@ -246,7 +306,7 @@ class Game: else: self.sio.emit('scrap', room=self.name, data=None) - def handle_disconnect(self, player: players.Player): + def handle_disconnect(self, player: pl.Player): print(f'player {player.name} left the game {self.name}') if player in self.players: if self.disconnect_bot and self.started: @@ -266,24 +326,25 @@ class Game: return True else: return False - def player_death(self, player: players.Player, disconnected=False): + def player_death(self, player: pl.Player, disconnected=False): if not player in self.players: return import bang.expansions.dodge_city.characters as chd print(player.attacker) - if player.attacker and isinstance(player.attacker.role, roles.Sheriff) and isinstance(player.role, roles.Vice): + if player.attacker and player.attacker in self.players and isinstance(player.attacker.role, roles.Sheriff) and isinstance(player.role, roles.Vice): for i in range(len(player.attacker.hand)): - self.deck.scrap(player.attacker.hand.pop()) + self.deck.scrap(player.attacker.hand.pop(), True) for i in range(len(player.attacker.equipment)): - self.deck.scrap(player.attacker.equipment.pop()) + self.deck.scrap(player.attacker.equipment.pop(), True) player.attacker.notify_self() - elif player.attacker and (isinstance(player.role, roles.Outlaw) or self.initial_players == 3): + elif player.attacker and player.attacker in self.players and (isinstance(player.role, roles.Outlaw) or self.initial_players == 3): for i in range(3): - player.attacker.hand.append(self.deck.draw()) + player.attacker.hand.append(self.deck.draw(True)) player.attacker.notify_self() print(f'player {player.name} died') if (self.waiting_for > 0): self.responders_did_respond_resume_turn() + if not player in self.players: return index = self.players.index(player) died_in_his_turn = self.started and index == self.turn if self.started and index <= self.turn: @@ -303,7 +364,7 @@ class Game: if self.started: print('Check win status') attacker_role = None - if player.attacker: + if player.attacker and player.attacker in self.players: attacker_role = player.attacker.role winners = [p for p in self.players if p.role != None and p.role.on_player_death(self.players, initial_players=self.initial_players, dead_role=player.role, attacker_role=attacker_role)] if len(winners) > 0: @@ -318,22 +379,36 @@ class Game: vulture = [p for p in self.players if isinstance(p.character, characters.VultureSam)] if len(vulture) == 0: for i in range(len(player.hand)): - self.deck.scrap(player.hand.pop()) + self.deck.scrap(player.hand.pop(), True) for i in range(len(player.equipment)): - self.deck.scrap(player.equipment.pop()) + self.deck.scrap(player.equipment.pop(), True) + elif len(vulture) == 2: + for i in range(len(player.hand)): + vulture[i%2].hand.append(player.hand.pop()) + for i in range(len(player.equipment)): + vulture[i%2].hand.append(player.equipment.pop()) + vulture[0].notify_self() + vulture[1].notify_self() else: for i in range(len(player.hand)): vulture[0].hand.append(player.hand.pop()) for i in range(len(player.equipment)): vulture[0].hand.append(player.equipment.pop()) vulture[0].notify_self() + + #se Vulture Sam è uno sceriffo e ha appena ucciso il suo Vice, deve scartare le carte che ha pescato con la sua abilità + if player.attacker and player.attacker in self.players and isinstance(player.attacker.role, roles.Sheriff) and isinstance(player.role, roles.Vice): + for i in range(len(player.attacker.hand)): + self.deck.scrap(player.attacker.hand.pop(), True) + player.attacker.notify_self() + greg = [p for p in self.players if isinstance(p.character, chd.GregDigger)] if len(greg) > 0: greg[0].lives = min(greg[0].lives+2, greg[0].max_lives) herb = [p for p in self.players if isinstance(p.character, chd.HerbHunter)] if len(herb) > 0: - herb[0].hand.append(self.deck.draw()) - herb[0].hand.append(self.deck.draw()) + herb[0].hand.append(self.deck.draw(True)) + herb[0].hand.append(self.deck.draw(True)) herb[0].notify_self() if died_in_his_turn: @@ -357,7 +432,7 @@ class Game: if len(self.deck.event_cards) == 0: return False return isinstance(self.deck.event_cards[0], ev) - def get_visible_players(self, player: players.Player): + def get_visible_players(self, player: pl.Player): i = self.players.index(player) sight = player.get_sight() mindist = 99 if not self.check_event(ce.Agguato) else 1 @@ -367,6 +442,7 @@ class Game: 'lives': self.players[j].lives, 'max_lives': self.players[j].max_lives, 'is_sheriff': isinstance(self.players[j].role, roles.Sheriff), + 'cards': len(self.players[j].hand)+len(self.players[j].equipment) } for j in range(len(self.players)) if i != j] def notify_all(self): diff --git a/backend/bang/players.py b/backend/bang/players.py index 2cea122..cb0297a 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -48,6 +48,13 @@ class Player: self.attacker: Player = None self.target_p: str = None self.is_drawing = False + self.can_play_vendetta = True + self.is_giving_life = False + self.is_using_checchino = False + self.choose_text = 'choose_card_to_get' + self.using_rimbalzo = 0 # 0 no, 1 scegli giocatore, 2 scegli carta + self.can_play_ranch = True + self.is_playing_ranch = False self.mancato_needed = 0 self.molly_discarded_cards = 0 self.is_bot = bot @@ -59,11 +66,14 @@ class Player: self.role: r.Role = None self.character: chars.Character = None self.real_character: chars.Character = None + self.is_using_checchino = False self.lives = 0 self.max_lives = 0 self.is_my_turn = False self.is_waiting_for_action = True self.has_played_bang = False + self.can_play_ranch = True + self.is_playing_ranch = False self.pending_action: PendingAction = None self.available_characters = [] self.was_shot = False @@ -146,13 +156,34 @@ class Player: self.sio.emit('notify_card', room=self.sid, data=mess) def notify_self(self): - if isinstance(self.character, chars.CalamityJanet): - if cs.Mancato(0, 0).name not in self.expected_response: - self.expected_response.append(cs.Mancato(0, 0).name) - elif cs.Bang(0, 0).name not in self.expected_response: - self.expected_response.append(cs.Bang(0, 0).name) - elif isinstance(self.character, chars.SuzyLafayette) and len(self.hand) == 0: - self.hand.append(self.game.deck.draw()) + if self.pending_action == PendingAction.DRAW and self.game.check_event(ce.Peyote): + self.available_cards = [{ + 'icon': '🔴' + },{ + 'icon': '⚫' + }] + self.is_drawing = True + self.choose_text = 'choose_guess' + self.pending_action = PendingAction.CHOOSE + elif self.can_play_ranch and self.pending_action == PendingAction.PLAY and self.game.check_event(ce.Ranch): + self.can_play_ranch = False + self.available_cards = [c for c in self.hand] + self.discarded_cards = [] + self.available_cards.append({'icon': '✅'}) + self.is_playing_ranch = True + self.choose_text = 'choose_ranch' + self.pending_action = PendingAction.CHOOSE + elif isinstance(self.character, chars.SuzyLafayette) and len(self.hand) == 0 and ( not self.is_my_turn or self.pending_action == PendingAction.PLAY): + self.hand.append(self.game.deck.draw(True)) + if self.lives <= 0 and self.max_lives > 0: + print('dying, attacker', self.attacker) + if isinstance(self.character, chars.SidKetchum) and len(self.hand) > 1: + self.lives += 1 + #TODO Sid dovrebbe poter decidere cosa scartare + self.game.deck.scrap(self.hand.pop( + randrange(0, len(self.hand))), True) + self.game.deck.scrap(self.hand.pop( + randrange(0, len(self.hand))), True) ser = self.__dict__.copy() ser.pop('game') ser.pop('sio') @@ -166,14 +197,6 @@ class Player: ser['sight'] = self.get_sight() ser['lives'] = max(ser['lives'], 0) - if self.lives <= 0 and self.max_lives > 0: - print('dying, attacker', self.attacker) - if isinstance(self.character, chars.SidKetchum) and len(self.hand) > 1: - self.lives += 1 - self.game.deck.scrap(self.hand.pop( - randrange(0, len(self.hand)))) - self.game.deck.scrap(self.hand.pop( - randrange(0, len(self.hand)))) if self.lives <= 0 and self.max_lives > 0: self.pending_action = PendingAction.WAIT ser['hand'] = [] @@ -196,7 +219,7 @@ class Player: def bot_logic(self): if self.game.shutting_down: return if self.pending_action != None and self.pending_action != PendingAction.WAIT: - eventlet.sleep(uniform(0.6, 1.5)) + eventlet.sleep(uniform(self.game.bot_speed/2-0.1, self.game.bot_speed)) else: return if self.pending_action == PendingAction.PICK: @@ -204,45 +227,39 @@ class Player: elif self.pending_action == PendingAction.DRAW: self.draw('') elif self.pending_action == PendingAction.PLAY: - has_played = False - if len([c for c in self.hand if (c.is_equipment or c.usable_next_turn) and not self.game.check_event(ce.IlGiudice)]) > 0: - for i in range(len(self.hand)): - if self.hand[i].is_equipment or self.hand[i].usable_next_turn: - self.play_card(i) - has_played = True - break - elif any([isinstance(c, cs.WellsFargo) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or isinstance(c, cs.Birra) for c in self.hand]): - for i in range(len(self.hand)): - c = self.hand[i] - if isinstance(c, cs.WellsFargo) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or (isinstance(c, cs.Birra) and self.lives < self.max_lives): - self.play_card(i) - has_played = True - break - elif len([c for c in self.hand if c.need_target and not (self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment]))]) > 0: - for i in range(len(self.hand)): - if self.hand[i].need_target and not (self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment])): - if self.hand[i].need_with and len(self.hand) < 2: - continue - _range = self.get_sight() if self.hand[i].name == 'Bang!' or self.hand[i].name == "Pepperbox" else self.hand[i].range - others = [p for p in self.game.get_visible_players(self) if _range >= p['dist'] and not (isinstance(self.role, r.Vice) and p['is_sheriff'])] - if len(others) == 0: - continue + equippables = [c for c in self.hand if (c.is_equipment or c.usable_next_turn) and not isinstance(c, cs.Prigione) and not any([type(c) == type(x) for x in self.equipment])] + misc = [c for c in self.hand if (isinstance(c, cs.WellsFargo) and not c.usable_next_turn) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or (isinstance(c, cs.Birra) and self.lives < self.max_lives)] + need_target = [c for c in self.hand if c.need_target and c.can_be_used_now and not (c.need_with and len(self.hand) < 2) and not (self.has_played_bang and not (any([isinstance(c, cs.Volcanic) for c in self.equipment]) and not self.game.check_event(ce.Lazo))) and not ( isinstance(c, cs.Prigione) and self.game.check_event(ce.IlGiudice))] + green_cards = [c for c in self.equipment if not self.game.check_event(ce.Lazo) and not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now] + if len(equippables) > 0 and not self.game.check_event(ce.IlGiudice): + for c in equippables: + if self.play_card(self.hand.index(c)): + return + elif len(misc) > 0: + for c in misc: + if self.play_card(self.hand.index(c)): + return + elif len(need_target) > 0: + for c in need_target: + _range = self.get_sight() if c.name == 'Bang!' or c.name == "Pepperbox" else c.range + others = [p for p in self.game.get_visible_players(self) if _range >= p['dist'] and not (isinstance(self.role, r.Vice) and p['is_sheriff']) and p['lives'] > 0 and not ((isinstance(c, cs.CatBalou) or isinstance(c, cs.Panico)) and p['cards'] == 0) and not (p['is_sheriff'] and isinstance(c, cs.Prigione))] + if len(others) == 0: + continue + target = others[randrange(0, len(others))] + if target['is_sheriff'] and isinstance(self.role, r.Renegade): target = others[randrange(0, len(others))] - if target['is_sheriff'] and isinstance(self.role, r.Renegade): - target = others[randrange(0, len(others))] - if not self.hand[i].need_with: - self.play_card(i, against=target['name']) - else: - self.play_card(i, against=target['name'], _with=sample([j for j in range(len(self.hand)) if j != i], 1)[0]) - has_played = True - break - elif any([not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now for c in self.equipment]): - print('hmm', [not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now for c in self.equipment]) - for i in range(len(self.equipment)): - c = self.equipment[i] + if not c.need_with: + if self.play_card(self.hand.index(c), against=target['name']): + return + else: + if self.play_card(self.hand.index(c), against=target['name'], _with=sample([j for j in range(len(self.hand)) if j != self.hand.index(c)], 1)[0]): + return + elif len(green_cards) > 0: + for c in green_cards: if not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now: if not c.need_target: - self.play_card(len(self.hand)+i) + if self.play_card(len(self.hand)+self.equipment.index(c)): + return else: _range = self.get_sight() if c.name == "Pepperbox" else c.range others = [p for p in self.game.get_visible_players(self) if _range >= p['dist'] and not (isinstance(self.role, r.Vice) and p['is_sheriff'])] @@ -251,18 +268,18 @@ class Player: target = others[randrange(0, len(others))] if target['is_sheriff'] and isinstance(self.role, r.Renegade): target = others[randrange(0, len(others))] - self.play_card(len(self.hand)+i, against=target['name']) - has_played = True + if self.play_card(len(self.hand)+self.equipment.index(c), against=target['name']): + return break maxcards = self.lives if not isinstance(self.character, chd.SeanMallory) else 10 - if not has_played and len(self.hand) > maxcards: + if len(self.hand) > maxcards: self.scrap(0) else: self.end_turn() elif self.pending_action == PendingAction.RESPOND: did_respond = False for i in range(len(self.hand)): - if self.hand[i].name in self.expected_response: + if self.hand[i].can_be_used_now and self.hand[i].name in self.expected_response: self.respond(i) did_respond = True break @@ -284,10 +301,13 @@ class Player: else: self.choose(randrange(0, len(target.hand)+len(target.equipment))) - def play_turn(self): + def play_turn(self, can_play_vendetta = True): if self.lives == 0: return self.end_turn(forced=True) self.scrapped_cards = 0 + self.can_play_ranch = True + self.is_playing_ranch = False + self.can_play_vendetta = can_play_vendetta self.sio.emit('chat_message', room=self.game.name, data=f'_turn|{self.name}') print(f'I {self.name} was notified that it is my turn') @@ -296,9 +316,21 @@ class Player: self.is_waiting_for_action = True self.has_played_bang = False self.special_use_count = 0 - if not self.game.check_event(ce.Lazo) and any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]): + if self.game.check_event(ce.FratelliDiSangue) and self.lives > 1 and not self.is_giving_life and len([p for p in self.game.players if p != self and p.lives < p.max_lives]): + self.available_cards = [{ + 'name': p.name, + 'icon': '⭐️' if isinstance(p.role, r.Sheriff) else '🤠', + 'alt_text': ''.join(['❤️']*p.lives)+''.join(['💀']*(p.max_lives-p.lives)) + } for p in self.game.players if p != self and p.lives < p.max_lives] + self.available_cards.append({'icon': '❌'}) + self.choose_text = 'choose_fratelli_di_sangue' + self.pending_action = PendingAction.CHOOSE + self.is_giving_life = True + elif not self.game.check_event(ce.Lazo) and any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]): + self.is_giving_life = False self.pending_action = PendingAction.PICK else: + self.is_giving_life = False if isinstance(self.real_character, chd.VeraCuster): self.set_available_character([p.character for p in self.game.players if p != self]) else: @@ -306,16 +338,43 @@ class Player: self.notify_self() def draw(self, pile): + if self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Cecchino) and len([c for c in self.hand if c.name == cs.Bang(0,0).name]) >= 2: + self.is_using_checchino = True + self.available_cards = [{ + 'name': p['name'], + 'icon': '⭐️' if p['is_sheriff'] else '🤠', + 'alt_text': ''.join(['❤️']*p['lives'])+''.join(['💀']*(p['max_lives']-p['lives'])) + } for p in self.game.get_visible_players(self) if p['dist'] <= self.get_sight()] + self.available_cards.append({'icon': '❌'}) + self.choose_text = 'choose_cecchino' + self.pending_action = PendingAction.CHOOSE + 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 len([c for c in self.hand if c.name == cs.Bang(0,0).name]) > 0: + self.available_cards = [{ + 'name': p.name, + 'icon': '⭐️' if isinstance(p.role, r.Sheriff) else '🤠' + } for p in self.game.players if len(p.equipment) > 0 and p != self] + self.available_cards.append({'icon': '❌'}) + self.choose_text = 'choose_rimbalzo_player' + self.pending_action = PendingAction.CHOOSE + self.using_rimbalzo = 1 + self.notify_self() if self.pending_action != PendingAction.DRAW: return - if isinstance(self.character, chars.KitCarlson): + if pile == 'event' and self.lives < self.max_lives and self.game.check_event(ce.LiquoreForte): + self.lives += 1 + self.pending_action = PendingAction.PLAY + self.notify_self() + elif isinstance(self.character, chars.KitCarlson): self.is_drawing = True self.available_cards = [self.game.deck.draw() for i in range(3)] + self.choose_text = 'choose_card_to_get' self.pending_action = PendingAction.CHOOSE self.notify_self() elif isinstance(self.character, chd.PatBrennan) and type(pile) == str and pile != self.name and pile in self.game.players_map and len(self.game.get_player_named(pile).equipment) > 0: self.is_drawing = True self.available_cards = self.game.get_player_named(pile).equipment + self.choose_text = 'choose_card_to_get' self.pending_action = PendingAction.CHOOSE self.notify_self() else: @@ -356,7 +415,7 @@ class Player: pickable_cards = 1 + self.character.pick_mod if self.is_my_turn: for i in range(len(self.equipment)): - if isinstance(self.equipment[i], cs.Dinamite): + if i < len(self.equipment) and isinstance(self.equipment[i], cs.Dinamite): while pickable_cards > 0: pickable_cards -= 1 picked: cs.Card = self.game.deck.pick_and_scrap() @@ -365,14 +424,13 @@ class Player: data=f'_flipped|{self.name}|{picked}') if picked.suit == cs.Suit.SPADES and 2 <= picked.number <= 9 and pickable_cards == 0: self.lives -= 3 - self.game.deck.scrap(self.equipment.pop(i)) - self.sio.emit('chat_message', room=self.game.name, - data=f'_explode|{self.name}') + self.game.deck.scrap(self.equipment.pop(i), True) + self.sio.emit('chat_message', room=self.game.name, data=f'_explode|{self.name}') + self.heal_if_needed() if isinstance(self.character, chars.BartCassidy) and self.lives > 0: for i in range(3): - self.hand.append(self.game.deck.draw()) - self.sio.emit('chat_message', room=self.game.name, - data=f'_special_bart_cassidy|{self.name}') + self.hand.append(self.game.deck.draw(True)) + self.sio.emit('chat_message', room=self.game.name, data=f'_special_bart_cassidy|{self.name}') print(f'{self.name} Boom, -3 hp') break else: @@ -391,11 +449,11 @@ class Player: self.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked}') if picked.suit != cs.Suit.HEARTS and pickable_cards == 0: - self.game.deck.scrap(self.equipment.pop(i)) + self.game.deck.scrap(self.equipment.pop(i), True) self.end_turn(forced=True) return elif pickable_cards == 0: - self.game.deck.scrap(self.equipment.pop(i)) + self.game.deck.scrap(self.equipment.pop(i), True) break break if any([isinstance(c, cs.Prigione) for c in self.equipment]): @@ -440,20 +498,31 @@ class Player: did_play_card = False event_blocks_card = (self.game.check_event(ce.IlGiudice) and (card.is_equipment or (card.usable_next_turn and not card.can_be_used_now))) or (self.game.check_event(ce.Lazo) and card.usable_next_turn and card.can_be_used_now) if not(against != None and isinstance(self.game.get_player_named(against).character, chd.ApacheKid) and card.suit == cs.Suit.DIAMONDS) and not event_blocks_card: - did_play_card = card.play_card(self, against, withCard) + if against == self.name and not isinstance(card, csd.Tequila): + did_play_card = False + else: + did_play_card = card.play_card(self, against, withCard) if not card.is_equipment and not card.usable_next_turn or event_blocks_card: if did_play_card: - self.game.deck.scrap(card) + self.game.deck.scrap(card, True) else: self.hand.insert(hand_index, card) if withCard: self.hand.insert(_with, withCard) elif card.usable_next_turn and card.can_be_used_now: if did_play_card: - self.game.deck.scrap(card) + self.game.deck.scrap(card, True) else: self.equipment.insert(hand_index-len(self.hand), card) + elif card.is_equipment or (card.usable_next_turn and not card.can_be_used_now): + if not did_play_card: + self.hand.insert(hand_index, card) + else: + did_play_card = True + print("did play card:", did_play_card) 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) def choose(self, card_index): if self.pending_action != PendingAction.CHOOSE: @@ -471,7 +540,7 @@ class Player: card.can_be_used_now = False self.hand.append(card) else: - self.game.deck.scrap(card) + self.game.deck.scrap(card, True) if self.event_type != 'rissa' or (self.event_type == 'rissa' and self.target_p == [p.name for p in self.game.players if p != self and (len(p.hand)+len(p.equipment)) > 0][-1]): self.event_type = '' self.target_p = '' @@ -482,6 +551,73 @@ class Player: while self.target_p == self.name or len(self.game.players[self.game.players_map[self.target_p]].hand) + len(self.game.players[self.game.players_map[self.target_p]].equipment) == 0: self.target_p = self.game.players[self.game.players_map[self.target_p]+1].name self.notify_self() + elif self.is_giving_life and self.game.check_event(ce.FratelliDiSangue): + try: + player = self.game.get_player_named(self.available_cards[card_index]['name']) + player.lives += 1 + self.lives -= 1 + player.notify_self() + self.sio.emit('chat_message', room=self.game.name, data=f'_fratelli_sangue|{self.name}|{player.name}') + except: pass + self.play_turn() + elif self.is_using_checchino and self.game.check_event(ce.Cecchino): + try: + if self.available_cards[card_index]['name'] != '': + for _ in range(2): + card = next(c for c in self.hand if c.name == cs.Bang(0,0).name) + self.hand.remove(card) + self.game.deck.scrap(card, True) + self.pending_action = PendingAction.PLAY + self.game.attack(self, self.available_cards[card_index]['name'], double=True) + except: + self.pending_action = PendingAction.PLAY + self.is_using_checchino = False + self.notify_self() + elif self.using_rimbalzo > 0 and self.game.check_event(ce.Rimbalzo): + if self.using_rimbalzo == 1 and 'name' in self.available_cards[card_index]: + self.rimbalzo_p = self.available_cards[card_index]['name'] + self.available_cards = self.game.get_player_named(self.available_cards[card_index]['name']).equipment + self.choose_text = 'choose_rimbalzo_card' + self.using_rimbalzo = 2 + elif self.using_rimbalzo == 2 and 'name' in self.available_cards[card_index].__dict__: + card = next(c for c in self.hand if c.name == cs.Bang(0,0).name) + self.hand.remove(card) + self.game.deck.scrap(card, True) + self.using_rimbalzo = 0 + self.available_cards = [] + self.pending_action = PendingAction.PLAY + self.game.rimbalzo(self, self.rimbalzo_p, card_index) + else: + self.using_rimbalzo = 0 + self.rimbalzo_p = '' + self.pending_action = PendingAction.PLAY + self.notify_self() + elif self.is_playing_ranch and self.game.check_event(ce.Ranch): + if card_index == len(self.available_cards) - 1: + self.hand = [c for c in self.hand if c not in self.discarded_cards] + for i in range(len(self.discarded_cards)): + self.game.deck.scrap(self.discarded_cards[i], True) + self.hand.append(self.game.deck.draw()) + self.discarded_cards = [] + self.is_playing_ranch = False + self.pending_action = PendingAction.PLAY + else: + self.discarded_cards.append(self.available_cards.pop(card_index)) + self.notify_self() + elif self.is_drawing and self.game.check_event(ce.Peyote): + self.is_drawing = False + card = self.game.deck.draw() + self.sio.emit('chat_message', room=self.game.name, data=f"_guess|{self.name}|{self.available_cards[card_index]['icon']}") + self.available_cards = [] + if card_index == card.suit%2: + self.hand.append(card) + self.sio.emit('chat_message', room=self.game.name, data=f"_guess_right|{self.name}") + self.pending_action = PendingAction.DRAW + else: + self.game.deck.scrap(card) + self.sio.emit('chat_message', room=self.game.name, data=f"_guess_wrong|{self.name}") + self.pending_action = PendingAction.PLAY + self.notify_self() # specifico per personaggio elif self.is_drawing and isinstance(self.character, chars.KitCarlson): self.hand.append(self.available_cards.pop(card_index)) @@ -515,23 +651,59 @@ class Player: self.mancato_needed -= 1 self.notify_self() if self.mancato_needed <= 0: - self.game.responders_did_respond_resume_turn() + self.game.responders_did_respond_resume_turn(did_lose=False) return if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Bang)) or isinstance(self.character, chd.ElenaFuente)]) == 0\ and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0: self.take_damage_response() - self.game.responders_did_respond_resume_turn() + self.game.responders_did_respond_resume_turn(did_lose=True) else: self.pending_action = PendingAction.RESPOND - self.expected_response = self.game.deck.mancato_cards - if isinstance(self.character, chd.ElenaFuente): + self.expected_response = self.game.deck.mancato_cards.copy() + if isinstance(self.character, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response: + self.expected_response.append(cs.Bang(0, 0).name) + elif isinstance(self.character, chd.ElenaFuente): self.expected_response = self.game.deck.all_cards_str self.on_failed_response_cb = self.take_damage_response self.notify_self() - def get_banged(self, attacker, double=False): + def barrel_pick_no_dmg(self): + pickable_cards = 1 + self.character.pick_mod + if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and isinstance(self.character, chars.Jourdonnais): + pickable_cards = 2 + while pickable_cards > 0: + pickable_cards -= 1 + picked: cs.Card = self.game.deck.pick_and_scrap() + print(f'Did pick {picked}') + self.sio.emit('chat_message', room=self.game.name, + data=f'_flipped|{self.name}|{picked}') + if picked.suit == cs.Suit.HEARTS: + self.mancato_needed -= 1 + self.notify_self() + if self.mancato_needed <= 0: + self.game.responders_did_respond_resume_turn(did_lose=False) + return + if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Bang)) or isinstance(self.character, chd.ElenaFuente)]) == 0\ + and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0: + self.take_no_damage_response() + self.game.responders_did_respond_resume_turn(did_lose=True) + else: + self.pending_action = PendingAction.RESPOND + self.expected_response = self.game.deck.mancato_cards.copy() + if isinstance(self.character, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response: + self.expected_response.append(cs.Bang(0, 0).name) + elif isinstance(self.character, chd.ElenaFuente): + self.expected_response = self.game.deck.all_cards_str + self.on_failed_response_cb = self.take_no_damage_response + self.notify_self() + + def get_banged(self, attacker, double=False, no_dmg=False, card_index=None): self.attacker = attacker self.mancato_needed = 1 if not double else 2 + if card_index != None: + self.dmg_card_index = card_index + else: + self.dmg_card_index = -1 for i in range(len(self.equipment)): if self.equipment[i].can_be_used_now: print('usable', self.equipment[i]) @@ -539,26 +711,38 @@ class Player: and len([c for c in self.hand if isinstance(c, cs.Mancato) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Bang)) or isinstance(self.character, chd.ElenaFuente)]) == 0\ and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0: print('Cant defend') - self.take_damage_response() + if not no_dmg: + self.take_damage_response() + else: + self.take_no_damage_response() return False else: if (not self.game.check_event(ce.Lazo) and len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0) or isinstance(self.character, chars.Jourdonnais): print('has barrel') self.pending_action = PendingAction.PICK - self.on_pick_cb = self.barrel_pick + if not no_dmg: + self.on_pick_cb = self.barrel_pick + else: + self.on_pick_cb = self.barrel_pick_no_dmg else: print('has mancato') self.pending_action = PendingAction.RESPOND - self.expected_response = self.game.deck.mancato_cards - if self.attacker and isinstance(self.attacker.character, chd.BelleStar) or self.game.check_event(ce.Lazo): + self.expected_response = self.game.deck.mancato_cards.copy() + if self.attacker and self.attacker in self.game.players and isinstance(self.attacker.character, chd.BelleStar) or self.game.check_event(ce.Lazo): self.expected_response = self.game.deck.mancato_cards_not_green - if isinstance(self.character, chd.ElenaFuente): + elif isinstance(self.character, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response: + self.expected_response.append(cs.Bang(0, 0).name) + elif isinstance(self.character, chd.ElenaFuente): self.expected_response = self.game.deck.all_cards_str - self.on_failed_response_cb = self.take_damage_response + if not no_dmg: + self.on_failed_response_cb = self.take_damage_response + else: + self.on_failed_response_cb = self.take_no_damage_response return True def get_indians(self, attacker): self.attacker = attacker + if isinstance(self.character, chd.ApacheKid): return False if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0: print('Cant defend') self.take_damage_response() @@ -567,6 +751,8 @@ class Player: print('has bang') self.pending_action = PendingAction.RESPOND self.expected_response = [cs.Bang(0, 0).name] + if isinstance(self.character, chars.CalamityJanet) and cs.Mancato(0, 0).name not in self.expected_response: + self.expected_response.append(cs.Mancato(0, 0).name) self.event_type = 'indians' self.on_failed_response_cb = self.take_damage_response return True @@ -576,39 +762,55 @@ class Player: if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0: print('Cant defend') self.take_damage_response() - self.game.responders_did_respond_resume_turn() + self.game.responders_did_respond_resume_turn(did_lose=True) return False else: self.pending_action = PendingAction.RESPOND self.expected_response = [cs.Bang(0, 0).name] + if isinstance(self.character, chars.CalamityJanet) and cs.Mancato(0, 0).name not in self.expected_response: + self.expected_response.append(cs.Mancato(0, 0).name) self.event_type = 'duel' self.on_failed_response_cb = self.take_damage_response return True + def heal_if_needed(self): + while self.lives <= 0 and len(self.game.players) > 2 and len([c for c in self.hand if isinstance(c, cs.Birra)]) > 0: + for i in range(len(self.hand)): + if isinstance(self.hand[i], cs.Birra): + if isinstance(self.character, chd.MollyStark) and not self.is_my_turn: + self.hand.append(self.game.deck.draw(True)) + self.lives += 1 if not isinstance(self.character, chd.TequilaJoe) else 2 + self.game.deck.scrap(self.hand.pop(i), True) + self.sio.emit('chat_message', room=self.game.name, + data=f'_beer_save|{self.name}') + break + def take_damage_response(self): self.lives -= 1 if self.lives > 0: if isinstance(self.character, chars.BartCassidy): self.sio.emit('chat_message', room=self.game.name, data=f'_special_bart_cassidy|{self.name}') - self.hand.append(self.game.deck.draw()) - elif isinstance(self.character, chars.ElGringo) and self.attacker and len(self.attacker.hand) > 0: + self.hand.append(self.game.deck.draw(True)) + elif isinstance(self.character, chars.ElGringo) and self.attacker and self.attacker in self.game.players and len(self.attacker.hand) > 0: self.hand.append(self.attacker.hand.pop( randrange(0, len(self.attacker.hand)))) self.sio.emit('chat_message', room=self.game.name, data=f'_special_el_gringo|{self.name}|{self.attacker.name}') self.attacker.notify_self() - while self.lives <= 0 and len(self.game.players) > 2 and len([c for c in self.hand if isinstance(c, cs.Birra)]) > 0: - for i in range(len(self.hand)): - if isinstance(self.hand[i], cs.Birra): - if isinstance(self.character, chd.MollyStark) and not self.is_my_turn: - self.hand.append(self.game.deck.draw()) - self.lives += 1 - self.game.deck.scrap(self.hand.pop(i)) - self.sio.emit('chat_message', room=self.game.name, - data=f'_beer_save|{self.name}') - break + self.heal_if_needed() self.mancato_needed = 0 + self.expected_response = [] + self.event_type = '' + self.notify_self() + self.attacker = None + + def take_no_damage_response(self): + if self.dmg_card_index != None and self.dmg_card_index != -1 and self.game.check_event(ce.Rimbalzo): + self.game.deck.scrap(self.equipment.pop(self.dmg_card_index)) + self.dmg_card_index = -1 + self.mancato_needed = 0 + self.expected_response = [] self.event_type = '' self.notify_self() self.attacker = None @@ -616,14 +818,14 @@ class Player: def respond(self, hand_index): if self.pending_action != PendingAction.RESPOND: return self.pending_action = PendingAction.WAIT - if hand_index != -1 and ( + if hand_index != -1 and hand_index < (len(self.hand)+len(self.equipment)) and ( ((hand_index < len(self.hand) and self.hand[hand_index].name in self.expected_response)) or - self.equipment[hand_index-len(self.hand)].name in self.expected_response): + (hand_index-len(self.hand) < len(self.equipment) and self.equipment[hand_index-len(self.hand)].name in self.expected_response)): card = self.hand.pop(hand_index) if hand_index < len(self.hand) else self.equipment.pop(hand_index-len(self.hand)) if isinstance(self.character, chd.MollyStark) and hand_index < len(self.hand)+1 and not self.is_my_turn and self.event_type != 'duel': - self.hand.append(self.game.deck.draw()) + self.hand.append(self.game.deck.draw(True)) card.use_card(self) - self.game.deck.scrap(card) + self.game.deck.scrap(card, True) self.notify_self() self.mancato_needed -= 1 if self.mancato_needed <= 0: @@ -632,24 +834,25 @@ class Player: if isinstance(self.character, chd.MollyStark) and hand_index < len(self.hand)+1 and not self.is_my_turn: self.molly_discarded_cards += 1 else: - self.game.responders_did_respond_resume_turn() + self.game.responders_did_respond_resume_turn(did_lose=False) self.event_type = '' + self.expected_response = [] else: self.pending_action = PendingAction.RESPOND self.notify_self() else: if isinstance(self.character, chd.MollyStark) and not self.is_my_turn: for i in range(self.molly_discarded_cards): - self.hand.append(self.game.deck.draw()) + self.hand.append(self.game.deck.draw(True)) self.molly_discarded_cards = 0 self.notify_self() - elif self.attacker and isinstance(self.attacker.character, chd.MollyStark) and self.is_my_turn: + elif self.attacker and self.attacker in self.game.players and isinstance(self.attacker.character, chd.MollyStark) and self.is_my_turn: for i in range(self.attacker.molly_discarded_cards): - self.attacker.hand.append(self.attacker.game.deck.draw()) + self.attacker.hand.append(self.attacker.game.deck.draw(True)) self.attacker.molly_discarded_cards = 0 self.attacker.notify_self() self.on_failed_response_cb() - self.game.responders_did_respond_resume_turn() + self.game.responders_did_respond_resume_turn(did_lose=True) if self.mancato_needed <= 0: self.attacker = None @@ -685,8 +888,8 @@ class Player: self.scrapped_cards = 0 self.lives = min(self.lives+1, self.max_lives) elif isinstance(self.character, chd.JoseDelgrado) and card.is_equipment and self.special_use_count < 2: - self.hand.append(self.game.deck.draw()) - self.hand.append(self.game.deck.draw()) + self.hand.append(self.game.deck.draw(True)) + self.hand.append(self.game.deck.draw(True)) self.special_use_count += 1 self.game.deck.scrap(card) self.notify_self() @@ -696,15 +899,15 @@ class Player: self.special_use_count += 1 cards = sorted(data['cards'], reverse=True) for c in cards: - self.game.deck.scrap(self.hand.pop(c)) + self.game.deck.scrap(self.hand.pop(c), True) self.notify_self() self.game.attack(self, data['against']) def chuck_lose_hp_draw(self): if isinstance(self.character, chd.ChuckWengam) and self.lives > 1 and self.is_my_turn: self.lives -= 1 - self.hand.append(self.game.deck.draw()) - self.hand.append(self.game.deck.draw()) + self.hand.append(self.game.deck.draw(True)) + self.hand.append(self.game.deck.draw(True)) self.notify_self() def end_turn(self, forced=False): @@ -715,11 +918,11 @@ class Player: print( f"I {self.name} have to many cards in my hand and I can't end the turn") elif self.pending_action == PendingAction.PLAY or forced: - if not forced and self.game.check_event(ce.Vendetta): + if not forced and self.game.check_event(ce.Vendetta) and self.can_play_vendetta: picked: cs.Card = self.game.deck.pick_and_scrap() self.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked}') if picked.suit == cs.Suit.HEARTS: - self.play_turn() + self.play_turn(can_play_vendetta=False) return self.is_my_turn = False for i in range(len(self.equipment)): diff --git a/frontend/src/App.vue b/frontend/src/App.vue index df07892..dce6cda 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -16,6 +16,15 @@ +
+ A new version is available. Refresh to load it? +
+