diff --git a/.gitignore b/.gitignore index b43fa6c..6e83984 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,4 @@ dmypy.json cython_debug/ frontend/package-lock.json +bang-workspace.code-workspace diff --git a/backend/__init__.py b/backend/__init__.py index 6dfda89..db07c6e 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -19,7 +19,7 @@ games: List[Game] = [] online_players = 0 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) < 7]) + 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]) @sio.event def connect(sid, environ): @@ -90,58 +90,58 @@ def join_room(sid, room): @sio.event def chat_message(sid, msg): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) sio.emit('chat_message', room=ses.game.name, data=f'[{ses.name}]: {msg}') @sio.event def start_game(sid): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.game.start_game() advertise_lobbies() @sio.event def set_character(sid, name): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.set_character(name) @sio.event def refresh(sid): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.notify_self() @sio.event def draw(sid, pile): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.draw(pile) @sio.event def pick(sid): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.pick() @sio.event def end_turn(sid): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.end_turn() @sio.event def play_card(sid, data): - ses = sio.get_session(sid) - ses.play_card(data['index'], data['against']) + ses: Player = sio.get_session(sid) + ses.play_card(data['index'], data['against'], data['with']) @sio.event def respond(sid, data): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.respond(data) @sio.event def choose(sid, card_index): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.choose(card_index) @sio.event def scrap(sid, card_index): - ses = sio.get_session(sid) + ses: Player = sio.get_session(sid) ses.scrap(card_index) if __name__ == '__main__': diff --git a/backend/bang/cards.py b/backend/bang/cards.py index c10f400..7cce19e 100644 --- a/backend/bang/cards.py +++ b/backend/bang/cards.py @@ -35,13 +35,15 @@ class Card(ABC): self.alt_text = f'{self.range} 🔍' self.desc = desc self.need_target = False + self.can_target_self = False + self.need_with = False def __str__(self): char = ['♦️', '♣️', '♥️', '♠️'][int(self.suit)] return f'{self.name} {char}{self.number}' return super().__str__() - def play_card(self, player, against):#self --> carta + def play_card(self, player, against=None, _with=None):#self --> carta if self.is_equipment: if self.is_weapon: has_weapon = False @@ -106,10 +108,10 @@ class Prigione(Card): self.desc = "Equipaggia questa carta a un altro giocatore, tranne lo Sceriffo. Il giocatore scelto all'inizio del suo turno, prima di pescare dovrà estrarre: se esce Cuori scarta questa carta e gioca normalmente il turno, altrimenti scarta questa carta e salta il turno" self.need_target = True - def play_card(self, player, against): + def play_card(self, player, against, _with=None): if against != None and not isinstance(player.game.get_player_named(against).role, r.Sheriff): player.sio.emit('chat_message', room=player.game.name, - data=f'{self.name} ha giocato {self.name} contro {against}.') + data=f'{player.name} ha giocato {self.name} contro {against}.') player.game.get_player_named(against).equipment.append(self) player.game.get_player_named(against).notify_self() return False @@ -158,10 +160,10 @@ class Bang(Card): def __init__(self, suit, number): super().__init__(suit, 'Bang!', number) self.icon = '💥' - self.desc = "Spara a un giocatore a distanta raggiungibile. Se non hai armi la distanza di default è 1" + self.desc = "Spara a un giocatore a distanza raggiungibile. Se non hai armi la distanza di default è 1" self.need_target = True - def play_card(self, player, against): + def play_card(self, player, against, _with=None): if player.has_played_bang and not any([isinstance(c, Volcanic) for c in player.equipment]) and against != None: return False elif against != None: @@ -180,8 +182,8 @@ class Birra(Card): self.icon = '🍺' self.desc = "Gioca questa carta per recuperare un punto vita. Non puoi andare oltre al limite massimo del tuo personaggio. Se stai per perdere l'ultimo punto vita puoi giocare questa carta anche nel turno dell'avversario. La birra non ha più effetto se ci sono solo due giocatori" - def play_card(self, player, against): - if len(player.game.players) != 2 and player.lives != player.max_lives: + def play_card(self, player, against, _with=None): + if len(player.game.players) != 2: super().play_card(player, against=against) player.lives = min(player.lives+1, player.max_lives) return True @@ -199,9 +201,10 @@ class CatBalou(Card): self.desc = "Fai scartare una carta a un qualsiasi giocatore, scegli a caso dalla mano, oppure fra quelle che ha in gioco" self.need_target = True - def play_card(self, player, against): + def play_card(self, player, against, _with=None): if against != None and (len(player.game.get_player_named(against).hand) + len(player.game.get_player_named(against).equipment)) > 0: - super().play_card(player, against=against) + if self.name == 'Cat Balou': + super().play_card(player, against=against) from bang.players import PendingAction player.pending_action = PendingAction.CHOOSE player.choose_action = 'discard' @@ -217,7 +220,7 @@ class Diligenza(Card): self.icon = '🚡' self.desc = "Pesca 2 carte dalla cima del mazzo" - def play_card(self, player, against): + def play_card(self, player, against, _with=None): super().play_card(player, against=against) player.sio.emit('chat_message', room=player.game.name, data=f'{player.name} ha giocato {self.name} e ha pescato 2 carte.') @@ -233,7 +236,7 @@ class Duello(Card): self.icon = '⚔️' self.desc = "Gioca questa carta contro un qualsiasi giocatore. A turno, cominciando dal tuo avversario, potete scartare una carta Bang!, il primo giocatore che non lo fa perde 1 vita" - def play_card(self, player, against): + def play_card(self, player, against, _with=None): if against != None: super().play_card(player, against=against) player.game.duel(player, against) @@ -247,7 +250,7 @@ class Emporio(Card): self.icon = '🏪' self.desc = "Scopri dal mazzo tante carte quanto il numero di giocatori, a turno, partendo da te, scegliete una carta e aggiungetela alla vostra mano" - def play_card(self, player, against): + def play_card(self, player, against, _with=None): super().play_card(player, against=against) player.game.emporio() return True @@ -259,7 +262,7 @@ class Gatling(Card): self.icon = '🛰' self.desc = "Spara a tutti gli altri giocatori" - def play_card(self, player, against): + def play_card(self, player, against, _with=None): super().play_card(player, against=against) player.game.attack_others(player) return True @@ -271,7 +274,7 @@ class Indiani(Card): self.icon = '🏹' self.desc = "Tutti gli altri giocatori devono scartare un Bang! o perdere una vita" - def play_card(self, player, against): + def play_card(self, player, against, _with=None): super().play_card(player, against=against) player.game.indian_others(player) return True @@ -283,7 +286,7 @@ class Mancato(Card): self.icon = '😅' self.desc = "Usa questa carta per annullare un bang" - def play_card(self, player, against): + def play_card(self, player, against, _with=None): import bang.characters as chars if (not player.has_played_bang and against != None and isinstance(player.character, chars.CalamityJanet)): player.sio.emit('chat_message', room=player.game.name, @@ -301,7 +304,7 @@ class Panico(Card): self.need_target = True self.desc = "Pesca una carta da un giocatore a distanza 1, scegli a caso dalla mano, oppure fra quelle che ha in gioco" - def play_card(self, player, against): + def play_card(self, player, against, _with=None): if against != None and (len(player.game.get_player_named(against).hand) + len(player.game.get_player_named(against).equipment)) > 0: super().play_card(player, against=against) from bang.players import PendingAction @@ -319,7 +322,7 @@ class Saloon(Card): self.desc = "Tutti i giocatori recuperano un punto vita compreso chi gioca la carta" self.icon = '🍻' - def play_card(self, player, against): + def play_card(self, player, against, _with=None): player.sio.emit('chat_message', room=player.game.name, data=f'{player.name} ha giocato {self.name} e ha curato 1 punto vita a tutti.') for p in player.game.players: @@ -334,7 +337,7 @@ class WellsFargo(Card): self.desc = "Pesca 3 carte dalla cima del mazzo" self.icon = '💸' - def play_card(self, player, against): + def play_card(self, player, against, _with=None): player.sio.emit('chat_message', room=player.game.name, data=f'{player.name} ha giocato {self.name} e ha pescato 3 carte.') for i in range(3): diff --git a/backend/bang/expansions/dodge_city/cards.py b/backend/bang/expansions/dodge_city/cards.py index 0e4c029..b5f57bc 100644 --- a/backend/bang/expansions/dodge_city/cards.py +++ b/backend/bang/expansions/dodge_city/cards.py @@ -16,12 +16,11 @@ class Pugno(Card): def __init__(self, suit, number): super().__init__(suit, 'Pugno!', number, range=1) self.icon = '👊' - self.desc = "Spara a un giocatore a distanta 1" + self.desc = "Spara a un giocatore a distanza 1" self.need_target = True - def play_card(self, player, against): + def play_card(self, player, against, _with=None): if against != None: - import bang.characters as chars super().play_card(player, against=against) player.game.attack(player, against) return True @@ -33,14 +32,105 @@ class Schivata(Mancato): self.name = 'Schivata' self.icon = '🙅♂️' self.desc += " e poi pesca una carta" + self.alt_text = '☝️🆓' - def play_card(self, player, against): + def play_card(self, player, against, _with=None): return False def use_card(self, player): player.hand.append(player.game.deck.draw()) player.notify_self() +class RagTime(Panico): + def __init__(self, suit, number): + Card.__init__(self, suit, 'Rag Time', number) + self.icon = '🎹' + self.desc = "Ruba 1 carta dalla mano di un giocatore a prescindere dalla distanza" + self.need_target = True + self.need_with = True + self.alt_text = '2🃏' + + def play_card(self, player, against, _with): + if against != None and _with != None: + player.game.deck.scrap(_with) + super().play_card(player, against=against) + return True + return False + +class Rissa(CatBalou): + def __init__(self, suit, number): + super().__init__(suit, number) + self.name = 'Rissa' + self.icon = '🥊' + self.desc = "Fai scartare una carta a tutti gli altri giocatori, scegli a caso dalla mano, oppure fra quelle che hanno in gioco" + self.need_with = True + self.need_target = False + self.alt_text = '2🃏' + + def play_card(self, player, against, _with): + if _with != None: + player.game.deck.scrap(_with) + player.event_type = 'rissa' + super().play_card(player, against=[p.name for p in player.game.players if p != player and (len(p.hand)+len(p.equipment)) > 0][0]) + player.sio.emit('chat_message', room=player.game.name, data=f'{player.name} ha giocato {self.name}') + return True + return False + +class SpringField(Card): + def __init__(self, suit, number): + super().__init__(suit, 'Springfield', number) + self.icon = '🌵' + self.desc = "Spara a un giocatore a prescindere dalla distanza" + self.need_target = True + self.need_with = True + self.alt_text = '2🃏' + + def play_card(self, player, against, _with=None): + if against != None and _with != None: + player.game.deck.scrap(_with) + super().play_card(player, against=against) + player.game.attack(player, against) + return True + return False + +class Tequila(Card): + def __init__(self, suit, number): + super().__init__(suit, 'Tequila', number) + self.icon = '🍹' + self.desc = "Fai recuperare 1 vita a un giocatore a tua scelta, anche te stesso" + self.need_target = True + self.can_target_self = True + self.need_with = True + self.alt_text = '2🃏' + + def play_card(self, player, against, _with=None): + if against != None and _with != None: + beneficiario = f'{against}' if against != player.name else 'se stesso' + player.sio.emit('chat_message', room=player.game.name, data=f'{player.name} ha giocato {self.name} per {beneficiario}') + player.game.deck.scrap(_with) + player.game.get_player_named(against).lives = min(player.game.get_player_named(against).lives+1, player.game.get_player_named(against).max_lives) + player.game.get_player_named(against).notify_self() + return True + return False + +class Whisky(Card): + def __init__(self, suit, number): + super().__init__(suit, 'Whisky', number) + self.icon = '🥃' + self.desc = "Gioca questa carta per recuperare fino a 2 punti vita" + self.need_with = True + self.alt_text = '2🃏' + + def play_card(self, player, against, _with=None): + if _with != None: + super().play_card(player, against=against) + player.game.deck.scrap(_with) + player.lives = min(player.lives+2, player.max_lives) + player.notify_self() + return True + return False + + def get_starting_deck() -> List[Card]: return [ #TODO: aggiungere anche le carte normalmente presenti https://bang.dvgiochi.com/cardslist.php?id=3 @@ -63,6 +153,11 @@ def get_starting_deck() -> List[Card]: Mancato(Suit.DIAMONDS, 8), Panico(Suit.HEARTS, 'J'), Pugno(Suit.SPADES, 10), + RagTime(Suit.HEARTS, 9), + Rissa(Suit.SPADES, 'J'), Schivata(Suit.DIAMONDS, 7), Schivata(Suit.HEARTS, 'K'), + SpringField(Suit.SPADES, 'K'), + Tequila(Suit.CLUBS, 9), + Whisky(Suit.HEARTS, 'Q'), ] diff --git a/backend/bang/game.py b/backend/bang/game.py index 27bb909..7c69cbe 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -41,8 +41,11 @@ class Game: self.notify_room() def add_player(self, player: players.Player): - if player in self.players or len(self.players) >= 7: + if player in self.players or len(self.players) >= 10: return + if len(self.players) > 7: + if 'dodge_city' not in self.expansions: + self.expansions.append('dodge_city') player.join_game(self) self.players.append(player) print(f'Added player {player.name} to game') @@ -61,7 +64,13 @@ class Game: self.readyCount += 1 self.notify_room() if self.readyCount == len(self.players): - self.distribute_roles() + for i in range(len(self.players)): + self.sio.emit('chat_message', room=self.name, data=f'{self.players[i].name} ha come personaggio {self.players[i].character.name}, la sua abilità speciale è: {self.players[i].character.desc}') + self.players[i].prepare() + for k in range(self.players[i].max_lives): + self.players[i].hand.append(self.deck.draw()) + self.players[i].notify_self() + self.players[self.turn].play_turn() def choose_characters(self): char_cards = random.sample(characters.all_characters(), len(self.players)*2) @@ -78,6 +87,7 @@ class Game: self.started = True self.deck = Deck(self) self.initial_players = len(self.players) + self.distribute_roles() self.choose_characters() def distribute_roles(self): @@ -89,21 +99,16 @@ class Game: roles.Outlaw('Elimina il Vice 🎖, se non lo elimini tu elimina anche il Rinnegato') ] elif len(self.players) >= 4: - available_roles = [roles.Sheriff(), roles.Renegade(), roles.Outlaw(), roles.Outlaw(), roles.Vice(), roles.Outlaw(), roles.Vice()] + available_roles = [roles.Sheriff(), roles.Renegade(), roles.Outlaw(), roles.Outlaw(), roles.Vice(), roles.Outlaw(), roles.Vice(), roles.Renegade(), roles.Outlaw(), roles.Vice(), roles.Outlaw()] available_roles = available_roles[:len(self.players)] random.shuffle(available_roles) for i in range(len(self.players)): - self.sio.emit('chat_message', room=self.name, data=f'{self.players[i].name} ha come personaggio {self.players[i].character.name}, la sua abilità speciale è: {self.players[i].character.desc}') self.players[i].set_role(available_roles[i]) if isinstance(available_roles[i], roles.Sheriff) or (len(available_roles) == 3 and isinstance(available_roles[i], roles.Vice)): if isinstance(available_roles[i], roles.Sheriff): self.sio.emit('chat_message', room=self.name, data=f'{self.players[i].name} È lo sceriffo') self.turn = i - self.players[i].prepare() - for k in range(self.players[i].max_lives): - self.players[i].hand.append(self.deck.draw()) self.players[i].notify_self() - self.play_turn() def attack_others(self, attacker: players.Player): attacker.pending_action = players.PendingAction.WAIT diff --git a/backend/bang/players.py b/backend/bang/players.py index 381f702..fd9162d 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -203,6 +203,7 @@ class Player: else: 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]): self.notify_self() return @@ -218,7 +219,7 @@ class Player: self.game.deck.scrap(self.equipment.pop(i)) self.end_turn(forced=True) return - else: + elif pickable_cards == 0: self.game.deck.scrap(self.equipment.pop(i)) break break @@ -248,18 +249,23 @@ class Player: s += f"equipment {[str(c) for c in self.equipment]}" return s - def play_card(self, hand_index: int, against=None): + def play_card(self, hand_index: int, against=None, _with=None): if not (0 <= hand_index < len(self.hand)): print('illegal') return card: cs.Card = self.hand.pop(hand_index) - print(self.name, 'is playing ', card, ' against:', against) - did_play_card = card.play_card(self, against) + withCard: cs.Card = None + if _with != None: + 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 = card.play_card(self, against, withCard) if not card.is_equipment: if did_play_card: self.game.deck.scrap(card) else: self.hand.insert(hand_index, card) + if withCard: + self.hand.insert(_with, withCard) self.notify_self() def choose(self, card_index): @@ -277,9 +283,16 @@ class Player: self.hand.append(card) else: self.game.deck.scrap(card) - self.target_p = '' - self.choose_action = '' - self.pending_action = PendingAction.PLAY + if self.event_type != 'rissa' or (self.event_type == 'rissa' and self.target_p == [p.name for p in self.game.players if p != self and (len(p.hand)+len(p.equipment)) > 0][-1]): + self.event_type = '' + self.target_p = '' + self.choose_action = '' + self.pending_action = PendingAction.PLAY + else: + while len(self.game.players[self.game.players_map[self.target_p]+1].hand) + len(self.game.players[self.game.players_map[self.target_p]+1].equipment) == 0: + self.target_p = self.game.players[self.game.players_map[self.target_p]+1].name + if self.target_p == self.name: + self.target_p = self.game.players[self.game.players_map[self.target_p]+1].name self.notify_self() # specifico per personaggio elif self.is_drawing and isinstance(self.character, chars.KitCarlson): diff --git a/backend/bang/roles.py b/backend/bang/roles.py index 834f173..e311a43 100644 --- a/backend/bang/roles.py +++ b/backend/bang/roles.py @@ -75,7 +75,7 @@ class Renegade(Role): return True elif initial_players == 3 and attacker_role != None: return isinstance(dead_role, Outlaw) and isinstance(attacker_role, Renegade) - elif initial_players != 3 and len(alive_players) == 1 and isinstance(alive_players[0].role, Renegade): + elif initial_players != 3 and len(alive_players) == 1 and alive_players[0].role == self: print("The Renegade won!") return True return False diff --git a/frontend/package.json b/frontend/package.json index b552a24..9ad6b0c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "pretty-checkbox-vue": "^1.1.9", "socket.io-client": "^3.0.3", "vue": "^2.6.11", + "vue-i18n": "^8.22.2", "vue-socket.io": "^3.0.10" }, "devDependencies": { diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 39f33c1..9052697 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -2,29 +2,29 @@
Scegli un username:
+{{$t("choose_username")}}
-Giocatori online: {{onlinePlayers}}
+{{$t("online_players")}}{{onlinePlayers}}
Giocatori online: {{onlinePlayers}}
+{{$t("online_players")}}{{onlinePlayers}}
Nessuna lobby disponibile
+{{$t("no_lobby_available")}}
Connessione al server assente.
+{{$t("connection_error")}}
.
diff --git a/frontend/src/components/Chooser.vue b/frontend/src/components/Chooser.vue index ad0976c..c8de680 100644 --- a/frontend/src/components/Chooser.vue +++ b/frontend/src/components/Chooser.vue @@ -5,7 +5,7 @@{{hintText}}
- +{{desc}}
{{desc}}
+{{desc}}
-{{desc}}
+{{hint}}
-{{hint}}
+