Merge pull request #15 from albertoxamin/dev

completed the implementation of dodge city
This commit is contained in:
Alberto Xamin 2020-12-19 17:15:32 +01:00 committed by GitHub
commit 7b1ff4cedf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 490 additions and 59 deletions

View File

@ -5,6 +5,8 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: '1'
steps:
- uses: actions/checkout@v2
- name: Build the Unified Docker image

View File

@ -5,6 +5,8 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: '1'
steps:
- uses: actions/checkout@v2
- name: Build the Unified Docker image

View File

@ -4,6 +4,8 @@ on:
jobs:
test_build:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: '1'
steps:
- uses: actions/checkout@v2
- name: Build the Unified Docker image

View File

@ -67,7 +67,7 @@ def get_me(sid, room):
print('room exists')
if room['username'] != None and any([p.name == room['username'] for p in de_games[0].players if p.is_bot]):
print('getting inside the bot')
bot = [p for p in de_games[0].players if p.is_bot][0]
bot = [p for p in de_games[0].players if p.is_bot and p.name == room['username'] ][0]
bot.sid = sid
bot.is_bot = False
sio.enter_room(sid, de_games[0].name)
@ -82,6 +82,8 @@ def get_me(sid, room):
sio.get_session(sid).game = de_games[0]
sio.enter_room(sid, de_games[0].name)
de_games[0].notify_room(sid)
de_games[0].notify_all()
de_games[0].notify_event_card()
else:
create_room(sid, room['name'])
if sio.get_session(sid).game == None:
@ -171,18 +173,53 @@ def chat_message(sid, msg):
elif '/suicide' in msg and ses.game.started and ses.lives > 0:
ses.lives = 0
ses.notify_self()
elif '/nextevent' in msg and ses.game.started:
ses.game.deck.flip_event()
elif '/notify' in msg and ses.game.started:
cmd = msg.split()
if len(cmd) >= 3:
if cmd[1] in ses.game.players_map:
ses.game.get_player_named(cmd[1]).notify_card(ses, {
'name': ' '.join(cmd[2:]),
'icon': '🚨',
'suit': 4,
'number': ' '.join(cmd[2:])
})
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/debug_show_cards' in msg and ses.game.started:
cmd = msg.split()
if len(cmd) == 2:
if cmd[1] in ses.game.players_map:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and is looking at {cmd[1]} hand'})
for c in ses.game.get_player_named(cmd[1]).hand:
ses.notify_card(ses, c)
eventlet.sleep(0.3)
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/togglecomp' in msg and ses.game:
ses.game.toggle_competitive()
elif '/togglebot' in msg and ses.game:
ses.game.toggle_disconnect_bot()
elif '/cancelgame' in msg and ses.game.started:
ses.game.reset()
elif '/startgame' in msg and not ses.game.started:
ses.game.start_game()
elif '/addex' in msg and not ses.game.started:
cmd = msg.split()
if len(cmd) == 2:
cmd[1] = cmd[1].replace('foc', 'fistful_of_cards')
if cmd[1] not in ses.game.available_expansions:
ses.game.available_expansions.append(cmd[1])
ses.game.notify_room()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/gameinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'info: {ses.game.__dict__}'})
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.game.__dict__}'})
elif '/meinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'info: {ses.__dict__}'})
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.__dict__}'})
else:
sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'{msg} COMMAND NOT FOUND'})
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} COMMAND NOT FOUND'})
else:
color = sid.encode('utf-8').hex()[-3:]
sio.emit('chat_message', room=ses.game.name, data={'color': f'#{color}','text':f'[{ses.name}]: {msg}'})
@ -238,5 +275,15 @@ def scrap(sid, card_index):
ses: Player = sio.get_session(sid)
ses.scrap(card_index)
@sio.event
def chuck_lose_hp_draw(sid):
ses: Player = sio.get_session(sid)
ses.chuck_lose_hp_draw()
@sio.event
def holyday_special(sid, data):
ses: Player = sio.get_session(sid)
ses.holyday_special(data)
if __name__ == '__main__':
eventlet.wsgi.server(eventlet.listen(('', 5001)), app)

View File

@ -180,7 +180,8 @@ class Bang(Card):
self.need_target = True
def play_card(self, player, against, _with=None):
if player.has_played_bang and not any([isinstance(c, Volcanic) for c in player.equipment]) and against != None:
import bang.expansions.fistful_of_cards.card_events as ce
if player.has_played_bang and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)) and against != None:
return False
elif against != None:
import bang.characters as chars

View File

@ -137,7 +137,7 @@ class VultureSam(Character):
super().__init__("Vulture Sam", max_lives=4)
self.desc = "Quando un personaggio viene eliminato prendi tutte le carte di quel giocatore e aggiungile alla tua mano, sia le carte in mano che quelle in gioco"
self.desc_eng = "When a player dies, he gets all the cards in the dead's hand and equipments"
self.icon = '💰'
self.icon = '🦉'
class WillyTheKid(Character):
def __init__(self):

View File

@ -1,6 +1,7 @@
from typing import List, Set, Dict, Tuple, Optional
import random
import bang.cards as cs
import bang.expansions.fistful_of_cards.card_events as ce
class Deck:
def __init__(self, game):
@ -18,10 +19,19 @@ class Deck:
if c.name not in self.all_cards_str:
self.all_cards_str.append(c.name)
self.game = game
self.event_cards: List[ce.CardEvent] = []
if 'fistful_of_cards' in game.expansions:
self.event_cards.extend(ce.get_all_events())
random.shuffle(self.event_cards)
random.shuffle(self.cards)
self.scrap_pile: List[cs.Card] = []
print(f'Deck initialized with {len(self.cards)} cards')
def flip_event(self):
if len(self.event_cards) > 0:
self.event_cards.append(self.event_cards.pop(0))
self.game.notify_event_card()
def peek(self, n_cards: int) -> list:
return self.cards[:n_cards]

View File

@ -1,7 +1,9 @@
from bang.expansions.dodge_city import cards, characters
class DodgeCity():
def get_characters():
from bang.expansions.dodge_city import characters
return characters.all_characters()
def get_cards():
return cards.get_starting_deck()
from bang.expansions.dodge_city import cards
return cards.get_starting_deck()

View File

@ -71,6 +71,41 @@ class BelleStar(Character):
self.desc_eng = "During her turn the green cards of the other players do not work."
self.icon = ''
class VeraCuster(Character):
def __init__(self):
super().__init__("Vera Custer", max_lives=3)
self.desc = "Prima di pescare le sue carte può scegliere l'abilità speciale di un altro giocatore fino al prossimo turno."
self.desc_eng = "Before drawing, she may choose the special ability on another alive player. This ability is used until next turn."
self.icon = '🎭'
class ChuckWengam(Character):
def __init__(self):
super().__init__("Chuck Wengam", max_lives=4)
self.desc = "Durante il suo turno può perdere una vita per pescare 2 carte dal mazzo."
self.desc_eng = "On his turn he may decide to lose 1 HP to draw 2 cards from the deck."
self.icon = '💰'
class PatBrennan(Character):
def __init__(self):
super().__init__("Pat Brennan", max_lives=4)
self.desc = "Invece di pescare può prendere una carta dall'equipaggiamento di un altro giocatore."
self.desc_eng = "Instead of drawing he can steal a card from the equipment of another player."
self.icon = '🤗'
class JoseDelgrado(Character):
def __init__(self):
super().__init__("José Delgrado", max_lives=4)
self.desc = "Può scartare una carta blu per pescare 2 carte."
self.desc_eng = "He can discard a blue card to draw 2 cards."
self.icon = '🎒'
class DocHolyday(Character):
def __init__(self):
super().__init__("Doc Holyday", max_lives=4)
self.desc = "Nel suo turno può scartare 2 carte per fare un bang."
self.desc_eng = "He can discard 2 cards to play a bang."
self.icon = '✌🏻'
def all_characters() -> List[Character]:
return [
PixiePete(),
@ -83,6 +118,11 @@ def all_characters() -> List[Character]:
ApacheKid(),
SeanMallory(),
BelleStar(),
VeraCuster(),
ChuckWengam(),
PatBrennan(),
JoseDelgrado(),
DocHolyday(),
]
#Apache Kid: il suo effetto non conta nei duelli

View File

@ -0,0 +1,115 @@
from abc import ABC, abstractmethod
class CardEvent(ABC):
def __init__(self, name, icon):
self.name = name
self.icon = icon
class Agguato(CardEvent):
def __init__(self):
super().__init__('Agguato', '🛁')
self.desc = 'La distanza base di tra 2 qualsiasi giocatori è 1'
self.desc_eng = 'The base distance from any 2 players is 1'
class Cecchino(CardEvent):
def __init__(self): #TODO
super().__init__('Cecchino', '👁')
self.desc = 'Nel proprio turno i giocatori possono scartare 2 Bang assieme per sparare un bang a cui servono 2 mancato'
self.desc_eng = 'During their turn, players can discard 2 Bang! to shoot a bang that requires 2 missed'
class DeadMan(CardEvent):
def __init__(self):
super().__init__('Dead Man', '⚰️')
self.desc = 'Al proprio turno il giocatore che è morto per primo torna in vita con 2 vite e 2 carte'
self.desc_eng = 'The first player that died return back to life with 2 hp and 2 cards'
class FratelliDiSangue(CardEvent):
def __init__(self):#TODO
super().__init__('Fratelli Di Sangue', '💉')
self.desc = 'All\'inizio del proprio turno i giocatori possono perdere 1 vita per darla a un altro giocatore'
self.desc_eng = 'At the begin of their turn, payers can lose 1 hp to give it to another player'
class IlGiudice(CardEvent):
def __init__(self):
super().__init__('Il Giudice', '👨‍⚖️')
self.desc = 'Non si possono equipaggiare carte a se stessi o agli altri'
self.desc_eng = 'You can\'t equip cards on your or other players'
class Lazo(CardEvent):
def __init__(self):
super().__init__('Lazo', '📿')
self.desc = 'Le carte equipaggiate non hanno effetto'
self.desc_eng = 'Cards in the equipment slot do not work'
class LeggeDelWest(CardEvent):
def __init__(self):
super().__init__('Legge Del West', '⚖️')
self.desc = 'I giocatori mostrano la seconda carta che pescano e sono obbligati a usarla in quel turno (se possibile)'
self.desc_eng = 'Every player shows the second card that they draw and must use it in that round'
class LiquoreForte(CardEvent):
def __init__(self):#TODO
super().__init__('Liquore Forte', '🥃')
self.desc = 'I giocatori possono evitare di pescare per recuperare 1 vita'
self.desc_eng = 'Players can skip drawing to regain 1 HP'
class MinieraAbbandonata(CardEvent):
def __init__(self):#TODO
super().__init__('Miniera Abbandonata', '')
self.desc = 'I giocatori pescano dagli scarti e scartano in cima al mazzo'
self.desc_eng = 'Players draw from the discarded pile and discard to the deck'
class PerUnPugnoDiCarte(CardEvent):
def __init__(self):#TODO
super().__init__('Per Un Pugno Di Carte', '🎴')
self.desc = 'Il giocatore subisce tanti bang quante carte ha in mano'
self.desc_eng = 'On his turn the player is target of as many Bang as how many cards he has in his hand'
class Peyote(CardEvent):
def __init__(self):#TODO
super().__init__('Peyote', '🌵')
self.desc = 'Invece che pescare il giocatore prova a indovinare il colore del seme, se lo indovina continua'
self.desc_eng = 'Instead of drawing, the player tries to guess the color of the suit, if he\'s right he repeats'
class Ranch(CardEvent):
def __init__(self):#TODO
super().__init__('Ranch', '🐮')
self.desc = 'Dopo aver pescato il giocatore può scartare quante carte vuole dalla mano e pescarne altrettante dal mazzo'
self.desc_eng = 'After drawing, the player can discard as many cards as he wants from his hand and draw as many from the deck'
class Rimbalzo(CardEvent):
def __init__(self):#TODO
super().__init__('Rimbalzo', '')
self.desc = 'Il giocatore di turno può giocare bang contro le carte equipaggiate dagli altri giocatori, se non giocano mancato vengono scartate'
self.desc_eng = 'The player can play bang against the cards equipped by the other players, if they do not play miss they are discarded'
class RouletteRussa(CardEvent):
def __init__(self):#TODO
super().__init__('Roulette Russa', '🇷🇺')
self.desc = 'A partire dallo sceriffo, ogni giocatore scarta 1 mancato, il primo che non lo fa perde 2 vite'
self.desc_eng = 'Starting from the sheriff, every player discards 1 missed, the first one that doesn\'t loses 2 HP'
class Vendetta(CardEvent):
def __init__(self):
super().__init__('Vendetta', '😤')
self.desc = 'Alla fine del proprio turno il giocatore estrae, se esce ♥️ gioca un altro turno'
self.desc_eng = 'When ending the turn, the player flips a card, if it\'s ♥️ he plays another turn'
def get_all_events():
return [
Agguato(),
# Cecchino(),
DeadMan(),
# FratelliDiSangue(),
IlGiudice(),
Lazo(),
LeggeDelWest(),
# LiquoreForte(),
# MinieraAbbandonata(),
# PerUnPugnoDiCarte(),
# Peyote(),
# Ranch(),
# Rimbalzo(),
# RouletteRussa(),
Vendetta(),
]

View File

@ -6,6 +6,7 @@ import bang.players as players
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 eventlet
class Game:
@ -23,6 +24,7 @@ class Game:
self.initial_players = 0
self.password = ''
self.expansions = []
self.available_expansions = ['dodge_city']
self.shutting_down = False
self.is_competitive = False
self.disconnect_bot = True
@ -37,6 +39,7 @@ class Game:
'is_competitive': self.is_competitive,
'disconnect_bot': self.disconnect_bot,
'expansions': self.expansions,
'available_expansions': self.available_expansions
})
def toggle_expansion(self, expansion_name):
@ -90,7 +93,11 @@ class Game:
for k in range(self.players[i].max_lives):
self.players[i].hand.append(self.deck.draw())
self.players[i].notify_self()
self.players[self.turn].play_turn()
current_roles = [type(x.role).__name__ for x in self.players]
random.shuffle(current_roles)
current_roles = str({x:current_roles.count(x) for x in current_roles}).replace('{','').replace('}','')
self.sio.emit('chat_message', room=self.name, data=f'_allroles|{current_roles}')
self.play_turn()
def choose_characters(self):
char_cards = random.sample(characters.all_characters(self.expansions), len(self.players)*2)
@ -121,6 +128,8 @@ class Game:
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])
@ -209,6 +218,15 @@ class Game:
return self.players[(self.turn + 1) % len(self.players)]
def play_turn(self):
if isinstance(self.players[self.turn].role, roles.Sheriff):
self.deck.flip_event()
if self.check_event(ce.DeadMan) and len(self.dead_players) > 0:
self.players.append(self.dead_players.pop(0))
self.players[-1].lives = 2
self.players[-1].hand.append(self.deck.draw())
self.players[-1].hand.append(self.deck.draw())
self.players_map = {c.name: i for i, c in enumerate(self.players)}
self.players[-1].notify_self()
self.players[self.turn].play_turn()
def next_turn(self):
@ -217,6 +235,10 @@ class Game:
self.turn = (self.turn + 1) % len(self.players)
self.play_turn()
def notify_event_card(self):
if len(self.deck.event_cards) > 0:
self.sio.emit('event_card', room=self.name, data=self.deck.event_cards[0].__dict__)
def notify_scrap_pile(self):
print('scrap')
if self.deck.peek_scrap_pile():
@ -229,6 +251,8 @@ class Game:
if player in self.players:
if self.disconnect_bot and self.started:
player.is_bot = True
eventlet.sleep(15) # he may reconnect
player.notify_self()
else:
self.player_death(player=player, disconnected=True)
else:
@ -329,12 +353,17 @@ class Game:
eventlet.sleep(0.5)
self.notify_room()
def check_event(self, ev):
if len(self.deck.event_cards) == 0: return False
return isinstance(self.deck.event_cards[0], ev)
def get_visible_players(self, player: players.Player):
i = self.players.index(player)
sight = player.get_sight()
mindist = 99 if not self.check_event(ce.Agguato) else 1
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),
'dist': min([abs(i - j), (i+ abs(j-len(self.players))), (j+ abs(i-len(self.players))), mindist]) + 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),
@ -352,6 +381,7 @@ class Game:
'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 '🤠'
} for p in self.players]
self.sio.emit('players_update', room=self.name, data=data)

View File

@ -8,6 +8,7 @@ import bang.cards as cs
import bang.expansions.dodge_city.cards as csd
import bang.characters as chars
import bang.expansions.dodge_city.characters as chd
import bang.expansions.fistful_of_cards.card_events as ce
import eventlet
class PendingAction(IntEnum):
@ -30,6 +31,7 @@ class Player:
self.equipment: cs.Card = []
self.role: r.Role = None
self.character: chars.Character = None
self.real_character: chars.Character = None
self.lives = 0
self.max_lives = 0
self.game: g = None
@ -49,12 +51,14 @@ class Player:
self.mancato_needed = 0
self.molly_discarded_cards = 0
self.is_bot = bot
self.special_use_count = 0
def reset(self):
self.hand: cs.Card = []
self.equipment: cs.Card = []
self.role: r.Role = None
self.character: chars.Character = None
self.real_character: chars.Character = None
self.lives = 0
self.max_lives = 0
self.is_my_turn = False
@ -70,6 +74,7 @@ class Player:
self.attacker: Player = None
self.target_p: str = None
self.is_drawing = False
self.special_use_count = 0
try:
del self.win_status
except:
@ -92,13 +97,24 @@ class Player:
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'_did_choose_character|{self.name}')
self.game.notify_character_selection()
if self.character == None:
self.character = next(
x for x in self.available_characters if x.name == character)
self.real_character = self.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'_did_choose_character|{self.name}')
self.game.notify_character_selection()
elif self.real_character and isinstance(self.real_character, chd.VeraCuster):
self.character = next(
x for x in self.available_characters if x.name == character)
self.available_characters = []
self.sio.emit('chat_message', room=self.game.name,
data=f'_did_choose_character|{self.name}')
self.pending_action = PendingAction.DRAW
self.notify_self()
def prepare(self):
self.max_lives = self.character.max_lives + self.role.health_mod
@ -116,10 +132,15 @@ class Player:
else:
self.set_character(available[randrange(0, len(available))].name)
def notify_card(self, player, card):
def notify_card(self, player, card, message=''):
try:
card = card.__dict__
except:
pass
mess = {
'player': player.name,
'card': card.__dict__
'card': card,
'message':message
}
print('notifying card')
self.sio.emit('notify_card', room=self.sid, data=mess)
@ -184,7 +205,7 @@ class Player:
self.draw('')
elif self.pending_action == PendingAction.PLAY:
has_played = False
if len([c for c in self.hand if c.is_equipment or c.usable_next_turn]) > 0:
if len([c for c in self.hand if (c.is_equipment or c.usable_next_turn) and not self.game.check_event(ce.IlGiudice)]) > 0:
for i in range(len(self.hand)):
if self.hand[i].is_equipment or self.hand[i].usable_next_turn:
self.play_card(i)
@ -246,7 +267,7 @@ class Player:
did_respond = True
break
for i in range(len(self.equipment)):
if self.equipment[i].name in self.expected_response:
if not self.game.check_event(ce.Lazo) and self.equipment[i].name in self.expected_response:
self.respond(len(self.hand)+i)
did_respond = True
break
@ -274,10 +295,14 @@ class Player:
self.is_my_turn = True
self.is_waiting_for_action = True
self.has_played_bang = False
if any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]):
self.special_use_count = 0
if not self.game.check_event(ce.Lazo) and any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]):
self.pending_action = PendingAction.PICK
else:
self.pending_action = PendingAction.DRAW
if isinstance(self.real_character, chd.VeraCuster):
self.set_available_character([p.character for p in self.game.players if p != self])
else:
self.pending_action = PendingAction.DRAW
self.notify_self()
def draw(self, pile):
@ -288,6 +313,11 @@ class Player:
self.available_cards = [self.game.deck.draw() for i in range(3)]
self.pending_action = PendingAction.CHOOSE
self.notify_self()
elif isinstance(self.character, chd.PatBrennan) and type(pile) == str and pile != self.name and pile in self.game.players_map and len(self.game.get_player_named(pile).equipment) > 0:
self.is_drawing = True
self.available_cards = self.game.get_player_named(pile).equipment
self.pending_action = PendingAction.CHOOSE
self.notify_self()
else:
self.pending_action = PendingAction.PLAY
if pile == 'scrap' and isinstance(self.character, chars.PedroRamirez):
@ -310,11 +340,11 @@ class Player:
for i in range(2):
card: cs.Card = self.game.deck.draw()
self.hand.append(card)
if i == 1 and isinstance(self.character, chars.BlackJack):
if i == 1 and isinstance(self.character, chars.BlackJack) or self.game.check_event(ce.LeggeDelWest):
for p in self.game.players:
if p != self:
p.notify_card(self, card)
if card.suit == cs.Suit.HEARTS or card.suit == cs.Suit.DIAMONDS:
p.notify_card(self, card, 'blackjack_special' if isinstance(self.character, chars.BlackJack) else 'foc.leggedelwest')
if card.suit == cs.Suit.HEARTS or card.suit == cs.Suit.DIAMONDS and isinstance(self.character, chars.BlackJack):
self.hand.append(self.game.deck.draw())
if isinstance(self.character, chd.PixiePete):
self.hand.append(self.game.deck.draw())
@ -371,7 +401,10 @@ class Player:
if any([isinstance(c, cs.Prigione) for c in self.equipment]):
self.notify_self()
return
self.pending_action = PendingAction.DRAW
if isinstance(self.real_character, chd.VeraCuster):
self.set_available_character([p.character for p in self.game.players if p != self])
else:
self.pending_action = PendingAction.DRAW
self.notify_self()
else:
self.pending_action = PendingAction.WAIT
@ -405,9 +438,10 @@ class Player:
withCard = self.hand.pop(_with) if hand_index > _with else self.hand.pop(_with - 1)
print(self.name, 'is playing ', card, ' against:', against, ' with:', _with)
did_play_card = False
if not(against != None and isinstance(self.game.get_player_named(against).character, chd.ApacheKid) and card.suit == cs.Suit.DIAMONDS):
event_blocks_card = (self.game.check_event(ce.IlGiudice) and (card.is_equipment or (card.usable_next_turn and not card.can_be_used_now))) or (self.game.check_event(ce.Lazo) and card.usable_next_turn and card.can_be_used_now)
if not(against != None and isinstance(self.game.get_player_named(against).character, chd.ApacheKid) and card.suit == cs.Suit.DIAMONDS) and not event_blocks_card:
did_play_card = card.play_card(self, against, withCard)
if not card.is_equipment and not card.usable_next_turn:
if not card.is_equipment and not card.usable_next_turn or event_blocks_card:
if did_play_card:
self.game.deck.scrap(card)
else:
@ -456,6 +490,14 @@ class Player:
self.is_drawing = False
self.pending_action = PendingAction.PLAY
self.notify_self()
elif self.is_drawing and isinstance(self.character, chd.PatBrennan):
card = self.available_cards.pop(card_index)
if card.usable_next_turn:
card.can_be_used_now = False
self.hand.append(card)
self.available_cards = []
self.pending_action = PendingAction.PLAY
self.notify_self()
else: # emporio
self.game.respond_emporio(self, card_index)
@ -500,7 +542,7 @@ class Player:
self.take_damage_response()
return False
else:
if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 or isinstance(self.character, chars.Jourdonnais):
if (not self.game.check_event(ce.Lazo) and len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0) or isinstance(self.character, chars.Jourdonnais):
print('has barrel')
self.pending_action = PendingAction.PICK
self.on_pick_cb = self.barrel_pick
@ -508,7 +550,7 @@ class Player:
print('has mancato')
self.pending_action = PendingAction.RESPOND
self.expected_response = self.game.deck.mancato_cards
if self.attacker and isinstance(self.attacker.character, chd.BelleStar):
if self.attacker and isinstance(self.attacker.character, chd.BelleStar) or self.game.check_event(ce.Lazo):
self.expected_response = self.game.deck.mancato_cards_not_green
if isinstance(self.character, chd.ElenaFuente):
self.expected_response = self.game.deck.all_cards_str
@ -621,6 +663,8 @@ class Player:
range += card.range
else:
aim += card.sight_mod
if self.game.check_event(ce.Lazo):
return 1 + self.character.sight_mod
return max(1, range) + aim + self.character.sight_mod
def get_visibility(self):
@ -629,15 +673,38 @@ class Player:
covers = 0
for card in self.equipment:
covers += card.vis_mod
if self.game.check_event(ce.Lazo):
return self.character.visibility_mod
return self.character.visibility_mod + covers
def scrap(self, card_index):
if self.is_my_turn or isinstance(self.character, chars.SidKetchum):
self.scrapped_cards += 1
card = self.hand.pop(card_index)
if isinstance(self.character, chars.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))
elif isinstance(self.character, chd.JoseDelgrado) and card.is_equipment and self.special_use_count < 2:
self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw())
self.special_use_count += 1
self.game.deck.scrap(card)
self.notify_self()
def holyday_special(self, data):
if isinstance(self.character, chd.DocHolyday) and self.special_use_count < 1:
self.special_use_count += 1
cards = sorted(data['cards'], reverse=True)
for c in cards:
self.game.deck.scrap(self.hand.pop(c))
self.notify_self()
self.game.attack(self, data['against'])
def chuck_lose_hp_draw(self):
if isinstance(self.character, chd.ChuckWengam) and self.lives > 1 and self.is_my_turn:
self.lives -= 1
self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw())
self.notify_self()
def end_turn(self, forced=False):
@ -648,6 +715,12 @@ class Player:
print(
f"I {self.name} have to many cards in my hand and I can't end the turn")
elif self.pending_action == PendingAction.PLAY or forced:
if not forced and self.game.check_event(ce.Vendetta):
picked: cs.Card = self.game.deck.pick_and_scrap()
self.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked}')
if picked.suit == cs.Suit.HEARTS:
self.play_turn()
return
self.is_my_turn = False
for i in range(len(self.equipment)):
if self.equipment[i].usable_next_turn and not self.equipment[i].can_be_used_now:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -3,7 +3,7 @@
<h4>{{card.name}}</h4>
<div class="emoji">{{card.icon}}</div>
<div class="alt_text">{{card.alt_text}}</div>
<div class="suit">{{card.number}}{{suit}}</div>
<div class="suit">{{number}}{{suit}}</div>
</div>
</template>
@ -20,6 +20,13 @@ export default {
return x[this.card.suit];
}
return '';
},
number() {
if (this.card.number === 1) return 'A'
else if (this.card.number === 11) return 'J'
else if (this.card.number === 12) return 'Q'
else if (this.card.number === 13) return 'K'
else return this.card.number
}
}
}
@ -72,6 +79,14 @@ export default {
box-shadow:
0 0 0 3pt #6aa16e, 0 0 0 6pt white, 0 0 5pt 6pt #aaa
}
.card.high-noon{
box-shadow: 0 0 0pt 4pt white, 0 0 5pt 4pt #aaa;
border: 2pt dotted rgb(198 78 45);
}
.card.fistful-of-cards{
box-shadow: 0 0 0pt 4pt white, 0 0 5pt 4pt #aaa;
border: 2pt dashed rgb(50 122 172);
}
.card h4 {
position: absolute;
text-align: center;
@ -85,6 +100,10 @@ export default {
font-size:26pt;
top: 35%;
}
.fistful-of-cards .emoji, .high-noon .emoji{
top:auto !important;
bottom:15% !important;
}
.card .suit {
position: absolute;
bottom: 3pt;
@ -135,5 +154,11 @@ export default {
box-shadow:
0 0 0 3pt #6aa16e, 0 0 0 6pt #181a1b, 0 0 5pt 6pt #aaa
}
.card.high-noon{
box-shadow: 0 0 0pt 4pt #181a1b, 0 0 5pt 4pt #aaa;
}
.card.fistful-of-cards{
box-shadow: 0 0 0pt 4pt #181a1b, 0 0 5pt 4pt #aaa;
}
}
</style>

View File

@ -2,6 +2,11 @@
<div>
<div class="deck">
<card v-if="endTurnAction && isPlaying" v-show="pending_action == 2" :card="endTurnCard" class="end-turn" @click.native="endTurnAction"/>
<div v-if="eventCard" style="position:relative">
<div class="card fistful-of-cards" style="position:relative; bottom:-3pt;right:-3pt;"/>
<div class="card fistful-of-cards" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
<card :card="eventCard" :key="eventCard" :class="{'last-event':true,'fistful-of-cards':true}"/>
</div>
<div style="position:relative">
<div class="card back" style="position:absolute; bottom:-3pt;right:-3pt;"/>
<div class="card back" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
@ -15,6 +20,7 @@
</div>
</div>
<transition name="list">
<p v-if="eventCard" class="center-stuff"><i>{{($i18n.locale=='it'?eventCard.desc:eventCard.desc_eng)}}</i></p>
<p v-if="desc" class="center-stuff"><i>{{desc}}</i></p>
</transition>
</div>
@ -37,6 +43,7 @@ export default {
icon: '💥',
},
lastScrap: null,
eventCard: null,
previousScrap: null,
pending_action: false,
isPlaying: true,
@ -50,7 +57,10 @@ export default {
},
scrap(card) {
this.lastScrap = card
}
},
event_card(card) {
this.eventCard = card
},
},
computed: {
endTurnCard() {
@ -81,10 +91,10 @@ export default {
</script>
<style scoped>
.deck {
display:flex;
margin:0;
align-items: center;
justify-content: center;
display:flex;
margin:0;
align-items: center;
justify-content: center;
flex-direction: row-reverse;
}
.last-scrap {
@ -105,6 +115,20 @@ export default {
transform: translate(0, 0) scale(1);
}
}
.last-event {
position: absolute;
top: 0;
animation-duration: 0.8s;
animation-name: slidein;
}
@keyframes slidein {
from {
transform: translate(30px, 20px) scale(1.3) rotate(-10deg);
}
to {
transform: translate(0, 0) scale(1);
}
}
.pick:hover {
transform: translate(-10px,0);
z-index: 1;

View File

@ -22,6 +22,7 @@
</transition-group>
<Card :card="p.card" :class="{is_my_turn:p.is_my_turn}"/>
<Card v-if="p.character" :card="p.character" class="character tiny-character" @click.native="selectedInfo = [p.character]"/>
<Card v-if="p.character && p.character.name !== p.real_character.name" style="transform:scale(0.5) translate(-90px, -50px);" :card="p.character" class="character tiny-character" @click.native="selectedInfo = [p.character]"/>
<tiny-hand :ncards="p.ncards" @click.native="drawFromPlayer(p.name)" :ismyturn="p.pending_action === 2"/>
<span style="position:absolute;top:10pt;" class="center-stuff">{{getActionEmoji(p)}}</span>
<div class="tiny-equipment">
@ -33,7 +34,10 @@
</div>
<div v-if="!started">
<h3>{{$t("expansions")}}</h3>
<PrettyCheck @click.native="toggleExpansions('dodge_city')" :disabled="!isRoomOwner" v-model="useDodgeCity" class="p-switch p-fill" style="margin-top:5px; margin-bottom:3px;">Dodge City</PrettyCheck>
<div v-for="ex in togglable_expansions" v-bind:key="ex">
<PrettyCheck @click.native="toggleExpansions(ex)" :disabled="!isRoomOwner" :value="is_toggled_expansion(ex)" class="p-switch p-fill" style="margin-top:5px; margin-bottom:3px;">{{get_expansion_name(ex)}}</PrettyCheck>
<br>
</div>
<h3>{{$t('mods')}}</h3>
<PrettyCheck @click.native="toggleCompetitive" :disabled="!isRoomOwner" v-model="is_competitive" class="p-switch p-fill" style="margin-top:5px; margin-bottom:3px;">{{$t('mod_comp')}}</PrettyCheck>
<br>
@ -82,7 +86,6 @@ export default {
players: [],
messages: [],
distances: {},
self: {},
hasToChoose: false,
target_p: '',
chooseCards: [],
@ -90,7 +93,8 @@ export default {
selectedInfo: null,
privateRoom: false,
password: '',
useDodgeCity: false,
togglable_expansions: [],
expansions: [],
hasToSetUsername: false,
is_competitive: false,
disconnect_bot: false,
@ -103,7 +107,8 @@ export default {
this.privateRoom = data.password !== ''
this.is_competitive = data.is_competitive
this.disconnect_bot = data.disconnect_bot
this.useDodgeCity = data.expansions.indexOf('dodge_city') !== -1
this.togglable_expansions = data.available_expansions
this.expansions = data.expansions
this.players = data.players.map(x => {
return {
name: x.name,
@ -168,6 +173,12 @@ export default {
}
},
methods: {
is_toggled_expansion(ex) {
return this.expansions.indexOf(ex) !== -1
},
get_expansion_name(ex) {
return ex.replace('_', ' ').replace(/\w\S*/g, m => m.charAt(0).toUpperCase()+m.substr(1).toLowerCase())
},
leaveRoom() {
window.location.replace(window.location.origin)
},

View File

@ -1,6 +1,6 @@
<template>
<div>
<p v-if="instruction" class="center-stuff">{{instruction}}</p>
<p v-if="instruction && lives > 0" class="center-stuff">{{instruction}}</p>
<!-- <button v-if="canEndTurn" @click="end_turn">Termina Turno</button> -->
<div class="equipment-slot">
<Card v-if="my_role" :card="my_role" class="back"
@ -20,6 +20,10 @@
<transition name="list">
<p v-if="desc"><i>{{desc}}</i></p>
</transition>
<button v-if="is_my_turn && character.name === 'Sid Ketchum' && lives < max_lives && hand.length > 1" @click="sidWantsScrapForHealth=true">{{$t('special_ability')}}</button>
<button v-if="is_my_turn && character.name === 'Chuck Wengam' && lives > 1" @click="chuckSpecial">{{$t('special_ability')}}</button>
<button v-if="is_my_turn && character.name === 'José Delgrado' && special_use_count < 2 && hand.filter(x => x.is_equipment).length > 0" @click="joseScrap=true">{{$t('special_ability')}}</button>
<button v-if="is_my_turn && character.name === 'Doc Holyday' && special_use_count < 1 && hand.length > 1" @click="holydayScrap=true">{{$t('special_ability')}}</button>
<div v-if="lives > 0" style="position:relative">
<span id="hand_text">{{$t('hand')}}</span>
<transition-group name="list" tag="div" class="hand">
@ -38,15 +42,19 @@
<Chooser v-if="lives <= 0 && max_lives > 0" :text="$t('you_died')" :cancelText="$t('spectate')" :cancel="()=>{max_lives = 0}"/>
<Chooser v-if="win_status !== undefined" :text="win_status?$t('you_win'):$t('you_lose')" />
<Chooser v-if="show_role" :text="$t('you_are')" :cards="[my_role]" :hintText="($i18n.locale=='it'?my_role.goal:my_role.goal_eng)" :select="() => {show_role=false}" :cancel="() => {show_role=false}" :cancelText="$t('ok')" />
<Chooser v-if="notifycard" :key="notifycard.card" :text="`${notifycard.player} ${$t('did_pick_as')}:`" :cards="[notifycard.card]" :hintText="$t('if_card_red')" class="turn-notify-4s"/>
<Chooser v-if="notifycard" :key="notifycard.card" :text="`${notifycard.player} ${$t('did_pick_as')}:`" :cards="[notifycard.card]" :hintText="$t(notifycard.message)" class="turn-notify-4s"/>
<Chooser v-if="!show_role && is_my_turn && pending_action < 2" :text="$t('play_your_turn')" :key="is_my_turn" class="turn-notify" />
<Chooser v-if="!show_role && availableCharacters.length > 0" :text="$t('choose_character')" :cards="availableCharacters" :select="setCharacter"/>
<Chooser v-if="hasToPickResponse" :text="`${$t('pick_a_card')} ${attacker?($t('to_defend_from')+' '+attacker):''}`" :key="hasToPickResponse" class="turn-notify" />
<Chooser v-if="!card_against && card_with" :text="`${$t('choose_scarp_card_to')} ${card_with.name.toUpperCase()}`" :cards="hand.filter(x => x !== card_with)" :select="selectWith" :cancel="()=>{card_with = null}"/>
<Chooser v-if="showScrapScreen" :text="`${$t('discard')} ${hand.length}/${lives}`" :cards="hand" :select="scrap" :cancel="cancelEndingTurn"/>
<Chooser v-if="sidWantsScrapForHealth && sidScrapForHealth.length < 2" :text="`${$t('discard')} ${2 - sidScrapForHealth.length} ${$t('to_regain_1_hp')}`"
:cards="sidScrapHand" :select="sidScrap" :cancel="() => {sidWantsScrapForHealth = false;sidScrapForHealth=[]}"/>
<button v-if="is_my_turn && character.name === 'Sid Ketchum'" @click="sidWantsScrapForHealth=true">{{$t('special_ability')}}</button>
<Chooser v-if="sidWantsScrapForHealth && scrapHand.length < 2" :text="`${$t('discard')} ${2 - scrapHand.length} ${$t('to_regain_1_hp')}`"
:cards="notScrappedHand" :select="sidScrap" :cancel="() => {sidWantsScrapForHealth = false;scrapHand=[]}"/>
<Chooser v-if="joseScrap" :text="`${$t('discard')}`"
:cards="hand.filter(x => x.is_equipment)" :select="(card) => {joseScrap=false;scrap(card)}" :cancel="() => {joseScrap=false}"/>
<Chooser v-if="holydayScrap && scrapHand.length < 2" :text="`${$t('discard')} ${2 - scrapHand.length}`"
:cards="notScrappedHand" :select="holydayScrapAdd" :cancel="() => {holydayScrap = false;scrapHand=[]}"/>
<Chooser v-if="holydayScrap && scrapHand.length == 2" :text="$t('card_against')" :cards="otherPlayers" :select="holydayScrapBang" :cancel="() => {holydayScrap = false;scrapHand=[]}"/>
</div>
</template>
@ -91,8 +99,11 @@ export default {
attacker: undefined,
notifycard: null,
desc: '',
sidScrapForHealth: [],
scrapHand: [],
sidWantsScrapForHealth: false,
joseScrap: false,
holydayScrap: false,
special_use_count: 0,
mancato_needed: 0,
name: '',
}),
@ -116,6 +127,7 @@ export default {
this.lives = self.lives
this.max_lives = self.max_lives
this.has_played_bang = self.has_played_bang
this.special_use_count = self.special_use_count
this.is_my_turn = self.is_my_turn
if (this.is_my_turn) document.title = this.$t('your_turn')+' | PewPew!'
else if (this.pending_action == 3) document.title = this.$t('your_response')+' | PewPew!'
@ -152,8 +164,20 @@ export default {
showScrapScreen() {
return this.isEndingTurn && !this.canEndTurn && this.is_my_turn;
},
sidScrapHand() {
return this.hand.filter((x, i) => (this.sidScrapForHealth.indexOf(i) === -1))
notScrappedHand() {
return this.hand.filter((x, i) => (this.scrapHand.indexOf(i) === -1))
},
otherPlayers() {
let vis = this.playersDistances.filter(x => {
return x.name !== this.name
}).map(player => {
return {
name: player.name,
number: player.dist !== undefined ? `${player.dist}` : '',
icon: player.is_sheriff ? '⭐' : '🤠',
is_character: true,
}})
return vis
},
visiblePlayers() {
this.range;
@ -212,14 +236,29 @@ export default {
this.$socket.emit('set_character', char.name)
},
sidScrap(c) {
this.sidScrapForHealth.push(this.hand.indexOf(c))
if (this.sidScrapForHealth.length == 2) {
this.$socket.emit('scrap', this.hand.indexOf(this.sidScrapForHealth[0]))
this.$socket.emit('scrap', this.hand.indexOf(this.sidScrapForHealth[1]))
this.sidScrapForHealth = []
this.scrapHand.push(this.hand.indexOf(c))
if (this.scrapHand.length == 2) {
let x = [this.hand.indexOf(this.scrapHand[0]), this.hand.indexOf(this.scrapHand[1])].sort().reverse()
this.$socket.emit('scrap', x[0])
this.$socket.emit('scrap', x[1])
this.scrapHand = []
this.sidWantsScrapForHealth = false
}
},
holydayScrapAdd(c) {
this.scrapHand.push(this.hand.indexOf(c))
},
holydayScrapBang(other) {
this.$socket.emit('holyday_special', {
cards : [this.hand.indexOf(this.scrapHand[0]), this.hand.indexOf(this.scrapHand[1])],
against: other.name
})
this.scrapHand = []
this.holydayScrap = false
},
chuckSpecial(){
this.$socket.emit('chuck_lose_hp_draw')
},
end_turn(){
console.log('ending turn')
this.cancelEndingTurn()

View File

@ -48,7 +48,7 @@
"play_your_turn": "PLAY YOUR TURN",
"you_are": "You are",
"did_pick_as": "picked this as second card",
"if_card_red":"If the card is diamonds or hearts, he picks another card.",
"blackjack_special":"If the card is diamonds or hearts, he picks another card.",
"choose_scarp_card_to": "CHOOSE WHICH CARD TO DISCARD TO USE",
"pick_a_card": "FLIP A CARD",
"to_defend_from": "TO DEFEND YOURSELF FROM",
@ -79,7 +79,11 @@
"saloon": "{0} player {1} and heals 1 HP to everyone alive.",
"special_bart_cassidy": "{0} received a compensation because he was injured.",
"special_el_gringo": "{0} stole a card from {1} when he was was injured.",
"special_calamity": "{0} played {1} as Bang! against {2}."
"special_calamity": "{0} played {1} as Bang! against {2}.",
"allroles": "In the game there are: {0}."
},
"foc": {
"leggedelwest": "He must play this card on this turn if possible."
},
"mods": "Modifiers",
"mod_comp": "Competitive mode (disables automatic take damage)",

View File

@ -48,7 +48,7 @@
"play_your_turn": "GIOCA IL TUO TURNO",
"you_are": "Tu sei",
"did_pick_as": "ha pescato come seconda carta",
"if_card_red": "Se la carta è cuori o quadri ne pesca un'altra",
"blackjack_special": "Se la carta è cuori o quadri ne pesca un'altra",
"choose_scarp_card_to": "SCEGLI CHE CARTA SCARTARE PER USARE",
"pick_a_card": "ESTRAI UNA CARTA",
"to_defend_from": "PER DIFENDERTI DA",
@ -79,7 +79,11 @@
"saloon": "{0} ha giocato {1} e ha curato 1 punto vita a tutti.",
"special_bart_cassidy": "{0} ha ricevuto un risarcimento perchè è stato ferito.",
"special_el_gringo": "{0} rubato una carta a {1} mentre veniva colpito.",
"special_calamity": "{0} ha giovato {1} come un Bang! contro {2}."
"special_calamity": "{0} ha giovato {1} come un Bang! contro {2}.",
"allroles": "Nella partita ci sono: {0}."
},
"foc": {
"leggedelwest": "Ed è obbligato a usarla nel suo turno, se possibile"
},
"mods": "Modificatori",
"mod_comp": "Modalità competitiva (disattiva il prendi danno automatico)",