bang/backend/players.py
2020-11-24 01:07:40 +01:00

495 lines
23 KiB
Python

import deck
from enum import IntEnum
import json
from random import randrange
import socketio
from cards import Mancato
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 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)
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()
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__))
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 and card.name not in [c.name for c in self.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)
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 != '':
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):
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:
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)]) == 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)]) == 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)]) == 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):
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()