700 lines
34 KiB
Python
700 lines
34 KiB
Python
|
|
import json
|
|
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 bang.expansions.high_noon.card_events as ceh
|
|
import bang.expansions.gold_rush.shop_cards as grc
|
|
import bang.expansions.gold_rush.characters as grch
|
|
|
|
class Game:
|
|
def __init__(self, name, sio:socketio):
|
|
super().__init__()
|
|
self.sio = sio
|
|
self.name = name
|
|
self.players: List[pl.Player] = []
|
|
self.spectators: List[pl.Player] = []
|
|
self.deck: Deck = None
|
|
self.started = False
|
|
self.turn = 0
|
|
self.ready_count = 0
|
|
self.waiting_for = 0
|
|
self.initial_players = 0
|
|
self.password = ''
|
|
self.expansions: List[str] = []
|
|
self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon', 'gold_rush']
|
|
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
|
|
self.pending_winners: List[pl.Player] = []
|
|
self.someone_won = False
|
|
self.attack_in_progress = False
|
|
self.characters_to_distribute = 2 # personaggi da dare a inizio partita
|
|
self.debug = self.name == 'debug'
|
|
self.is_changing_pwd = False
|
|
self.is_hidden = False
|
|
|
|
def reset(self):
|
|
print(f'{self.name}: resetting lobby')
|
|
self.players.extend(self.spectators)
|
|
self.spectators = []
|
|
for bot in [p for p in self.players if p.is_bot]:
|
|
if bot.was_player:
|
|
bot.is_bot = False
|
|
else:
|
|
bot.game = None
|
|
self.players = [p for p in self.players if not p.is_bot]
|
|
print(f'{self.name}: players: {self.players}')
|
|
self.started = False
|
|
self.is_handling_death = False
|
|
self.waiting_for = 0
|
|
self.incremental_turn = 0
|
|
self.turn = 0
|
|
self.pending_winners = []
|
|
for p in self.players:
|
|
p.reset()
|
|
p.notify_self()
|
|
eventlet.sleep(0.5)
|
|
self.notify_room()
|
|
|
|
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, '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,
|
|
})
|
|
self.sio.emit('debug', room=self.name, data=self.debug)
|
|
if self.debug:
|
|
commands = [
|
|
{'cmd':'/debug', 'help':'Toggles the debug mode'},
|
|
{'cmd':'/set_chars', 'help':'Set how many characters to distribute - sample /set_chars 3'},
|
|
{'cmd':'/suicide', 'help':'Kills you'},
|
|
{'cmd':'/nextevent', 'help':'Flip the next event card'},
|
|
{'cmd':'/notify', 'help':'Send a message to a player - sample /notify player hi!'},
|
|
{'cmd':'/show_cards', 'help':'View the hand of another - sample /show_cards player'},
|
|
{'cmd':'/ddc', 'help':'Destroy all cards - sample /ddc player'},
|
|
{'cmd':'/dsh', 'help':'Set health - sample /dsh player'},
|
|
# {'cmd':'/togglebot', 'help':''},
|
|
{'cmd':'/cancelgame', 'help':'Stops the current game'},
|
|
{'cmd':'/startgame', 'help':'Force starts the game'},
|
|
{'cmd':'/setbotspeed', 'help':'Changes the bot response time - sample /setbotspeed 0.5'},
|
|
# {'cmd':'/addex', 'help':''},
|
|
{'cmd':'/setcharacter', 'help':'Changes your current character - sample /setcharacter Willy The Kid'},
|
|
{'cmd':'/setevent', 'help':'Changes the event deck - sample /setevent 0 Manette'},
|
|
{'cmd':'/removecard', 'help':'Remove a card from hand/equip - sample /removecard 0'},
|
|
{'cmd':'/getcard', 'help':'Get a brand new card - sample /getcard Birra'},
|
|
{'cmd':'/meinfo', 'help':'Get player data'},
|
|
{'cmd':'/gameinfo', 'help':'Get game data'},
|
|
{'cmd':'/mebot', 'help':'Toggles bot mode'},
|
|
{'cmd':'/getnuggets', 'help':'Adds nuggets to yourself - sample /getnuggets 5'}]
|
|
self.sio.emit('commands', room=self.name, data=commands)
|
|
else:
|
|
self.sio.emit('commands', room=self.name, data=[{'cmd':'/debug', 'help':'Toggles the debug mode'}])
|
|
self.sio.emit('spectators', room=self.name, data=len(self.spectators))
|
|
|
|
def toggle_expansion(self, expansion_name):
|
|
if not self.started:
|
|
print(f'{self.name}: toggling', expansion_name)
|
|
if expansion_name in self.expansions:
|
|
self.expansions.remove(expansion_name)
|
|
else:
|
|
self.expansions.append(expansion_name)
|
|
self.notify_room()
|
|
|
|
def toggle_competitive(self):
|
|
self.is_competitive = not self.is_competitive
|
|
self.notify_room()
|
|
|
|
def toggle_disconnect_bot(self):
|
|
self.disconnect_bot = not self.disconnect_bot
|
|
self.notify_room()
|
|
|
|
def add_player(self, player: pl.Player):
|
|
if player.is_bot and len(self.players) >= 8:
|
|
return
|
|
if player in self.players or len(self.players) >= 10:
|
|
return
|
|
if len(self.players) > 7:
|
|
if 'dodge_city' not in self.expansions:
|
|
self.expansions.append('dodge_city')
|
|
player.join_game(self)
|
|
self.players.append(player)
|
|
print(f'{self.name}: Added player {player.name} to game')
|
|
self.notify_room()
|
|
self.sio.emit('chat_message', room=self.name, data=f'_joined|{player.name}')
|
|
|
|
def set_private(self):
|
|
if not self.is_changing_pwd:
|
|
self.is_changing_pwd = True
|
|
if self.password == '':
|
|
self.password = ''.join(random.choice("AEIOUJKZT123456789") for x in range(6))
|
|
print(self.name, 'is now private pwd', self.password)
|
|
else:
|
|
self.password = ''
|
|
self.notify_room()
|
|
eventlet.sleep(0.2)
|
|
self.is_changing_pwd = False
|
|
|
|
def notify_character_selection(self):
|
|
self.notify_room()
|
|
if len([p for p in self.players if p.character == None]) == 0:
|
|
for i in range(len(self.players)):
|
|
print(self.name, self.players[i].name, self.players[i].character)
|
|
self.sio.emit('chat_message', room=self.name, data=f'_choose_character|{self.players[i].name}|{self.players[i].character.name}')
|
|
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()
|
|
current_roles = [x.role.name for x in self.players]
|
|
random.shuffle(current_roles)
|
|
cr = ''
|
|
for x in current_roles:
|
|
if (x not in cr):
|
|
cr += '|' +x + '|' + str(current_roles.count(x))
|
|
self.sio.emit('chat_message', room=self.name, data=f'_allroles{cr}')
|
|
self.play_turn()
|
|
|
|
def choose_characters(self):
|
|
n = self.characters_to_distribute
|
|
char_cards = random.sample(characters.all_characters(self.expansions), len(self.players)*n)
|
|
for i in range(len(self.players)):
|
|
self.players[i].set_available_character(char_cards[i * n : i * n + n])
|
|
|
|
def start_game(self):
|
|
print(f'{self.name}: 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'_starting')
|
|
self.sio.emit('start', room=self.name)
|
|
self.started = True
|
|
self.someone_won = False
|
|
self.attack_in_progress = False
|
|
self.deck = Deck(self)
|
|
self.initial_players = len(self.players)
|
|
self.distribute_roles()
|
|
self.choose_characters()
|
|
if 'gold_rush' in self.expansions:
|
|
self.notify_gold_rush_shop()
|
|
|
|
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', 'Kill the Renegade 🦅, if you are not the one who kills him then kill the Outlaw!'),
|
|
roles.Renegade('Elimina il Fuorilegge 🐺, se non lo elimini tu elimina anche il Vice', 'Kill the Outlaw 🐺, if you are not the one who kills him then kill the Vice!'),
|
|
roles.Outlaw('Elimina il Vice 🎖, se non lo elimini tu elimina anche il Rinnegato', 'Kill the Vice 🎖, if you are not the one who kills him then kill the Renegade!')
|
|
]
|
|
elif len(self.players) >= 4:
|
|
available_roles = [roles.Sheriff(), roles.Renegade(), roles.Outlaw(), roles.Outlaw(), roles.Vice(), roles.Outlaw(), roles.Vice(), roles.Renegade(), roles.Outlaw(), roles.Vice(), roles.Outlaw()]
|
|
available_roles = available_roles[:len(self.players)]
|
|
else:
|
|
available_roles = [roles.Renegade(), roles.Renegade()]
|
|
random.shuffle(available_roles)
|
|
for i in range(len(self.players)):
|
|
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'_sheriff|{self.players[i].name}')
|
|
self.turn = i
|
|
self.players[i].notify_self()
|
|
self.notify_event_card()
|
|
|
|
def attack_others(self, attacker: pl.Player):
|
|
self.attack_in_progress = True
|
|
attacker.pending_action = pl.PendingAction.WAIT
|
|
attacker.notify_self()
|
|
self.waiting_for = 0
|
|
self.ready_count = 0
|
|
for p in self.get_alive_players():
|
|
if p != attacker:
|
|
if p.get_banged(attacker=attacker):
|
|
self.waiting_for += 1
|
|
p.notify_self()
|
|
if self.waiting_for == 0:
|
|
attacker.pending_action = pl.PendingAction.PLAY
|
|
attacker.notify_self()
|
|
self.attack_in_progress = False
|
|
if self.pending_winners and not self.someone_won:
|
|
return self.announces_winners()
|
|
|
|
def indian_others(self, attacker: pl.Player):
|
|
self.attack_in_progress = True
|
|
attacker.pending_action = pl.PendingAction.WAIT
|
|
attacker.notify_self()
|
|
self.waiting_for = 0
|
|
self.ready_count = 0
|
|
for p in self.get_alive_players():
|
|
if p != attacker:
|
|
if p.get_indians(attacker=attacker):
|
|
self.waiting_for += 1
|
|
p.notify_self()
|
|
if self.waiting_for == 0:
|
|
attacker.pending_action = pl.PendingAction.PLAY
|
|
attacker.notify_self()
|
|
self.attack_in_progress = False
|
|
if self.pending_winners and not self.someone_won:
|
|
return self.announces_winners()
|
|
|
|
def attack(self, attacker: pl.Player, target_username:str, double:bool=False):
|
|
if self.get_player_named(target_username).get_banged(attacker=attacker, double=double):
|
|
self.ready_count = 0
|
|
self.waiting_for = 1
|
|
attacker.pending_action = pl.PendingAction.WAIT
|
|
attacker.notify_self()
|
|
self.get_player_named(target_username).notify_self()
|
|
|
|
def rimbalzo(self, attacker: pl.Player, target_username:str, card_index:int):
|
|
if self.get_player_named(target_username).get_banged(attacker=attacker, no_dmg=True, card_index=card_index):
|
|
self.ready_count = 0
|
|
self.waiting_for = 1
|
|
attacker.pending_action = pl.PendingAction.WAIT
|
|
attacker.notify_self()
|
|
self.get_player_named(target_username).notify_self()
|
|
|
|
def duel(self, attacker: pl.Player, target_username:str):
|
|
if self.get_player_named(target_username).get_dueled(attacker=attacker):
|
|
self.ready_count = 0
|
|
self.waiting_for = 1
|
|
attacker.pending_action = pl.PendingAction.WAIT
|
|
attacker.notify_self()
|
|
self.get_player_named(target_username).notify_self()
|
|
|
|
def emporio(self):
|
|
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
|
|
self.sio.emit('emporio', room=self.name, data=json.dumps(
|
|
{'name':self.players[self.turn].name,'cards': self.available_cards}, default=lambda o: o.__dict__))
|
|
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 = pl.PendingAction.WAIT
|
|
player.notify_self()
|
|
pls = self.get_alive_players()
|
|
nextPlayer = pls[(pls.index(self.players[self.turn])+(len(pls)-len(self.available_cards))) % len(pls)]
|
|
if len(self.available_cards) == 1:
|
|
nextPlayer.hand.append(self.available_cards.pop())
|
|
nextPlayer.notify_self()
|
|
self.sio.emit('emporio', room=self.name, data='{"name":"","cards":[]}')
|
|
self.players[self.turn].pending_action = pl.PendingAction.PLAY
|
|
self.players[self.turn].notify_self()
|
|
elif nextPlayer == self.players[self.turn]:
|
|
self.sio.emit('emporio', room=self.name, data='{"name":"","cards":[]}')
|
|
self.players[self.turn].pending_action = pl.PendingAction.PLAY
|
|
self.players[self.turn].notify_self()
|
|
else:
|
|
nextPlayer.pending_action = pl.PendingAction.CHOOSE
|
|
nextPlayer.choose_text = 'choose_card_to_get'
|
|
nextPlayer.available_cards = self.available_cards
|
|
self.sio.emit('emporio', room=self.name, data=json.dumps(
|
|
{'name':nextPlayer.name,'cards': self.available_cards}, default=lambda o: o.__dict__))
|
|
nextPlayer.notify_self()
|
|
|
|
def get_player_named(self, name:str):
|
|
return self.players[self.players_map[name]]
|
|
|
|
def responders_did_respond_resume_turn(self, did_lose=False):
|
|
print(f'{self.name}: did_lose', did_lose)
|
|
if self.player_bangs > 0 and self.check_event(ce.PerUnPugnoDiCarte):
|
|
self.player_bangs -= 1
|
|
if self.player_bangs >= 1:
|
|
print(f'{self.name}: bang again')
|
|
if self.players[self.turn].get_banged(self.deck.event_cards[0]):
|
|
self.players[self.turn].notify_self()
|
|
else:
|
|
self.responders_did_respond_resume_turn()
|
|
else:
|
|
print(f'{self.name}: ok play turn now')
|
|
self.player_bangs = 0
|
|
self.players[self.turn].play_turn()
|
|
elif self.is_russian_roulette_on and self.check_event(ce.RouletteRussa):
|
|
pls = self.get_alive_players()
|
|
if did_lose:
|
|
target_pl = pls[(pls.index(self.players[self.turn]) + self.player_bangs) % len(pls)]
|
|
print(f'{self.name}: stop roulette')
|
|
target_pl.lives -= 1
|
|
if len([c for c in target_pl.equipment if isinstance(c, grc.Talismano)]) > 0:
|
|
target_pl.gold_nuggets += 1
|
|
if target_pl.character.check(self, grch.SimeonPicos):
|
|
target_pl.gold_nuggets += 1
|
|
if len([c for c in target_pl.equipment if isinstance(c, grc.Stivali)]) > 0:
|
|
target_pl.hand.append(self.deck.draw())
|
|
target_pl.notify_self()
|
|
self.is_russian_roulette_on = False
|
|
self.players[self.turn].play_turn()
|
|
else:
|
|
self.player_bangs += 1
|
|
target_pl = pls[(pls.index(self.players[self.turn]) + self.player_bangs) % len(pls)]
|
|
print(f'{self.name}: 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:
|
|
self.ready_count += 1
|
|
if self.ready_count == self.waiting_for:
|
|
self.waiting_for = 0
|
|
self.ready_count = 0
|
|
self.attack_in_progress = False
|
|
if self.pending_winners and not self.someone_won:
|
|
return self.announces_winners()
|
|
if self.dalton_on:
|
|
self.dalton_on = False
|
|
print(f'{self.name}: 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 announces_winners(self, winners=None):
|
|
if (winners is None):
|
|
print(f'{self.name}: WE HAVE A WINNER - pending winners')
|
|
else:
|
|
print(f'{self.name}: WE HAVE A WINNER')
|
|
for p in self.get_alive_players():
|
|
if winners is None:
|
|
p.win_status = p in self.pending_winners
|
|
else:
|
|
p.win_status = p in winners
|
|
if p.win_status:
|
|
if not self.someone_won:
|
|
self.someone_won = True
|
|
self.sio.emit('chat_message', room=self.name, data=f'_won|{p.name}|{p.role.name}')
|
|
p.notify_self()
|
|
if hasattr(self.sio, 'is_fake'):
|
|
print('announces_winners(): Running for tests, you will have to call reset manually!')
|
|
return
|
|
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()
|
|
|
|
def next_player(self):
|
|
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.name}: {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.name}: {self.players[self.turn]} is dead, event ghost')
|
|
self.players[self.turn].is_ghost = True
|
|
else:
|
|
print(f'{self.name}: {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) or ((self.initial_players == 3 and isinstance(self.players[self.turn].role, roles.Vice) and not self.players[self.turn].is_ghost) or (self.initial_players == 3 and any([p for p in self.players if p.is_dead and p.role.name == 'Vice']) and isinstance(self.players[self.turn].role, roles.Renegade))):
|
|
self.deck.flip_event()
|
|
if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] != None:
|
|
print(f'{self.name}: flip new event {self.deck.event_cards[0].name}')
|
|
self.sio.emit('chat_message', room=self.name, data={'color': f'orange','text':f'_flip_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]):
|
|
self.players[self.turn].notify_self()
|
|
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:
|
|
if p.lives != p.max_lives:
|
|
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.ready_count = 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]):
|
|
self.players[self.turn].notify_self()
|
|
else:
|
|
self.responders_did_respond_resume_turn()
|
|
else:
|
|
print(f'{self.name}: notifying {self.players[self.turn].name} about his turn')
|
|
self.players[self.turn].play_turn()
|
|
|
|
def next_turn(self):
|
|
if self.shutting_down: return
|
|
print(f'{self.name}: {self.players[self.turn].name} invoked next turn')
|
|
if self.pending_winners and not self.someone_won:
|
|
return self.announces_winners()
|
|
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, sid=None):
|
|
if len(self.deck.event_cards) > 0:
|
|
room = self.name if sid == None else sid
|
|
if self.deck.event_cards[0] != None:
|
|
self.sio.emit('event_card', room=room, data=self.deck.event_cards[0].__dict__)
|
|
else:
|
|
self.sio.emit('event_card', room=room, data=None)
|
|
|
|
def notify_gold_rush_shop(self, sid=None):
|
|
if 'gold_rush' in self.expansions and self.deck and self.deck.shop_cards and len(self.deck.shop_cards) > 0:
|
|
room = self.name if sid == None else sid
|
|
print(f'{self.name}: gold_rush_shop room={room}, data={self.deck.shop_cards}')
|
|
self.sio.emit('gold_rush_shop', room=room, data=json.dumps(self.deck.shop_cards, default=lambda o: o.__dict__))
|
|
|
|
def notify_scrap_pile(self, sid=None):
|
|
print(f'{self.name}: scrap')
|
|
room = self.name if sid == None else sid
|
|
if self.deck.peek_scrap_pile():
|
|
self.sio.emit('scrap', room=room, data=self.deck.peek_scrap_pile().__dict__)
|
|
else:
|
|
self.sio.emit('scrap', room=room, data=None)
|
|
|
|
def handle_disconnect(self, player: pl.Player):
|
|
print(f'{self.name}: player {player.name} left the game')
|
|
if player in self.spectators:
|
|
self.spectators.remove(player)
|
|
self.sio.emit('spectators', room=self.name, data=len(self.spectators))
|
|
return False
|
|
if player.is_bot and not self.started:
|
|
player.game = None
|
|
if self.disconnect_bot and self.started:
|
|
player.is_bot = True
|
|
if len([p for p in self.players if not p.is_bot]) == 0:
|
|
eventlet.sleep(5)
|
|
if len([p for p in self.players if not p.is_bot]) == 0:
|
|
print(f'{self.name}: no players left in game, shutting down')
|
|
self.shutting_down = True
|
|
self.players = []
|
|
self.spectators = []
|
|
self.deck = None
|
|
return 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.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'{self.name}: no players left in game, shutting down')
|
|
self.shutting_down = True
|
|
self.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 or player.is_ghost: return
|
|
self.is_handling_death = True
|
|
import bang.expansions.dodge_city.characters as chd
|
|
print(f'{self.name}: the killer is {player.attacker}')
|
|
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Ricercato)]) > 0 and player.attacker:
|
|
player.attacker.gold_nuggets += 1
|
|
player.attacker.hand.append(self.deck.draw(True))
|
|
player.attacker.hand.append(self.deck.draw(True))
|
|
player.attacker.notify_self()
|
|
# se lo sceriffo uccide il proprio vice
|
|
if player.attacker and player.attacker in self.players and isinstance(player.attacker.role, roles.Sheriff) and isinstance(player.role, roles.Vice):
|
|
for i in range(len(player.attacker.hand)):
|
|
self.deck.scrap(player.attacker.hand.pop(), True)
|
|
for i in range(len(player.attacker.equipment)):
|
|
self.deck.scrap(player.attacker.equipment.pop(), True)
|
|
for i in range(len(player.attacker.gold_rush_equipment)):
|
|
self.deck.shop_deck.append(player.attacker.gold_rush_equipment.pop())
|
|
player.attacker.notify_self()
|
|
elif player.attacker and player.attacker in self.players and (isinstance(player.role, roles.Outlaw) or self.initial_players == 3):
|
|
for i in range(3):
|
|
player.attacker.hand.append(self.deck.draw(True))
|
|
player.attacker.notify_self()
|
|
print(f'{self.name}: player {player.name} died')
|
|
if self.waiting_for > 0 and player.pending_action == pl.PendingAction.RESPOND:
|
|
self.responders_did_respond_resume_turn()
|
|
player.pending_action = pl.PendingAction.WAIT
|
|
|
|
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)
|
|
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:
|
|
self.sio.emit('chat_message', room=self.name, data=f'_died_role|{player.name}|{player.role.name}')
|
|
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)}
|
|
if self.started:
|
|
print(f'{self.name}: 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.get_alive_players(), initial_players=self.initial_players, dead_role=player.role, attacker_role=attacker_role)]
|
|
#print(f'win check: ready-{self.ready_count} waiting-{self.waiting_for} winners:{len(winners)}')
|
|
if not self.attack_in_progress and len(winners) > 0 and not self.someone_won:
|
|
return self.announces_winners(winners)
|
|
elif len(winners) > 0 and not self.someone_won: # non tutti hanno risposto, ma ci sono vincitori.
|
|
self.pending_winners = winners
|
|
|
|
for i in range(len(player.gold_rush_equipment)):
|
|
self.deck.shop_deck.append(player.gold_rush_equipment.pop()) # vulture sam doesnt get these cards
|
|
|
|
#il giocatore quando muore perde tutte le pepite se non è pistolero ombra
|
|
player.gold_nuggets = 0
|
|
|
|
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)
|
|
for i in range(len(player.equipment)):
|
|
self.deck.scrap(player.equipment.pop(), True)
|
|
elif len(vulture) == 2:
|
|
for i in range(len(player.hand)):
|
|
vulture[i%2].hand.append(player.hand.pop())
|
|
vulture[i%2].hand[-1].reset_card()
|
|
for i in range(len(player.equipment)):
|
|
vulture[i%2].hand.append(player.equipment.pop())
|
|
vulture[i%2].hand[-1].reset_card()
|
|
vulture[0].notify_self()
|
|
vulture[1].notify_self()
|
|
else:
|
|
for i in range(len(player.hand)):
|
|
vulture[0].hand.append(player.hand.pop())
|
|
vulture[0].hand[-1].reset_card()
|
|
for i in range(len(player.equipment)):
|
|
vulture[0].hand.append(player.equipment.pop())
|
|
vulture[0].hand[-1].reset_card()
|
|
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.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.get_alive_players() if p.character.check(self, chd.GregDigger)]
|
|
for i in range(len(greg)):
|
|
greg[i].lives = min(greg[i].lives+2, greg[i].max_lives)
|
|
herb = [p for p in self.get_alive_players() if p.character.check(self, chd.HerbHunter)]
|
|
for i in range(len(herb)):
|
|
herb[i].hand.append(self.deck.draw(True))
|
|
herb[i].hand.append(self.deck.draw(True))
|
|
herb[i].notify_self()
|
|
|
|
self.is_handling_death = False
|
|
if corpse.is_my_turn:
|
|
corpse.is_my_turn = False
|
|
corpse.notify_self()
|
|
self.next_turn()
|
|
|
|
def check_event(self, ev):
|
|
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): # returns a dictionary because we need to add the distance
|
|
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': 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,
|
|
'icon': pls[j].role.icon if (pls[j].role is not None) else '🤠',
|
|
'role': pls[j].role,
|
|
} 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:
|
|
data = [{
|
|
'name': p.name,
|
|
'ncards': len(p.hand),
|
|
'equipment': [e.__dict__ for e in p.equipment],
|
|
'gold_rush_equipment': [e.__dict__ for e in p.gold_rush_equipment],
|
|
'lives': p.lives,
|
|
'max_lives': p.max_lives,
|
|
'gold_nuggets': p.gold_nuggets,
|
|
'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,
|
|
'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 '🤠',
|
|
'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)
|