Merge branch 'wild-west-show' into dev
This commit is contained in:
commit
d98c7de4b9
@ -177,4 +177,6 @@ def all_characters(expansions: List[str]):
|
||||
base_chars.extend(GoldRush.get_characters())
|
||||
if 'the_valley_of_shadows' in expansions:
|
||||
base_chars.extend(TheValleyOfShadows.get_characters())
|
||||
if 'wild_west_show' in expansions:
|
||||
base_chars.extend(WildWestShow.get_characters())
|
||||
return base_chars
|
@ -1,13 +1,17 @@
|
||||
from typing import List, Set, Dict, Tuple, Optional
|
||||
from typing import List, Set, Dict, Tuple, Optional, TYPE_CHECKING
|
||||
import random
|
||||
import bang.cards as cs
|
||||
import bang.expansions.fistful_of_cards.card_events as ce
|
||||
import bang.expansions.high_noon.card_events as ceh
|
||||
import bang.expansions.wild_west_show.card_events as cew
|
||||
import bang.expansions.wild_west_show.characters as chw
|
||||
import bang.expansions.gold_rush.shop_cards as grc
|
||||
from globals import G
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bang.game import Game
|
||||
class Deck:
|
||||
def __init__(self, game):
|
||||
def __init__(self, game: 'Game'):
|
||||
super().__init__()
|
||||
self.cards: List[cs.Card] = cs.get_starting_deck(game.expansions)
|
||||
self.mancato_cards: List[str] = []
|
||||
@ -33,6 +37,9 @@ class Deck:
|
||||
if 'high_noon' in game.expansions:
|
||||
self.event_cards.extend(ceh.get_all_events(game.rng))
|
||||
endgame_cards.append(ceh.get_endgame_card())
|
||||
if 'wild_west_show' in game.expansions:
|
||||
self.event_cards.extend(cew.get_all_events(game.rng))
|
||||
endgame_cards.append(cew.get_endgame_card())
|
||||
if len(self.event_cards) > 0:
|
||||
game.rng.shuffle(self.event_cards)
|
||||
self.event_cards = self.event_cards[:12]
|
||||
@ -51,9 +58,10 @@ class Deck:
|
||||
print(f'Deck initialized with {len(self.cards)} cards')
|
||||
|
||||
def flip_event(self):
|
||||
if len(self.event_cards) > 0 and not (isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte) or isinstance(self.event_cards[0], ceh.MezzogiornoDiFuoco)):
|
||||
if len(self.event_cards) > 0 and not (isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte) or isinstance(self.event_cards[0], ceh.MezzogiornoDiFuoco) or isinstance(self.event_cards[0], cew.WildWestShow)):
|
||||
self.event_cards.append(self.event_cards.pop(0))
|
||||
self.game.notify_event_card()
|
||||
self.game.notify_all()
|
||||
|
||||
def fill_gold_rush_shop(self):
|
||||
if not any((c is None for c in self.shop_cards)):
|
||||
@ -76,7 +84,16 @@ class Deck:
|
||||
|
||||
def pick_and_scrap(self) -> cs.Card:
|
||||
card = self.cards.pop(0)
|
||||
self.scrap_pile.append(card)
|
||||
jpain = None
|
||||
for p in self.game.players:
|
||||
if p.character.check(self.game, chw.JohnPain) and len(p.hand) < 6:
|
||||
jpain = p
|
||||
break
|
||||
if jpain:
|
||||
jpain.hand.append(card)
|
||||
jpain.notify_self()
|
||||
else:
|
||||
self.scrap_pile.append(card)
|
||||
if len(self.cards) == 0:
|
||||
self.reshuffle()
|
||||
self.game.notify_scrap_pile()
|
||||
|
@ -22,3 +22,8 @@ class TheValleyOfShadows():
|
||||
def get_cards():
|
||||
from bang.expansions.the_valley_of_shadows import cards
|
||||
return cards.get_starting_deck()
|
||||
|
||||
class WildWestShow():
|
||||
def get_characters():
|
||||
from bang.expansions.wild_west_show import characters
|
||||
return characters.all_characters()
|
94
backend/bang/expansions/wild_west_show/card_events.py
Normal file
94
backend/bang/expansions/wild_west_show/card_events.py
Normal file
@ -0,0 +1,94 @@
|
||||
import random
|
||||
|
||||
from bang.expansions.fistful_of_cards.card_events import CardEvent
|
||||
|
||||
|
||||
# class Bavaglio(CardEvent):
|
||||
# def __init__(self):
|
||||
# super().__init__("Bavaglio", "🤐")
|
||||
# # I giocatori non possono parlare (ma possono gesticolare, mugugnare...). Chi parla perde 1 punto vita.
|
||||
# # NOT IMPLEMENTED
|
||||
|
||||
class Camposanto(CardEvent):
|
||||
"""
|
||||
All'inizio del proprio turno, ogni giocatore eliminato torna in gioco con 1 punto vita. Pesca il ruolo a caso fra quelli dei giocatori eliminati.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Camposanto", "⚰")
|
||||
|
||||
class DarlingValentine(CardEvent):
|
||||
"""
|
||||
All'inizio del proprio turno, ogni giocatore scarta le carte in mano e ne pesca dal mazzo altrettante.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Darling Valentine", "💋")
|
||||
|
||||
class DorothyRage(CardEvent):
|
||||
"""
|
||||
Nel proprio turno, ogni giocatore può obbligarne un altro a giocare una carta.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Dorothy Rage", "👩⚖️")
|
||||
|
||||
class HelenaZontero(CardEvent):
|
||||
"""
|
||||
Quando Helena entra in gioco, "estrai!": se esce Cuori o Quadri, rimescola i ruoli attivi tranne lo Sceriffo, e ridistribuiscili a caso.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Helena Zontero", "💞")
|
||||
|
||||
class LadyRosaDelTexas(CardEvent):
|
||||
"""
|
||||
Nel proprio turno, ogni giocatore può scambiarsi di posto con quello alla sua destra, il quale salta il prossimo turno.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Lady Rosa del Texas", "🩰")
|
||||
|
||||
class MissSusanna(CardEvent):
|
||||
"""
|
||||
Nel proprio turno ogni giocatore deve giocare almeno 3 carte. Se non lo fa, perde 1 punto vita.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Miss Susanna", "👩🎤")
|
||||
|
||||
class RegolamentoDiConti(CardEvent):
|
||||
"""
|
||||
Tutte le carte possono essere giocate come se fossero BANG!. Le carte BANG! come se fossero Mancato!
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Regolamento di Conti", "🤠")
|
||||
|
||||
class Sacagaway(CardEvent):
|
||||
"""
|
||||
Tutti i giocatori giocano a carte scoperte (tranne il ruolo!).
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Sacagaway", "🌄")
|
||||
|
||||
class WildWestShow(CardEvent):
|
||||
"""
|
||||
L'obiettivo di ogni giocatore diventa: "Rimani l'ultimo in gioco!"
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Wild West Show", "🎪")
|
||||
|
||||
def get_endgame_card():
|
||||
end_game = WildWestShow()
|
||||
end_game.expansion = 'wild-west-show'
|
||||
return end_game
|
||||
|
||||
def get_all_events(rng=random):
|
||||
cards = [
|
||||
Camposanto(),
|
||||
DarlingValentine(),
|
||||
# DorothyRage(),
|
||||
HelenaZontero(),
|
||||
LadyRosaDelTexas(),
|
||||
MissSusanna(),
|
||||
RegolamentoDiConti(),
|
||||
Sacagaway(),
|
||||
]
|
||||
rng.shuffle(cards)
|
||||
for c in cards:
|
||||
c.expansion = 'wild-west-show'
|
||||
return cards
|
93
backend/bang/expansions/wild_west_show/characters.py
Normal file
93
backend/bang/expansions/wild_west_show/characters.py
Normal file
@ -0,0 +1,93 @@
|
||||
from typing import List
|
||||
from bang.characters import Character
|
||||
|
||||
class BigSpencer(Character):
|
||||
"""
|
||||
Inizia con 5 carte. Non può giocare Mancato!
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Big Spencer", max_lives=9)
|
||||
self.icon = '🫘'
|
||||
|
||||
class FlintWestwood(Character):
|
||||
"""
|
||||
Nel suo turno può scambiare una carta dalla mano con 2 carte a caso dalla mano di un altro giocatore.
|
||||
> NOTE: La carta dalla tua mano è a scelta, non a caso. Se il giocatore bersaglio ha una sola carta, ne ricevi solo una.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Flint Westwood", max_lives=4)
|
||||
self.icon = '🔫'
|
||||
|
||||
def special(self, player, data):
|
||||
if not player.is_my_turn or not any((len(p.hand) > 0 for p in player.game.get_alive_players())) or not super().special(player, data):
|
||||
return False
|
||||
from bang.players import PendingAction
|
||||
player.available_cards = player.hand.copy()
|
||||
player.choose_text = 'choose_flint_special'
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.special_use_count += 1
|
||||
player.notify_self()
|
||||
|
||||
class GaryLooter(Character):
|
||||
"""
|
||||
Pesca tutte le carte in eccesso scartate dagli altri giocatori a fine turno.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Gary Looter", max_lives=5)
|
||||
self.icon = '🥲'
|
||||
|
||||
class GreygoryDeckard(Character):
|
||||
"""
|
||||
All'inizio del suo turno può pescare 2 personaggi a caso. Ha tutte le abilità dei personaggi pescati.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Greygory Deckard", max_lives=4)
|
||||
self.icon = '👨🦳'
|
||||
|
||||
class JohnPain(Character):
|
||||
"""
|
||||
Se ha meno di 6 carte in mano, quando un giocatore "estrae!" John aggiunge alla mano la carta appena estratta.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("John Pain", max_lives=4)
|
||||
self.icon = '🤕'
|
||||
|
||||
class LeeVanKliff(Character):
|
||||
"""
|
||||
Nel suo turno, può scartare un BANG! per ripetere l'effetto di una carta a bordo marrone che ha appena giocato.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Lee Van Kliff", max_lives=4)
|
||||
self.icon = '👨🦲'
|
||||
|
||||
class TerenKill(Character):
|
||||
"""
|
||||
Ogni volta che sarebbe eliminato "estrai!": se non è Picche, Teren resta a 1 punto vita e pesca 1 carta.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Teren Kill", max_lives=3)
|
||||
self.icon = '👨🦰'
|
||||
|
||||
class YoulGrinner(Character):
|
||||
"""
|
||||
Prima di pescare, i giocatori con più carte in mano di lui devono dargli una carta a scelta.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("Youl Grinner", max_lives=4)
|
||||
self.icon = '🤡'
|
||||
|
||||
def all_characters() -> List[Character]:
|
||||
cards = [
|
||||
BigSpencer(),
|
||||
FlintWestwood(),
|
||||
# GaryLooter(),
|
||||
# GreygoryDeckard(),
|
||||
JohnPain(),
|
||||
# LeeVanKliff(),
|
||||
TerenKill(),
|
||||
YoulGrinner(),
|
||||
]
|
||||
for c in cards:
|
||||
c.expansion_icon = '🎪'
|
||||
c.expansion = 'wild_west_show'
|
||||
return cards
|
@ -12,6 +12,7 @@ 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.wild_west_show.card_events as cew
|
||||
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
|
||||
@ -61,7 +62,7 @@ class Game:
|
||||
self.initial_players = 0
|
||||
self.password = ''
|
||||
self.expansions: List[str] = []
|
||||
self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon', 'gold_rush', 'the_valley_of_shadows']
|
||||
self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon', 'gold_rush', 'the_valley_of_shadows', 'wild_west_show']
|
||||
self.shutting_down = False
|
||||
self.is_competitive = False
|
||||
self.disconnect_bot = True
|
||||
@ -78,6 +79,7 @@ class Game:
|
||||
self.attack_in_progress = False
|
||||
self.characters_to_distribute = 2 # personaggi da dare a inizio partita
|
||||
self.debug = self.name == 'debug'
|
||||
self.dead_roles: List[roles.Role] = []
|
||||
self.is_changing_pwd = False
|
||||
self.is_hidden = False
|
||||
self.rng = random.Random()
|
||||
@ -113,6 +115,7 @@ class Game:
|
||||
self.incremental_turn = 0
|
||||
self.turn = 0
|
||||
self.pending_winners = []
|
||||
self.dead_roles: List[roles.Role] = []
|
||||
for p in self.players:
|
||||
p.reset()
|
||||
p.notify_self()
|
||||
@ -579,12 +582,16 @@ class Game:
|
||||
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]:
|
||||
if self.check_event([ce.DeadMan, cew.Camposanto]) 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
|
||||
if self.check_event(ce.DeadMan):
|
||||
self.did_resuscitate_deadman = True
|
||||
pl.lives = 2
|
||||
elif self.check_event(cew.Camposanto):
|
||||
pl.lives = 1
|
||||
pl.set_role = self.dead_roles.pop(random.randint(0, len(self.dead_roles)-1))
|
||||
pl.is_dead = False
|
||||
pl.is_ghost = False
|
||||
pl.lives = 2
|
||||
self.deck.draw(player=pl)
|
||||
self.deck.draw(player=pl)
|
||||
if (ghost := next((c for c in pl.equipment if isinstance(c, tvosc.Fantasma)), None)) is not None:
|
||||
@ -603,6 +610,16 @@ class Game:
|
||||
if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] is not 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(cew.HelenaZontero):
|
||||
c = self.deck.pick_and_scrap()
|
||||
G.sio.emit('chat_message', room=self.name, data=f'_flipped|Helena Zontero|{c.name}|{c.num_suit()}')
|
||||
if c.check_suit(self, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]):
|
||||
G.sio.emit('chat_message', room=self.name, data=f'_swapped_roles|Helena Zontero|{c.name}|{c.num_suit()}')
|
||||
pls = [p for p in self.players if not isinstance(p.role, roles.Sheriff)]
|
||||
newroles = [p.role for p in pls]
|
||||
random.shuffle(newroles)
|
||||
for p in pls:
|
||||
p.set_role(newroles.pop(random.randint(0, len(newroles)-1)))
|
||||
if self.check_event(ce.DeadMan):
|
||||
self.did_resuscitate_deadman = False
|
||||
elif self.check_event(ce.RouletteRussa):
|
||||
@ -632,6 +649,9 @@ class Game:
|
||||
if self.waiting_for != 0:
|
||||
return
|
||||
self.dalton_on = False
|
||||
elif self.check_event(cew.WildWestShow):
|
||||
for p in self.players:
|
||||
p.set_role(roles.Renegade())
|
||||
|
||||
if self.check_event(ce.PerUnPugnoDiCarte) and len(self.players[self.turn].hand) > 0:
|
||||
self.player_bangs = len(self.players[self.turn].hand)
|
||||
@ -762,6 +782,7 @@ class Game:
|
||||
# if not disconnected:
|
||||
# self.dead_players.append(corpse)
|
||||
self.notify_room()
|
||||
self.dead_roles.append(player.role)
|
||||
G.sio.emit('chat_message', room=self.name, data=f'_died|{player.name}')
|
||||
for p in self.players:
|
||||
if not p.is_bot:
|
||||
@ -845,7 +866,10 @@ class Game:
|
||||
|
||||
def check_event(self, ev):
|
||||
if self.deck is None or len(self.deck.event_cards) == 0: return False
|
||||
return isinstance(self.deck.event_cards[0], ev)
|
||||
if isinstance(ev, type):
|
||||
return isinstance(self.deck.event_cards[0], ev)
|
||||
else:
|
||||
return any(isinstance(self.deck.event_cards[0], evc) for evc in ev)
|
||||
|
||||
def get_visible_players(self, player: pl.Player): # returns a dictionary because we need to add the distance
|
||||
pls = self.get_alive_players()
|
||||
@ -874,9 +898,11 @@ class Game:
|
||||
|
||||
def notify_all(self):
|
||||
if self.started and self.replay_speed > 0:
|
||||
show_cards = self.check_event(cew.Sacagaway)
|
||||
data = [{
|
||||
'name': p.name,
|
||||
'ncards': len(p.hand),
|
||||
'hand_cards': [c.__dict__ for c in p.hand] if show_cards else [],
|
||||
'equipment': [e.__dict__ for e in p.equipment],
|
||||
'gold_rush_equipment': [e.__dict__ for e in p.gold_rush_equipment],
|
||||
'lives': p.lives,
|
||||
|
@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
from enum import IntEnum
|
||||
import json
|
||||
from random import random, randrange, sample, uniform
|
||||
from random import random, randrange, sample, uniform, randint
|
||||
import socketio
|
||||
import bang.deck as deck
|
||||
import bang.roles as r
|
||||
@ -11,6 +11,8 @@ import bang.characters as chars
|
||||
import bang.expansions.dodge_city.characters as chd
|
||||
import bang.expansions.fistful_of_cards.card_events as ce
|
||||
import bang.expansions.high_noon.card_events as ceh
|
||||
import bang.expansions.wild_west_show.card_events as cew
|
||||
import bang.expansions.wild_west_show.characters as chw
|
||||
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
|
||||
@ -88,6 +90,7 @@ class Player:
|
||||
self.is_bot = bot
|
||||
self.discord_token = discord_token
|
||||
self.discord_id = None
|
||||
self.played_cards = 0
|
||||
self.avatar = ''
|
||||
if self.is_bot:
|
||||
self.avatar = robot_pictures[randrange(len(robot_pictures))]
|
||||
@ -262,6 +265,12 @@ class Player:
|
||||
if isinstance(self.gold_rush_equipment[i], grc.Zaino):
|
||||
self.gold_rush_equipment[i].play_card(self, None)
|
||||
return # play card will notify the player
|
||||
if self.character.check(self.game, chw.TerenKill):
|
||||
picked: cs.Card = self.game.deck.pick_and_scrap()
|
||||
G.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
|
||||
if not picked.check_suit(self.game, [cs.Suit.SPADES]):
|
||||
self.lives = 1
|
||||
self.game.deck.draw(True, player=self)
|
||||
if self.character.check(self.game, chars.SidKetchum) and len(self.hand) > 1 and self.lives == 0:
|
||||
if self.game.players[self.game.turn] != self:
|
||||
self.game.players[self.game.turn].pending_action = PendingAction.WAIT
|
||||
@ -439,6 +448,7 @@ class Player:
|
||||
return self.end_turn(forced=True)
|
||||
self.scrapped_cards = 0
|
||||
self.setaccio_count = 0
|
||||
self.played_cards = 0
|
||||
self.can_play_ranch = True
|
||||
self.is_playing_ranch = False
|
||||
self.can_play_vendetta = can_play_vendetta
|
||||
@ -453,6 +463,11 @@ class Player:
|
||||
self.has_played_bang = False
|
||||
self.special_use_count = 0
|
||||
self.bang_used = 0
|
||||
if self.game.check_event(cew.DarlingValentine):
|
||||
hand = len(self.hand)
|
||||
for _ in range(hand):
|
||||
self.game.deck.scrap(self.hand.pop(0), True, player=self)
|
||||
self.game.deck.draw(True, player=self)
|
||||
if self.game.check_event(ceh.MezzogiornoDiFuoco):
|
||||
self.lives -= 1
|
||||
if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
|
||||
@ -511,6 +526,19 @@ class Player:
|
||||
self.choose_text = 'choose_cecchino'
|
||||
self.pending_action = PendingAction.CHOOSE
|
||||
self.notify_self()
|
||||
if self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(cew.RegolamentoDiConti) and len(self.hand) > 0:
|
||||
if not self.has_played_bang and any((self.get_sight() >= p['dist'] for p in self.game.get_visible_players(self))):
|
||||
self.available_cards = self.hand.copy()
|
||||
self.pending_action = PendingAction.CHOOSE
|
||||
self.choose_text = 'choose_play_as_bang'
|
||||
self.notify_self()
|
||||
elif self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(cew.LadyRosaDelTexas):
|
||||
nextp = self.game.next_player()
|
||||
i, j = self.game.players_map[self.name], self.game.players_map[nextp.name]
|
||||
self.game.players[i], self.game.players[j] = nextp, self
|
||||
self.game.players_map[self.name], self.game.players_map[nextp.name] = j, i
|
||||
self.game.turn = j
|
||||
self.game.notify_all()
|
||||
elif self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Rimbalzo) and any((c.name == cs.Bang(0,0).name for c in self.hand)):
|
||||
self.available_cards = [{
|
||||
'name': p.name,
|
||||
@ -551,6 +579,12 @@ class Player:
|
||||
self.notify_self()
|
||||
else:
|
||||
self.pending_action = PendingAction.PLAY
|
||||
if self.character.check(self.game, chw.YoulGrinner):
|
||||
hsize = len(self.hand)
|
||||
for p in self.game.get_alive_players():
|
||||
if p != self and len(p.hand) > hsize:
|
||||
G.sio.emit('card_drawn', room=self.game.name, data={'player': self.name, 'pile': p.name})
|
||||
self.hand.append(p.hand.pop(randint(0, len(p.hand)-1)))
|
||||
num = 2 if not self.character.check(self.game, chd.BillNoface) else self.max_lives-self.lives+1
|
||||
if self.character.check(self.game, chd.PixiePete): num += 1
|
||||
if self.character.check(self.game, tvosch.TucoFranziskaner) and not any((True for c in self.equipment if not c.usable_next_turn)): num += 2
|
||||
@ -756,6 +790,8 @@ class Player:
|
||||
if not self.game.is_replay:
|
||||
Metrics.send_metric('play_card', points=[1], tags=[f'success:{did_play_card}', f'card:{card.name}', f'bot:{self.is_bot}', f'exp:{card.expansion if "expansion" in card.__dict__ else "vanilla"}'])
|
||||
print("did play card:", did_play_card)
|
||||
if did_play_card:
|
||||
self.played_cards += 1
|
||||
self.notify_self()
|
||||
if self.is_bot:
|
||||
return did_play_card or card.is_equipment or (card.usable_next_turn and not card.can_be_used_now)
|
||||
@ -918,6 +954,27 @@ class Player:
|
||||
self.pending_action = PendingAction.WAIT
|
||||
self.game.responders_did_respond_resume_turn()
|
||||
self.notify_self()
|
||||
elif 'choose_flint_special' == self.choose_text:
|
||||
if card_index < len(self.hand):
|
||||
self.available_cards = [{
|
||||
'name': p.name,
|
||||
'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if p['is_sheriff'] else '🤠',
|
||||
'avatar': p.avatar,
|
||||
'is_character': True,
|
||||
'is_player': True
|
||||
} for p in self.game.get_alive_players() if p.name != self.name and len(p.hand) > 0]
|
||||
self.choose_text = f'choose_flint_special;{card_index}'
|
||||
self.notify_self()
|
||||
elif 'choose_flint_special' in self.choose_text:
|
||||
if card_index < len(self.available_cards):
|
||||
my_card = self.hand.pop(int(self.choose_text.split(';')[1]))
|
||||
other_player = self.game.get_player_named(self.available_cards[card_index]['name'])
|
||||
for i in range(min(2, len(other_player.hand))):
|
||||
self.hand.append(other_player.hand.pop(randint(0, len(other_player.hand)-1)))
|
||||
other_player.hand.append(my_card)
|
||||
other_player.notify_self()
|
||||
self.pending_action = PendingAction.PLAY
|
||||
self.notify_self()
|
||||
elif self.game.check_event(ceh.NuovaIdentita) and self.choose_text == 'choose_nuova_identita':
|
||||
if card_index == 1: # the other character
|
||||
self.character = self.not_chosen_character
|
||||
@ -1088,6 +1145,8 @@ class Player:
|
||||
self.expected_response = self.game.deck.mancato_cards.copy()
|
||||
if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response:
|
||||
self.expected_response.append(cs.Bang(0, 0).name)
|
||||
if self.character.check(self.game, chw.BigSpencer):
|
||||
self.expected_response = []
|
||||
self.on_failed_response_cb = self.take_damage_response
|
||||
self.notify_self()
|
||||
|
||||
@ -1118,6 +1177,8 @@ class Player:
|
||||
self.expected_response = self.game.deck.mancato_cards.copy()
|
||||
if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response:
|
||||
self.expected_response.append(cs.Bang(0, 0).name)
|
||||
if self.character.check(self.game, chw.BigSpencer):
|
||||
self.expected_response = []
|
||||
self.on_failed_response_cb = self.take_no_damage_response
|
||||
self.notify_self()
|
||||
|
||||
@ -1214,6 +1275,8 @@ class Player:
|
||||
self.expected_response = self.game.deck.mancato_cards_not_green_or_blue.copy()
|
||||
if self.character.check(self.game, chars.CalamityJanet) and cs.Bang(0, 0).name not in self.expected_response:
|
||||
self.expected_response.append(cs.Bang(0, 0).name)
|
||||
if self.character.check(self.game, chw.BigSpencer):
|
||||
self.expected_response = []
|
||||
if self.can_escape(card_name, with_mancato=False):
|
||||
self.expected_response.append(tvosc.Fuga(0, 0).name)
|
||||
if not no_dmg:
|
||||
@ -1536,6 +1599,9 @@ class Player:
|
||||
for i in range(len(self.equipment)):
|
||||
self.game.deck.scrap(self.equipment.pop(), True)
|
||||
self.is_my_turn = False
|
||||
if self.played_cards < 3 and self.game.check_event(cew.MissSusanna):
|
||||
self.lives -= 1
|
||||
self.played_cards = 0
|
||||
self.can_play_again_don_bell = True
|
||||
self.committed_suit_manette = None
|
||||
self.pending_action = PendingAction.WAIT
|
||||
|
@ -631,11 +631,14 @@ def chat_message(sid, msg, pl=None):
|
||||
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and changed event'})
|
||||
import bang.expansions.fistful_of_cards.card_events as ce
|
||||
import bang.expansions.high_noon.card_events as ceh
|
||||
import bang.expansions.wild_west_show.card_events as cew
|
||||
chs = []
|
||||
chs.extend(ce.get_all_events())
|
||||
chs.append(ce.get_endgame_card())
|
||||
chs.extend(ceh.get_all_events())
|
||||
chs.append(ceh.get_endgame_card())
|
||||
chs.extend(cew.get_all_events())
|
||||
chs.append(cew.get_endgame_card())
|
||||
ses.game.deck.event_cards.insert(int(cmd[1]), [c for c in chs if c is not None and c.name == ' '.join(cmd[2:])][0])
|
||||
ses.game.notify_event_card()
|
||||
elif '/removecard' in msg:
|
||||
|
@ -1,3 +1,39 @@
|
||||
import pytest
|
||||
from bang.characters import Character
|
||||
from bang.game import Game
|
||||
from bang.players import Player
|
||||
from tests.dummy_socket import DummySocket
|
||||
from globals import G
|
||||
G.sio = DummySocket()
|
||||
|
||||
|
||||
def started_game(expansions, players=4, character=Character('test_char', 4)):
|
||||
g = Game('test')
|
||||
g.expansions = expansions
|
||||
ps = [Player(f'p{i}', f'p{i}') for i in range(players)]
|
||||
for p in ps:
|
||||
g.add_player(p)
|
||||
g.start_game()
|
||||
for p in ps:
|
||||
p.available_characters = [character]
|
||||
p.set_character(p.available_characters[0].name)
|
||||
return g
|
||||
|
||||
|
||||
def set_events(g: Game, event_cards):
|
||||
g.deck.event_cards = event_cards
|
||||
|
||||
|
||||
def current_player(g: Game):
|
||||
return g.players[g.turn]
|
||||
|
||||
|
||||
def next_player(g: Game):
|
||||
return g.players[(g.turn + 1) % len(g.players)]
|
||||
|
||||
|
||||
def current_player_with_cards(g: Game, cards):
|
||||
p = current_player(g)
|
||||
p.draw('')
|
||||
p.hand = cards
|
||||
return p
|
||||
|
34
backend/tests/wild_west_show_characters_test.py
Normal file
34
backend/tests/wild_west_show_characters_test.py
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
|
||||
|
||||
from bang.expansions.wild_west_show.characters import *
|
||||
from bang.cards import Card, Suit
|
||||
import bang.roles as roles
|
||||
from bang.players import PendingAction
|
||||
|
||||
|
||||
# test TerenKill
|
||||
def test_TerenKill():
|
||||
g = started_game(['wild_west_show'], 4, TerenKill())
|
||||
p = current_player_with_cards(g, [])
|
||||
p.lives = 0
|
||||
g.deck.cards = [Card(Suit.HEARTS, 'card', 0), Card(Suit.HEARTS, 'card', 0)]
|
||||
p.notify_self()
|
||||
assert p.lives == 1
|
||||
assert len(p.hand) == 1
|
||||
p.lives = 0
|
||||
g.deck.cards = [Card(Suit.SPADES, 'card', 0), Card(Suit.HEARTS, 'card', 0)]
|
||||
p.notify_self()
|
||||
assert p.lives == 0
|
||||
|
||||
|
||||
# test YoulGrinner
|
||||
def test_YoulGrinner():
|
||||
g = started_game(['wild_west_show'], 4, YoulGrinner())
|
||||
p = current_player(g)
|
||||
p.hand = []
|
||||
p.draw('')
|
||||
assert len(p.hand) == 5
|
||||
for pl in g.players:
|
||||
if pl != p:
|
||||
assert len(pl.hand) == 3
|
98
backend/tests/wild_west_show_events_test.py
Normal file
98
backend/tests/wild_west_show_events_test.py
Normal file
@ -0,0 +1,98 @@
|
||||
|
||||
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
|
||||
|
||||
from bang.expansions.wild_west_show.card_events import *
|
||||
from bang.cards import Card, Suit
|
||||
import bang.roles as roles
|
||||
from bang.players import PendingAction
|
||||
|
||||
|
||||
# test Camposanto
|
||||
def test_camposanto():
|
||||
g = started_game(['wild_west_show'], 4)
|
||||
set_events(g, [Camposanto()])
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
p = current_player_with_cards(g, [])
|
||||
p.lives = 0
|
||||
p.notify_self()
|
||||
p1 = current_player_with_cards(g, [])
|
||||
p1.lives = 0
|
||||
p1.notify_self()
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
assert p.is_my_turn
|
||||
assert p.lives == 1
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
assert p1.is_my_turn
|
||||
assert p1.lives == 1
|
||||
|
||||
|
||||
# test DarlingValentine
|
||||
def test_darling_valentine():
|
||||
g = started_game(['wild_west_show'], 4)
|
||||
set_events(g, [DarlingValentine()])
|
||||
p = next_player(g)
|
||||
hand = p.hand.copy()
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
assert hand != current_player(g).hand
|
||||
|
||||
|
||||
# test DorothyRage
|
||||
|
||||
# test HelenaZontero
|
||||
def test_helena_zontero():
|
||||
g = started_game(['wild_west_show'], 8)
|
||||
set_events(g, [None, HelenaZontero()])
|
||||
roles = [p.role.name for p in g.players]
|
||||
for i in range(len(g.players)-1):
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
g.deck.cards = [Card(Suit.HEARTS, 'card', 0)]*5
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
roles2 = [p.role.name for p in g.players]
|
||||
assert roles != roles2
|
||||
|
||||
# test LadyRosaDelTexas
|
||||
def test_LadyRosaDelTexas():
|
||||
g = started_game(['wild_west_show'], 4)
|
||||
set_events(g, [LadyRosaDelTexas()])
|
||||
p = current_player_with_cards(g, [Card(0,'card',0)]*4)
|
||||
t = g.turn
|
||||
p.draw('event')
|
||||
assert g.turn == (t+1)%len(g.players)
|
||||
|
||||
# test MissSusanna
|
||||
def test_miss_suzanna():
|
||||
g = started_game(['wild_west_show'], 4)
|
||||
set_events(g, [MissSusanna()])
|
||||
p = current_player_with_cards(g, [])
|
||||
p.end_turn()
|
||||
assert p.lives == 4 # sceriffo 5-1
|
||||
p = current_player_with_cards(g, [Card(0,'card',0)]*4)
|
||||
p.play_card(0)
|
||||
p.play_card(0)
|
||||
p.play_card(0)
|
||||
p.end_turn()
|
||||
assert p.lives == 4
|
||||
p = current_player_with_cards(g, [])
|
||||
p.end_turn()
|
||||
assert p.lives == 3
|
||||
|
||||
|
||||
# test RegolamentoDiConti
|
||||
def test_RegolamentoDiConti():
|
||||
g = started_game(['wild_west_show'], 4)
|
||||
set_events(g, [RegolamentoDiConti()])
|
||||
p = current_player_with_cards(g, [Card(0,'card',0)]*4)
|
||||
p.draw('event')
|
||||
assert p.pending_action == PendingAction.CHOOSE
|
||||
p.choose(0)
|
||||
|
||||
|
||||
# test WildWestShow
|
||||
def test_WildWestShow():
|
||||
g = started_game(['wild_west_show'], 8)
|
||||
set_events(g, [None, WildWestShow()])
|
||||
for i in range(len(g.players)):
|
||||
current_player_with_cards(g, []).end_turn()
|
||||
for p in g.players:
|
||||
assert isinstance(p.role, roles.Renegade)
|
@ -1,267 +1,299 @@
|
||||
<template>
|
||||
<div :class="{ card: true, avatarred:card.avatar, equipment: card.is_equipment, character:card.is_character, back:card.is_back, 'usable-next-turn':card.usable_next_turn, 'must-be-used':card.must_be_used, 'gold-rush': card.expansion === 'gold_rush', 'brown':card.kind === 0, 'black':card.kind === 1,}">
|
||||
<h4>{{cardName}}</h4>
|
||||
<div v-if="card.avatar" class="avatar" :style="`background-image: url(${card.avatar});`"></div>
|
||||
<div :class="{emoji:true, bottomed:card.avatar}">{{emoji}}</div>
|
||||
<div class="alt_text">{{card.alt_text}}</div>
|
||||
<div class="suit">{{number}}<span :style="`${(card.suit !== undefined && card.suit%2 === 0)? 'color:red':''}`">{{suit}}</span></div>
|
||||
<div class="expansion" v-if="card.expansion_icon">{{card.expansion_icon}}</div>
|
||||
</div>
|
||||
<div
|
||||
:class="{
|
||||
card: true,
|
||||
avatarred: card.avatar,
|
||||
equipment: card.is_equipment,
|
||||
character: card.is_character,
|
||||
back: card.is_back,
|
||||
'usable-next-turn': card.usable_next_turn,
|
||||
'must-be-used': card.must_be_used,
|
||||
'gold-rush': card.expansion === 'gold_rush',
|
||||
brown: card.kind === 0,
|
||||
black: card.kind === 1,
|
||||
}"
|
||||
>
|
||||
<h4>{{ cardName }}</h4>
|
||||
<div
|
||||
v-if="card.avatar"
|
||||
class="avatar"
|
||||
:style="`background-image: url(${card.avatar});`"
|
||||
></div>
|
||||
<div :class="{ emoji: true, bottomed: card.avatar }">{{ emoji }}</div>
|
||||
<div class="alt_text">{{ card.alt_text }}</div>
|
||||
<div class="suit">
|
||||
{{ number
|
||||
}}<span
|
||||
:style="`${
|
||||
card.suit !== undefined && card.suit % 2 === 0 ? 'color:red' : ''
|
||||
}`"
|
||||
>{{ suit }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="expansion" v-if="card.expansion_icon">
|
||||
{{ card.expansion_icon }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Card',
|
||||
props: {
|
||||
card: Object,
|
||||
donotlocalize: Boolean
|
||||
},
|
||||
computed: {
|
||||
cardName(){
|
||||
// console.log(this.$t(`cards.${this.card.name}.name`))
|
||||
if (!this.donotlocalize && this.$t(`cards.${this.card.name}.name`) !== `cards.${this.card.name}.name`) {
|
||||
return this.$t(`cards.${this.card.name}.name`)
|
||||
}
|
||||
if (this.card.name == "you") {
|
||||
return this.$t('you')
|
||||
}
|
||||
return this.card.name
|
||||
},
|
||||
emoji(){
|
||||
return this.card.icon != "you" ? this.card.icon : this.$t('you')
|
||||
},
|
||||
suit() {
|
||||
if (this.card && !isNaN(this.card.suit)) {
|
||||
let x = ['♦️','♣️','♥️','♠️','🤑']
|
||||
return x[this.card.suit];
|
||||
} else if (this.card.suit) {
|
||||
return this.card.suit;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
number() {
|
||||
if (isNaN(this.card.suit))
|
||||
return this.card.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
|
||||
}
|
||||
}
|
||||
}
|
||||
name: "Card",
|
||||
props: {
|
||||
card: Object,
|
||||
donotlocalize: Boolean,
|
||||
},
|
||||
computed: {
|
||||
cardName() {
|
||||
// console.log(this.$t(`cards.${this.card.name}.name`))
|
||||
if (
|
||||
!this.donotlocalize &&
|
||||
this.$t(`cards.${this.card.name}.name`) !==
|
||||
`cards.${this.card.name}.name`
|
||||
) {
|
||||
return this.$t(`cards.${this.card.name}.name`);
|
||||
}
|
||||
if (this.card.name == "you") {
|
||||
return this.$t("you");
|
||||
}
|
||||
return this.card.name;
|
||||
},
|
||||
emoji() {
|
||||
return this.card.icon != "you" ? this.card.icon : this.$t("you");
|
||||
},
|
||||
suit() {
|
||||
if (this.card && !isNaN(this.card.suit)) {
|
||||
let x = ["♦️", "♣️", "♥️", "♠️", "🤑"];
|
||||
return x[this.card.suit];
|
||||
} else if (this.card.suit) {
|
||||
return this.card.suit;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
number() {
|
||||
if (isNaN(this.card.suit)) return this.card.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;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
cursor: pointer;
|
||||
width: 60pt;
|
||||
min-width:60pt;
|
||||
height: 100pt;
|
||||
margin: 12pt;
|
||||
background: var(--bg-color);
|
||||
box-shadow:
|
||||
0 0 0 3pt #987e51,
|
||||
0 0 0 6pt var(--bg-color),
|
||||
0 0 5pt 6pt #aaa;
|
||||
border-radius: 6pt;
|
||||
position: relative;
|
||||
transition: all 0.5s ease-in-out;
|
||||
color: var(--font-color);
|
||||
/* overflow: hidden; */
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: normal;
|
||||
/* word-wrap: break-word; */
|
||||
cursor: pointer;
|
||||
width: 60pt;
|
||||
min-width: 60pt;
|
||||
height: 100pt;
|
||||
margin: 12pt;
|
||||
background: var(--bg-color);
|
||||
box-shadow: 0 0 0 3pt #987e51, 0 0 0 6pt var(--bg-color), 0 0 5pt 6pt #aaa;
|
||||
border-radius: 6pt;
|
||||
position: relative;
|
||||
transition: all 0.5s ease-in-out;
|
||||
color: var(--font-color);
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: normal;
|
||||
}
|
||||
.avatarred {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.card.back{
|
||||
color:white;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#987e51,
|
||||
#987e51 5px,
|
||||
#816b45 5px,
|
||||
#816b45 10px
|
||||
);
|
||||
.card.back {
|
||||
color: white;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#987e51,
|
||||
#987e51 5px,
|
||||
#816b45 5px,
|
||||
#816b45 10px
|
||||
);
|
||||
}
|
||||
.card:not(.back,.fistful-of-cards,.high-noon,.gold-rush):before{
|
||||
content: "";
|
||||
background-image: radial-gradient(var(--bg-color) 13%, #0000 5%),
|
||||
radial-gradient(var(--bg-color) 14%, transparent 5%),
|
||||
radial-gradient(var(--bg-color) 8%, transparent 5%);
|
||||
background-position: -12px 0, 12px 14px, 0 -12pt;
|
||||
background-size: 50px 50px;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: -10px;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
.card:not(.back, .fistful-of-cards, .high-noon, .gold-rush):before {
|
||||
content: "";
|
||||
background-image: radial-gradient(var(--bg-color) 13%, #0000 5%),
|
||||
radial-gradient(var(--bg-color) 14%, transparent 5%),
|
||||
radial-gradient(var(--bg-color) 8%, transparent 5%);
|
||||
background-position: -12px 0, 12px 14px, 0 -12pt;
|
||||
background-size: 50px 50px;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: -10px;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
}
|
||||
.card.equipment {
|
||||
box-shadow:
|
||||
0 0 0 3pt #5c5e83,
|
||||
0 0 0 6pt var(--bg-color),
|
||||
0 0 5pt 6pt #aaa;
|
||||
box-shadow: 0 0 0 3pt #5c5e83, 0 0 0 6pt var(--bg-color), 0 0 5pt 6pt #aaa;
|
||||
}
|
||||
.card.character {
|
||||
box-shadow:
|
||||
0 0 0 3pt #7c795b,
|
||||
0 0 0 6pt var(--bg-color),
|
||||
0 0 5pt 6pt #aaa;
|
||||
box-shadow: 0 0 0 3pt #7c795b, 0 0 0 6pt var(--bg-color), 0 0 5pt 6pt #aaa;
|
||||
}
|
||||
.card.usable-next-turn {
|
||||
box-shadow:
|
||||
0 0 0 3pt #6aa16e, 0 0 0 6pt var(--bg-color), 0 0 5pt 6pt #aaa
|
||||
box-shadow: 0 0 0 3pt #6aa16e, 0 0 0 6pt var(--bg-color), 0 0 5pt 6pt #aaa;
|
||||
}
|
||||
.card.high-noon{
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dotted rgb(198 78 45);
|
||||
.card.wild-west-show {
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dotted #987e51;
|
||||
}
|
||||
.card.fistful-of-cards{
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dashed rgb(50 122 172);
|
||||
.card.high-noon {
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dotted rgb(198 78 45);
|
||||
}
|
||||
.card.back.fistful-of-cards{
|
||||
color:var(--bg-color);
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgb(50 122 172),
|
||||
rgb(50 122 172) 5px,
|
||||
rgb(30 102 152) 5px,
|
||||
rgb(30 102 152) 10px
|
||||
);
|
||||
border: 2pt solid rgb(50 122 172);
|
||||
.card.fistful-of-cards {
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dashed rgb(50 122 172);
|
||||
}
|
||||
.card.back.high-noon{
|
||||
color:var(--bg-color);
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgb(198 78 45),
|
||||
rgb(198 78 45) 5px,
|
||||
rgb(178 58 25) 5px,
|
||||
rgb(178 58 25) 10px
|
||||
);
|
||||
border: 2pt solid rgb(198 78 45);
|
||||
.card.back.fistful-of-cards {
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgb(50 122 172),
|
||||
rgb(50 122 172) 5px,
|
||||
rgb(30 102 152) 5px,
|
||||
rgb(30 102 152) 10px
|
||||
);
|
||||
border: 2pt solid rgb(50 122 172);
|
||||
}
|
||||
.card.back.the-valley-of-shadows{
|
||||
color:var(--bg-color);
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgb(98 88 85),
|
||||
rgb(98 88 85) 5px,
|
||||
rgb(78 68 65) 5px,
|
||||
rgb(78 68 65) 10px
|
||||
);
|
||||
box-shadow:
|
||||
0 0 0 3pt rgb(98 88 85),
|
||||
0 0 0 6pt var(--bg-color),
|
||||
0 0 5pt 6pt #aaa;
|
||||
.card.back.high-noon {
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgb(198 78 45),
|
||||
rgb(198 78 45) 5px,
|
||||
rgb(178 58 25) 5px,
|
||||
rgb(178 58 25) 10px
|
||||
);
|
||||
border: 2pt solid rgb(198 78 45);
|
||||
}
|
||||
.card.back.the-valley-of-shadows {
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
rgb(98 88 85),
|
||||
rgb(98 88 85) 5px,
|
||||
rgb(78 68 65) 5px,
|
||||
rgb(78 68 65) 10px
|
||||
);
|
||||
box-shadow: 0 0 0 3pt rgb(98 88 85), 0 0 0 6pt var(--bg-color),
|
||||
0 0 5pt 6pt #aaa;
|
||||
}
|
||||
.card.back.cant-play {
|
||||
transform: scale(0.9);
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
transform: scale(0.9);
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.card.back.cant-play:hover {
|
||||
transform: scale(0.95);
|
||||
filter: grayscale(0.6);
|
||||
opacity: 0.8;
|
||||
transform: scale(0.95);
|
||||
filter: grayscale(0.6);
|
||||
opacity: 0.8;
|
||||
}
|
||||
.beta::after {
|
||||
content: "Beta";
|
||||
position: absolute;
|
||||
bottom: -12pt;
|
||||
right: -12pt;
|
||||
background: red;
|
||||
font-size: 10pt;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: bold;
|
||||
padding: 4pt;
|
||||
border-radius: 12pt;
|
||||
content: "Beta";
|
||||
position: absolute;
|
||||
bottom: -12pt;
|
||||
right: -12pt;
|
||||
background: red;
|
||||
font-size: 10pt;
|
||||
color: white;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: bold;
|
||||
padding: 4pt;
|
||||
border-radius: 12pt;
|
||||
}
|
||||
.alpha::after {
|
||||
content: "Alpha";
|
||||
position: absolute;
|
||||
bottom: -12pt;
|
||||
right: -12pt;
|
||||
background: red;
|
||||
font-size: 10pt;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: bold;
|
||||
padding: 4pt;
|
||||
border-radius: 12pt;
|
||||
}
|
||||
.avatar {
|
||||
position: absolute;
|
||||
width: 36pt;
|
||||
margin: auto;
|
||||
top: 25%;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 36pt;
|
||||
height: 36pt;
|
||||
position: absolute;
|
||||
width: 36pt;
|
||||
margin: auto;
|
||||
top: 25%;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 36pt;
|
||||
height: 36pt;
|
||||
}
|
||||
.card.brown.gold-rush {
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dotted #9C7340;
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dotted #9c7340;
|
||||
}
|
||||
.card.black.gold-rush {
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dotted #000;
|
||||
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
|
||||
border: 2pt dotted #000;
|
||||
}
|
||||
.card.back.gold-rush {
|
||||
background: repeating-linear-gradient(347deg, #ffb32f, #987e51 );
|
||||
background: repeating-linear-gradient(347deg, #ffb32f, #987e51);
|
||||
}
|
||||
.card h4 {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
top: -10pt;
|
||||
font-size: 11pt;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
top: -10pt;
|
||||
font-size: 11pt;
|
||||
}
|
||||
.card.back h4{
|
||||
font-size: 12pt;
|
||||
.card.back h4 {
|
||||
font-size: 12pt;
|
||||
}
|
||||
.card .emoji {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size:26pt;
|
||||
top: 35%;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 26pt;
|
||||
top: 35%;
|
||||
}
|
||||
.emoji.bottomed {
|
||||
top: 45%;
|
||||
left: 8pt;
|
||||
top: 45%;
|
||||
left: 8pt;
|
||||
}
|
||||
.card.must-be-used {
|
||||
filter: drop-shadow(0 0 5px red);
|
||||
filter: drop-shadow(0 0 5px red);
|
||||
}
|
||||
.fistful-of-cards .emoji, .high-noon .emoji, .exp-pack .emoji{
|
||||
top:auto !important;
|
||||
bottom:15% !important;
|
||||
.fistful-of-cards .emoji,
|
||||
.high-noon .emoji,
|
||||
.card.wild-west-show .emoji,
|
||||
.exp-pack .emoji {
|
||||
top: auto !important;
|
||||
bottom: 15% !important;
|
||||
}
|
||||
.card .suit {
|
||||
position: absolute;
|
||||
bottom: 3pt;
|
||||
left:3pt;
|
||||
position: absolute;
|
||||
bottom: 3pt;
|
||||
left: 3pt;
|
||||
}
|
||||
.card.character .suit {
|
||||
font-size: small;
|
||||
right: 3pt;
|
||||
text-align: center;
|
||||
font-size: small;
|
||||
right: 3pt;
|
||||
text-align: center;
|
||||
}
|
||||
.alt_text {
|
||||
right: 3pt;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: small;
|
||||
bottom: 20pt;
|
||||
left: 3pt;
|
||||
right: 3pt;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: small;
|
||||
bottom: 20pt;
|
||||
left: 3pt;
|
||||
}
|
||||
.cant-play {
|
||||
filter: brightness(0.5);
|
||||
filter: brightness(0.5);
|
||||
}
|
||||
.expansion {
|
||||
position: absolute;
|
||||
bottom: -5pt;
|
||||
right: -5pt;
|
||||
background: var(--bg-color);
|
||||
border-radius: 100%;
|
||||
transform: scale(0.8);
|
||||
position: absolute;
|
||||
bottom: -5pt;
|
||||
right: -5pt;
|
||||
background: var(--bg-color);
|
||||
border-radius: 100%;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,7 @@
|
||||
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Raddie Snake' && special_use_count < 2 && gold_nuggets >=1" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
|
||||
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Der Spot Burst Ringer' && special_use_count < 1" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
|
||||
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Black Flower' && special_use_count < 1" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
|
||||
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Flint Westwood' && special_use_count < 1" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
|
||||
</div>
|
||||
<div v-if="lives > 0 || is_ghost" style="position:relative">
|
||||
<span id="hand_text">{{$t('hand')}}</span>
|
||||
|
@ -1,20 +1,30 @@
|
||||
<template>
|
||||
<div style="position:absolute;transform:scale(0.4);bottom:52pt;">
|
||||
<div :class="{card:true, back:true, delay:ismyturn}" v-for="(n, i) in ncards"
|
||||
:style="`position:absolute; transform:rotate(${(i-ncards/2)*2}deg) translate(${i*15}px,0); animation-delay:${0.1*i}s`"
|
||||
v-bind:key="n" :alt="i">
|
||||
<h4 v-if="n==ncards">PewPew!</h4>
|
||||
<div class="emoji" v-if="n==ncards">💥</div>
|
||||
<div v-if="!(cards && cards.length >0)">
|
||||
<div :class="{card:true, back:true, delay:ismyturn}" v-for="(n, i) in ncards"
|
||||
:style="`position:absolute; transform:rotate(${(i-ncards/2)*2}deg) translate(${i*15}px,0); animation-delay:${0.1*i}s`"
|
||||
v-bind:key="n" :alt="i">
|
||||
<h4 v-if="n==ncards">PewPew!</h4>
|
||||
<div class="emoji" v-if="n==ncards">💥</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<card :card="c" :key="c" v-for="(c, i) in cards"
|
||||
:class="{delay:ismyturn, zoomable:true}"
|
||||
:style="`position:absolute; transform:rotate(${(i-ncards/2)*5}deg) translate(${(i-ncards/3)*40}px,0); animation-delay:${0.1*i}s`"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Card from './Card.vue'
|
||||
export default {
|
||||
components: { Card },
|
||||
name: 'TinyHand',
|
||||
props: {
|
||||
ncards: Number,
|
||||
ismyturn: Boolean
|
||||
cards: Array,
|
||||
ismyturn: Boolean,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -24,6 +34,9 @@ export default {
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.zoomable:hover {
|
||||
z-index: 1;
|
||||
}
|
||||
@keyframes updown {
|
||||
0% {
|
||||
top: 0;
|
||||
|
@ -77,6 +77,7 @@
|
||||
"choose_ricercato": "Choose who you will play Wanted against.",
|
||||
"choose_birra_function": "Choose between getting 1 gold nugget by discarding beer or if you want to play the beer.",
|
||||
"choose_play_as_bang": "Choose which card to play as Bang!",
|
||||
"choose_flint_special": "Choose which card to swap.",
|
||||
"emporio_others": "{0} is choosing which card to get from the General Store",
|
||||
"you_died": "YOU DIED",
|
||||
"spectate": "SPECTATE",
|
||||
|
@ -77,6 +77,7 @@
|
||||
"choose_ricercato": "Scegli il giocatore su cui vuoi giocare Ricercato",
|
||||
"choose_birra_function": "Scegli tra ottenere 1 pepita scartando la birra oppure giocare la birra.",
|
||||
"choose_play_as_bang": "Scegli che carta giocare come Bang!",
|
||||
"choose_flint_special": "Scegli che carta scabiare.",
|
||||
"emporio_others": "{0} sta scegliendo che carta prendere dall'emporio",
|
||||
"you_died": "SEI MORTO",
|
||||
"spectate": "SPETTATORE",
|
||||
|
@ -4,4 +4,5 @@ export const emojiMap = {
|
||||
'high_noon': '🔥',
|
||||
'fistful_of_cards': '🎴',
|
||||
'the_valley_of_shadows': '👻',
|
||||
'wild_west_show': '🎪',
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
export const expansionsMap = {
|
||||
'dodge_city' : {
|
||||
'dodge_city': {
|
||||
name: 'Dodge City',
|
||||
icon: '🐄',
|
||||
back: true,
|
||||
expansion: 'dodge-city',
|
||||
},
|
||||
'fistful_of_cards' : {
|
||||
'fistful_of_cards': {
|
||||
name: 'Fistful of Cards',
|
||||
icon: '🎴',
|
||||
back: true,
|
||||
@ -28,5 +28,13 @@ export const expansionsMap = {
|
||||
icon: '👻',
|
||||
back: true,
|
||||
expansion: 'the-valley-of-shadows',
|
||||
status: 'beta',
|
||||
},
|
||||
'wild_west_show': {
|
||||
name: 'Wild West Show',
|
||||
icon: '🎪',
|
||||
back: true,
|
||||
expansion: 'wild-west-show',
|
||||
status: 'alpha',
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user