bang/backend/game.py
2020-11-25 15:00:00 +01:00

259 lines
11 KiB
Python

from typing import List, Set, Dict, Tuple, Optional
import random
import socketio
import cards
from cards import Bang
import players
from players import PendingAction, Player
import characters
from characters import all_characters
from deck import Deck
import roles
class Game:
def __init__(self, name, sio:socketio):
super().__init__()
self.sio = sio
self.name = name
self.players: List[players.Player] = []
self.deck: Deck = None
self.started = False
self.turn = 0
self.readyCount = 0
self.waiting_for = 0
self.initial_players = 0
def add_player(self, player: players.Player):
if player in self.players or len(self.players) >= 7:
return
player.join_game(self)
self.players.append(player)
print(f'Added player {player.name} to game')
self.sio.emit('room', room=self.name, data={'name': self.name, 'started': self.started, 'players': [p.name for p in self.players]})
self.sio.emit('chat_message', room=self.name, data=f'{player.name} è entrato nella lobby.')
def notify_character_selection(self):
self.readyCount += 1
if self.readyCount == len(self.players):
self.distribute_roles()
def choose_characters(self):
char_cards = random.sample(all_characters(), len(self.players)*2)
for i in range(len(self.players)):
self.players[i].set_available_character(char_cards[i * 2 : i * 2 + 2])
def start_game(self):
print('GAME IS STARING')
if self.started:
return
self.players_map = {c.name: i for i, c in enumerate(self.players)}
self.sio.emit('chat_message', room=self.name, data=f'La partita sta iniziando...')
self.sio.emit('start', room=self.name)
self.started = True
self.deck = Deck(self)
self.initial_players = len(self.players)
self.choose_characters()
def distribute_roles(self):
available_roles: List[roles.Role] = []
if len(self.players) == 3:
available_roles = [
roles.Vice('Elimina il Rinnegato 🦅, se non lo elimini tu elimina anche il Fuorilegge'),
roles.Renegade('Elimina il Fuorilegge 🐺, se non lo elimini tu elimina anche il Vice'),
roles.Outlaw('Elimina il Vice 🎖, se non lo elimini tu elimina anche il Rinnegato')
]
elif len(self.players) >= 4:
available_roles = [roles.Sheriff(), roles.Renegade(), roles.Outlaw(), roles.Outlaw(), roles.Vice(), roles.Outlaw(), roles.Vice()]
available_roles = available_roles[:len(self.players)]
random.shuffle(available_roles)
for i in range(len(self.players)):
self.sio.emit('chat_message', room=self.name, data=f'{self.players[i].name} ha come personaggio {self.players[i].character.name}, la sua abilità speciale è: {self.players[i].character.desc}')
self.players[i].set_role(available_roles[i])
if isinstance(available_roles[i], roles.Sheriff) or (len(available_roles) == 3 and isinstance(available_roles[i], roles.Vice)):
if isinstance(available_roles[i], roles.Sheriff):
self.sio.emit('chat_message', room=self.name, data=f'{self.players[i].name} È lo sceriffo')
self.turn = i
self.players[i].prepare()
for k in range(self.players[i].max_lives):
self.players[i].hand.append(self.deck.draw())
self.players[i].notify_self()
self.play_turn()
def attack_others(self, attacker:Player):
attacker.pending_action = players.PendingAction.WAIT
attacker.notify_self()
self.waiting_for = 0
self.readyCount = 0
for p in self.players:
if p != attacker:
if p.get_banged(attacker=attacker):
self.waiting_for += 1
if self.waiting_for == 0:
attacker.pending_action = players.PendingAction.PLAY
attacker.notify_self()
def indian_others(self, attacker:Player):
attacker.pending_action = players.PendingAction.WAIT
attacker.notify_self()
self.waiting_for = 0
self.readyCount = 0
for p in self.players:
if p != attacker:
if p.get_indians(attacker=attacker):
self.waiting_for += 1
if self.waiting_for == 0:
attacker.pending_action = players.PendingAction.PLAY
attacker.notify_self()
def attack(self, attacker:Player, target_username:str):
if self.players[self.players_map[target_username]].get_banged(attacker=attacker, double=isinstance(attacker.character, characters.SlabTheKiller)):
self.readyCount = 0
self.waiting_for = 1
attacker.pending_action = players.PendingAction.WAIT
attacker.notify_self()
def duel(self, attacker:Player, target_username:str):
if self.players[self.players_map[target_username]].get_dueled(attacker=attacker):
self.readyCount = 0
self.waiting_for = 1
attacker.pending_action = players.PendingAction.WAIT
attacker.notify_self()
def emporio(self):
self.available_cards = [self.deck.draw() for i in range(len(self.players))]
self.players[self.turn].pending_action = players.PendingAction.CHOOSE
self.players[self.turn].available_cards = self.available_cards
self.players[self.turn].notify_self()
def respond_emporio(self, player, i):
player.hand.append(self.available_cards.pop(i))
player.available_cards = []
player.pending_action = players.PendingAction.WAIT
player.notify_self()
nextPlayer = self.players[(self.turn + (len(self.players)-len(self.available_cards))) % len(self.players)]
if nextPlayer == self.players[self.turn]:
self.players[self.turn].pending_action = players.PendingAction.PLAY
self.players[self.turn].notify_self()
else:
nextPlayer.pending_action = players.PendingAction.CHOOSE
nextPlayer.available_cards = self.available_cards
nextPlayer.notify_self()
def get_player_named(self, name:str):
return self.players[self.players_map[name]]
def responders_did_respond_resume_turn(self):
self.readyCount += 1
if self.readyCount == self.waiting_for:
self.waiting_for = 0
self.readyCount = 0
self.players[self.turn].pending_action = players.PendingAction.PLAY
self.players[self.turn].notify_self()
def next_player(self):
return self.players[(self.turn + 1) % len(self.players)]
def play_turn(self):
self.players[self.turn].play_turn()
def next_turn(self):
self.turn = (self.turn + 1) % len(self.players)
self.play_turn()
def notify_scrap_pile(self):
print('scrap')
if self.deck.peek_scrap_pile():
self.sio.emit('scrap', room=self.name, data=self.deck.peek_scrap_pile().__dict__)
else:
self.sio.emit('scrap', room=self.name, data=None)
def handle_disconnect(self, player: players.Player):
print(f'player {player.name} left the game {self.name}')
self.player_death(player=player)
if len(self.players) == 0:
print(f'no players left in game {self.name}')
return True
else: return False
def player_death(self, player: players.Player):
print(player.attacker)
if player.attacker and isinstance(player.attacker, roles.Sheriff) and isinstance(player.role, roles.Vice):
for i in range(len(player.attacker.hand)):
self.deck.scrap(player.attacker.hand.pop())
for i in range(len(player.attacker.equipment)):
self.deck.scrap(player.attacker.equipment.pop())
player.attacker.notify_self()
elif player.attacker and (isinstance(player.role, roles.Outlaw) or self.initial_players == 3):
for i in range(3):
player.attacker.hand.append(self.deck.draw())
player.attacker.notify_self()
print(f'player {player.name} died')
if (self.waiting_for > 0):
self.responders_did_respond_resume_turn()
vulture = [p for p in self.players if isinstance(p.character, characters.VultureSam)]
if len(vulture) == 0:
for i in range(len(player.hand)):
self.deck.scrap(player.hand.pop())
for i in range(len(player.equipment)):
self.deck.scrap(player.equipment.pop())
else:
for i in range(len(player.hand)):
vulture[0].hand.append(player.hand.pop())
for i in range(len(player.equipment)):
vulture[0].hand.append(player.equipment.pop())
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
self.players.pop(index)
self.sio.emit('room', room=self.name, data={'name': self.name, 'started': self.started, 'players': [p.name for p in self.players]})
self.sio.emit('chat_message', room=self.name, data=f'{player.name} è morto.')
if self.started:
self.sio.emit('chat_message', room=self.name, data=f'{player.name} era {player.role.name}!')
for p in self.players:
p.notify_self()
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:
attacker_role = player.attacker.role
winners = [p for p in self.players if p.role != None and p.role.on_player_death(self.players, initial_players=self.initial_players, dead_role=player.role, attacker_role=attacker_role)]
if len(winners) > 0:
print('WE HAVE A WINNER')
for p in self.players:
p.win_status = p in winners
p.notify_self()
return
if died_in_his_turn:
self.next_turn()
def get_visible_players(self, player:Player):
i = self.players.index(player)
sight = player.get_sight()
return [{
'name': self.players[j].name,
'dist': min(abs(i - j), (i+ abs(j-len(self.players))), (j+ abs(i-len(self.players)))) + 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),
} for j in range(len(self.players)) if i != j]
def notify_all(self):
if self.started:
data = [{
'name': p.name,
'ncards': len(p.hand),
'equipment': [e.__dict__ for e in p.equipment],
'lives': p.lives,
'max_lives': p.max_lives,
'is_sheriff': isinstance(p.role, roles.Sheriff),
'is_my_turn': p.is_my_turn,
'pending_action': p.pending_action,
'character': p.character.__dict__ if p.character else None,
'icon': p.role.icon if self.initial_players == 3 and p.role else '🤠'
} for p in self.players]
self.sio.emit('players_update', room=self.name, data=data)