diff --git a/backend/__init__.py b/backend/__init__.py index b49947d..ed34fa0 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -15,7 +15,7 @@ sio = socketio.Server(cors_allowed_origins="*") static_files={ '/': {'content_type': 'text/html', 'filename': 'index.html'}, '/game': {'content_type': 'text/html', 'filename': 'index.html'}, - '/robots.txt': {'content_type': 'text/html', 'filename': 'robots.txt'}, + # '/robots.txt': {'content_type': 'text/html', 'filename': 'robots.txt'}, '/favicon.ico': {'filename': 'favicon.ico'}, '/img/icons': './img/icons', '/manifest.json': {'filename': 'manifest.json'}, @@ -42,18 +42,21 @@ def connect(sid, environ): @sio.event def set_username(sid, username): - if not isinstance(sio.get_session(sid), Player): + ses = sio.get_session(sid) + if not isinstance(ses, Player): sio.save_session(sid, Player(username, sid, sio)) print(f'{sid} is now {username}') advertise_lobbies() - elif sio.get_session(sid).game == None or not sio.get_session(sid).game.started: + elif ses.game == None or not ses.game.started: print(f'{sid} changed username to {username}') - if len([p for p in sio.get_session(sid).game.players if p.name == username]) > 0: - sio.get_session(sid).name = f'{username}_{random.randint(0,100)}' + prev = ses.name + if len([p for p in ses.game.players if p.name == username]) > 0: + ses.name = f"{username}_{random.randint(0,100)}" else: - sio.get_session(sid).name = username - sio.emit('me', data=sio.get_session(sid).name, room=sid) - sio.get_session(sid).game.notify_room() + ses.name = username + sio.emit('chat_message', room=ses.game.name, data=f'_change_username|{prev}|{ses.name}') + sio.emit('me', data=ses.name, room=sid) + ses.game.notify_room() @sio.event def get_me(sid, room): @@ -80,8 +83,10 @@ def get_me(sid, room): de_games[0].notify_all() sio.emit('role', room=sid, data=json.dumps(bot.role, default=lambda o: o.__dict__)) bot.notify_self() + if len(bot.available_characters) > 0: + bot.set_available_character(bot.available_characters) else: #spectate - de_games[0].dead_players.append(sio.get_session(sid)) + de_games[0].spectators.append(sio.get_session(sid)) sio.get_session(sid).game = de_games[0] sio.enter_room(sid, de_games[0].name) de_games[0].notify_room(sid) @@ -96,6 +101,7 @@ def get_me(sid, room): if room['username'] == None or any([p.name == room['username'] for p in sio.get_session(sid).game.players]): sio.emit('change_username', room=sid) else: + sio.emit('chat_message', room=sio.get_session(sid).game.name, data=f"_change_username|{sio.get_session(sid).name}|{room['username']}") sio.get_session(sid).name = room['username'] sio.emit('me', data=sio.get_session(sid).name, room=sid) if not sio.get_session(sid).game.started: @@ -166,10 +172,13 @@ def chat_message(sid, msg): if msg[0] == '/': if '/addbot' in msg and not ses.game.started: if len(msg.split()) > 1: - for _ in range(int(msg.split()[1])): - ses.game.add_player(Player(f'AI_{random.randint(0,100)}', 'bot', sio, bot=True)) + # for _ in range(int(msg.split()[1])): + # ses.game.add_player(Player(f'AI_{random.randint(0,1000)}', 'bot', sio, bot=True)) + sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'Only 1 bot at the time'}) else: - ses.game.add_player(Player(f'AI_{random.randint(0,100)}', 'bot', sio, bot=True)) + bot = Player(f'AI_{random.randint(0,1000)}', 'bot', sio, bot=True) + ses.game.add_player(bot) + bot.bot_spin() elif '/removebot' in msg and not ses.game.started: if any([p.is_bot for p in ses.game.players]): [p for p in ses.game.players if p.is_bot][-1].disconnect() @@ -228,14 +237,21 @@ def chat_message(sid, msg): ses.character = [c for c in chs if c.name == ' '.join(cmd[1:])][0] ses.real_character = ses.character ses.notify_self() + elif '/setevent' in msg and ses.game and ses.game.deck: + cmd = msg.split() + if len(cmd) >= 3: + sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and changed event'}) + chs = ses.game.deck.event_cards + ses.game.deck.event_cards.insert(int(cmd[1]), [c for c in chs if c!=None and c.name == ' '.join(cmd[2:])][0]) + ses.game.notify_event_card() 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]): + if int(cmd[1]) < len(ses.hand): ses.hand.pop(int(cmd[1])) else: - ses.hand.pop(int(cmd[1])-len(ses.hand)) + ses.equipment.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'}) @@ -243,15 +259,17 @@ def chat_message(sid, msg): 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() + card_names = ' '.join(cmd[1:]).split(',') + for cn in card_names: + ses.hand.append([c for c in cards if c.name == cn][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() + ses.bot_spin() 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 20816d4..94d8ba9 100644 --- a/backend/bang/cards.py +++ b/backend/bang/cards.py @@ -75,11 +75,21 @@ class Card(ABC): def is_duplicate_card(self, player): return self.name in [c.name for c in player.equipment] + def check_suit(self, game, accepted): + import bang.expansions.high_noon.card_events as ceh + 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 = 'πŸ›’' + 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" @@ -88,6 +98,7 @@ class Dinamite(Card): def __init__(self, suit, number): 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" @@ -96,6 +107,7 @@ class Mirino(Card): def __init__(self, suit, number): 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" @@ -104,6 +116,7 @@ class Mustang(Card): def __init__(self, suit, number): 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" @@ -115,6 +128,7 @@ class Prigione(Card): 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 against != None and not isinstance(player.game.get_player_named(against).role, r.Sheriff): @@ -180,14 +194,19 @@ class Bang(Card): def play_card(self, player, against, _with=None): import bang.expansions.fistful_of_cards.card_events as ce + import bang.expansions.high_noon.card_events as ceh + if player.game.check_event(ceh.Sermone): + return False if player.has_played_bang and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)) and against != None: return False elif against != None: import bang.characters as chars super().play_card(player, against=against) - player.has_played_bang = not isinstance( - player.character, chars.WillyTheKid) - player.game.attack(player, against, double=isinstance(player.character, chars.SlabTheKiller)) + 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)) return True return False @@ -200,14 +219,17 @@ class Birra(Card): 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" def play_card(self, player, against, _with=None): - if len(player.game.players) != 2: + import bang.expansions.high_noon.card_events as ceh + if player.game.check_event(ceh.IlReverendo): + return False + if len(player.game.get_alive_players()) != 2: super().play_card(player, against=against) player.lives = min(player.lives+1, player.max_lives) import bang.expansions.dodge_city.characters as chd - if isinstance(player.character, chd.TequilaJoe): + if player.character.check(player.game, chd.TequilaJoe): player.lives = min(player.lives+1, player.max_lives) return True - elif len(player.game.players) == 2: + elif len(player.game.get_alive_players()) == 2: player.sio.emit('chat_message', room=player.game.name, data=f'_spilled_beer|{player.name}|{self.name}') return True @@ -239,6 +261,7 @@ class Diligenza(Card): def __init__(self, suit, number): 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." @@ -285,6 +308,7 @@ class Gatling(Card): self.icon = 'πŸ›°' self.desc = "Spara a tutti gli altri giocatori" self.desc_eng = "Shoot all the other players" + self.alt_text = "πŸ‘₯πŸ’₯" def play_card(self, player, against, _with=None): super().play_card(player, against=against) @@ -314,10 +338,17 @@ class Mancato(Card): def play_card(self, player, against, _with=None): import bang.characters as chars - if (not player.has_played_bang and against != None and isinstance(player.character, chars.CalamityJanet)): + if against != None and player.character.check(player.game, chars.CalamityJanet): + import bang.expansions.fistful_of_cards.card_events as ce + 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 + import bang.expansions.high_noon.card_events as ceh + if player.game.check_event(ceh.Sermone): + return False player.sio.emit('chat_message', room=player.game.name, data=f'_special_calamity|{player.name}|{self.name}|{against}') - player.has_played_bang = True + 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) return True return False @@ -349,11 +380,12 @@ class Saloon(Card): 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.alt_text = "πŸ‘₯🍺" def play_card(self, player, against, _with=None): player.sio.emit('chat_message', room=player.game.name, data=f'_saloon|{player.name}|{self.name}') - for p in player.game.players: + for p in player.game.get_alive_players(): p.lives = min(p.lives+1, p.max_lives) p.notify_self() return True @@ -365,6 +397,7 @@ class WellsFargo(Card): self.desc = "Pesca 3 carte dalla cima del mazzo" self.desc_eng = "Draw 3 cards from the deck" self.icon = 'πŸ’Έ' + self.alt_text = "🎴🎴🎴" def play_card(self, player, against, _with=None): player.sio.emit('chat_message', room=player.game.name, diff --git a/backend/bang/characters.py b/backend/bang/characters.py index ea6c0ce..a72df42 100644 --- a/backend/bang/characters.py +++ b/backend/bang/characters.py @@ -13,21 +13,11 @@ class Character(ABC): self.icon = 'πŸ€·β€β™‚οΈ' self.number = ''.join(['❀️']*self.max_lives) - # @abstractmethod - # def on_hurt(self, dmg: int): - # pass - - # @abstractmethod - # def on_pick(self, card): # tipo dinamite e prigione - # pass - - # @abstractmethod - # def on_empty_hand(self): - # pass - - # @abstractmethod - # def on_empty_hand(self): - # pass + def check(self, game, character): + import bang.expansions.high_noon.card_events as ceh + if game.check_event(ceh.Sbornia): + return False + return isinstance(self, character) class BartCassidy(Character): def __init__(self): diff --git a/backend/bang/deck.py b/backend/bang/deck.py index 136127d..6b85276 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -2,6 +2,7 @@ from typing import List, Set, Dict, Tuple, Optional import random import bang.cards as cs import bang.expansions.fistful_of_cards.card_events as ce +import bang.expansions.high_noon.card_events as ceh class Deck: def __init__(self, game): @@ -20,16 +21,25 @@ class Deck: self.all_cards_str.append(c.name) self.game = game self.event_cards: List[ce.CardEvent] = [] + endgame_cards = [] if 'fistful_of_cards' in game.expansions: self.event_cards.extend(ce.get_all_events()) + endgame_cards.append(ce.get_endgame_card()) + if 'high_noon' in game.expansions: + self.event_cards.extend(ceh.get_all_events()) + endgame_cards.append(ceh.get_endgame_card()) + if len(self.event_cards) > 0: + random.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.append(random.choice(endgame_cards)) 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 and not isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte): + if len(self.event_cards) > 0 and not (isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte) or isinstance(self.event_cards[0], ceh.MezzogiornoDiFuoco)): self.event_cards.append(self.event_cards.pop(0)) self.game.notify_event_card() diff --git a/backend/bang/expansions/dodge_city/cards.py b/backend/bang/expansions/dodge_city/cards.py index 051ada6..8b79931 100644 --- a/backend/bang/expansions/dodge_city/cards.py +++ b/backend/bang/expansions/dodge_city/cards.py @@ -16,6 +16,7 @@ class Pugno(Card): def __init__(self, suit, number): super().__init__(suit, 'Pugno!', number, range=1) self.icon = 'πŸ‘Š' + self.alt_text = "1πŸ”Ž πŸ’₯" self.desc = "Spara a un giocatore a distanza 1" self.desc_eng = "Shoot a player at distance 1" self.need_target = True @@ -34,7 +35,7 @@ class Schivata(Mancato): self.icon = 'πŸ™…β€β™‚οΈ' self.desc += " e poi pesca una carta" self.desc_eng += " and then draw a card." - self.alt_text = 'β˜οΈπŸ†“' + self.alt_text = "πŸ˜… | 🎴" def play_card(self, player, against, _with=None): return False @@ -51,7 +52,7 @@ class RagTime(Panico): self.desc_eng = "Steal a card from another player at any distance" self.need_target = True self.need_with = True - self.alt_text = '2πŸƒ' + self.alt_text = '2πŸƒ | πŸ‘€πŸ˜±' def play_card(self, player, against, _with): if against != None and _with != None: @@ -69,7 +70,7 @@ class Rissa(CatBalou): self.desc_eng = "Choose a card to discard from the hand/equipment of all the other players" self.need_with = True self.need_target = False - self.alt_text = '2πŸƒ' + self.alt_text = '2πŸƒ | πŸ‘€πŸ’ƒ' def play_card(self, player, against, _with): if _with != None: @@ -91,7 +92,7 @@ class SpringField(Card): self.desc_eng = "Shoot a player at any distance" self.need_target = True self.need_with = True - self.alt_text = '2πŸƒ' + self.alt_text = '2πŸƒ | πŸ‘€πŸ’₯' def play_card(self, player, against, _with=None): if against != None and _with != None: @@ -110,7 +111,7 @@ class Tequila(Card): self.need_target = True self.can_target_self = True self.need_with = True - self.alt_text = '2πŸƒ' + self.alt_text = "2πŸƒ | πŸ‘€πŸΊ" def play_card(self, player, against, _with=None): if against != None and _with != None: @@ -128,7 +129,7 @@ class Whisky(Card): self.desc = "Gioca questa carta per recuperare fino a 2 punti vita" self.desc_eng = "Heal 2 HP" self.need_with = True - self.alt_text = '2πŸƒ' + self.alt_text = '2πŸƒ | 🍺🍺' def play_card(self, player, against, _with=None): if _with != None: @@ -165,6 +166,7 @@ class Cappello(Mancato): self.icon = '🧒' self.usable_next_turn = True self.can_be_used_now = False + self.alt_text = "πŸ˜…" def play_card(self, player, against, _with=None): if self.can_be_used_now: @@ -212,7 +214,7 @@ class Derringer(Pugnale): super().__init__(suit, number) self.name = 'Derringer' self.icon = '🚬' - self.alt_text += ' β˜οΈπŸ†“' + self.alt_text += ' 🎴' self.desc += ' e poi pesca una carta' self.desc_eng += ' and then draw a card.' @@ -237,6 +239,7 @@ class Borraccia(Card): self.icon = '🍼' self.desc = 'Recupera 1 vita' self.desc_eng = 'Regain 1 HP' + self.alt_text = "🍺" self.usable_next_turn = True self.can_be_used_now = False @@ -258,6 +261,7 @@ class PonyExpress(WellsFargo): super().__init__(suit, number) self.name = 'Pony Express' self.icon = 'πŸ¦„' + self.alt_text = "🎴🎴🎴" self.usable_next_turn = True self.can_be_used_now = False @@ -276,6 +280,7 @@ class Howitzer(Gatling): super().__init__(suit, number) self.name = 'Howitzer' self.icon = 'πŸ“‘' + self.alt_text = "πŸ‘₯πŸ’₯" self.usable_next_turn = True self.can_be_used_now = False @@ -292,8 +297,9 @@ class Howitzer(Gatling): class CanCan(CatBalou): def __init__(self, suit, number): super().__init__(suit, number) - self.name = 'Can Can' - self.icon = 'πŸ‘―β€β™€οΈ' + self.name = "Can Can" + self.icon = "πŸ‘―β€β™€οΈ" + self.alt_text = "πŸ‘€πŸ’ƒ" self.usable_next_turn = True self.can_be_used_now = False @@ -310,9 +316,10 @@ class CanCan(CatBalou): class Conestoga(Panico): def __init__(self, suit, number): Card.__init__(self, suit, 'Conestoga', number) - self.icon = 'πŸ•' + self.icon = "πŸ•" self.desc = "Ruba 1 carta da un giocatore a prescindere dalla distanza" self.desc_eng = "Steal a card from another player at any distance" + self.alt_text = "πŸ‘€πŸ˜±" self.need_target = True self.usable_next_turn = True self.can_be_used_now = False @@ -332,6 +339,7 @@ class Pepperbox(Bang): super().__init__(suit, number) self.name = 'Pepperbox' self.icon = '🌢' + self.alt_text = "πŸ’₯" self.usable_next_turn = True self.can_be_used_now = False @@ -354,6 +362,7 @@ class FucileDaCaccia(Card): super().__init__(suit, 'Fucile Da Caccia', number) self.icon = 'πŸŒ‚' self.desc = "Spara a un giocatore a prescindere dalla distanza" + self.alt_text = "πŸ‘€πŸ’₯" self.need_target = True self.usable_next_turn = True self.can_be_used_now = False diff --git a/backend/bang/expansions/dodge_city/characters.py b/backend/bang/expansions/dodge_city/characters.py index 4655a1d..feb3623 100644 --- a/backend/bang/expansions/dodge_city/characters.py +++ b/backend/bang/expansions/dodge_city/characters.py @@ -24,7 +24,7 @@ class GregDigger(Character): class HerbHunter(Character): def __init__(self): - super().__init__("HerbHunter", max_lives=4) + super().__init__("Herb Hunter", max_lives=4) self.desc = "Quando un giocatore muore, pesca 2 carte" self.desc_eng = "Whenever a player dies, he draws 2 cards" self.icon = '⚰️' @@ -66,7 +66,7 @@ class SeanMallory(Character): class BelleStar(Character): def __init__(self): - super().__init__("Belle Star", max_lives=3) + super().__init__("Belle Star", max_lives=4) self.desc = "Nel suo turno le carte verdi degli altri giocatori non hanno effetto." self.desc_eng = "During her turn the green cards of the other players do not work." self.icon = '❎' diff --git a/backend/bang/expansions/fistful_of_cards/card_events.py b/backend/bang/expansions/fistful_of_cards/card_events.py index ed14be4..6059083 100644 --- a/backend/bang/expansions/fistful_of_cards/card_events.py +++ b/backend/bang/expansions/fistful_of_cards/card_events.py @@ -22,7 +22,7 @@ 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" + self.desc_eng = "The first player that died returns back to life with 2 hp and 2 cards" class FratelliDiSangue(CardEvent): def __init__(self): @@ -97,6 +97,11 @@ class Vendetta(CardEvent): 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_endgame_card(): + end_game = PerUnPugnoDiCarte() + end_game.expansion = 'fistful-of-cards' + return end_game + def get_all_events(): cards = [ Agguato(), @@ -115,7 +120,6 @@ def get_all_events(): Vendetta(), ] 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/expansions/high_noon/card_events.py b/backend/bang/expansions/high_noon/card_events.py new file mode 100644 index 0000000..e0e0f67 --- /dev/null +++ b/backend/bang/expansions/high_noon/card_events.py @@ -0,0 +1,119 @@ +import random +from bang.expansions.fistful_of_cards.card_events import CardEvent + +class Benedizione(CardEvent): + def __init__(self): + super().__init__("Benedizione", "πŸ™") + self.desc = "Tutte le carte sono considerate di cuori β™₯️" + self.desc_eng = "All cards are of hearts β™₯️" + +class Maledizione(CardEvent): + def __init__(self): + super().__init__("Maledizione", "🀬") + self.desc = "Tutte le carte sono considerate di picche β™ " + self.desc_eng = "All cards are of spades β™ " + +class Sbornia(CardEvent): + def __init__(self): + super().__init__("Sbornia", "πŸ₯΄") + self.desc = "I personaggi perdono le loro abilitΓ  speciali" + self.desc_eng = "The characters lose their special abilities" + +class Sete(CardEvent): + 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" + +class IlTreno(CardEvent): + 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" + +class IlReverendo(CardEvent): + def __init__(self): + super().__init__("Il Reverendo", "β›ͺ️") + self.desc = "Non si possono giocare le carte Birra" + self.desc_eng = "Beers can't be played" + +class IlDottore(CardEvent): + 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" + +class Sermone(CardEvent): + 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" + +class Sparatoria(CardEvent): + 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" + +class CorsaAllOro(CardEvent): + 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" + +class IDalton(CardEvent): + 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" + +class Manette(CardEvent): + 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 = "" + +class NuovaIdentita(CardEvent): + def __init__(self): + super().__init__("Nuova IdentitΓ ", "πŸ•Ά") + 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 = "" + +class CittaFantasma(CardEvent): + 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." + +class MezzogiornoDiFuoco(CardEvent): + 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" + +def get_endgame_card(): + end_game = MezzogiornoDiFuoco() + end_game.expansion = 'high-noon' + return end_game + +def get_all_events(): + cards = [ + Benedizione(), + Maledizione(), + CittaFantasma(), + CorsaAllOro(), + IDalton(), + IlDottore(), + IlReverendo(), + IlTreno(), + Sbornia(), + Sermone(), + Sete(), + Sparatoria(), + # Manette(), + # NuovaIdentita(), + ] + random.shuffle(cards) + for c in cards: + c.expansion = 'high-noon' + return cards \ No newline at end of file diff --git a/backend/bang/game.py b/backend/bang/game.py index b4a210b..f137ced 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -2,12 +2,14 @@ from typing import List, Set, Dict, Tuple, Optional import random import socketio +import eventlet + import bang.players as pl import bang.characters as characters from bang.deck import Deck import bang.roles as roles import bang.expansions.fistful_of_cards.card_events as ce -import eventlet +import bang.expansions.high_noon.card_events as ceh class Game: def __init__(self, name, sio:socketio): @@ -15,7 +17,7 @@ class Game: self.sio = sio self.name = name self.players: List[pl.Player] = [] - self.dead_players: List[pl.Player] = [] + self.spectators: List[pl.Player] = [] self.deck: Deck = None self.started = False self.turn = 0 @@ -24,25 +26,29 @@ class Game: self.initial_players = 0 self.password = '' self.expansions = [] - self.available_expansions = ['dodge_city', 'fistful_of_cards'] + self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon'] self.shutting_down = False self.is_competitive = False self.disconnect_bot = True self.player_bangs = 0 self.is_russian_roulette_on = False + self.dalton_on = False self.bot_speed = 1.5 + self.incremental_turn = 0 + self.did_resuscitate_deadman = False + self.is_handling_death = False def notify_room(self, sid=None): if len([p for p in self.players if p.character == None]) != 0 or sid: self.sio.emit('room', room=self.name if not sid else sid, data={ 'name': self.name, 'started': self.started, - 'players': [{'name':p.name, 'ready': p.character != None} for p in self.players], + 'players': [{'name':p.name, 'ready': p.character != None, 'is_bot': p.is_bot} for p in self.players], 'password': self.password, 'is_competitive': self.is_competitive, 'disconnect_bot': self.disconnect_bot, 'expansions': self.expansions, - 'available_expansions': self.available_expansions + 'available_expansions': self.available_expansions, }) def toggle_expansion(self, expansion_name): @@ -148,7 +154,7 @@ class Game: attacker.notify_self() self.waiting_for = 0 self.readyCount = 0 - for p in self.players: + for p in self.get_alive_players(): if p != attacker: if p.get_banged(attacker=attacker): self.waiting_for += 1 @@ -162,7 +168,7 @@ class Game: attacker.notify_self() self.waiting_for = 0 self.readyCount = 0 - for p in self.players: + for p in self.get_alive_players(): if p != attacker: if p.get_indians(attacker=attacker): self.waiting_for += 1 @@ -196,7 +202,8 @@ class Game: self.get_player_named(target_username).notify_self() def emporio(self): - self.available_cards = [self.deck.draw(True) for i in range(len([p for p in self.players if p.lives > 0]))] + pls = self.get_alive_players() + self.available_cards = [self.deck.draw(True) for i in range(len(pls))] self.players[self.turn].pending_action = pl.PendingAction.CHOOSE self.players[self.turn].choose_text = 'choose_card_to_get' self.players[self.turn].available_cards = self.available_cards @@ -207,13 +214,14 @@ class Game: player.available_cards = [] player.pending_action = pl.PendingAction.WAIT player.notify_self() - nextPlayer = self.players[(self.turn + (len(self.players)-len(self.available_cards))) % len(self.players)] + pls = self.get_alive_players() + nextPlayer = pls[(pls.index(self.players[self.turn])+(len(pls)-len(self.available_cards))) % len(pls)] if nextPlayer == self.players[self.turn]: self.players[self.turn].pending_action = pl.PendingAction.PLAY self.players[self.turn].notify_self() else: nextPlayer.pending_action = pl.PendingAction.CHOOSE - self.players[self.turn].choose_text = 'choose_card_to_get' + nextPlayer.choose_text = 'choose_card_to_get' nextPlayer.available_cards = self.available_cards nextPlayer.notify_self() @@ -235,17 +243,20 @@ class Game: self.player_bangs = 0 self.players[self.turn].play_turn() elif self.is_russian_roulette_on and self.check_event(ce.RouletteRussa): + pls = self.get_alive_players() if did_lose: + target_pl = pls[(pls.index(self.players[self.turn]) + self.player_bangs) % len(pls)] 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() + target_pl.lives -= 1 + target_pl.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() + target_pl = pls[(pls.index(self.players[self.turn]) + self.player_bangs) % len(pls)] + print(f'next in line {target_pl.name}') + if target_pl.get_banged(self.deck.event_cards[0]): + target_pl.notify_self() else: self.responders_did_respond_resume_turn(did_lose=True) else: @@ -253,23 +264,44 @@ class Game: if self.readyCount == self.waiting_for: self.waiting_for = 0 self.readyCount = 0 - self.players[self.turn].pending_action = pl.PendingAction.PLAY + if self.dalton_on: + self.dalton_on = False + print(f'notifying {self.players[self.turn].name} about his turn') + self.players[self.turn].play_turn() + else: + 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)] + pls = self.get_alive_players() + return pls[(pls.index(self.players[self.turn]) + 1) % len(pls)] def play_turn(self): + self.incremental_turn += 1 + if self.players[self.turn].is_dead: + pl = sorted(self.get_dead_players(), key=lambda x:x.death_turn)[0] + if self.check_event(ce.DeadMan) and not self.did_resuscitate_deadman and pl == self.players[self.turn]: + print(f'{self.players[self.turn]} is dead, revive') + self.did_resuscitate_deadman = True + pl.is_dead = False + pl.is_ghost = False + pl.lives = 2 + pl.hand.append(self.deck.draw()) + pl.hand.append(self.deck.draw()) + pl.notify_self() + elif self.check_event(ceh.CittaFantasma): + print(f'{self.players[self.turn]} is dead, event ghost') + self.players[self.turn].is_ghost = True + else: + print(f'{self.players[self.turn]} is dead, next turn') + return self.next_turn() 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: - self.players.append(self.dead_players.pop(0)) - self.players[-1].lives = 2 - self.players[-1].hand.append(self.deck.draw()) - 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() + if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] != None: + print(f'flip new event {self.deck.event_cards[0].name}') + if self.check_event(ce.DeadMan): + self.did_resuscitate_deadman = False elif self.check_event(ce.RouletteRussa): self.is_russian_roulette_on = True if self.players[self.turn].get_banged(self.deck.event_cards[0]): @@ -277,6 +309,26 @@ class Game: else: self.responders_did_respond_resume_turn(did_lose=True) return + elif self.check_event(ceh.IlDottore): + most_hurt = [p.lives for p in self.players if p.lives > 0 and p.max_lives > p.lives] + if len(most_hurt) > 0: + hurt_players = [p for p in self.players if p.lives == min(most_hurt)] + for p in hurt_players: + p.lives += 1 + self.sio.emit('chat_message', room=self.name, data=f'_doctor_heal|{p.name}') + p.notify_self() + elif self.check_event(ceh.IDalton): + self.waiting_for = 0 + self.readyCount = 0 + self.dalton_on = True + for p in self.players: + if p.get_dalton(): + self.waiting_for += 1 + p.notify_self() + if self.waiting_for != 0: + return + self.dalton_on = False + 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]): @@ -284,12 +336,18 @@ class Game: else: self.responders_did_respond_resume_turn() else: + print(f'notifying {self.players[self.turn].name} about his turn') self.players[self.turn].play_turn() def next_turn(self): if self.shutting_down: return - if len(self.players) > 0: - self.turn = (self.turn + 1) % len(self.players) + print(f'{self.players[self.turn].name} invoked next turn') + pls = self.get_alive_players() + if len(pls) > 0: + if self.check_event(ceh.CorsaAllOro): + self.turn = (self.turn - 1) % len(self.players) + else: + self.turn = (self.turn + 1) % len(self.players) self.play_turn() def notify_event_card(self): @@ -308,26 +366,35 @@ class Game: 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: - player.is_bot = True - eventlet.sleep(15) # he may reconnect - player.notify_self() - else: - self.player_death(player=player, disconnected=True) + if player in self.spectators: + self.spectators.remove(player) + return False + if player.is_bot and not self.started: + player.game = None + if self.disconnect_bot and self.started: + player.is_bot = True + eventlet.sleep(15) # he may reconnect + if player.is_bot: + if len(player.available_characters) > 0: + player.set_available_character(player.available_characters) + player.bot_spin() else: - self.dead_players.remove(player) - if len([p for p in self.players if not p.is_bot])+len([p for p in self.dead_players if not p.is_bot]) == 0: - print(f'no players left in game {self.name}') + self.player_death(player=player, disconnected=True) + # else: + # player.lives = 0 + # self.players.remove(player) + if len([p for p in self.players if not p.is_bot]) == 0: + print(f'no players left in game {self.name}, shutting down') self.shutting_down = True self.players = [] - self.dead_players = [] + self.spectators = [] self.deck = None return True else: return False def player_death(self, player: pl.Player, disconnected=False): - if not player in self.players: return + if not player in self.players or player.is_ghost: return + self.is_handling_death = True import bang.expansions.dodge_city.characters as chd print(player.attacker) if player.attacker and player.attacker in self.players and isinstance(player.attacker.role, roles.Sheriff) and isinstance(player.role, roles.Vice): @@ -344,15 +411,20 @@ class Game: 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: - self.turn -= 1 + if player.is_dead: return + if not self.started: + self.players.remove(player) + elif disconnected: + self.players.remove(player) + self.players_map = {c.name: i for i, c in enumerate(self.players)} + player.lives = 0 + player.is_dead = True + player.death_turn = self.incremental_turn - corpse = self.players.pop(index) - if not disconnected: - self.dead_players.append(corpse) + # corpse = self.players.pop(index) + corpse = player + # if not disconnected: + # self.dead_players.append(corpse) self.notify_room() self.sio.emit('chat_message', room=self.name, data=f'_died|{player.name}') if self.started: @@ -360,23 +432,25 @@ class Game: for p in self.players: if not p.is_bot: p.notify_self() - self.players_map = {c.name: i for i, c in enumerate(self.players)} + # self.players_map = {c.name: i for i, c in enumerate(self.players)} if self.started: print('Check win status') attacker_role = None 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)] + winners = [p for p in self.players if p.role != None and p.role.on_player_death(self.get_alive_players(), initial_players=self.initial_players, dead_role=player.role, attacker_role=attacker_role)] if len(winners) > 0: print('WE HAVE A WINNER') - for p in self.players: + for p in self.get_alive_players(): p.win_status = p in winners self.sio.emit('chat_message', room=self.name, data=f'_won|{p.name}') p.notify_self() - eventlet.sleep(5.0) + for i in range(5): + self.sio.emit('chat_message', room=self.name, data=f'_lobby_reset|{5-i}') + eventlet.sleep(1) return self.reset() - vulture = [p for p in self.players if isinstance(p.character, characters.VultureSam)] + vulture = [p for p in self.get_alive_players() if p.character.check(self, characters.VultureSam)] if len(vulture) == 0: for i in range(len(player.hand)): self.deck.scrap(player.hand.pop(), True) @@ -397,31 +471,35 @@ class Game: 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): + if player.attacker and player.attacker in self.get_alive_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)] + greg = [p for p in self.get_alive_players() if p.character.check(self, 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)] + herb = [p for p in self.get_alive_players() if p.character.check(self, chd.HerbHunter)] if len(herb) > 0: 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: + self.is_handling_death = False + if corpse.is_my_turn: self.next_turn() def reset(self): print('resetting lobby') - self.players.extend(self.dead_players) - self.dead_players = [] + self.players.extend(self.spectators) + self.spectators = [] + for bot in [p for p in self.players if p.is_bot]: + bot.game = None self.players = [p for p in self.players if not p.is_bot] print(self.players) self.started = False + self.is_handling_death = False self.waiting_for = 0 + self.incremental_turn = 0 for p in self.players: p.reset() p.notify_self() @@ -429,21 +507,31 @@ class Game: self.notify_room() def check_event(self, ev): - if len(self.deck.event_cards) == 0: return False + if self.deck == None or len(self.deck.event_cards) == 0: return False return isinstance(self.deck.event_cards[0], ev) def get_visible_players(self, player: pl.Player): - i = self.players.index(player) + pls = self.get_alive_players() + if len(pls) == 0 or player not in pls: return [] + i = pls.index(player) sight = player.get_sight() mindist = 99 if not self.check_event(ce.Agguato) else 1 return [{ - 'name': self.players[j].name, - 'dist': min([abs(i - j), (i+ abs(j-len(self.players))), (j+ abs(i-len(self.players))), mindist]) + self.players[j].get_visibility() - (player.get_sight(countWeapon=False)-1), - '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] + 'name': pls[j].name, + 'dist': min([abs(i - j), (i+ abs(j-len(pls))), (j+ abs(i-len(pls))), mindist]) + pls[j].get_visibility() - (player.get_sight(countWeapon=False)-1), + 'lives': pls[j].lives, + 'max_lives': pls[j].max_lives, + 'is_sheriff': isinstance(pls[j].role, roles.Sheriff), + 'cards': len(pls[j].hand)+len(pls[j].equipment), + 'is_ghost': pls[j].is_ghost, + 'is_bot': pls[j].is_bot, + } for j in range(len(pls)) if i != j] + + def get_alive_players(self): + return [p for p in self.players if not p.is_dead or p.is_ghost] + + def get_dead_players(self): + return [p for p in self.players if p.is_dead] def notify_all(self): if self.started: @@ -458,6 +546,8 @@ class Game: 'pending_action': p.pending_action, 'character': p.character.__dict__ if p.character else None, 'real_character': p.real_character.__dict__ if p.real_character else None, - 'icon': p.role.icon if self.initial_players == 3 and p.role else '🀠' - } for p in self.players] + 'icon': p.role.icon if self.initial_players == 3 and p.role else '🀠', + 'is_ghost': p.is_ghost, + 'is_bot': p.is_bot, + } for p in self.get_alive_players()] self.sio.emit('players_update', room=self.name, data=data) diff --git a/backend/bang/players.py b/backend/bang/players.py index cb0297a..4d3fa3c 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -9,6 +9,7 @@ import bang.expansions.dodge_city.cards as csd import bang.characters as chars import bang.expansions.dodge_city.characters as chd import bang.expansions.fistful_of_cards.card_events as ce +import bang.expansions.high_noon.card_events as ceh import eventlet class PendingAction(IntEnum): @@ -58,7 +59,11 @@ class Player: self.mancato_needed = 0 self.molly_discarded_cards = 0 self.is_bot = bot + self.bang_used = 0 self.special_use_count = 0 + self.is_dead = False + self.death_turn = 0 + self.is_ghost = False def reset(self): self.hand: cs.Card = [] @@ -91,6 +96,9 @@ class Player: pass self.mancato_needed = 0 self.molly_discarded_cards = 0 + self.is_dead = False + self.is_ghost = False + self.death_turn = 0 def join_game(self, game): self.game = game @@ -156,6 +164,7 @@ class Player: self.sio.emit('notify_card', room=self.sid, data=mess) def notify_self(self): + if self.is_ghost: self.lives = 0 if self.pending_action == PendingAction.DRAW and self.game.check_event(ce.Peyote): self.available_cards = [{ 'icon': 'πŸ”΄' @@ -173,11 +182,11 @@ class Player: 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): + elif self.character and self.character.check(self.game, chars.SuzyLafayette) and self.lives > 0 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: + if self.lives <= 0 and self.max_lives > 0 and not self.is_dead: print('dying, attacker', self.attacker) - if isinstance(self.character, chars.SidKetchum) and len(self.hand) > 1: + if self.character.check(self.game, chars.SidKetchum) and len(self.hand) > 1: self.lives += 1 #TODO Sid dovrebbe poter decidere cosa scartare self.game.deck.scrap(self.hand.pop( @@ -190,36 +199,36 @@ class Player: ser.pop('sid') ser.pop('on_pick_cb') ser.pop('on_failed_response_cb') - # ser.pop('expected_response') ser.pop('attacker') if self.attacker: ser['attacker'] = self.attacker.name ser['sight'] = self.get_sight() ser['lives'] = max(ser['lives'], 0) - if self.lives <= 0 and self.max_lives > 0: + if self.lives <= 0 and self.max_lives > 0 and not self.is_dead: self.pending_action = PendingAction.WAIT ser['hand'] = [] ser['equipment'] = [] self.sio.emit('self', room=self.sid, data=json.dumps( ser, default=lambda o: o.__dict__)) self.game.player_death(self) - elif not self.is_bot: - self.sio.emit('self_vis', room=self.sid, data=json.dumps( - self.game.get_visible_players(self), default=lambda o: o.__dict__)) - if not self.is_bot: - self.sio.emit('self', room=self.sid, data=json.dumps( - ser, default=lambda o: o.__dict__)) - self.game.notify_all() - else: - self.game.notify_all() - self.bot_logic() + if self.game: # falso quando un bot viene eliminato dalla partita + self.sio.emit('self_vis', room=self.sid, data=json.dumps(self.game.get_visible_players(self), default=lambda o: o.__dict__)) self.game.notify_all() + self.sio.emit('self', room=self.sid, data=json.dumps( + ser, default=lambda o: o.__dict__)) + + def bot_spin(self): + while self.is_bot and self.game != None and not self.game.shutting_down: + eventlet.sleep(uniform(self.game.bot_speed/2-0.1, self.game.bot_speed)) + if self.lives > 0 or self.is_ghost: + self.bot_logic() def bot_logic(self): - if self.game.shutting_down: return + if self.game == None or self.game.shutting_down: return if self.pending_action != None and self.pending_action != PendingAction.WAIT: - eventlet.sleep(uniform(self.game.bot_speed/2-0.1, self.game.bot_speed)) + # eventlet.sleep(uniform(self.game.bot_speed/2-0.1, self.game.bot_speed)) + pass else: return if self.pending_action == PendingAction.PICK: @@ -228,8 +237,10 @@ class Player: self.draw('') elif self.pending_action == PendingAction.PLAY: 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))] + 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 and not self.game.check_event(ceh.IlReverendo))] + 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.game.check_event(ceh.Sermone) or self.has_played_bang and not (any([isinstance(c, cs.Volcanic) for c in self.equipment]) and type(c) == type(cs.Bang) + ) 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: @@ -243,7 +254,7 @@ class Player: 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: + if len(others) == 0 or c not in self.hand: continue target = others[randrange(0, len(others))] if target['is_sheriff'] and isinstance(self.role, r.Renegade): @@ -271,7 +282,7 @@ class Player: 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 + maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10 if len(self.hand) > maxcards: self.scrap(0) else: @@ -302,7 +313,7 @@ class Player: self.choose(randrange(0, len(target.hand)+len(target.equipment))) def play_turn(self, can_play_vendetta = True): - if self.lives == 0: + if (self.lives == 0 or self.is_dead) and not self.is_ghost: return self.end_turn(forced=True) self.scrapped_cards = 0 self.can_play_ranch = True @@ -316,12 +327,23 @@ class Player: self.is_waiting_for_action = True self.has_played_bang = False self.special_use_count = 0 - 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.bang_used = 0 + if self.game.check_event(ceh.MezzogiornoDiFuoco): + self.lives -= 1 + if self.character.check(self.game, chars.BartCassidy) and self.lives > 0: + self.hand.append(self.game.deck.draw(True)) + self.sio.emit('chat_message', room=self.game.name, data=f'_special_bart_cassidy|{self.name}') + self.heal_if_needed() + if self.lives <= 0: + return self.notify_self() + + #non Γ¨ un elif perchΓ¨ vera custer deve fare questo poi cambiare personaggio + 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.get_alive_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] + } for p in self.game.get_alive_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 @@ -332,7 +354,7 @@ class Player: 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]) + self.set_available_character([p.character for p in self.game.get_alive_players() if p != self]) else: self.pending_action = PendingAction.DRAW self.notify_self() @@ -353,7 +375,7 @@ class Player: 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] + } for p in self.game.get_alive_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 @@ -365,48 +387,62 @@ class Player: self.lives += 1 self.pending_action = PendingAction.PLAY self.notify_self() - elif isinstance(self.character, chars.KitCarlson): + elif self.character.check(self.game, 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: + elif self.character.check(self.game, 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.pat_target = pile self.choose_text = 'choose_card_to_get' self.pending_action = PendingAction.CHOOSE self.notify_self() else: self.pending_action = PendingAction.PLAY - if pile == 'scrap' and isinstance(self.character, chars.PedroRamirez): + if pile == 'scrap' and self.character.check(self.game, chars.PedroRamirez): self.hand.append(self.game.deck.draw_from_scrap_pile()) - self.hand.append(self.game.deck.draw()) + if not self.game.check_event(ceh.Sete): + self.hand.append(self.game.deck.draw()) + if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.ceck_event(ceh.CittaFantasma)): + self.hand.append(self.game.deck.draw()) self.sio.emit('chat_message', room=self.game.name, data=f'_draw_from_scrap|{self.name}') - elif type(pile) == str and pile != self.name and pile in self.game.players_map and isinstance(self.character, chars.JesseJones) and len(self.game.get_player_named(pile).hand) > 0: + elif type(pile) == str and pile != self.name and pile in self.game.players_map and self.character.check(self.game, chars.JesseJones) and len(self.game.get_player_named(pile).hand) > 0: self.hand.append(self.game.get_player_named(pile).hand.pop( randrange(0, len(self.game.get_player_named(pile).hand)))) self.game.get_player_named(pile).notify_self() self.sio.emit('chat_message', room=self.game.name, data=f'_draw_from_player|{self.name}|{pile}') + if not self.game.check_event(ceh.Sete): + self.hand.append(self.game.deck.draw()) + if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.ceck_event(ceh.CittaFantasma)): + self.hand.append(self.game.deck.draw()) + elif self.character.check(self.game, chd.BillNoface): self.hand.append(self.game.deck.draw()) - elif isinstance(self.character, chd.BillNoface): - self.hand.append(self.game.deck.draw()) - for i in range(self.max_lives-self.lives): + if not self.game.check_event(ceh.Sete): + for i in range(self.max_lives-self.lives): + self.hand.append(self.game.deck.draw()) + if self.game.check_event(ceh.IlTreno): self.hand.append(self.game.deck.draw()) else: for i in range(2): card: cs.Card = self.game.deck.draw() self.hand.append(card) - if i == 1 and isinstance(self.character, chars.BlackJack) or self.game.check_event(ce.LeggeDelWest): - for p in self.game.players: + if i == 1 and self.character.check(self.game, chars.BlackJack) or self.game.check_event(ce.LeggeDelWest): + for p in self.game.get_alive_players(): if p != self: - p.notify_card(self, card, 'blackjack_special' if isinstance(self.character, chars.BlackJack) else 'foc.leggedelwest') - if card.suit == cs.Suit.HEARTS or card.suit == cs.Suit.DIAMONDS and isinstance(self.character, chars.BlackJack): + p.notify_card(self, card, 'blackjack_special' if self.character.check(self.game, chars.BlackJack) else 'foc.leggedelwest') + if card.check_suit(self.game, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]) and self.character.check(self.game, chars.BlackJack): self.hand.append(self.game.deck.draw()) - if isinstance(self.character, chd.PixiePete): + if self.game.check_event(ceh.Sete): + return self.notify_self() + if self.character.check(self.game, chd.PixiePete): self.hand.append(self.game.deck.draw()) + if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.ceck_event(ceh.CittaFantasma)): + self.hand.append(self.game.deck.draw()) self.notify_self() def pick(self): @@ -422,12 +458,12 @@ class Player: 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.SPADES and 2 <= picked.number <= 9 and pickable_cards == 0: + if picked.check_suit(self.game, [cs.Suit.SPADES]) and 2 <= picked.number <= 9 and pickable_cards == 0: self.lives -= 3 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: + if self.character.check(self.game, chars.BartCassidy) and self.lives > 0: for i in range(3): self.hand.append(self.game.deck.draw(True)) self.sio.emit('chat_message', room=self.game.name, data=f'_special_bart_cassidy|{self.name}') @@ -448,7 +484,7 @@ class Player: 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 and pickable_cards == 0: + if not picked.check_suit(self.game, [cs.Suit.HEARTS]) and pickable_cards == 0: self.game.deck.scrap(self.equipment.pop(i), True) self.end_turn(forced=True) return @@ -460,7 +496,7 @@ class Player: self.notify_self() return if isinstance(self.real_character, chd.VeraCuster): - self.set_available_character([p.character for p in self.game.players if p != self]) + self.set_available_character([p.character for p in self.game.get_alive_players() if p != self]) else: self.pending_action = PendingAction.DRAW self.notify_self() @@ -486,7 +522,7 @@ class Player: return s def play_card(self, hand_index: int, against=None, _with=None): - if not self.is_my_turn or self.pending_action != PendingAction.PLAY: + if not self.is_my_turn or self.pending_action != PendingAction.PLAY or self.game.is_handling_death: return if not (0 <= hand_index < len(self.hand) + len(self.equipment)): return @@ -497,7 +533,7 @@ class Player: print(self.name, 'is playing ', card, ' against:', against, ' with:', _with) 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: + if not(against != None and isinstance(self.game.get_player_named(against).character, chd.ApacheKid) and card.check_suit(self.game, [cs.Suit.DIAMONDS])) and not event_blocks_card: if against == self.name and not isinstance(card, csd.Tequila): did_play_card = False else: @@ -541,15 +577,15 @@ class Player: self.hand.append(card) else: 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]): + if self.event_type != 'rissa' or (self.event_type == 'rissa' and (len([p.name for p in self.game.get_alive_players() if p != self and (len(p.hand)+len(p.equipment)) > 0]) == 0 or self.target_p == [p.name for p in self.game.get_alive_players() if p != self and (len(p.hand)+len(p.equipment)) > 0][-1])): self.event_type = '' self.target_p = '' self.choose_action = '' self.pending_action = PendingAction.PLAY else: - self.target_p = self.game.players[self.game.players_map[self.target_p]+1].name + self.target_p = self.game.players[(self.game.players_map[self.target_p]+1)%len(self.game.players)].name 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.target_p = self.game.players[(self.game.players_map[self.target_p]+1)%len(self.game.players)].name self.notify_self() elif self.is_giving_life and self.game.check_event(ce.FratelliDiSangue): try: @@ -604,6 +640,13 @@ class Player: else: self.discarded_cards.append(self.available_cards.pop(card_index)) self.notify_self() + elif self.game.dalton_on and self.game.check_event(ceh.IDalton): + card = next(c for c in self.equipment if c == self.available_cards[card_index]) + self.equipment.remove(card) + self.game.deck.scrap(card, True) + self.pending_action = PendingAction.WAIT + self.notify_self() + self.game.responders_did_respond_resume_turn() elif self.is_drawing and self.game.check_event(ce.Peyote): self.is_drawing = False card = self.game.deck.draw() @@ -619,19 +662,25 @@ class Player: self.pending_action = PendingAction.PLAY self.notify_self() # specifico per personaggio - elif self.is_drawing and isinstance(self.character, chars.KitCarlson): + elif self.is_drawing and self.character.check(self.game, chars.KitCarlson): self.hand.append(self.available_cards.pop(card_index)) - if len(self.available_cards) == 1: - self.game.deck.put_on_top(self.available_cards.pop()) + pickable_stop = 1 + if self.game.check_event(ceh.Sete): pickable_stop = 2 + if self.game.check_event(ceh.IlTreno): pickable_stop = 0 + if len(self.available_cards) == pickable_stop: + if len(self.available_cards) > 0: + self.game.deck.put_on_top(self.available_cards.pop()) self.is_drawing = False self.pending_action = PendingAction.PLAY self.notify_self() - elif self.is_drawing and isinstance(self.character, chd.PatBrennan): + elif self.is_drawing and self.character.check(self.game, chd.PatBrennan): + self.is_drawing = False card = self.available_cards.pop(card_index) if card.usable_next_turn: card.can_be_used_now = False self.hand.append(card) self.available_cards = [] + self.game.get_player_named(self.pat_target).notify_self() self.pending_action = PendingAction.PLAY self.notify_self() else: # emporio @@ -639,7 +688,7 @@ class Player: def barrel_pick(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): + if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais): pickable_cards = 2 while pickable_cards > 0: pickable_cards -= 1 @@ -647,29 +696,29 @@ class Player: 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: + if picked.check_suit(self.game, [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\ + if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, 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(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: + if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response: self.expected_response.append(cs.Bang(0, 0).name) - elif isinstance(self.character, chd.ElenaFuente): - self.expected_response = self.game.deck.all_cards_str + elif self.character.check(self.game, chd.ElenaFuente): + self.expected_response = self.game.deck.all_cards_str.copy() self.on_failed_response_cb = self.take_damage_response self.notify_self() 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): + if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais): pickable_cards = 2 while pickable_cards > 0: pickable_cards -= 1 @@ -677,23 +726,23 @@ class Player: 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: + if picked.check_suit(self.game, [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\ + if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, 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: + if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response: self.expected_response.append(cs.Bang(0, 0).name) - elif isinstance(self.character, chd.ElenaFuente): - self.expected_response = self.game.deck.all_cards_str + elif self.character.check(self.game, chd.ElenaFuente): + self.expected_response = self.game.deck.all_cards_str.copy() self.on_failed_response_cb = self.take_no_damage_response self.notify_self() @@ -707,8 +756,8 @@ class Player: for i in range(len(self.equipment)): if self.equipment[i].can_be_used_now: print('usable', self.equipment[i]) - if not self.game.is_competitive and len([c for c in self.equipment if isinstance(c, cs.Barile)]) == 0 and not isinstance(self.character, chars.Jourdonnais)\ - 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\ + if not self.game.is_competitive and len([c for c in self.equipment if isinstance(c, cs.Barile)]) == 0 and not self.character.check(self.game, chars.Jourdonnais)\ + and len([c for c in self.hand if (isinstance(c, cs.Mancato) and c.can_be_used_now) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, 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') if not no_dmg: @@ -717,7 +766,7 @@ class Player: 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): + if (not self.game.check_event(ce.Lazo) and len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0) or self.character.check(self.game, chars.Jourdonnais): print('has barrel') self.pending_action = PendingAction.PICK if not no_dmg: @@ -728,22 +777,32 @@ class Player: print('has mancato') self.pending_action = PendingAction.RESPOND 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 - elif isinstance(self.character, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response: + if self.attacker and self.attacker in self.game.get_alive_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.copy() + elif self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response: self.expected_response.append(cs.Bang(0, 0).name) - elif isinstance(self.character, chd.ElenaFuente): - self.expected_response = self.game.deck.all_cards_str + elif self.character.check(self.game, chd.ElenaFuente): + self.expected_response = self.game.deck.all_cards_str.copy() 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_dalton(self): + equipments = [c for c in self.equipment if not c.usable_next_turn] + if len(equipments) == 0: + return False + else: + self.choose_text = 'choose_dalton' + self.pending_action = PendingAction.CHOOSE + self.available_cards = equipments + 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: + if self.character.check(self.game, chd.ApacheKid): return False + if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0: print('Cant defend') self.take_damage_response() return False @@ -751,7 +810,7 @@ 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: + if self.character.check(self.game, 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 @@ -759,7 +818,7 @@ class Player: def get_dueled(self, attacker): self.attacker = attacker - 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: + if (self.game.check_event(ceh.Sermone) and self.is_my_turn) or (not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0): print('Cant defend') self.take_damage_response() self.game.responders_did_respond_resume_turn(did_lose=True) @@ -767,19 +826,19 @@ class Player: 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: + if self.character.check(self.game, 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: + while self.lives <= 0 and len(self.game.get_alive_players()) > 2 and len([c for c in self.hand if isinstance(c, cs.Birra)]) > 0 and not self.game.check_event(ceh.IlReverendo): 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: + if self.character.check(self.game, 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.lives += 1 if not self.character.check(self.game, 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}') @@ -788,11 +847,11 @@ class Player: def take_damage_response(self): self.lives -= 1 if self.lives > 0: - if isinstance(self.character, chars.BartCassidy): + if self.character.check(self.game, 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(True)) - elif isinstance(self.character, chars.ElGringo) and self.attacker and self.attacker in self.game.players and len(self.attacker.hand) > 0: + elif self.character.check(self.game, chars.ElGringo) and self.attacker and self.attacker in self.game.get_alive_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, @@ -822,31 +881,31 @@ class Player: ((hand_index < len(self.hand) and self.hand[hand_index].name in self.expected_response)) or (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': + if self.character.check(self.game, 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(True)) card.use_card(self) + self.sio.emit('chat_message', room=self.game.name, data=f'_respond|{self.name}|{card}') self.game.deck.scrap(card, True) self.notify_self() self.mancato_needed -= 1 if self.mancato_needed <= 0: if self.event_type == 'duel': self.game.duel(self, self.attacker.name) - if isinstance(self.character, chd.MollyStark) and hand_index < len(self.hand)+1 and not self.is_my_turn: + if self.character.check(self.game, 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(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: + if self.character.check(self.game, chd.MollyStark) and not self.is_my_turn: for i in range(self.molly_discarded_cards): self.hand.append(self.game.deck.draw(True)) self.molly_discarded_cards = 0 self.notify_self() - elif self.attacker and self.attacker in self.game.players and isinstance(self.attacker.character, chd.MollyStark) and self.is_my_turn: + elif self.attacker and self.attacker in self.game.get_alive_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(True)) self.attacker.molly_discarded_cards = 0 @@ -881,13 +940,13 @@ class Player: return self.character.visibility_mod + covers def scrap(self, card_index): - if self.is_my_turn or isinstance(self.character, chars.SidKetchum): + if self.is_my_turn or self.character.check(self.game, chars.SidKetchum): self.scrapped_cards += 1 card = self.hand.pop(card_index) - if isinstance(self.character, chars.SidKetchum) and self.scrapped_cards == 2: + if self.character.check(self.game, chars.SidKetchum) and self.scrapped_cards == 2: 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: + elif self.character.check(self.game, chd.JoseDelgrado) and card.is_equipment and self.special_use_count < 2: self.hand.append(self.game.deck.draw(True)) self.hand.append(self.game.deck.draw(True)) self.special_use_count += 1 @@ -895,7 +954,7 @@ class Player: self.notify_self() def holyday_special(self, data): - if isinstance(self.character, chd.DocHolyday) and self.special_use_count < 1: + if self.character.check(self.game, chd.DocHolyday) and self.special_use_count < 1: self.special_use_count += 1 cards = sorted(data['cards'], reverse=True) for c in cards: @@ -904,16 +963,17 @@ class Player: 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: + if self.character.check(self.game, chd.ChuckWengam) and self.lives > 1 and self.is_my_turn: self.lives -= 1 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): + print(f"{self.name} wants to end his turn") if not self.is_my_turn: return - maxcards = self.lives if not isinstance(self.character, chd.SeanMallory) else 10 + maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10 if len(self.hand) > maxcards and not forced: print( f"I {self.name} have to many cards in my hand and I can't end the turn") @@ -921,13 +981,20 @@ class Player: 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: + if picked.check_suit(self.game, [cs.Suit.HEARTS]): self.play_turn(can_play_vendetta=False) return self.is_my_turn = False + self.has_played_bang = False for i in range(len(self.equipment)): if self.equipment[i].usable_next_turn and not self.equipment[i].can_be_used_now: self.equipment[i].can_be_used_now = True + if self.is_dead and self.is_ghost and self.game.check_event(ceh.CittaFantasma): + self.is_ghost = False + for i in range(len(self.hand)): + self.game.deck.scrap(self.hand.pop(), True) + for i in range(len(self.equipment)): + self.game.deck.scrap(self.equipment.pop(), True) self.pending_action = PendingAction.WAIT self.notify_self() self.game.next_turn() diff --git a/frontend/src/App.vue b/frontend/src/App.vue index dce6cda..2ff8f5b 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -36,6 +36,7 @@ export default { data: () => ({ isConnected: false, c: false, + showUpdateUI: false, }), computed: { }, diff --git a/frontend/src/components/Card.vue b/frontend/src/components/Card.vue index 091739e..07184a7 100644 --- a/frontend/src/components/Card.vue +++ b/frontend/src/components/Card.vue @@ -103,7 +103,8 @@ export default { text-align: center; width: 100%; top: -10pt; -} + font-size: 11pt; +} .card .emoji { position: absolute; text-align: center; @@ -133,6 +134,9 @@ export default { bottom: 20pt; left: 3pt; } +.cant-play { + filter: brightness(0.5); +} @media (prefers-color-scheme: dark) { :root, #app { background-color: #181a1b; diff --git a/frontend/src/components/Chat.vue b/frontend/src/components/Chat.vue index 967cb59..eba8765 100644 --- a/frontend/src/components/Chat.vue +++ b/frontend/src/components/Chat.vue @@ -2,7 +2,7 @@

{{$t("chat.chat")}}

-

{{msg.text}}

+

{{msg.text}}

.

@@ -35,8 +35,15 @@ export default { }, methods: { sendChatMessage(e) { - if (this.text.trim().length > 0){ - this.$socket.emit('chat_message', this.text.trim()) + let msg = this.text.trim() + if (msg.length > 0){ + if (msg.indexOf('/addbot') !== -1 && msg.split(' ').length > 1){ + for (let i = 0; i < parseInt(msg.split(' ')[1]); i++) { + this.$socket.emit('chat_message', msg.split(' ')[0]) + } + }else{ + this.$socket.emit('chat_message', msg) + } this.text = '' } e.preventDefault(); diff --git a/frontend/src/components/Chooser.vue b/frontend/src/components/Chooser.vue index 0e7cdda..51d8653 100644 --- a/frontend/src/components/Chooser.vue +++ b/frontend/src/components/Chooser.vue @@ -2,7 +2,7 @@

{{text}}

- +

{{hintText}}

{{realCancelText}}
diff --git a/frontend/src/components/Deck.vue b/frontend/src/components/Deck.vue index 2b0ed6a..418e380 100644 --- a/frontend/src/components/Deck.vue +++ b/frontend/src/components/Deck.vue @@ -5,7 +5,7 @@
- +
@@ -15,12 +15,14 @@
-

{{($i18n.locale=='it'?eventCard.desc:eventCard.desc_eng)}}

+
+

{{desc}}

@@ -52,7 +54,7 @@ export default { sockets: { self(self){ self = JSON.parse(self) - this.isPlaying = self.lives > 0 + this.isPlaying = self.lives > 0 || self.is_ghost this.pending_action = self.pending_action }, scrap(card) { diff --git a/frontend/src/components/Lobby.vue b/frontend/src/components/Lobby.vue index 7f01be0..5cdd8af 100644 --- a/frontend/src/components/Lobby.vue +++ b/frontend/src/components/Lobby.vue @@ -16,10 +16,13 @@
- - ❀️ - πŸ’€ + + ❀️ + πŸ’€ +
+ πŸ‘» +
@@ -28,6 +31,9 @@
+
+ πŸ€– +
@@ -40,12 +46,12 @@

{{$t('mods')}}

{{$t('mod_comp')}} -
- {{$t('disconnect_bot')}} + +
- +
@@ -113,6 +119,7 @@ export default { return { name: x.name, ready: x.ready, + is_bot: x.is_bot, ncards: 0, } }) diff --git a/frontend/src/components/Player.vue b/frontend/src/components/Player.vue index 78fc6f3..a2adbcf 100644 --- a/frontend/src/components/Player.vue +++ b/frontend/src/components/Player.vue @@ -1,6 +1,6 @@