bang/backend/bang/game.py

878 lines
42 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.cards as cs
import bang.characters as characters
import bang.expansions.dodge_city.characters as chd
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
import bang.expansions.the_valley_of_shadows.cards as tvosc
from metrics import Metrics
from globals import G
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':'/playerinfo', 'help':'Get player data - sample /playerinfo player'},
{'cmd':'/cardinfo', 'help':'Get card data - sample /cardinfo handindex'},
{'cmd':'/mebot', 'help':'Toggles bot mode'},
{'cmd':'/getnuggets', 'help':'Adds nuggets to yourself - sample /getnuggets 5'},
{'cmd':'/startwithseed', 'help':'start the game with custom seed'},
{'cmd':'/getset', 'help':'get extension set of cards sample - /get valley', 'admin':True},
]
class Game:
def __init__(self, name):
super().__init__()
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.poker_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
self.rng = random.Random()
self.rpc_log = []
self.is_replay = False
self.replay_speed = 1
def shuffle_players(self):
if not self.started:
random.shuffle(self.players)
self.notify_room()
def reset(self):
for p in self.players:
if (p.sid == p.name):
p.is_bot = True
if (self.is_replay):
self.is_replay = False
print('replay ended')
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 replay(self, log, speed=1.0, fast_forward = -1):
self.players = []
self.is_hidden = True
self.is_replay = True
self.replay_speed = speed
for i in range(len(log)-1):
print('replay:', i, 'of', len(log)-3, '->', log[i])
if len(self.spectators) == 0:
break
if (log[i] == "@@@"):
eventlet.sleep(10)
if self.is_replay:
self.reset()
return
cmd = log[i].split(';')
if cmd[1] == 'players':
self.expansions = json.loads(cmd[4].replace("'",'"'))
pnames = json.loads(cmd[3].replace("'",'"'))
for p in pnames:
self.add_player(pl.Player(p, 'a', bot=False))
continue
if cmd[1] == 'start_game':
self.start_game(int(cmd[2]))
continue
player = [p for p in self.players if p.name == cmd[0]][0]
if cmd[1] == 'set_character':
if player.character != None and isinstance(player.real_character, chd.VeraCuster):
player.set_available_character([p.character for p in self.get_alive_players() if p != player])
player.set_character(cmd[2])
if cmd[1] == 'draw':
player.draw(cmd[2])
if cmd[1] == 'pick':
player.pick()
if cmd[1] == 'end_turn':
player.end_turn()
if cmd[1] == 'play_card':
data = json.loads(cmd[2])
if len(data) != 0:
player.play_card(data['index'], data['against'], data['with'])
else:
player.special(data) #TODO: remove this, is only for the typo in the log
if cmd[1] == 'respond':
player.respond(int(cmd[2]))
if cmd[1] == 'choose':
player.choose(int(cmd[2]))
if cmd[1] == 'scrap':
player.scrap(int(cmd[2]))
if cmd[1] == 'special':
player.special(json.loads(cmd[2]))
if cmd[1] == 'gold_rush_discard':
player.gold_rush_discard()
if cmd[1] == 'buy_gold_rush_card':
player.buy_gold_rush_card(int(cmd[2]))
# if cmd[1] == 'chat_message':
# chat_message(None, cmd[2], player)
if i == fast_forward:
self.replay_speed = 1.0
self.notify_room()
eventlet.sleep(max(self.replay_speed, 0.001))
eventlet.sleep(6)
if self.is_replay:
self.reset()
def notify_room(self, sid=None):
if any((p.character == None for p in self.players)) or sid:
G.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, 'avatar': p.avatar} 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,
'is_replay': self.is_replay,
})
G.sio.emit('debug', room=self.name, data=self.debug)
if self.debug:
G.sio.emit('commands', room=self.name, data=[x for x in debug_commands if 'admin' not in x])
else:
G.sio.emit('commands', room=self.name, data=[{'cmd':'/debug', 'help':'Toggles the debug mode'}])
G.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 feature_flags(self):
if 'the_valley_of_shadows' not in self.expansions and 'the_valley_of_shadows' not in self.available_expansions :
self.available_expansions.append('the_valley_of_shadows')
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)
if player.is_admin():
self.feature_flags()
self.players.append(player)
print(f'{self.name}: Added player {player.name} to game')
self.notify_room()
G.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 not any((p.character == None for p in self.players)):
for i in range(len(self.players)):
print(self.name, self.players[i].name, self.players[i].character)
G.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]
self.rng.shuffle(current_roles)
cr = ''
for x in current_roles:
if (x not in cr):
cr += '|' +x + '|' + str(current_roles.count(x))
G.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 = self.rng.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, SEED=None):
if self.started:
return
print(f'{self.name}: GAME IS STARING')
if SEED == None:
import time
SEED = int(time.time())
print(f'{self.name}: SEED IS {SEED}')
self.SEED = SEED
self.rpc_log = [f';players;{len(self.players)};{[p.name for p in self.players]};{self.expansions}', f';start_game;{SEED}']
self.rng = random.Random(SEED)
self.players_map = {c.name: i for i, c in enumerate(self.players)}
G.sio.emit('chat_message', room=self.name, data=f'_starting')
G.sio.emit('start', room=self.name)
self.started = True
self.someone_won = False
self.attack_in_progress = False
self.attack_queue = []
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()
if not self.is_replay:
Metrics.send_metric('start_game', points=[1], tags=([f"exp:{e}" for e in self.expansions] + [f"players:{self.initial_players}", f"competitive:{self.is_competitive}"]))
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()]
self.rng.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):
G.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 discard_others(self, attacker: pl.Player, card_name:str=None):
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 len(p.hand) > 0 and (p != attacker or card_name == 'Tornado'):
if p.get_discarded(attacker=attacker, card_name=card_name):
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
elif card_name == 'Poker':
self.poker_on = True
def attack_others(self, attacker: pl.Player, card_name:str=None):
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, card_name=card_name):
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 can_card_reach(self, card: cs.Card, player: pl.Player, target:str):
if card and card.range != 0 and card.range < 99:
return not any((True for p in self.get_visible_players(player) if p['name'] == target and p['dist'] > card.range))
return True
def attack(self, attacker: pl.Player, target_username:str, double:bool=False, card_name:str=None, skip_queue:bool=False):
if self.attack_in_progress and not skip_queue:
self.attack_queue.append((attacker, target_username, double, card_name))
print(f'attack in progress, queueing the attack queue len:{len(self.attack_queue)}')
return
if self.get_player_named(target_username).get_banged(attacker=attacker, double=double, card_name=card_name):
self.attack_in_progress = True
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()
elif not attacker.is_my_turn:
self.players[self.turn].pending_action = pl.PendingAction.PLAY
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
G.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):
card = self.available_cards.pop(i)
G.sio.emit('chat_message', room=self.name, data=f'_choose_emporio|{player.name}|{card.name}')
player.hand.append(card)
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()
G.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]:
G.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
G.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, next=False):
if next:
return self.players[(self.players_map[name]+1) % len(self.players)]
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
target_pl.heal_if_needed()
if any((isinstance(c, grc.Talismano) for c in target_pl.gold_rush_equipment)):
target_pl.gold_nuggets += 1
if target_pl.character.check(self, grch.SimeonPicos):
target_pl.gold_nuggets += 1
if any((isinstance(c, grc.Stivali) for c in target_pl.gold_rush_equipment)):
target_pl.hand.append(self.deck.draw(True))
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:
tmp = self.ready_count
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()
elif self.poker_on and not any(c.number == 1 for c in self.deck.scrap_pile[-tmp:]):
self.players[self.turn].pending_action = pl.PendingAction.CHOOSE
self.players[self.turn].choose_text = f'choose_from_poker;{min(2, tmp)}'
self.players[self.turn].available_cards = self.deck.scrap_pile[-tmp:]
elif self.attack_queue:
print('attack completed, next attack')
atk = self.attack_queue.pop(0)
self.attack(atk[0], atk[1], atk[2], atk[3], skip_queue=True)
else:
self.players[self.turn].pending_action = pl.PendingAction.PLAY
self.poker_on = False
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.players:
if winners is None:
p.win_status = p in self.pending_winners
else:
p.win_status = p in winners
if p.win_status and not (isinstance(p.role, roles.Renegade) and p.is_dead):
if not self.someone_won:
self.someone_won = True
G.sio.emit('chat_message', room=self.name, data=f'_won|{p.name}|{p.role.name}')
if not self.is_replay:
Metrics.send_metric('player_win', points=[1], tags=[f"char:{p.character.name}", f"role:{p.role.name}"])
p.notify_self()
if hasattr(G.sio, 'is_fake'):
print('announces_winners(): Running for tests, you will have to call reset manually!')
return
for i in range(5):
G.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 not self.is_replay:
Metrics.send_metric('incremental_turn', points=[self.incremental_turn], tags=[f'game:{self.SEED}'])
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())
if any((True for c in pl.equipment if isinstance(c, tvosc.Fantasma))):
for c in pl.equipment:
if isinstance(c, tvosc.Fantasma):
self.deck.scrap(pl.equipment.pop(c))
break
pl.notify_self()
elif self.check_event(ceh.CittaFantasma) or self.players[self.turn].is_ghost:
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}')
G.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
G.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:
G.sio.emit('event_card', room=room, data=self.deck.event_cards[0].__dict__)
else:
G.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}')
G.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():
G.sio.emit('scrap', room=room, data=self.deck.peek_scrap_pile().__dict__)
else:
G.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)
G.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 not any((not p.is_bot for p in self.players)):
eventlet.sleep(5)
if not any((not p.is_bot for p in self.players)):
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:
player.was_player = False
if len(player.available_characters) > 0:
player.set_available_character(player.available_characters)
G.sio.start_background_task(player.bot_spin)
else:
self.player_death(player=player, disconnected=True)
# else:
# player.lives = 0
# self.players.remove(player)
if not any((not p.is_bot for p in self.players)):
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 player.character and player.role:
if not self.is_replay:
Metrics.send_metric('player_death', points=[1], tags=[f"char:{player.character.name}", f"role:{player.role.name}"])
if any((isinstance(c, grc.Ricercato) for c in player.gold_rush_equipment)) and player.attacker and player.attacker in self.players:
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()
G.sio.emit('chat_message', room=self.name, data=f'_died|{player.name}')
if self.started:
G.sio.emit('chat_message', room=self.name, data=f'_died_role|{player.name}|{player.role.name}')
if not isinstance(player.role, roles.Sheriff) and not self.initial_players == 3:
G.sio.emit('notify_dead_role', room=self.name, data={
'name': player.name,
'lives': 0,
'max_lives': player.max_lives,
'is_ghost': player.is_ghost,
'is_bot': player.is_bot,
'icon': '🤠',
'avatar': player.avatar,
'role': player.role.__dict__,
})
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([p for p in self.get_alive_players() if not p.is_ghost], initial_players=self.initial_players, dead_role=player.role, attacker_role=attacker_role)]
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()
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()
#se Vulture Sam o Herb Hounter è 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()
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)
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(),
'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 '🤠',
'avatar': pls[j].avatar,
'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, include_ghosts=True):
return [p for p in self.players if p.is_dead and (include_ghosts or not p.is_ghost)]
def notify_all(self):
if self.started and self.replay_speed > 0:
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 '🤠',
'avatar': p.avatar,
'is_ghost': p.is_ghost,
'is_bot': p.is_bot,
} for p in self.get_alive_players()]
G.sio.emit('players_update', room=self.name, data=data)