diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 2fe0e63..f902814 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -3,7 +3,7 @@ on:
pull_request:
jobs:
- buildx:
+ build-amd64:
runs-on: ubuntu-latest
steps:
-
@@ -31,6 +31,24 @@ jobs:
--output "type=image,push=false" \
--tag albertoxamin/bang:test \
--file ./Dockerfile ./
+ build-arm64:
+ runs-on: ubuntu-latest
+ steps:
+ -
+ name: Checkout
+ uses: actions/checkout@v3
+ -
+ name: Set up Docker Buildx
+ uses: crazy-max/ghaction-docker-buildx@v3
+ -
+ name: Cache Docker layers
+ uses: actions/cache@v3
+ id: cache
+ with:
+ path: /tmp/.buildx-cache
+ key: ${{ runner.os }}-buildx-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-buildx-
-
name: Docker Buildx (test build arm64)
run: |
@@ -41,3 +59,31 @@ jobs:
--output "type=image,push=false" \
--tag albertoxamin/bang:test \
--file ./Dockerfile ./
+ build-armv-7:
+ runs-on: ubuntu-latest
+ steps:
+ -
+ name: Checkout
+ uses: actions/checkout@v3
+ -
+ name: Set up Docker Buildx
+ uses: crazy-max/ghaction-docker-buildx@v3
+ -
+ name: Cache Docker layers
+ uses: actions/cache@v3
+ id: cache
+ with:
+ path: /tmp/.buildx-cache
+ key: ${{ runner.os }}-buildx-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-buildx-
+ -
+ name: Docker Buildx (test build armv-7)
+ run: |
+ docker buildx build \
+ --cache-from "type=local,src=/tmp/.buildx-cache" \
+ --cache-to "type=local,dest=/tmp/.buildx-cache" \
+ --platform linux/arm/v7 \
+ --output "type=image,push=false" \
+ --tag albertoxamin/bang:test \
+ --file ./Dockerfile ./
diff --git a/backend/bang/cards.py b/backend/bang/cards.py
index 17970d9..1c6a2b5 100644
--- a/backend/bang/cards.py
+++ b/backend/bang/cards.py
@@ -216,7 +216,7 @@ class Bang(Card):
def play_card(self, player, against, _with=None):
if player.game.check_event(ceh.Sermone) and not self.number == 42: # 42 gold rush
return False
- if ((player.has_played_bang and not self.number == 42) and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)) and against != None): # 42 gold rush:
+ if ((player.has_played_bang and not self.number == 42) and (not any((isinstance(c, Volcanic) for c in player.equipment)) or player.game.check_event(ce.Lazo)) and against != None): # 42 gold rush:
return False
elif against != None:
import bang.characters as chars
@@ -377,7 +377,7 @@ class Mancato(Card):
def play_card(self, player, against, _with=None):
import bang.characters as chars
if against != None and player.character.check(player.game, chars.CalamityJanet):
- if player.has_played_bang and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)):
+ if player.has_played_bang and (not any((isinstance(c, Volcanic) for c in player.equipment)) or player.game.check_event(ce.Lazo)):
return False
if player.game.check_event(ceh.Sermone):
return False
@@ -445,7 +445,7 @@ class WellsFargo(Card):
def get_starting_deck(expansions:List[str]) -> List[Card]:
- from bang.expansions import DodgeCity
+ from bang.expansions import DodgeCity, TheValleyOfShadows
base_cards = [
Barile(Suit.SPADES, 'Q'),
Barile(Suit.SPADES, 'K'),
@@ -530,5 +530,7 @@ def get_starting_deck(expansions:List[str]) -> List[Card]:
]
if 'dodge_city' in expansions:
base_cards.extend(DodgeCity.get_cards())
+ if 'the_valley_of_shadows' in expansions:
+ base_cards.extend(TheValleyOfShadows.get_cards())
return base_cards
diff --git a/backend/bang/characters.py b/backend/bang/characters.py
index 47c66c4..460f996 100644
--- a/backend/bang/characters.py
+++ b/backend/bang/characters.py
@@ -9,6 +9,7 @@ class Character(ABC):
self.max_lives = max_lives
self.sight_mod = sight_mod
self.visibility_mod = visibility_mod
+ self.is_character = True
self.pick_mod = pick_mod
self.desc = desc
self.icon = 'π€·ββοΈ'
@@ -145,7 +146,7 @@ class WillyTheKid(Character):
self.icon = 'π'
def all_characters(expansions: List[str]):
- from bang.expansions import DodgeCity
+ from bang.expansions import DodgeCity, TheValleyOfShadows
base_chars = [
BartCassidy(),
BlackJack(),
@@ -168,4 +169,6 @@ def all_characters(expansions: List[str]):
base_chars.extend(DodgeCity.get_characters())
if 'gold_rush' in expansions:
base_chars.extend(GoldRush.get_characters())
+ if 'the_valley_of_shadows' in expansions:
+ base_chars.extend(TheValleyOfShadows.get_characters())
return base_chars
\ No newline at end of file
diff --git a/backend/bang/deck.py b/backend/bang/deck.py
index 26a207a..cb138e1 100644
--- a/backend/bang/deck.py
+++ b/backend/bang/deck.py
@@ -52,7 +52,7 @@ class Deck:
self.game.notify_event_card()
def fill_gold_rush_shop(self):
- if not any([c == None for c in self.shop_cards]):
+ if not any((c == None for c in self.shop_cards)):
return
for i in range(3):
if self.shop_cards[i] == None:
diff --git a/backend/bang/expansions/__init__.py b/backend/bang/expansions/__init__.py
index 616b8c7..ebae2dd 100644
--- a/backend/bang/expansions/__init__.py
+++ b/backend/bang/expansions/__init__.py
@@ -13,3 +13,12 @@ class GoldRush():
def get_characters():
from bang.expansions.gold_rush import characters
return characters.all_characters()
+
+class TheValleyOfShadows():
+ def get_characters():
+ from bang.expansions.the_valley_of_shadows import characters
+ return characters.all_characters()
+
+ def get_cards():
+ from bang.expansions.the_valley_of_shadows import cards
+ return cards.get_starting_deck()
diff --git a/backend/bang/expansions/dodge_city/cards.py b/backend/bang/expansions/dodge_city/cards.py
index 8dd7b49..af0f7aa 100644
--- a/backend/bang/expansions/dodge_city/cards.py
+++ b/backend/bang/expansions/dodge_city/cards.py
@@ -75,7 +75,7 @@ class Rissa(CatBalou):
def play_card(self, player, against, _with):
if _with != None:
- if len([p for p in player.game.players if p != player and (len(p.hand)+len(p.equipment)) > 0]) == 0:
+ if not any((p != player and (len(p.hand)+len(p.equipment)) > 0 for p in player.game.players)):
return False
#se sono qui vuol dire che ci sono giocatori con carte in mano oltre a me
player.rissa_targets = []
diff --git a/backend/bang/expansions/dodge_city/characters.py b/backend/bang/expansions/dodge_city/characters.py
index d2c96de..0e1d3cd 100644
--- a/backend/bang/expansions/dodge_city/characters.py
+++ b/backend/bang/expansions/dodge_city/characters.py
@@ -90,9 +90,9 @@ class ChuckWengam(Character):
if player.lives > 1 and player.is_my_turn:
import bang.expansions.gold_rush.shop_cards as grc
player.lives -= 1
- if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
+ if any((isinstance(c, grc.Talismano) for c in player.gold_rush_equipment)):
player.gold_nuggets += 1
- if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
+ if any((isinstance(c, grc.Stivali) for c in player.gold_rush_equipment)):
player.hand.append(player.game.deck.draw(True))
player.hand.append(player.game.deck.draw(True))
player.hand.append(player.game.deck.draw(True))
diff --git a/backend/bang/expansions/the_valley_of_shadows/__init__.py b/backend/bang/expansions/the_valley_of_shadows/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/bang/expansions/the_valley_of_shadows/cards.py b/backend/bang/expansions/the_valley_of_shadows/cards.py
new file mode 100644
index 0000000..b1549b9
--- /dev/null
+++ b/backend/bang/expansions/the_valley_of_shadows/cards.py
@@ -0,0 +1,231 @@
+from typing import List
+import bang.roles as r
+import bang.players as pl
+from bang.cards import Card, Suit, Bang, Mancato
+import bang.expansions.fistful_of_cards.card_events as ce
+
+class Fantasma(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Fantasma', number, is_equipment=True)
+ self.icon = 'π»οΈ' #porta in vita i giocatori morti ma non
+
+ def play_card(self, player, against, _with=None):
+ if (player.game.check_event(ce.IlGiudice)):
+ return False
+ if len(player.game.get_dead_players(include_ghosts=False)) > 0:
+ player.pending_action = pl.PendingAction.CHOOSE
+ player.choose_text = 'choose_fantasma'
+ player.available_cards = [{
+ 'name': p.name,
+ 'icon': p.role.icon if(player.game.initial_players == 3) else 'βοΈ' if isinstance(p.role, r.Sheriff) else 'π€ ',
+ 'avatar': p.avatar,
+ 'alt_text': ''.join(['β€οΈ']*p.lives)+''.join(['π']*(p.max_lives-p.lives)),
+ 'is_character': True,
+ 'noDesc': True
+ } for p in player.game.get_dead_players(include_ghosts=False)]
+ player.game.deck.scrap(self, True)
+ return True
+ return False
+
+class Lemat(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Lemat', number, is_equipment=True, is_weapon=True, range=1)
+ self.icon = 'π«' # ogni carta puΓ² essere usata come bang
+ #TODO
+
+class SerpenteASonagli(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'SerpenteASonagli', number, is_equipment=True)
+ self.need_target = True
+ self.icon = 'ποΈ' # Ogni turno pesca se il seme picche -1hp
+ self.alt_text = "β οΈ =π"
+
+ def play_card(self, player, against, _with=None):
+ if (player.game.check_event(ce.IlGiudice)):
+ return False
+ if against != None:
+ self.reset_card()
+ player.sio.emit('chat_message', room=player.game.name,
+ data=f'_play_card_against|{player.name}|{self.name}|{against}')
+ player.game.get_player_named(against).equipment.append(self)
+ player.game.get_player_named(against).notify_self()
+ return True
+ return False
+
+class Shotgun(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Shotgun', number, is_equipment=True, is_weapon=True, range=1)
+ self.icon = 'π«' # Ogni volta che colpisci un giocatore deve scartare una carta
+
+class Taglia(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Taglia', number, is_equipment=True)
+ self.need_target = True
+ self.icon = 'π°' # chiunque colpisca il giocatore con la taglia pesca una carta dal mazzo, si toglie solo con panico, cat balou, dalton
+
+ def play_card(self, player, against, _with=None):
+ if (player.game.check_event(ce.IlGiudice)):
+ return False
+ if against != None:
+ self.reset_card()
+ player.sio.emit('chat_message', room=player.game.name,
+ data=f'_play_card_against|{player.name}|{self.name}|{against}')
+ player.game.get_player_named(against).equipment.append(self)
+ player.game.get_player_named(against).notify_self()
+ return True
+ return False
+
+class UltimoGiro(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'UltimoGiro', number)
+ self.icon = 'π₯'
+ # self.desc = 'Recupera 1 vita'
+ # self.desc_eng = 'Regain 1 HP'
+ self.alt_text = "πΊ"
+
+ def play_card(self, player, against, _with=None):
+ super().play_card(player, against)
+ player.lives = min(player.lives+1, player.max_lives)
+ player.notify_self()
+ return True
+
+class Tomahawk(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Tomahawk', number, range=2)
+ self.icon = 'πͺοΈ'
+ self.alt_text = "2π π₯"
+ # "Spara a un giocatore a distanza 2"
+ self.need_target = True
+
+ def play_card(self, player, against, _with=None):
+ if against != None and player.game.can_card_reach(self, player, against):
+ super().play_card(player, against=against)
+ player.game.attack(player, against, card_name=self.name)
+ return True
+ return False
+
+class Tornado(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Tornado', number)
+ self.icon = 'πͺοΈ'
+
+ def play_card(self, player, against, _with=None):
+ player.game.discard_others(player, card_name=self.name)
+ return True
+
+class Sventagliata(Bang):
+ def __init__(self, suit, number):
+ super().__init__(suit, number)
+ self.name = 'Sventagliata'
+ self.icon = 'π'
+ self.alt_text = "π₯π₯" # spara al target e anche, a uno a distanza 1 dal target
+ self.need_target = True
+
+ def play_card(self, player, against, _with=None):
+ if against != None:
+ #TODO
+ # super().play_card(player, against=against)
+ # player.game.attack(player, against, card_name=self.name)
+ return True
+ return False
+
+class Salvo(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Salvo', number)
+ self.icon = 'ποΈ'
+ self.alt_text = "π€ποΈ"
+ self.need_target = True
+
+ def play_card(self, player, against, _with=None):
+ if against != None:
+ #TODO
+ # super().play_card(player, against=against)
+ # player.game.attack(player, against, card_name=self.name)
+ return True
+ return False
+
+class Mira(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Mira', number)
+ self.icon = 'ππ»'
+ self.alt_text = "π₯πππ"
+ self.need_target = True
+ self.need_with = True
+
+ def play_card(self, player, against, _with=None):
+ if against != None:
+ #TODO
+ # super().play_card(player, against=against)
+ # player.game.attack(player, against, card_name=self.name)
+ return True
+ return False
+
+class Bandidos(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Bandidos', number)
+ self.icon = 'π€ οΈ'
+ self.alt_text = "π€ππ/π"
+
+ def play_card(self, player, against, _with=None):
+ player.game.discard_others(player, card_name=self.name)
+ return True
+
+class Fuga(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Fuga', number)
+ self.icon = 'ππ»'
+ self.alt_text = "β"
+
+ def play_card(self, player, against, _with=None):
+ #TODO
+ # super().play_card(player, against=against)
+ # player.game.attack(player, against)
+ return True
+
+class Poker(Card):
+ def __init__(self, suit, number):
+ super().__init__(suit, 'Poker', number)
+ self.icon = 'π'
+ self.alt_text = "π€π ππ"
+
+ def play_card(self, player, against, _with=None):
+ #TODO
+ # super().play_card(player, against=against)
+ # player.game.attack(player, against)
+ return True
+
+class RitornoDiFiamma(Mancato):
+ def __init__(self, suit, number):
+ super().__init__(suit, number)
+ self.name = 'RitornoDiFiamma'
+ self.icon = 'π₯'
+ self.alt_text = "π
| π₯"
+
+ def play_card(self, player, against, _with=None):
+ return False
+
+ def use_card(self, player):
+ player.notify_self()
+
+def get_starting_deck() -> List[Card]:
+ cards = [
+ Fantasma(Suit.SPADES, 9),
+ Fantasma(Suit.SPADES, 10),
+ # Lemat(Suit.DIAMONDS, 4),
+ SerpenteASonagli(Suit.HEARTS, 7),
+ Shotgun(Suit.SPADES, 'K'),
+ Taglia(Suit.CLUBS, 9),
+ UltimoGiro(Suit.DIAMONDS, 8),
+ Tomahawk(Suit.DIAMONDS, 'A'),
+ # Sventagliata(Suit.SPADES, 2),
+ # Salvo(Suit.HEARTS, 5),
+ Bandidos(Suit.DIAMONDS,'Q'), # gli altri giocatori scelgono se scartare 2 carte o perdere 1 punto vita
+ # Fuga(Suit.HEARTS, 3), # evita l'effetto di carte marroni (tipo panico cat balou) di cui sei bersaglio
+ # Mira(Suit.CLUBS, 6),
+ # Poker(Suit.HEARTS, 'J'), # tutti gli altri scartano 1 carta a scelta, se non ci sono assi allora pesca 2 dal mazzo
+ RitornoDiFiamma(Suit.CLUBS, 'Q'), # un mancato che fa bang
+ Tornado(Suit.CLUBS, "A"),
+ ]
+ for c in cards:
+ c.expansion_icon = 'π»οΈ'
+ return cards
diff --git a/backend/bang/expansions/the_valley_of_shadows/characters.py b/backend/bang/expansions/the_valley_of_shadows/characters.py
new file mode 100644
index 0000000..f2cc016
--- /dev/null
+++ b/backend/bang/expansions/the_valley_of_shadows/characters.py
@@ -0,0 +1,10 @@
+from typing import List
+from bang.characters import Character
+
+def all_characters() -> List[Character]:
+ cards = [
+
+ ]
+ for c in cards:
+ c.expansion_icon = 'π»οΈ'
+ return cards
diff --git a/backend/bang/game.py b/backend/bang/game.py
index 3548454..9cdcc13 100644
--- a/backend/bang/game.py
+++ b/backend/bang/game.py
@@ -5,6 +5,7 @@ import socketio
import eventlet
import bang.players as pl
+import bang.cards as cs
import bang.characters as characters
import bang.expansions.dodge_city.characters as chd
from bang.deck import Deck
@@ -15,6 +16,34 @@ import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch
from metrics import Metrics
+debug_commands = [
+ {'cmd':'/debug', 'help':'Toggles the debug mode'},
+ {'cmd':'/set_chars', 'help':'Set how many characters to distribute - sample /set_chars 3'},
+ {'cmd':'/suicide', 'help':'Kills you'},
+ {'cmd':'/nextevent', 'help':'Flip the next event card'},
+ {'cmd':'/notify', 'help':'Send a message to a player - sample /notify player hi!'},
+ {'cmd':'/show_cards', 'help':'View the hand of another - sample /show_cards player'},
+ {'cmd':'/ddc', 'help':'Destroy all cards - sample /ddc player'},
+ {'cmd':'/dsh', 'help':'Set health - sample /dsh player'},
+ # {'cmd':'/togglebot', 'help':''},
+ {'cmd':'/cancelgame', 'help':'Stops the current game'},
+ {'cmd':'/startgame', 'help':'Force starts the game'},
+ {'cmd':'/setbotspeed', 'help':'Changes the bot response time - sample /setbotspeed 0.5'},
+ # {'cmd':'/addex', 'help':''},
+ {'cmd':'/setcharacter', 'help':'Changes your current character - sample /setcharacter Willy The Kid'},
+ {'cmd':'/setevent', 'help':'Changes the event deck - sample /setevent 0 Manette'},
+ {'cmd':'/removecard', 'help':'Remove a card from hand/equip - sample /removecard 0'},
+ {'cmd':'/getcard', 'help':'Get a brand new card - sample /getcard Birra'},
+ {'cmd':'/meinfo', 'help':'Get player data'},
+ {'cmd':'/gameinfo', 'help':'Get game data'},
+ {'cmd':'/playerinfo', 'help':'Get player data - sample /playerinfo player'},
+ {'cmd':'/cardinfo', 'help':'Get card data - sample /cardinfo handindex'},
+ {'cmd':'/mebot', 'help':'Toggles bot mode'},
+ {'cmd':'/getnuggets', 'help':'Adds nuggets to yourself - sample /getnuggets 5'},
+ {'cmd':'/startwithseed', 'help':'start the game with custom seed'},
+ {'cmd':'/getset', 'help':'get extension set of cards sample - /get valley', 'admin':True},
+]
+
class Game:
def __init__(self, name, sio:socketio):
super().__init__()
@@ -52,6 +81,11 @@ class Game:
self.rpc_log = []
self.is_replay = False
+ def shuffle_players(self):
+ if not self.started:
+ random.shuffle(self.players)
+ self.notify_room()
+
def reset(self):
for p in self.players:
if (p.sid == p.name):
@@ -137,7 +171,7 @@ class Game:
# chat_message(None, cmd[2], player)
if i == fast_forward:
self.replay_speed = 1.0
-
+ self.notify_room()
eventlet.sleep(max(self.replay_speed, 0.1))
eventlet.sleep(6)
if self.is_replay:
@@ -145,11 +179,11 @@ class Game:
def notify_room(self, sid=None):
- if len([p for p in self.players if p.character == None]) != 0 or sid:
+ if any((p.character == None for p in self.players)) or sid:
self.sio.emit('room', room=self.name if not sid else sid, data={
'name': self.name,
'started': self.started,
- 'players': [{'name':p.name, 'ready': p.character != None, 'is_bot': p.is_bot} for p in self.players],
+ 'players': [{'name':p.name, 'ready': p.character != None, 'is_bot': p.is_bot, 'avatar': p.avatar} for p in self.players],
'password': self.password,
'is_competitive': self.is_competitive,
'disconnect_bot': self.disconnect_bot,
@@ -158,30 +192,7 @@ class Game:
})
self.sio.emit('debug', room=self.name, data=self.debug)
if self.debug:
- commands = [
- {'cmd':'/debug', 'help':'Toggles the debug mode'},
- {'cmd':'/set_chars', 'help':'Set how many characters to distribute - sample /set_chars 3'},
- {'cmd':'/suicide', 'help':'Kills you'},
- {'cmd':'/nextevent', 'help':'Flip the next event card'},
- {'cmd':'/notify', 'help':'Send a message to a player - sample /notify player hi!'},
- {'cmd':'/show_cards', 'help':'View the hand of another - sample /show_cards player'},
- {'cmd':'/ddc', 'help':'Destroy all cards - sample /ddc player'},
- {'cmd':'/dsh', 'help':'Set health - sample /dsh player'},
- # {'cmd':'/togglebot', 'help':''},
- {'cmd':'/cancelgame', 'help':'Stops the current game'},
- {'cmd':'/startgame', 'help':'Force starts the game'},
- {'cmd':'/setbotspeed', 'help':'Changes the bot response time - sample /setbotspeed 0.5'},
- # {'cmd':'/addex', 'help':''},
- {'cmd':'/setcharacter', 'help':'Changes your current character - sample /setcharacter Willy The Kid'},
- {'cmd':'/setevent', 'help':'Changes the event deck - sample /setevent 0 Manette'},
- {'cmd':'/removecard', 'help':'Remove a card from hand/equip - sample /removecard 0'},
- {'cmd':'/getcard', 'help':'Get a brand new card - sample /getcard Birra'},
- {'cmd':'/meinfo', 'help':'Get player data'},
- {'cmd':'/gameinfo', 'help':'Get game data'},
- {'cmd':'/mebot', 'help':'Toggles bot mode'},
- {'cmd':'/getnuggets', 'help':'Adds nuggets to yourself - sample /getnuggets 5'},
- {'cmd':'/startwithseed', 'help':'start the game with custom seed'}]
- self.sio.emit('commands', room=self.name, data=commands)
+ self.sio.emit('commands', room=self.name, data=[x for x in debug_commands if 'admin' not in x])
else:
self.sio.emit('commands', room=self.name, data=[{'cmd':'/debug', 'help':'Toggles the debug mode'}])
self.sio.emit('spectators', room=self.name, data=len(self.spectators))
@@ -203,6 +214,11 @@ class Game:
self.disconnect_bot = not self.disconnect_bot
self.notify_room()
+ def feature_flags(self):
+ if 'the_valley_of_shadows' not in self.expansions:
+ self.available_expansions.append('the_valley_of_shadows')
+ self.notify_room()
+
def add_player(self, player: pl.Player):
if player.is_bot and len(self.players) >= 8:
return
@@ -212,6 +228,8 @@ class Game:
if 'dodge_city' not in self.expansions:
self.expansions.append('dodge_city')
player.join_game(self)
+ if player.is_admin():
+ self.feature_flags()
self.players.append(player)
print(f'{self.name}: Added player {player.name} to game')
self.notify_room()
@@ -231,7 +249,7 @@ class Game:
def notify_character_selection(self):
self.notify_room()
- if len([p for p in self.players if p.character == None]) == 0:
+ if not any((p.character == None for p in self.players)):
for i in range(len(self.players)):
print(self.name, self.players[i].name, self.players[i].character)
self.sio.emit('chat_message', room=self.name, data=f'_choose_character|{self.players[i].name}|{self.players[i].character.name}')
@@ -303,6 +321,22 @@ class Game:
self.players[i].notify_self()
self.notify_event_card()
+ def discard_others(self, attacker: pl.Player, card_name:str=None):
+ self.attack_in_progress = True
+ attacker.pending_action = pl.PendingAction.WAIT
+ attacker.notify_self()
+ self.waiting_for = 0
+ self.ready_count = 0
+ for p in self.get_alive_players():
+ if len(p.hand) > 0 and (p != attacker or card_name == 'Tornado'):
+ if p.get_discarded(attacker=attacker, card_name=card_name):
+ self.waiting_for += 1
+ p.notify_self()
+ if self.waiting_for == 0:
+ attacker.pending_action = pl.PendingAction.PLAY
+ attacker.notify_self()
+ self.attack_in_progress = False
+
def attack_others(self, attacker: pl.Player, card_name:str=None):
self.attack_in_progress = True
attacker.pending_action = pl.PendingAction.WAIT
@@ -339,6 +373,11 @@ class Game:
if self.pending_winners and not self.someone_won:
return self.announces_winners()
+ def can_card_reach(self, card: cs.Card, player: pl.Player, target:str):
+ if card and card.range != 0 and card.range < 99:
+ return not any((True for p in self.get_visible_players(player) if p['name'] == target and p['dist'] > card.range))
+ return True
+
def attack(self, attacker: pl.Player, target_username:str, double:bool=False, card_name:str=None):
if self.get_player_named(target_username).get_banged(attacker=attacker, double=double, card_name=card_name):
self.ready_count = 0
@@ -426,11 +465,11 @@ class Game:
print(f'{self.name}: stop roulette')
target_pl.lives -= 1
target_pl.heal_if_needed()
- if len([c for c in target_pl.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
+ if any((isinstance(c, grc.Talismano) for c in target_pl.gold_rush_equipment)):
target_pl.gold_nuggets += 1
if target_pl.character.check(self, grch.SimeonPicos):
target_pl.gold_nuggets += 1
- if len([c for c in target_pl.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
+ if any((isinstance(c, grc.Stivali) for c in target_pl.gold_rush_equipment)):
target_pl.hand.append(self.deck.draw(True))
target_pl.notify_self()
self.is_russian_roulette_on = False
@@ -503,14 +542,14 @@ class Game:
pl.hand.append(self.deck.draw())
pl.hand.append(self.deck.draw())
pl.notify_self()
- elif self.check_event(ceh.CittaFantasma):
+ elif self.check_event(ceh.CittaFantasma) or self.players[self.turn].is_ghost:
print(f'{self.name}: {self.players[self.turn]} is dead, event ghost')
self.players[self.turn].is_ghost = True
else:
print(f'{self.name}: {self.players[self.turn]} is dead, next turn')
return self.next_turn()
self.player_bangs = 0
- if isinstance(self.players[self.turn].role, roles.Sheriff) or ((self.initial_players == 3 and isinstance(self.players[self.turn].role, roles.Vice) and not self.players[self.turn].is_ghost) or (self.initial_players == 3 and any([p for p in self.players if p.is_dead and p.role.name == 'Vice']) and isinstance(self.players[self.turn].role, roles.Renegade))):
+ if isinstance(self.players[self.turn].role, roles.Sheriff) or ((self.initial_players == 3 and isinstance(self.players[self.turn].role, roles.Vice) and not self.players[self.turn].is_ghost) or (self.initial_players == 3 and any((p for p in self.players if p.is_dead and p.role.name == 'Vice')) and isinstance(self.players[self.turn].role, roles.Renegade))):
self.deck.flip_event()
if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] != None:
print(f'{self.name}: flip new event {self.deck.event_cards[0].name}')
@@ -600,9 +639,9 @@ class Game:
player.game = None
if self.disconnect_bot and self.started:
player.is_bot = True
- if len([p for p in self.players if not p.is_bot]) == 0:
+ if not any((not p.is_bot for p in self.players)):
eventlet.sleep(5)
- if len([p for p in self.players if not p.is_bot]) == 0:
+ if not any((not p.is_bot for p in self.players)):
print(f'{self.name}: no players left in game, shutting down')
self.shutting_down = True
self.players = []
@@ -620,7 +659,7 @@ class Game:
# else:
# player.lives = 0
# self.players.remove(player)
- if len([p for p in self.players if not p.is_bot]) == 0:
+ if not any((not p.is_bot for p in self.players)):
print(f'{self.name}: no players left in game, shutting down')
self.shutting_down = True
self.players = []
@@ -637,7 +676,7 @@ class Game:
if player.character and player.role:
if not self.is_replay:
Metrics.send_metric('player_death', points=[1], tags=[f"char:{player.character.name}", f"role:{player.role.name}"])
- if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Ricercato)]) > 0 and player.attacker and player.attacker in self.players:
+ if any((isinstance(c, grc.Ricercato) for c in player.gold_rush_equipment)) and player.attacker and player.attacker in self.players:
player.attacker.gold_nuggets += 1
player.attacker.hand.append(self.deck.draw(True))
player.attacker.hand.append(self.deck.draw(True))
@@ -763,14 +802,15 @@ class Game:
'is_ghost': pls[j].is_ghost,
'is_bot': pls[j].is_bot,
'icon': pls[j].role.icon if (pls[j].role is not None) else 'π€ ',
+ 'avatar': pls[j].avatar,
'role': pls[j].role,
} for j in range(len(pls)) if i != j]
def get_alive_players(self):
return [p for p in self.players if not p.is_dead or p.is_ghost]
- def get_dead_players(self):
- return [p for p in self.players if p.is_dead]
+ def get_dead_players(self, include_ghosts=True):
+ return [p for p in self.players if p.is_dead and (include_ghosts or not p.is_ghost)]
def notify_all(self):
if self.started:
@@ -788,6 +828,7 @@ class Game:
'character': p.character.__dict__ if p.character else None,
'real_character': p.real_character.__dict__ if p.real_character else None,
'icon': p.role.icon if self.initial_players == 3 and p.role else 'π€ ',
+ 'avatar': p.avatar,
'is_ghost': p.is_ghost,
'is_bot': p.is_bot,
} for p in self.get_alive_players()]
diff --git a/backend/bang/players.py b/backend/bang/players.py
index 8daf297..641892f 100644
--- a/backend/bang/players.py
+++ b/backend/bang/players.py
@@ -12,10 +12,23 @@ import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch
+import bang.expansions.the_valley_of_shadows.cards as tvosc
import eventlet
from typing import List
from metrics import Metrics
+robot_pictures = [
+ 'https://i.imgur.com/40rAFIb.jpg',
+ 'https://i.imgur.com/gG77VRR.jpg',
+ 'https://i.imgur.com/l2DTQeH.jpg',
+ 'https://i.imgur.com/aPM2gix.jpg',
+ 'https://i.imgur.com/ep5EB8c.jpg',
+ 'https://i.imgur.com/qsOWIsf.jpg',
+ 'https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/apple/325/robot_1f916.png',
+ 'https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/openmoji/338/robot_1f916.png',
+ 'https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/microsoft/319/robot_1f916.png',
+]
+
class PendingAction(IntEnum):
PICK = 0
DRAW = 1
@@ -26,13 +39,46 @@ class PendingAction(IntEnum):
class Player:
- def __init__(self, name, sid, sio, bot=False):
+ def is_admin(self):
+ return self.discord_id in {'244893980960096266'}
+
+ def get_avatar(self):
+ import requests
+ headers = {
+ 'Authorization': 'Bearer ' + self.discord_token,
+ }
+ r = requests.get('https://discordapp.com/api/users/@me', headers=headers)
+ if r.status_code == 200:
+ res = r.json()
+ self.avatar = f'https://cdn.discordapp.com/avatars/{res["id"]}/{res["avatar"]}.png'
+ if self.game:
+ self.sio.emit('chat_message', room=self.game.name, data=f'_change_username|{self.name}|{res["username"]}')
+ self.name = res['username']
+ self.discord_id = res['id']
+ if self.is_admin():
+ if self.game: self.game.feature_flags()
+ self.sio.emit('chat_message', room=self.sid, data={'color':'green', 'text':'(you are admin)'})
+ if self.game:
+ self.game.notify_room()
+ self.sio.emit('me', data=self.name, room=self.sid)
+ else:
+ print('error getting avatar', r.status_code, r.text)
+ print(r)
+
+ def __init__(self, name, sid, sio, bot=False, discord_token=None):
import bang.game as g
super().__init__()
self.name = name
self.sid = sid
self.sio = sio
self.is_bot = bot
+ self.discord_token = discord_token
+ self.discord_id = None
+ self.avatar = ''
+ if self.is_bot:
+ self.avatar = robot_pictures[randrange(len(robot_pictures))]
+ if self.discord_token:
+ sio.start_background_task(self.get_avatar)
self.game: g = None
self.reset()
@@ -163,6 +209,10 @@ class Player:
self.sio.emit('notify_card', room=self.sid, data=mess)
def notify_self(self):
+ if any((True for c in self.equipment if isinstance(c, tvosc.Fantasma))):
+ self.is_ghost = True
+ elif self.is_ghost and not self.game.check_event(ceh.CittaFantasma):
+ self.is_ghost = False
if self.is_ghost: self.lives = 0
if self.pending_action == PendingAction.DRAW and self.game.check_event(ce.Peyote):
self.available_cards = [{
@@ -187,7 +237,7 @@ class Player:
self.hand.append(self.game.deck.draw(True))
if self.lives <= 0 and self.max_lives > 0 and not self.is_dead:
print('dying, attacker', self.attacker)
- if self.gold_nuggets >= 2 and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Zaino)]) > 0:
+ if self.gold_nuggets >= 2 and any((isinstance(c, grc.Zaino) for c in self.gold_rush_equipment)):
for i in range(len(self.gold_rush_equipment)):
if isinstance(self.gold_rush_equipment[i], grc.Zaino):
self.gold_rush_equipment[i].play_card(self, None)
@@ -202,11 +252,13 @@ class Player:
self.choose_text = 'choose_sid_scrap'
self.available_cards = self.hand
self.lives += 1
+
ser = self.__dict__.copy()
ser.pop('game')
ser.pop('sio')
ser.pop('sid')
ser.pop('on_pick_cb')
+ ser.pop('discord_token')
ser.pop('on_failed_response_cb')
ser.pop('attacker')
ser.pop('rissa_targets')
@@ -214,7 +266,7 @@ class Player:
ser['attacker'] = self.attacker.name
ser['sight'] = self.get_sight()
ser['sight_extra'] = self.get_sight(False) -1
- ser['can_gold_rush_discard'] = len([p for p in self.game.get_alive_players() if p != self and len([e for e in p.gold_rush_equipment if e.number <= self.gold_nuggets + 1]) > 0]) > 0
+ ser['can_gold_rush_discard'] = any((p != self and any((e.number <= self.gold_nuggets + 1 for e in p.gold_rush_equipment)) for p in self.game.get_alive_players()))
if self.character:
ser['gold_rush_discount'] = 1 if self.character.check(self.game, grch.PrettyLuzena) and self.special_use_count < 1 else 0
ser['lives'] = max(ser['lives'], 0)
@@ -223,14 +275,12 @@ class Player:
self.pending_action = PendingAction.WAIT
ser['hand'] = []
ser['equipment'] = []
- self.sio.emit('self', room=self.sid, data=json.dumps(
- ser, default=lambda o: o.__dict__))
+ self.sio.emit('self', room=self.sid, data=json.dumps(ser, default=lambda o: o.__dict__))
self.game.player_death(self)
if self.game and self.game.started: # falso quando un bot viene eliminato dalla partita
self.sio.emit('self_vis', room=self.sid, data=json.dumps(self.game.get_visible_players(self), default=lambda o: o.__dict__))
self.game.notify_all()
- self.sio.emit('self', room=self.sid, data=json.dumps(
- ser, default=lambda o: o.__dict__))
+ self.sio.emit('self', room=self.sid, data=json.dumps(ser, default=lambda o: o.__dict__))
def bot_spin(self):
while self.is_bot and self.game != None and not self.game.shutting_down:
@@ -253,10 +303,10 @@ class Player:
self.draw('')
elif self.pending_action == PendingAction.PLAY:
non_blocked_cards = [card for card in self.hand if (not self.game.check_event(ceh.Manette) or card.suit == self.committed_suit_manette)]
- equippables = [c for c in non_blocked_cards if (c.is_equipment or c.usable_next_turn) and not isinstance(c, cs.Prigione) and not any([type(c) == type(x) and not (c.is_weapon and c.must_be_used) for x in self.equipment])]
+ equippables = [c for c in non_blocked_cards if (c.is_equipment or c.usable_next_turn) and not isinstance(c, cs.Prigione) and not c.need_target and not any((type(c) == type(x) and not (c.is_weapon and c.must_be_used) for x in self.equipment))]
misc = [c for c in non_blocked_cards if not c.need_target and (isinstance(c, cs.WellsFargo) or isinstance(c, cs.Indiani) or isinstance(c, cs.Gatling) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or ((isinstance(c, cs.Birra) and self.lives < self.max_lives or c.must_be_used) and not self.game.check_event(ceh.IlReverendo)) or (c.need_with and len(self.hand) > 1 and not (isinstance(c, csd.Whisky) and self.lives == self.max_lives)))
and not (not c.can_be_used_now and self.game.check_event(ce.IlGiudice)) and not c.is_equipment]
- need_target = [c for c in non_blocked_cards if c.need_target and c.can_be_used_now and not (c.need_with and len(self.hand) < 2) and not (type(c) == type(cs.Bang) and (self.game.check_event(ceh.Sermone) or (self.has_played_bang and (not any([isinstance(c, cs.Volcanic) for c in self.equipment]) or self.game.check_event(ce.Lazo))))) and not (isinstance(c, cs.Prigione) and self.game.check_event(ce.IlGiudice)) or isinstance(c, cs.Duello) or isinstance(c, cs.CatBalou) or isinstance(c, csd.Pugno)]
+ need_target = [c for c in non_blocked_cards if c.need_target and c.can_be_used_now and not (c.need_with and len(self.hand) < 2) and not (type(c) == type(cs.Bang) and (self.game.check_event(ceh.Sermone) or (self.has_played_bang and (not any((isinstance(c, cs.Volcanic) for c in self.equipment)) or self.game.check_event(ce.Lazo))))) and not (isinstance(c, cs.Prigione) and self.game.check_event(ce.IlGiudice)) or isinstance(c, cs.Duello) or isinstance(c, cs.CatBalou) or isinstance(c, csd.Pugno)]
green_cards = [c for c in self.equipment if not self.game.check_event(ce.Lazo) and not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now]
if self.game.debug:
print(f'hand: {self.hand}')
@@ -265,7 +315,7 @@ class Player:
print(f'misc: {misc}')
print(f'need_target: {need_target}')
print(f'green_cards: {green_cards}')
- if self.gold_nuggets > 0 and any([c.number <= self.gold_nuggets for c in self.game.deck.shop_cards]):
+ if self.gold_nuggets > 0 and any((c.number <= self.gold_nuggets for c in self.game.deck.shop_cards)):
for i in range(len(self.game.deck.shop_cards)):
if self.game.deck.shop_cards[i].number <= self.gold_nuggets:
self.game.rpc_log.append(f'{self.name};buy_gold_rush_card;{i}')
@@ -273,6 +323,8 @@ class Player:
return
if len(equippables) > 0 and not self.game.check_event(ce.IlGiudice):
for c in equippables:
+ if isinstance(c, tvosc.Fantasma) and len(self.game.get_dead_players(include_ghosts=False)) == 0:
+ continue
if self.play_card(self.hand.index(c)):
return
elif len(misc) > 0:
@@ -318,7 +370,7 @@ class Player:
return
break
maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10
- if maxcards == self.lives and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Cinturone)]) > 0:
+ if maxcards == self.lives and any((isinstance(c, grc.Cinturone) for c in self.gold_rush_equipment)):
maxcards = 8
if len(self.hand) > maxcards:
self.game.rpc_log.append(f'{self.name};scrap;{0}')
@@ -376,19 +428,19 @@ class Player:
data=f'_turn|{self.name}')
print(f'{self.name}: I was notified that it is my turn')
self.was_shot = False
+ self.attacker = None
self.is_my_turn = True
self.is_waiting_for_action = True
self.has_played_bang = False
self.special_use_count = 0
self.bang_used = 0
if self.game.check_event(ceh.MezzogiornoDiFuoco):
- self.attacker = None
self.lives -= 1
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
+ if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
+ if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw(True))
if self.character.check(self.game, chars.BartCassidy) and self.lives > 0:
self.hand.append(self.game.deck.draw(True))
@@ -398,11 +450,12 @@ class Player:
return self.notify_self()
#non è un elif perchè vera custer deve fare questo poi cambiare personaggio
- if self.game.check_event(ce.FratelliDiSangue) and self.lives > 1 and not self.is_giving_life and len([p for p in self.game.get_alive_players() if p != self and p.lives < p.max_lives]):
+ if self.game.check_event(ce.FratelliDiSangue) and self.lives > 1 and not self.is_giving_life and sum(p != self and p.lives < p.max_lives for p in self.game.get_alive_players()):
self.available_cards = [{
'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else 'βοΈ' if isinstance(p.role, r.Sheriff) else 'π€ ',
'alt_text': ''.join(['β€οΈ']*p.lives)+''.join(['π']*(p.max_lives-p.lives)),
+ 'avatar': p.avatar,
'is_character': True,
'noDesc': True
} for p in self.game.get_alive_players() if p != self and p.lives < p.max_lives]
@@ -414,7 +467,7 @@ class Player:
self.available_cards = [self.character, self.not_chosen_character]
self.choose_text = 'choose_nuova_identita'
self.pending_action = PendingAction.CHOOSE
- elif not self.game.check_event(ce.Lazo) and any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]):
+ elif not self.game.check_event(ce.Lazo) and any((isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) or isinstance(c, tvosc.SerpenteASonagli) for c in self.equipment)):
self.is_giving_life = False
self.pending_action = PendingAction.PICK
else:
@@ -426,7 +479,7 @@ class Player:
self.notify_self()
def draw(self, pile):
- if self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Cecchino) and len([c for c in self.hand if c.name == cs.Bang(0,0).name]) >= 2:
+ if self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Cecchino) and sum((c.name == cs.Bang(0,0).name for c in self.hand)) >= 2:
self.is_using_checchino = True
self.available_cards = [{
'name': p['name'],
@@ -439,11 +492,12 @@ class Player:
self.choose_text = 'choose_cecchino'
self.pending_action = PendingAction.CHOOSE
self.notify_self()
- elif self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Rimbalzo) and len([c for c in self.hand if c.name == cs.Bang(0,0).name]) > 0:
+ 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,
'icon': p.role.icon if(self.game.initial_players == 3) else 'βοΈ' if isinstance(p.role, r.Sheriff) else 'π€ ',
'is_character': True,
+ 'avatar': p.avatar,
'noDesc': True
} for p in self.game.get_alive_players() if len(p.equipment) > 0 and p != self]
self.available_cards.append({'icon': 'β', 'noDesc': True})
@@ -480,7 +534,7 @@ class Player:
self.pending_action = PendingAction.PLAY
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 (len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0): num += 1
+ if (any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment))): num += 1
if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.check_event(ceh.CittaFantasma)): num += 1
elif self.game.check_event(ceh.Sete): num -= 1
for i in range(num):
@@ -522,9 +576,9 @@ class Player:
if self.pending_action != PendingAction.PICK:
return
pickable_cards = 1 + self.character.pick_mod
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0:
+ if any((isinstance(c, grc.FerroDiCavallo) for c in self.gold_rush_equipment)):
pickable_cards += 1
- if self.is_my_turn:
+ if self.is_my_turn and self.attacker == None:
for i in range(len(self.equipment)):
if i < len(self.equipment) and isinstance(self.equipment[i], cs.Dinamite):
while pickable_cards > 0:
@@ -535,11 +589,11 @@ class Player:
data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
if picked.check_suit(self.game, [cs.Suit.SPADES]) and 2 <= picked.number <= 9 and pickable_cards == 0:
self.lives -= 3
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
+ if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 3
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 3
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
+ if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw())
@@ -557,7 +611,7 @@ class Player:
self.game.next_player().equipment.append(self.equipment.pop(i))
self.game.next_player().notify_self()
break
- if any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]):
+ if any((isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) or isinstance(c, tvosc.SerpenteASonagli) for c in self.equipment)):
self.notify_self()
return
for i in range(len(self.equipment)):
@@ -578,7 +632,21 @@ class Player:
self.sio.emit('chat_message', room=self.game.name, data=f'_prison_free|{self.name}')
break
break
- if any([isinstance(c, cs.Prigione) for c in self.equipment]):
+ for i in range(len(self.equipment)):
+ if isinstance(self.equipment[i], tvosc.SerpenteASonagli):
+ while pickable_cards > 0:
+ pickable_cards -= 1
+ picked: cs.Card = self.game.deck.pick_and_scrap()
+ print(f'Did pick {picked}')
+ self.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]):
+ break
+ elif pickable_cards == 0:
+ self.lives -= 1
+ self.sio.emit('chat_message', room=self.game.name, data=f'_snake_bit|{self.name}')
+ break
+ if any((isinstance(c, cs.Prigione) for c in self.equipment)):
self.notify_self()
return
if isinstance(self.real_character, chd.VeraCuster):
@@ -594,7 +662,7 @@ class Player:
playable_cards = []
for i in range(len(self.hand)):
card = self.hand[i]
- if isinstance(card, cs.Bang) and self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment]):
+ if isinstance(card, cs.Bang) and self.has_played_bang and not any((isinstance(c, cs.Volcanic) for c in self.equipment)):
continue
elif isinstance(card, cs.Birra) and self.lives >= self.max_lives:
continue
@@ -627,9 +695,9 @@ 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
- 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) or (self.game.check_event(ceh.Manette) and card.suit != self.committed_suit_manette and not (card.usable_next_turn and card.can_be_used_now))
- if not(against != None and (self.game.get_player_named(against).character.check(self.game, chd.ApacheKid) or len([c for c in self.game.get_player_named(against).gold_rush_equipment if isinstance(c, grc.Calumet)]) > 0) and card.check_suit(self.game, [cs.Suit.DIAMONDS])) or (isinstance(card, grc.ShopCard) and card.kind == grc.ShopCardKind.BLACK) and not event_blocks_card:
- if against == self.name and not isinstance(card, csd.Tequila) and not isinstance(card, cs.Panico) and not isinstance(card, cs.CatBalou):
+ 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) or ((self.game.check_event(ceh.Manette) and card.suit != self.committed_suit_manette) and not (card.usable_next_turn and card.can_be_used_now))
+ if not(against != None and (self.game.get_player_named(against).character.check(self.game, chd.ApacheKid) or any((isinstance(c, grc.Calumet) for c in self.game.get_player_named(against).gold_rush_equipment))) and card.check_suit(self.game, [cs.Suit.DIAMONDS])) or (isinstance(card, grc.ShopCard) and card.kind == grc.ShopCardKind.BLACK) and not event_blocks_card:
+ if (against == self.name and not isinstance(card, csd.Tequila) and not isinstance(card, cs.Panico) and not isinstance(card, cs.CatBalou)) or event_blocks_card:
did_play_card = False
else:
did_play_card = card.play_card(self, against, withCard)
@@ -665,7 +733,7 @@ class Player:
target = self.game.get_player_named(self.target_p)
card = None
if (target.name == self.name):
- card = self.equipment.pop(card_index)
+ card = self.equipment.pop(card_index if card_index < len(target.hand) else card_index - len(target.hand))
elif card_index >= len(target.hand):
card = target.equipment.pop(card_index - len(target.hand))
else:
@@ -688,7 +756,7 @@ class Player:
elif self.choose_text == 'choose_ricercato':
player = self.game.get_player_named(self.available_cards[card_index]['name'])
player.sio.emit('chat_message', room=player.game.name, data=f'_play_card_against|{self.name}|Ricercato|{player.name}')
- if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Ricercato)]) > 0:
+ if any((isinstance(c, grc.Ricercato) for c in player.gold_rush_equipment)):
self.game.deck.shop_deck.append(grc.Ricercato())
else:
player.gold_rush_equipment.append(grc.Ricercato())
@@ -753,6 +821,34 @@ class Player:
player.notify_self()
self.pending_action = PendingAction.PLAY
self.notify_self()
+ elif 'choose_fantasma' in self.choose_text:
+ if card_index <= len(self.available_cards):
+ player = self.game.get_player_named(self.available_cards[card_index]['name'])
+ player.equipment.append(self.game.deck.scrap_pile.pop(-1))
+ player.notify_self()
+ self.game.notify_all()
+ self.sio.emit('chat_message', room=player.game.name, data=f'_play_card_against|{player.name}|Fantasma|{player.name}')
+ self.pending_action = PendingAction.PLAY
+ self.notify_self()
+ elif 'choose_tornado' in self.choose_text:
+ if card_index <= len(self.available_cards):
+ self.game.deck.scrap_pile.append(self.hand.pop(card_index))
+ self.hand.append(self.game.deck.draw())
+ self.hand.append(self.game.deck.draw())
+ self.pending_action = PendingAction.WAIT
+ self.game.responders_did_respond_resume_turn()
+ self.notify_self()
+ elif 'choose_bandidos' in self.choose_text:
+ if card_index <= len(self.available_cards):
+ self.available_cards.pop(card_index)
+ self.game.deck.scrap_pile.append(self.hand.pop(card_index))
+ self.mancato_needed -= 1
+ else:
+ self.lives -= 1
+ if self.mancato_needed <= 0:
+ self.pending_action = PendingAction.WAIT
+ self.game.responders_did_respond_resume_turn()
+ 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
@@ -771,11 +867,11 @@ class Player:
player = self.game.get_player_named(self.available_cards[card_index]['name'])
player.lives += 1
self.lives -= 1
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
+ if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
+ if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw())
player.notify_self()
self.sio.emit('chat_message', room=self.game.name, data=f'_fratelli_sangue|{self.name}|{player.name}')
@@ -855,7 +951,7 @@ class Player:
self.hand.append(card)
pickable_stop = 1
if self.game.check_event(ceh.Sete): pickable_stop += 1
- if self.game.check_event(ceh.IlTreno) or len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0:
+ if self.game.check_event(ceh.IlTreno) or any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment)):
pickable_stop -= 1
if len(self.available_cards) == pickable_stop:
if len(self.available_cards) > 0: #la carta non scelta la rimettiamo in cima al mazzo
@@ -863,7 +959,7 @@ class Player:
if len(self.available_cards) > 0: #se sono rimaste carte le scartiamo
self.game.deck.scrap(self.available_cards.pop())
#se c'Γ¨ sia treno che piccone pesco un'altra carta
- if self.game.check_event(ceh.IlTreno) and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0:
+ if self.game.check_event(ceh.IlTreno) and any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw())
self.is_drawing = False
self.pending_action = PendingAction.PLAY
@@ -879,7 +975,7 @@ class Player:
#legge del west non si applica perchè la seconda carta viene scartata
if self.game.check_event(ceh.IlTreno):
self.hand.append(self.game.deck.draw())
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0:
+ if any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw())
self.gold_nuggets += 1
self.is_drawing = False
@@ -903,9 +999,9 @@ class Player:
def barrel_pick(self):
pickable_cards = 1 + self.character.pick_mod
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0:
+ if any((isinstance(c, grc.FerroDiCavallo) for c in self.gold_rush_equipment)):
pickable_cards += 1
- if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais):
+ if any((isinstance(c, cs.Barile) for c in self.equipment)) and self.character.check(self.game, chars.Jourdonnais):
pickable_cards = 2
while pickable_cards > 0:
pickable_cards -= 1
@@ -919,8 +1015,8 @@ class Player:
if self.mancato_needed <= 0:
self.game.responders_did_respond_resume_turn(did_lose=False)
return
- if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente)]) == 0\
- and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0:
+ if not self.game.is_competitive and not any((isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand))\
+ and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
self.take_damage_response()
self.game.responders_did_respond_resume_turn(did_lose=True)
else:
@@ -933,9 +1029,9 @@ class Player:
def barrel_pick_no_dmg(self):
pickable_cards = 1 + self.character.pick_mod
- if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais):
+ if any((isinstance(c, cs.Barile) for c in self.equipment)) and self.character.check(self.game, chars.Jourdonnais):
pickable_cards = 2
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0:
+ if any((isinstance(c, grc.FerroDiCavallo) for c in self.gold_rush_equipment)):
pickable_cards += 1
while pickable_cards > 0:
pickable_cards -= 1
@@ -949,8 +1045,8 @@ class Player:
if self.mancato_needed <= 0:
self.game.responders_did_respond_resume_turn(did_lose=False)
return
- if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente)]) == 0\
- and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0:
+ if not self.game.is_competitive and not any((isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand))\
+ and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
self.take_no_damage_response()
self.game.responders_did_respond_resume_turn(did_lose=True)
else:
@@ -961,6 +1057,17 @@ class Player:
self.on_failed_response_cb = self.take_no_damage_response
self.notify_self()
+ def get_discarded(self, attacker=None, card_name=None):
+ self.pending_action = PendingAction.CHOOSE
+ self.available_cards = self.hand.copy()
+ if card_name == 'Tornado':
+ self.choose_text = 'choose_tornado'
+ if card_name == 'Bandidos':
+ self.choose_text = 'choose_bandidos'
+ self.mancato_needed = min(2, len(self.hand))
+ self.available_cards.append({'name': '-1hp', 'icon': 'π', 'noDesc': True})
+ return True
+
def get_banged(self, attacker, double=False, no_dmg=False, card_index=None, card_name=None):
self.attacker = attacker
self.attacking_card = card_name
@@ -973,9 +1080,9 @@ class Player:
for i in range(len(self.equipment)):
if self.equipment[i].can_be_used_now:
print('usable', self.equipment[i])
- if not self.game.is_competitive and len([c for c in self.equipment if isinstance(c, cs.Barile)]) == 0 and not self.character.check(self.game, chars.Jourdonnais)\
- and len([c for c in self.hand if (isinstance(c, cs.Mancato) and c.can_be_used_now) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente)]) == 0\
- and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0:
+ if not self.game.is_competitive and not any((isinstance(c, cs.Barile) for c in self.equipment)) and not self.character.check(self.game, chars.Jourdonnais)\
+ and not any(((isinstance(c, cs.Mancato) and c.can_be_used_now) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand))\
+ and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
print('Cant defend')
if not no_dmg:
self.take_damage_response()
@@ -983,7 +1090,7 @@ class Player:
self.take_no_damage_response()
return False
else:
- if ((not self.game.check_event(ce.Lazo) and len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0) \
+ if ((not self.game.check_event(ce.Lazo) and any((isinstance(c, cs.Barile) for c in self.equipment))) \
and not (self.game.players[self.game.turn].character.check(self.game, chd.BelleStar) and isinstance(attacker, Player))) \
or self.character.check(self.game, chars.Jourdonnais): #se ho un barile e non c'Γ¨ lazo e non mi sta attaccando Belle Star o se sono Jourdonnais
print('has barrel')
@@ -1019,8 +1126,8 @@ class Player:
def get_indians(self, attacker):
self.attacker = attacker
self.attacking_card = "Indiani!"
- if self.character.check(self.game, chd.ApacheKid) or len([c for c in self.gold_rush_equipment if isinstance(c, grc.Calumet)]) > 0: return False
- if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0:
+ if self.character.check(self.game, chd.ApacheKid) or any((isinstance(c, grc.Calumet) for c in self.gold_rush_equipment)): return False
+ if not self.game.is_competitive and not any((isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato)) for c in self.hand)):
print('Cant defend')
self.take_damage_response()
return False
@@ -1037,7 +1144,7 @@ class Player:
def get_dueled(self, attacker):
self.attacker = attacker
self.attacking_card = "Duello"
- if (self.game.check_event(ceh.Sermone) and self.is_my_turn) or (not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0):
+ if (self.game.check_event(ceh.Sermone) and self.is_my_turn) or (not self.game.is_competitive and not any((isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato)) for c in self.hand))):
print('Cant defend')
self.take_damage_response()
self.game.responders_did_respond_resume_turn(did_lose=True)
@@ -1052,7 +1159,7 @@ class Player:
return True
def heal_if_needed(self):
- while self.lives <= 0 and len(self.game.get_alive_players()) > 2 and len([c for c in self.hand if isinstance(c, cs.Birra)]) > 0 and not self.game.check_event(ceh.IlReverendo):
+ while self.lives <= 0 and len(self.game.get_alive_players()) > 2 and any((isinstance(c, cs.Birra) for c in self.hand)) and not self.game.check_event(ceh.IlReverendo):
for i in range(len(self.hand)):
if isinstance(self.hand[i], cs.Birra):
if self.character.check(self.game, chd.MollyStark) and not self.is_my_turn:
@@ -1066,6 +1173,7 @@ class Player:
def take_damage_response(self):
self.lives -= 1
+ self.sio.emit('hurt', room=self.sid, data=f'')
if self.lives > 0:
if self.character.check(self.game, chars.BartCassidy):
self.sio.emit('chat_message', room=self.game.name,
@@ -1077,15 +1185,24 @@ class Player:
self.sio.emit('chat_message', room=self.game.name,
data=f'_special_el_gringo|{self.name}|{self.attacker.name}')
self.attacker.notify_self()
+ if self.attacker and any((isinstance(c, tvosc.Taglia) for c in self.equipment)):
+ self.attacker.hand.append(self.game.deck.draw(True))
+ self.sio.emit('chat_message', room=self.game.name,
+ data=f'_taglia_reward|{self.name}|{self.attacker.name}')
+ self.attacker.notify_self()
+ if self.attacker and len(self.hand) > 0 and any((isinstance(c, tvosc.Shotgun) for c in self.attacker.equipment)):
+ c = self.hand.pop(randrange(0, len(self.hand)))
+ self.game.deck.scrap(c, True)
+ self.sio.emit('chat_message', room=self.game.name, data=f'_shotgun_scrap|{self.name}|{c.name}')
if self.attacker and 'gold_rush' in self.game.expansions:
if (isinstance(self.attacker, Player)):
self.attacker.gold_nuggets += 1
self.attacker.notify_self()
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
+ if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1
- if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
+ if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw(True))
self.heal_if_needed()
self.mancato_needed = 0
@@ -1137,7 +1254,15 @@ class Player:
self.molly_discarded_cards = 0
self.notify_self()
self.game.responders_did_respond_resume_turn(did_lose=False)
+ if isinstance(card, tvosc.RitornoDiFiamma):
+ self.game.attack(self, self.attacker.name, card_name=card.name)
self.event_type = ''
+ elif not any(((isinstance(c, cs.Mancato) and c.can_be_used_now) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand)) and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
+ self.on_failed_response_cb()
+ if self.game:
+ self.game.responders_did_respond_resume_turn(did_lose=True)
+ if isinstance(card, tvosc.RitornoDiFiamma) and self.attacker:
+ self.game.attack(self, self.attacker.name, card_name=card.name)
else:
self.pending_action = PendingAction.RESPOND
self.notify_self()
@@ -1205,9 +1330,10 @@ class Player:
'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else 'βοΈ' if isinstance(p.role, r.Sheriff) else 'π€ ',
'is_character': True,
+ 'avatar': p.avatar,
'alt_text': ''.join(['π΄οΈ'] * len(p.gold_rush_equipment)),
'noDesc': True
- } for p in self.game.get_alive_players() if p != self and len([e for e in p.gold_rush_equipment if e.number + 1 <= self.gold_nuggets]) > 0]
+ } for p in self.game.get_alive_players() if p != self and any((e.number + 1 <= self.gold_nuggets for e in p.gold_rush_equipment))]
self.available_cards.append({'icon': 'β', 'noDesc': True})
self.choose_text = 'gold_rush_discard'
self.pending_action = PendingAction.CHOOSE
@@ -1234,23 +1360,23 @@ class Player:
if self.game.check_event(ce.LeggeDelWest) and len(must_be_used_cards) > 0:
card = must_be_used_cards[0]
print(f'Legge del west card: {card.name}')
- print(self.has_played_bang and not (any([isinstance(c, cs.Volcanic) for c in self.equipment]) and type(card) == type(cs.Bang)))
- if card.suit == cs.Suit.DIAMONDS and card.need_target and len([p for p in self.game.get_alive_players() if (not p.character.check(self.game, chd.ApacheKid) and not any([isinstance(c, grc.Calumet) for c in p.gold_rush_equipment]))]) == 0:
+ print(self.has_played_bang and not (any((isinstance(c, cs.Volcanic) for c in self.equipment)) and type(card) == type(cs.Bang)))
+ if card.suit == cs.Suit.DIAMONDS and card.need_target and not any(((not p.character.check(self.game, chd.ApacheKid) and not any((isinstance(c, grc.Calumet) for c in p.gold_rush_equipment))) for p in self.game.get_alive_players())):
if isinstance(card, cs.Bang):
return True
else:
return len(self.equipment) == 0 # se non ho carte equipaggiamento
- elif (isinstance(card, cs.Bang) or (isinstance(card, cs.Mancato) and self.character.check(self.game, chars.CalamityJanet))) and self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment]) or len([p for p in self.game.get_visible_players(self) if self.get_sight() >= p['dist']]) == 0:
+ elif (isinstance(card, cs.Bang) or (isinstance(card, cs.Mancato) and self.character.check(self.game, chars.CalamityJanet))) and self.has_played_bang and not any((isinstance(c, cs.Volcanic) for c in self.equipment)) or not any((self.get_sight() >= p['dist'] for p in self.game.get_visible_players(self))):
return True
elif isinstance(card, cs.Mancato) or (card.need_with and len(self.hand) < 2):
return True
- elif isinstance(card, cs.Panico) and len([p for p in self.game.get_visible_players(self) if self.get_sight(False) >= p['dist']]) == 0 and len(self.equipment) == 0:
+ elif isinstance(card, cs.Panico) and not any((self.get_sight(False) >= p['dist'] for p in self.game.get_visible_players(self))) and len(self.equipment) == 0:
return True
- elif isinstance(card, csd.Pugno) and len([p for p in self.game.get_visible_players(self) if self.get_sight(False) >= p['dist']]) == 0:
+ elif isinstance(card, csd.Pugno) and not any((self.get_sight(False) >= p['dist'] for p in self.game.get_visible_players(self))):
return True
- elif isinstance(card, cs.Prigione) and len([p for p in self.game.get_visible_players(self) if not p['is_sheriff']]) == 0:
+ elif isinstance(card, cs.Prigione) and not any((not p['is_sheriff'] for p in self.game.get_visible_players(self))):
return True
- elif not card.is_weapon and len([c for c in self.equipment if c.name == card.name]) > 0:
+ elif not card.is_weapon and any((c.name == card.name for c in self.equipment)):
return True
return False
return True
@@ -1260,7 +1386,7 @@ class Player:
if not self.is_my_turn and not forced:
return
maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10
- if maxcards == self.lives and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Cinturone)]) > 0:
+ if maxcards == self.lives and any((isinstance(c, grc.Cinturone) for c in self.gold_rush_equipment)):
maxcards = 8
if len(self.hand) > maxcards and not forced:
print(f"{self.name}: I have to many cards in my hand and I can't end the turn")
@@ -1290,7 +1416,7 @@ class Player:
self.play_turn(can_play_vendetta=False)
return
##Ghost##
- if self.is_dead and self.is_ghost and self.game.check_event(ceh.CittaFantasma):
+ if self.is_dead and self.is_ghost and self.game.check_event(ceh.CittaFantasma) and not any((True for c in self.equipment if isinstance(c, tvosc.Fantasma))):
self.is_ghost = False
for i in range(len(self.hand)):
self.game.deck.scrap(self.hand.pop(), True)
diff --git a/backend/bang/roles.py b/backend/bang/roles.py
index 32eb94d..9032e40 100644
--- a/backend/bang/roles.py
+++ b/backend/bang/roles.py
@@ -21,7 +21,7 @@ class Sheriff(Role):
def on_player_death(self, alive_players: list, initial_players: int, dead_role=None, attacker_role=None):
if initial_players == 3 and len(alive_players) == 1:
return True
- elif initial_players != 3 and not any([isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players]):
+ elif initial_players != 3 and not any((isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players)):
print("The Sheriff won!")
return True
return False
@@ -42,7 +42,7 @@ class Vice(Role):
return True
elif initial_players == 3 and attacker_role != None:
return isinstance(dead_role, Renegade) and isinstance(attacker_role, Vice)
- elif initial_players != 3 and not any([isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players]):
+ elif initial_players != 3 and not any((isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players)):
print("The Vice won!")
return True
return False
@@ -62,9 +62,9 @@ class Outlaw(Role):
return True
elif initial_players == 3 and attacker_role != None:
return isinstance(dead_role, Vice) and isinstance(attacker_role, Outlaw)
- elif (initial_players != 3 and (not any([isinstance(p.role, Sheriff) for p in alive_players]))
- and (any([isinstance(p.role, Outlaw) for p in alive_players])
- or any([isinstance(p.role, Renegade) for p in alive_players]) and len(alive_players) > 1)):
+ elif (initial_players != 3 and (not any((isinstance(p.role, Sheriff) for p in alive_players)))
+ and (any((isinstance(p.role, Outlaw) for p in alive_players))
+ or any((isinstance(p.role, Renegade) for p in alive_players)) and len(alive_players) > 1)):
print("The Outlaw won!")
return True
return False
diff --git a/backend/server.py b/backend/server.py
index 9b43579..7f68f56 100644
--- a/backend/server.py
+++ b/backend/server.py
@@ -14,11 +14,16 @@ from discord_webhook import DiscordWebhook
from metrics import Metrics
from ddtrace import tracer
-Metrics.init()
-
-import sys
+import sys
+import traceback
sys.setrecursionlimit(10**6) # this should prevents bots from stopping
+import logging
+logging.basicConfig(filename='out.log', level='ERROR')
+from functools import wraps
+
+Metrics.init()
+
sio = socketio.Server(cors_allowed_origins="*")
static_files={
@@ -45,13 +50,30 @@ games: List[Game] = []
online_players = 0
blacklist: List[str] = []
+def send_to_debug(error):
+ for g in games:
+ if g.debug or any((p.is_admin() for p in g.players)):
+ sio.emit('chat_message', room=g.name, data={'color': f'red','text':json.dumps({'ERROR':error}), 'type':'json'})
+
+def bang_handler(func):
+ @wraps(func)
+ def wrapper_func(*args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except Exception as e:
+ logging.exception(e)
+ print(traceback.format_exc())
+ send_to_debug(traceback.format_exc())
+ return wrapper_func
+
def advertise_lobbies():
sio.emit('lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if not g.started and len(g.players) < 10 and not g.is_hidden])
- sio.emit('spectate_lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if g.started])
- Metrics.send_metric('lobbies', points=[len([g for g in games if not g.is_replay])])
+ sio.emit('spectate_lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if g.started and not g.is_hidden])
+ Metrics.send_metric('lobbies', points=[sum(not g.is_replay for g in games)])
Metrics.send_metric('online_players', points=[online_players])
@sio.event
+@bang_handler
def connect(sid, environ):
global online_players
online_players += 1
@@ -61,40 +83,46 @@ def connect(sid, environ):
Metrics.send_metric('online_players', points=[online_players])
@sio.event
+@bang_handler
def get_online_players(sid):
global online_players
sio.emit('players', room='lobby', data=online_players)
@sio.event
+@bang_handler
def report(sid, text):
+ print(f'New report from {sid}: {text}')
ses: Player = sio.get_session(sid)
data=''
if hasattr(ses, 'game'):
data = "\n".join(ses.game.rpc_log[:-1]).strip()
data = data +"\n@@@\n" +text
#print(data)
- response = requests.post("https://www.toptal.com/developers/hastebin/documents", data)
+ response = requests.post("https://hastebin.com/documents", data.encode('utf-8'))
key = json.loads(response.text).get('key')
if "DISCORD_WEBHOOK" in os.environ and len(os.environ['DISCORD_WEBHOOK']) > 0:
- webhook = DiscordWebhook(url=os.environ['DISCORD_WEBHOOK'], content=f'New bug report, replay at https://www.toptal.com/developers/hastebin/{key}')
+ webhook = DiscordWebhook(url=os.environ['DISCORD_WEBHOOK'], content=f'New bug report, replay at https://bang.xamin.it/game?replay={key}')
response = webhook.execute()
sio.emit('chat_message', room=sid, data={'color': f'green','text':f'Report OK'})
else:
print("WARNING: DISCORD_WEBHOOK not found")
Metrics.send_event('BUG_REPORT', event_data=text)
- print(f'New bug report, replay at https://www.toptal.com/developers/hastebin/{key}')
+ print(f'New bug report, replay at https://bang.xamin.it/game?replay={key}')
@sio.event
+@bang_handler
def set_username(sid, username):
ses = sio.get_session(sid)
if not isinstance(ses, Player):
- sio.save_session(sid, Player(username, sid, sio))
+ dt = username["discord_token"] if 'discord_token' in username else None
+ sio.save_session(sid, Player(username["name"], sid, sio, discord_token=dt))
print(f'{sid} is now {username}')
advertise_lobbies()
elif ses.game == None or not ses.game.started:
+ username = username["name"]
print(f'{sid} changed username to {username}')
prev = ses.name
- if len([p for p in ses.game.players if p.name == username]) > 0:
+ if ses.game and any((p.name == username for p in ses.game.players)):
ses.name = f"{username}_{random.randint(0,100)}"
else:
ses.name = username
@@ -104,19 +132,34 @@ def set_username(sid, username):
@tracer.wrap
@sio.event
+@bang_handler
def get_me(sid, room):
if isinstance(sio.get_session(sid), Player):
sio.emit('me', data=sio.get_session(sid).name, room=sid)
if sio.get_session(sid).game:
sio.get_session(sid).game.notify_room()
else:
- sio.save_session(sid, Player('player', sid, sio))
+ dt = room["discord_token"] if 'discord_token' in room else None
+ sio.save_session(sid, Player('player', sid, sio, discord_token=dt))
+ if 'replay' in room and room['replay'] != None:
+ create_room(sid, room['replay'])
+ sid = sio.get_session(sid)
+ sid.game.is_hidden = True
+ eventlet.sleep(0.5)
+ response = requests.get(f"https://hastebin.com/raw/{room['replay']}")
+ if response.status_code != 200:
+ sio.emit('chat_message', room=sid, data={'color': f'green','text':f'Invalid replay code'})
+ return
+ log = response.text.splitlines()
+ sid.game.spectators.append(sid)
+ sid.game.replay(log)
+ return
de_games = [g for g in games if g.name == room['name']]
if len(de_games) == 1 and not de_games[0].started:
join_room(sid, room)
elif len(de_games) == 1 and de_games[0].started:
print('room exists')
- if room['username'] != None and any([p.name == room['username'] for p in de_games[0].players if p.is_bot]):
+ if room['username'] != None and any((p.name == room['username'] for p in de_games[0].players if (p.is_bot or (dt != None and p.discord_token == dt)))):
print('getting inside the bot')
bot = [p for p in de_games[0].players if p.is_bot and p.name == room['username'] ][0]
bot.sid = sid
@@ -126,6 +169,7 @@ def get_me(sid, room):
de_games[0].notify_room(sid)
eventlet.sleep(0.1)
de_games[0].notify_all()
+ de_games[0].notify_scrap_pile(sid)
sio.emit('role', room=sid, data=json.dumps(bot.role, default=lambda o: o.__dict__))
bot.notify_self()
if len(bot.available_characters) > 0:
@@ -135,6 +179,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)
+ eventlet.sleep(0.1)
+
de_games[0].notify_event_card(sid)
de_games[0].notify_scrap_pile(sid)
de_games[0].notify_all()
@@ -146,7 +192,7 @@ def get_me(sid, room):
sio.emit('me', data={'error':'Wrong password/Cannot connect'}, room=sid)
else:
sio.emit('me', data=sio.get_session(sid).name, room=sid)
- if room['username'] == None or any([p.name == room['username'] for p in sio.get_session(sid).game.players]):
+ if room['username'] == None or any((p.name == room['username'] for p in sio.get_session(sid).game.players)):
sio.emit('change_username', room=sid)
else:
sio.emit('chat_message', room=sio.get_session(sid).game.name, data=f"_change_username|{sio.get_session(sid).name}|{room['username']}")
@@ -157,6 +203,7 @@ def get_me(sid, room):
@tracer.wrap
@sio.event
+@bang_handler
def disconnect(sid):
global online_players
online_players -= 1
@@ -164,16 +211,18 @@ def disconnect(sid):
sio.emit('players', room='lobby', data=online_players)
if sio.get_session(sid).game and sio.get_session(sid).disconnect():
sio.close_room(sio.get_session(sid).game.name)
- games.pop(games.index(sio.get_session(sid).game))
+ if sio.get_session(sid).game in games:
+ games.pop(games.index(sio.get_session(sid).game))
print('disconnect ', sid)
advertise_lobbies()
Metrics.send_metric('online_players', points=[online_players])
@tracer.wrap
@sio.event
+@bang_handler
def create_room(sid, room_name):
if sio.get_session(sid).game == None:
- while len([g for g in games if g.name == room_name]):
+ while any((g.name == room_name for g in games)):
room_name += f'_{random.randint(0,100)}'
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
@@ -186,26 +235,31 @@ def create_room(sid, room_name):
advertise_lobbies()
@sio.event
+@bang_handler
def private(sid):
g = sio.get_session(sid).game
g.set_private()
advertise_lobbies()
@sio.event
+@bang_handler
def toggle_expansion(sid, expansion_name):
g = sio.get_session(sid).game
g.toggle_expansion(expansion_name)
@sio.event
+@bang_handler
def toggle_comp(sid):
sio.get_session(sid).game.toggle_competitive()
@sio.event
+@bang_handler
def toggle_replace_with_bot(sid):
sio.get_session(sid).game.toggle_disconnect_bot()
@tracer.wrap
@sio.event
+@bang_handler
def join_room(sid, room):
room_name = room['name']
i = [g.name for g in games].index(room_name)
@@ -215,7 +269,7 @@ def join_room(sid, room):
print(f'{sid} joined a room named {room_name}')
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
- while len([p for p in games[i].players if p.name == sio.get_session(sid).name]):
+ while any((p.name == sio.get_session(sid).name and not p.is_bot for p in games[i].players)):
sio.get_session(sid).name += f'_{random.randint(0,100)}'
sio.emit('me', data=sio.get_session(sid).name, room=sid)
games[i].add_player(sio.get_session(sid))
@@ -236,8 +290,9 @@ Sockets for the status page
@tracer.wrap
@sio.event
+@bang_handler
def get_all_rooms(sid, deploy_key):
- if 'DEPLOY_KEY' in os.environ and deploy_key == os.environ['DEPLOY_KEY']:
+ if ('DEPLOY_KEY' in os.environ and deploy_key == os.environ['DEPLOY_KEY']) or sio.get_session(sid).is_admin():
sio.emit('all_rooms', room=sid, data=[{
'name': g.name,
'hidden': g.is_hidden,
@@ -252,13 +307,15 @@ def get_all_rooms(sid, deploy_key):
} for g in games])
@sio.event
+@bang_handler
def kick(sid, data):
- if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']:
+ if ('DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']) or sio.get_session(sid).is_admin():
sio.emit('kicked', room=data['sid'])
@sio.event
+@bang_handler
def hide_toogle(sid, data):
- if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']:
+ if ('DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']) or sio.get_session(sid).is_admin():
game = [g for g in games if g.name==data['room']]
if len(games) > 0:
game[0].is_hidden = not game[0].is_hidden
@@ -274,6 +331,7 @@ Sockets for the game
"""
@sio.event
+@bang_handler
def start_game(sid):
ses: Player = sio.get_session(sid)
ses.game.start_game()
@@ -281,6 +339,13 @@ def start_game(sid):
@tracer.wrap
@sio.event
+@bang_handler
+def shuffle_players(sid):
+ ses: Player = sio.get_session(sid)
+ ses.game.shuffle_players()
+
+@sio.event
+@bang_handler
def set_character(sid, name):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};set_character;{name}')
@@ -289,12 +354,14 @@ def set_character(sid, name):
ses.set_character(name)
@sio.event
+@bang_handler
def refresh(sid):
ses: Player = sio.get_session(sid)
ses.notify_self()
@tracer.wrap
@sio.event
+@bang_handler
def draw(sid, pile):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};draw;{pile}')
@@ -302,6 +369,7 @@ def draw(sid, pile):
@tracer.wrap
@sio.event
+@bang_handler
def pick(sid):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};pick')
@@ -309,6 +377,7 @@ def pick(sid):
@tracer.wrap
@sio.event
+@bang_handler
def end_turn(sid):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};end_turn')
@@ -316,6 +385,7 @@ def end_turn(sid):
@tracer.wrap
@sio.event
+@bang_handler
def play_card(sid, data):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};play_card;{json.dumps(data)}')
@@ -323,6 +393,7 @@ def play_card(sid, data):
@tracer.wrap
@sio.event
+@bang_handler
def respond(sid, card_index):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};respond;{card_index}')
@@ -330,6 +401,7 @@ def respond(sid, card_index):
@tracer.wrap
@sio.event
+@bang_handler
def choose(sid, card_index):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};choose;{card_index}')
@@ -337,6 +409,7 @@ def choose(sid, card_index):
@tracer.wrap
@sio.event
+@bang_handler
def scrap(sid, card_index):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};scrap;{card_index}')
@@ -351,6 +424,7 @@ def special(sid, data):
@tracer.wrap
@sio.event
+@bang_handler
def gold_rush_discard(sid):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};gold_rush_discard;')
@@ -358,6 +432,7 @@ def gold_rush_discard(sid):
@tracer.wrap
@sio.event
+@bang_handler
def buy_gold_rush_card(sid, data:int):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};buy_gold_rush_card;{data}')
@@ -365,6 +440,7 @@ def buy_gold_rush_card(sid, data:int):
@tracer.wrap
@sio.event
+@bang_handler
def chat_message(sid, msg, pl=None):
ses: Player = sio.get_session(sid) if pl is None else pl
ses.game.rpc_log.append(f'{ses.name};chat_message;{msg}')
@@ -379,7 +455,7 @@ def chat_message(sid, msg, pl=None):
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'Only 1 bot at the time'})
else:
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
- while any([p for p in ses.game.players if p.name == bot.name]):
+ while any((p for p in ses.game.players if p.name == bot.name)):
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
ses.game.add_player(bot)
bot.bot_spin()
@@ -388,7 +464,7 @@ def chat_message(sid, msg, pl=None):
_cmd = msg.split()
if len(_cmd) >= 2:
replay_id = _cmd[1]
- response = requests.get(f"https://www.toptal.com/developers/hastebin/raw/{replay_id}")
+ response = requests.get(f"https://hastebin.com/raw/{replay_id}")
log = response.text.splitlines()
ses.game.spectators.append(ses)
if len(_cmd) == 2:
@@ -407,7 +483,7 @@ def chat_message(sid, msg, pl=None):
ses.game.start_game(int(msg.split()[1]))
return
elif '/removebot' in msg and not ses.game.started:
- if any([p.is_bot for p in ses.game.players]):
+ if any((p.is_bot for p in ses.game.players)):
[p for p in ses.game.players if p.is_bot][-1].disconnect()
return
elif '/togglecomp' in msg and ses.game:
@@ -418,13 +494,13 @@ def chat_message(sid, msg, pl=None):
if len(cmd) == 2 and 'DEPLOY_KEY' in os.environ and cmd[1] == os.environ['DEPLOY_KEY']: # solo chi ha la deploy key puΓ² attivare la modalitΓ debug
ses.game.debug = not ses.game.debug
ses.game.notify_room()
- elif ses == ses.game.players[0]: # solo l'owner puΓ² attivare la modalitΓ debug
+ elif ses == ses.game.players[0] or ses.is_admin(): # solo l'owner puΓ² attivare la modalitΓ debug
ses.game.debug = not ses.game.debug
ses.game.notify_room()
if ses.game.debug:
sio.emit('chat_message', room=sid, data={'color': f'red','text':f'debug mode is now active, only the owner of the room can disable it with /debug'})
return
- if not ses.game.debug:
+ if not ses.game.debug and not ses.is_admin():
sio.emit('chat_message', room=sid, data={'color': f'','text':f'debug mode is not active, only the owner of the room can enable it with /debug'})
elif '/set_chars' in msg and not ses.game.started:
cmd = msg.split()
@@ -544,6 +620,27 @@ def chat_message(sid, msg, pl=None):
for cn in card_names:
ses.hand.append([c for c in cards if c.name.lower() == cn.lower() or c.name[0:-1].lower() == cn.lower()][0])
ses.notify_self()
+ elif '/equipcard' in msg:
+ sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'π¨ {ses.name} is in debug mode and got a card'})
+ import bang.cards as cs
+ cmd = msg.split()
+ if len(cmd) >= 2:
+ cards = cs.get_starting_deck(ses.game.expansions)
+ card_names = ' '.join(cmd[1:]).split(',')
+ for cn in card_names:
+ ses.equipment.append([c for c in cards if c.name.lower() == cn.lower() or c.name[0:-1].lower() == cn.lower()][0])
+ ses.notify_self()
+ elif '/getset' in msg:
+ sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'π¨ {ses.name} is in debug mode and got a card'})
+ cmd = msg.split()
+ if len(cmd) >= 2:
+ from bang.expansions import DodgeCity, TheValleyOfShadows
+ if cmd[1] == 'dodgecity':
+ ses.hand = DodgeCity.get_cards()
+ ses.notify_self()
+ elif 'valley' in cmd[1].lower():
+ ses.hand = TheValleyOfShadows.get_cards()
+ ses.notify_self()
elif '/getnuggets' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'π¨ {ses.name} is in debug mode and got nuggets'})
import bang.cards as cs
@@ -552,16 +649,26 @@ def chat_message(sid, msg, pl=None):
ses.gold_nuggets += int(cmd[1])
ses.notify_self()
elif '/gameinfo' in msg:
- sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {dict(filter(lambda x:x[0] != "rpc_log",ses.game.__dict__.items()))}'})
+ sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.game.__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
+ elif '/status' in msg and ses.is_admin():
+ sio.emit('mount_status', room=sid)
elif '/meinfo' in msg:
- sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.__dict__}'})
+ sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
+ elif '/playerinfo' in msg:
+ cmd = msg.split()
+ if len(cmd) == 2:
+ sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.game.get_player_named(cmd[1]).__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
+ elif '/cardinfo' in msg:
+ cmd = msg.split()
+ if len(cmd) == 2:
+ sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.hand[int(cmd[1])].__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
elif '/mebot' in msg:
ses.is_bot = not ses.is_bot
if (ses.is_bot):
ses.was_player = True
ses.bot_spin()
elif '/arcadekick' in msg and ses.game.started:
- if len([p for p in ses.game.players if p.pending_action != PendingAction.WAIT]) == 0:
+ if not any((p.pending_action != PendingAction.WAIT for p in ses.game.players)):
sio.emit('chat_message', room=ses.game.name, data={'color': f'','text':f'KICKING THE ARCADE CABINET'})
ses.game.next_turn()
else:
@@ -582,6 +689,7 @@ Sockets for the help screen
"""
@sio.event
+@bang_handler
def get_cards(sid):
import bang.cards as c
cards = c.get_starting_deck(['dodge_city'])
@@ -594,12 +702,14 @@ def get_cards(sid):
Metrics.send_metric('help_screen_viewed', points=[1])
@sio.event
+@bang_handler
def get_characters(sid):
import bang.characters as ch
cards = ch.all_characters(['dodge_city', 'gold_rush'])
sio.emit('characters_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event
+@bang_handler
def get_highnooncards(sid):
import bang.expansions.high_noon.card_events as ceh
chs = []
@@ -608,6 +718,7 @@ def get_highnooncards(sid):
sio.emit('highnooncards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
@sio.event
+@bang_handler
def get_foccards(sid):
import bang.expansions.fistful_of_cards.card_events as ce
chs = []
@@ -616,6 +727,7 @@ def get_foccards(sid):
sio.emit('foccards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
@sio.event
+@bang_handler
def get_goldrushcards(sid):
import bang.expansions.gold_rush.shop_cards as grc
cards = grc.get_cards()
@@ -626,9 +738,34 @@ def get_goldrushcards(sid):
cards = [cards_dict[i] for i in cards_dict]
sio.emit('goldrushcards_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
+@sio.event
+@bang_handler
+def get_valleyofshadowscards(sid):
+ import bang.expansions.the_valley_of_shadows.cards as tvos
+ cards = tvos.get_starting_deck()
+ cards_dict = {}
+ for ca in cards:
+ if ca.name not in cards_dict:
+ cards_dict[ca.name] = ca
+ cards = [cards_dict[i] for i in cards_dict]
+ sio.emit('valleyofshadows_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
+
+@sio.event
+@bang_handler
+def discord_auth(sid, data):
+ res = requests.post('https://discord.com/api/oauth2/token', data={
+ 'client_id': '1059452581027532880',
+ 'client_secret': 'Mc8ZlMQhayzi1eOqWFtGHs3L0iXCzaEu',
+ 'grant_type': 'authorization_code',
+ 'redirect_uri': data['origin'],
+ 'code': data['code'],
+ })
+ if res.status_code == 200:
+ sio.emit('discord_auth_succ', room=sid, data=res.json())
+
def pool_metrics():
sio.sleep(60)
- Metrics.send_metric('lobbies', points=[len([g for g in games if not g.is_replay])])
+ Metrics.send_metric('lobbies', points=[sum(not g.is_replay for g in games)])
Metrics.send_metric('online_players', points=[online_players])
pool_metrics()
@@ -650,6 +787,10 @@ class CustomProxyFix(object):
return ['']
return self.app(environ, start_response)
+
+discord_ci = '1059452581027532880'
+discord_cs = 'Mc8ZlMQhayzi1eOqWFtGHs3L0iXCzaEu'
+
if __name__ == '__main__':
sio.start_background_task(pool_metrics)
eventlet.wsgi.server(eventlet.listen(('', 5001)), CustomProxyFix(app))
diff --git a/backend/tests/character_test.py b/backend/tests/character_test.py
index 740f757..daaf109 100644
--- a/backend/tests/character_test.py
+++ b/backend/tests/character_test.py
@@ -297,8 +297,6 @@ def test_SlabTheKiller():
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].pending_action == PendingAction.RESPOND
g.players[(g.turn+1)%2].respond(0)
- assert g.players[(g.turn+1)%2].pending_action == PendingAction.RESPOND
- g.players[(g.turn+1)%2].respond(-1)
assert g.players[(g.turn+1)%2].pending_action == PendingAction.WAIT
assert g.players[(g.turn+1)%2].lives == 3
diff --git a/backend/tests/valley_of_shadows_test.py b/backend/tests/valley_of_shadows_test.py
new file mode 100644
index 0000000..c477ab2
--- /dev/null
+++ b/backend/tests/valley_of_shadows_test.py
@@ -0,0 +1,142 @@
+from random import randint
+from bang.characters import Character
+from bang.expansions.the_valley_of_shadows.cards import *
+from tests.dummy_socket import DummySocket
+from bang.deck import Deck
+from bang.game import Game
+from bang.players import Player, PendingAction
+import bang.cards as cs
+
+# test UltimoGiro
+def test_ultimo_giro():
+ sio = DummySocket()
+ g = Game('test', sio)
+ ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
+ for p in ps:
+ g.add_player(p)
+ g.start_game()
+ for p in ps:
+ p.available_characters = [Character('test_char', 4)]
+ p.set_character(p.available_characters[0].name)
+ ultimo_giro_guy = g.players[g.turn]
+ ultimo_giro_guy.draw('')
+ ultimo_giro_guy.lives = 3
+ ultimo_giro_guy.hand = [UltimoGiro(0,0)]
+ assert ultimo_giro_guy.lives == 3
+ ultimo_giro_guy.play_card(0)
+ assert ultimo_giro_guy.lives == 4
+
+# test Tomahawk
+def test_tomahawk():
+ sio = DummySocket()
+ g = Game('test', sio)
+ ps = [Player(f'p{i}', f'p{i}', sio) for i in range(6)]
+ for p in ps:
+ g.add_player(p)
+ g.start_game()
+ for p in ps:
+ p.available_characters = [Character('test_char', 4)]
+ p.set_character(p.available_characters[0].name)
+ tomahawk_guy = g.players[g.turn]
+ tomahawk_guy.draw('')
+ tomahawk_guy.hand = [Tomahawk(0,0)]
+ assert len(tomahawk_guy.hand) == 1
+ tomahawk_guy.play_card(0, g.players[(g.turn+3)%6].name)
+ assert len(tomahawk_guy.hand) == 1
+ tomahawk_guy.play_card(0, g.players[(g.turn+1)%6].name)
+ assert len(tomahawk_guy.hand) == 0
+
+# test Fantasma
+def test_fantasma():
+ sio = DummySocket()
+ g = Game('test', sio)
+ ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
+ for p in ps:
+ g.add_player(p)
+ g.start_game()
+
+ for p in ps:
+ p.available_characters = [Character('test_char', 4)]
+ p.set_character(p.available_characters[0].name)
+ fantasma_guy = g.players[g.turn]
+ fantasma_guy.lives = 0
+ fantasma_guy.notify_self()
+ pl = g.players[g.turn]
+ pl.draw('')
+ pl.hand = [Fantasma(0,0)]
+ pl.play_card(0)
+ assert pl.pending_action == PendingAction.CHOOSE
+ assert pl.available_cards[0]['name'] == fantasma_guy.name
+ pl.choose(0)
+ assert pl.pending_action == PendingAction.PLAY
+ assert len(fantasma_guy.equipment) == 1 and isinstance(fantasma_guy.equipment[0], Fantasma)
+
+# test SerpenteASonagli
+def test_serpente_a_sonagli():
+ sio = DummySocket()
+ g = Game('test', sio)
+ ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
+ for p in ps:
+ g.add_player(p)
+ g.start_game()
+ for p in ps:
+ p.available_characters = [Character('test_char', 4)]
+ p.set_character(p.available_characters[0].name)
+ p = g.players[g.turn]
+ serp = g.players[(g.turn+1)%3]
+ p.draw('')
+ p.hand = [SerpenteASonagli(0,0)]
+ assert len(p.hand) == 1
+ p.play_card(0, serp.name)
+ assert len(p.hand) == 0
+ assert len(serp.equipment) == 1 and isinstance(serp.equipment[0], SerpenteASonagli)
+ p.end_turn()
+ assert serp.pending_action == PendingAction.PICK
+ g.deck.cards[0] = Bang(Suit.SPADES, 5)
+ serp.pick()
+ assert serp.lives == 3
+ serp.draw('')
+ serp.hand = [SerpenteASonagli(0,0)]
+ serp.play_card(0, g.players[(g.turn+1)%3].name)
+ assert len(serp.hand) == 0
+ serp.end_turn()
+ assert g.players[g.turn].pending_action == PendingAction.PICK
+ g.deck.cards[0] = Bang(Suit.HEARTS, 5)
+ g.players[g.turn].pick()
+ assert g.players[g.turn].lives == 4
+
+# test RitornoDiFiamma
+def test_ritorno_di_fiamma():
+ sio = DummySocket()
+ g = Game('test', sio)
+ g.expansions = ['the_valley_of_shadows']
+ ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
+ for p in ps:
+ g.add_player(p)
+ g.start_game()
+ for p in ps:
+ p.available_characters = [Character('test_char', 4)]
+ p.set_character(p.available_characters[0].name)
+ p = g.players[g.turn]
+ p1 = g.players[(g.turn+1)%3]
+ p.draw('')
+ p.hand = [Bang(1, 1)]
+ p1.hand = [RitornoDiFiamma(0,0)]
+ p.play_card(0, p1.name)
+ assert len(p.hand) == 0
+ assert len(p1.hand) == 1
+ p1.respond(0)
+ assert len(p1.hand) == 0
+ assert p.lives == 3
+ p.end_turn()
+ p1.draw('')
+ p1.hand = [Bang(1, 1)]
+ p.equipment = [cs.Barile(0,0)]
+ p.hand = [RitornoDiFiamma(0,0)]
+ p1.play_card(0, p.name)
+ assert p.pending_action == PendingAction.PICK
+ g.deck.cards[0] = Bang(Suit.SPADES, 5)
+ p.pick()
+ assert p.pending_action == PendingAction.RESPOND
+ p.respond(0)
+ assert p1.lives == 3
diff --git a/frontend/package.json b/frontend/package.json
index 16600f6..01c6ea9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,7 +17,8 @@
"vue": "^2.6.14",
"vue-clipboard2": "^0.3.3",
"vue-i18n": "^8.27.1",
- "vue-router": "^3.5.4",
+ "vue-json-viewer": "^2.2.22",
+ "vue-router": "^3.5.3",
"vue-socket.io": "^3.0.10"
},
"devDependencies": {
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 14e7005..fa95f74 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -135,6 +135,7 @@ export default {
this.$i18n.locale = userLang.split('-')[0]
}
this.detectColorScheme()
+ if (window.location.origin.indexOf('localhost') !== -1) return;
datadogRum.init({
applicationId: '076b1a5e-16a9-44eb-b320-27afd32c57a5',
clientToken: 'pub1cc4d0d6ea0a7235aa1eab86e7a192d4',
diff --git a/frontend/src/components/Card.vue b/frontend/src/components/Card.vue
index d1dd002..552c035 100644
--- a/frontend/src/components/Card.vue
+++ b/frontend/src/components/Card.vue
@@ -1,7 +1,8 @@
- . {{hintText}} {{desc}} {{$t(`cards.${c.name}.desc`)}}{{cardName}}
- {{text}}
{{remainingTime}}
+
{{$t('help.valleyofshadowscards')}}
+
{{$t("online_players")}}{{onlinePlayers}}