import deck from enum import IntEnum import json from random import randrange import socketio import cards import roles import cards import characters class PendingAction(IntEnum): PICK = 0 DRAW = 1 PLAY = 2 RESPOND = 3 WAIT = 4 CHOOSE = 5 class Player: def __init__(self, name, sid, sio): super().__init__() self.name = name self.sid = sid self.sio = sio self.hand: cards.Card = [] self.equipment: cards.Card = [] self.role: roles.Role = None self.character: characters.Character = None self.lives = 0 self.max_lives = 0 self.game = None self.is_my_turn = False self.is_waiting_for_action = True self.has_played_bang = False self.pending_action: PendingAction = None self.available_characters = [] self.was_shot = False self.on_pick_cb = None self.on_failed_response_cb = None self.event_type: str = None self.expected_response = None self.attacker = None self.target_p: str = None self.is_drawing = False self.mancato_needed = 0 def join_game(self, game): self.game = game print(f'I {self.name} joined {self.game}') def disconnect(self): return self.game.handle_disconnect(self) def set_role(self, role: roles.Role): self.role = role print(f'I {self.name} am a {role.name}, my goal is "{role.goal}"') self.sio.emit('role', room=self.sid, data=json.dumps(role, default=lambda o: o.__dict__)) def set_character(self, character: str): print(self.available_characters, character) self.character = next(x for x in self.available_characters if x.name==character) self.available_characters = [] print(f'I {self.name} chose character {self.character.name}') self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha scelto il personaggio.') self.game.notify_character_selection() def prepare(self): self.max_lives = self.character.max_lives + self.role.health_mod self.lives = self.max_lives self.hand = [] self.equipment = [] self.pending_action = PendingAction.WAIT def set_available_character(self, available): self.available_characters = available print(f'I {self.name} have to choose between {available}') self.sio.emit('characters', room=self.sid, data=json.dumps(available, default=lambda o: o.__dict__)) def notify_card(self, player, card): mess = { 'player': player.name, 'card': card.__dict__ } print('notifying card') self.sio.emit('notify_card', room=self.sid, data=mess) def notify_self(self): if isinstance(self.character, characters.CalamityJanet): self.expected_response = [cards.Mancato(0,0).name, cards.Bang(0,0).name] elif isinstance(self.character, characters.SuzyLafayette) and len(self.hand) == 0: self.hand.append(self.game.deck.draw()) ser = self.__dict__.copy() ser.pop('game') ser.pop('sio') 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) self.sio.emit('self', room=self.sid, data=json.dumps(ser, default=lambda o: o.__dict__)) self.sio.emit('self_vis', room=self.sid, data=json.dumps(self.game.get_visible_players(self), default=lambda o: o.__dict__)) if self.lives <= 0 and self.max_lives > 0: print('dying, attacker', self.attacker) if isinstance(self.character, characters.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)))) self.game.player_death(self) self.game.notify_all() def play_turn(self): if self.lives == 0: return self.end_turn(forced=True) self.scrapped_cards = 0 self.sio.emit('chat_message', room=self.game.name, data=f'È il turno di {self.name}.') print(f'I {self.name} was notified that it is my turn') self.was_shot = False self.is_my_turn = True self.is_waiting_for_action = True self.has_played_bang = False if any([isinstance(c, cards.Dinamite) or isinstance(c, cards.Prigione) for c in self.equipment]): self.pending_action = PendingAction.PICK else: self.pending_action = PendingAction.DRAW self.notify_self() def draw(self, pile): if self.pending_action != PendingAction.DRAW: return if isinstance(self.character, characters.KitCarlson): self.is_drawing = True self.available_cards = [self.game.deck.draw() for i in range(3)] self.pending_action = PendingAction.CHOOSE self.notify_self() else: self.pending_action = PendingAction.PLAY if pile == 'scrap' and isinstance(self.character, characters.PedroRamirez): self.hand.append(self.game.deck.draw_from_scrap_pile()) self.hand.append(self.game.deck.draw()) elif type(pile) == str and pile != self.name and pile in self.game.players_map and isinstance(self.character, characters.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.hand.append(self.game.deck.draw()) else: for i in range(2): card: cards.Card = self.game.deck.draw() self.hand.append(card) if i == 1 and isinstance(self.character, characters.BlackJack): for p in self.game.players: if p != self: p.notify_card(self, card) if card.suit == cards.Suit.HEARTS or card.suit == cards.Suit.DIAMONDS: self.hand.append(self.game.deck.draw()) self.notify_self() def pick(self): if self.pending_action != PendingAction.PICK: return pickable_cards = 1 + self.character.pick_mod if self.is_my_turn: for i in range(len(self.equipment)): if isinstance(self.equipment[i], cards.Dinamite): while pickable_cards > 0: pickable_cards -= 1 picked: cards.Card = self.game.deck.pick_and_scrap() print(f'Did pick {picked}') self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha estratto {picked}.') if picked.suit == cards.Suit.SPADES and 2 <= picked.number <= 9 and pickable_cards == 0: self.lives -= 3 self.game.deck.scrap(self.equipment.pop(i)) if isinstance(self.character, characters.BartCassidy): self.hand.append(self.game.deck.draw()) self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha fatto esplodere la dinamite.') print(f'{self.name} Boom, -3 hp') else: self.game.next_player().equipment.append(self.equipment.pop(i)) self.game.next_player().notify_self() if any([isinstance(c, cards.Dinamite) or isinstance(c, cards.Prigione) for c in self.equipment]): self.notify_self() return for i in range(len(self.equipment)): if isinstance(self.equipment[i], cards.Prigione): while pickable_cards > 0: pickable_cards -= 1 picked: cards.Card = self.game.deck.pick_and_scrap() print(f'Did pick {picked}') self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha estratto {picked}.') if picked.suit != cards.Suit.HEARTS and pickable_cards == 0: self.game.deck.scrap(self.equipment.pop(i)) self.end_turn(forced=True) return else: self.game.deck.scrap(self.equipment.pop(i)) break break self.pending_action = PendingAction.DRAW self.notify_self() else: self.pending_action = PendingAction.WAIT self.on_pick_cb() def get_playable_cards(self): playable_cards = [] for i in range(len(self.hand)): card = self.hand[i] if isinstance(card, cards.Bang) and self.has_played_bang and not any([isinstance(c, cards.Volcanic) for c in self.equipment]): continue elif isinstance(card, cards.Birra) and self.lives >= self.max_lives: continue else: playable_cards.append(i) return playable_cards def get_public_description(self): s = f"{self.name} {'Sheriff ⭐️' if isinstance(self.role, roles.Sheriff) else ''} ({self.lives}/{self.max_lives} ⁍) {len(self.hand)} Cards in hand, " s += f"equipment {[str(c) for c in self.equipment]}" return s def play_card(self, hand_index: int, againts=None): if not (0 <= hand_index < len(self.hand)): print('illegal') return card: cards.Card = self.hand.pop(hand_index) print(self.name, 'is playing ', card, ' against:', againts) if isinstance(card, cards.Prigione) and not isinstance(self.game.get_player_named(againts).role, roles.Sheriff): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.') self.game.get_player_named(againts).equipment.append(card) self.game.get_player_named(againts).notify_self() elif card.is_equipment: if card.is_weapon: has_weapon = False for i in range(len(self.equipment)): if self.equipment[i].is_weapon: self.game.deck.scrap(self.equipment[i]) self.equipment[i] = card has_weapon = True break if not has_weapon: self.equipment.append(card) elif card.name in [c.name for c in self.equipment if not isinstance(c, cards.Dinamite)]: for i in range(len(self.equipment)): if type(self.equipment[i]) == type(card): self.game.deck.scrap(self.equipment[i]) self.equipment[i] = card break else: self.equipment.append(card) else: did_play_card = False if isinstance(card, cards.Bang) and self.has_played_bang and not any([isinstance(c, cards.Volcanic) for c in self.equipment]) and againts != None: self.hand.insert(hand_index, card) return elif isinstance(card, cards.Bang) and againts != None: self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.') self.has_played_bang = not isinstance(self.character, characters.WillyTheKid) self.game.attack(self, againts) did_play_card = True elif isinstance(card, cards.Birra): if len(self.game.players) != 2 and self.lives != self.max_lives: self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato una {card.name}.') self.lives = min(self.lives+1, self.max_lives) did_play_card = True elif len(self.game.players) == 2: self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha rovesciato una {card.name}.') did_play_card = True elif isinstance(card, cards.CatBalou) and againts != None: self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.') self.pending_action = PendingAction.CHOOSE self.choose_action = 'discard' self.target_p = againts did_play_card = True print('choose now') elif isinstance(card, cards.Diligenza): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} e ha pescato 2 carte.') for i in range(2): self.hand.append(self.game.deck.draw()) did_play_card = True elif isinstance(card, cards.Duello): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.') self.game.duel(self, againts) did_play_card = True elif isinstance(card, cards.Emporio): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name}.') self.game.emporio() did_play_card = True elif isinstance(card, cards.Gatling): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name}.') self.game.attack_others(self) did_play_card = True elif isinstance(card, cards.Indiani): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name}.') self.game.indian_others(self) did_play_card = True elif isinstance(card, cards.Mancato) and (not self.has_played_bang and againts != None and isinstance(self.character, characters.CalamityJanet)): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} come un BANG! contro {againts}.') self.has_played_bang = True self.game.attack(self, againts) did_play_card = True elif isinstance(card, cards.Panico) and againts != None: self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.') self.pending_action = PendingAction.CHOOSE self.choose_action = 'steal' self.target_p = againts print('choose now') did_play_card = True elif isinstance(card, cards.Saloon): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} e ha curato 1 punto vita a tutti.') for p in self.game.players: p.lives = min(p.lives+1, p.max_lives) p.notify_self() did_play_card = True elif isinstance(card, cards.WellsFargo): self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} e ha pescato 3 carte.') for i in range(3): self.hand.append(self.game.deck.draw()) did_play_card = True if did_play_card: self.game.deck.scrap(card) else: self.hand.insert(hand_index, card) self.notify_self() def choose(self, card_index): if self.pending_action != PendingAction.CHOOSE: return if self.target_p and self.target_p != '': # panico, cat balou target = self.game.get_player_named(self.target_p) card = None if card_index >= len(target.hand): card = target.equipment.pop(card_index - len(target.hand)) else: card = target.hand.pop(card_index) target.notify_self() if self.choose_action == 'steal': self.hand.append(card) else: self.game.deck.scrap(card) self.target_p = '' self.choose_action = '' self.pending_action = PendingAction.PLAY self.notify_self() elif self.is_drawing and isinstance(self.character, characters.KitCarlson): # specifico per personaggio 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()) self.is_drawing = False self.pending_action = PendingAction.PLAY self.notify_self() else: # emporio self.game.respond_emporio(self, card_index) def barrel_pick(self): pickable_cards = 1 + self.character.pick_mod if len([c for c in self.equipment if isinstance(c, cards.Barile)]) > 0 and isinstance(self.character, characters.Jourdonnais): pickable_cards = 2 while pickable_cards > 0: pickable_cards -= 1 picked: cards.Card = self.game.deck.pick_and_scrap() print(f'Did pick {picked}') self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha estratto {picked}.') if picked.suit == cards.Suit.HEARTS: self.mancato_needed -= 1 self.notify_self() if self.mancato_needed <= 0: self.game.responders_did_respond_resume_turn() return if len([c for c in self.hand if isinstance(c, cards.Mancato) or (isinstance(self.character, characters.CalamityJanet) and isinstance(c, cards.Bang))]) == 0: self.take_damage_response() self.game.responders_did_respond_resume_turn() else: self.pending_action = PendingAction.RESPOND self.expected_response = [cards.Mancato(0,0).name] self.on_failed_response_cb = self.take_damage_response self.notify_self() def get_banged(self, attacker, double=False): self.attacker = attacker self.mancato_needed = 1 if not double else 2 if len([c for c in self.hand if isinstance(c, cards.Mancato) or (isinstance(self.character, characters.CalamityJanet) and isinstance(c, cards.Bang))]) == 0 and len([c for c in self.equipment if isinstance(c, cards.Barile)]) == 0 and not isinstance(self.character, characters.Jourdonnais): print('Cant defend') self.take_damage_response() return False else: if len([c for c in self.equipment if isinstance(c, cards.Barile)]) > 0 or isinstance(self.character, characters.Jourdonnais): print('has barrel') self.pending_action = PendingAction.PICK self.on_pick_cb = self.barrel_pick else: print('has mancato') self.pending_action = PendingAction.RESPOND self.expected_response = [cards.Mancato(0,0).name] self.on_failed_response_cb = self.take_damage_response self.notify_self() return True def get_indians(self, attacker): self.attacker = attacker if len([c for c in self.hand if isinstance(c, cards.Bang) or (isinstance(self.character, characters.CalamityJanet) and isinstance(c, cards.Mancato))]) == 0: print('Cant defend') self.take_damage_response() return False else: print('has bang') self.pending_action = PendingAction.RESPOND self.expected_response = [cards.Bang(0,0).name] self.event_type = 'indians' self.on_failed_response_cb = self.take_damage_response self.notify_self() return True def get_dueled(self, attacker): self.attacker = attacker if len([c for c in self.hand if isinstance(c, cards.Bang) or (isinstance(self.character, characters.CalamityJanet) and isinstance(c, cards.Mancato))]) == 0: print('Cant defend') self.take_damage_response() self.game.responders_did_respond_resume_turn() return False else: self.pending_action = PendingAction.RESPOND self.expected_response = [cards.Bang(0,0).name] self.event_type = 'duel' self.on_failed_response_cb = self.take_damage_response self.notify_self() return True def take_damage_response(self): self.lives -= 1 if self.lives > 0: if isinstance(self.character, characters.BartCassidy): self.hand.append(self.game.deck.draw()) elif isinstance(self.character, characters.ElGringo) and self.attacker and len(self.attacker.hand) > 0: self.hand.append(self.attacker.hand.pop(randrange(0, len(self.attacker.hand)))) 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, cards.Birra)]) > 0: for i in range(len(self.hand)): if isinstance(self.hand[i], cards.Birra): self.lives += 1 self.game.deck.scrap(self.hand.pop(i)) break self.mancato_needed = 0 self.notify_self() self.attacker = None def respond(self, hand_index): self.pending_action = PendingAction.WAIT if hand_index != -1 and self.hand[hand_index].name in self.expected_response: self.game.deck.scrap(self.hand.pop(hand_index)) self.notify_self() self.mancato_needed -= 1 if self.mancato_needed <= 0: if self.event_type == 'duel': self.game.duel(self, self.attacker.name) else: self.game.responders_did_respond_resume_turn() self.event_type = '' else: self.pending_action = PendingAction.RESPOND self.notify_self() else: self.on_failed_response_cb() self.game.responders_did_respond_resume_turn() self.attacker = None def get_sight(self): if not self.character: return 0 aim = 0 range = 0 for card in self.equipment: if card.is_weapon: range += card.range else: aim += card.sight_mod return max(1, range) + aim + self.character.sight_mod def get_visibility(self): covers = 0 for card in self.equipment: covers += card.vis_mod return self.character.visibility_mod + covers def scrap(self, card_index): if self.is_my_turn or isinstance(self.character, characters.SidKetchum): self.scrapped_cards += 1 if isinstance(self.character, characters.SidKetchum) and self.scrapped_cards == 2: self.scrapped_cards = 0 self.lives = min(self.lives+1, self.max_lives) self.game.deck.scrap(self.hand.pop(card_index)) self.notify_self() def end_turn(self, forced=False): if not self.is_my_turn: return if len(self.hand) > self.max_lives and not forced: print(f"I {self.name} have to many cards in my hand and I can't end the turn") else: self.is_my_turn = False self.pending_action = PendingAction.WAIT self.notify_self() self.game.next_turn()