Compare commits

..

No commits in common. "main" and "0.0.1b" have entirely different histories.
main ... 0.0.1b

142 changed files with 9381 additions and 25677 deletions

View File

@ -1,45 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "BangCodespace",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/python:1": {
"version": "3.11.1"
}
},
// Use 'settings' to set *default* container specific settings.json values on container create.
// You can edit these settings after create using File > Preferences > Settings > Remote.
"settings": {
"python.testing.pytestArgs": [
"--rootdir=backend",
"backend/tests/"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.linting.enabled": true
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [5001, 8080],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "cd frontend;npm i;cd ../backend;pip install -r requirements.txt",
// Configure tool-specific properties.
// "customizations": {},
// Add the IDs of extensions you want installed when the container is created in the array below.
"extensions": [
"Vue.volar",
"ms-python.python",
"Gruntfuggly.todo-tree"
]
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@ -1,25 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
target-branch: "dev"
- package-ecosystem: "npm"
directory: "/frontend"
schedule:
interval: "daily"
target-branch: "dev"
- package-ecosystem: "pip"
directory: "/backend"
schedule:
interval: "daily"
target-branch: "dev"

View File

@ -1,72 +0,0 @@
name: Docker Images CI
on:
push:
branches: dev
jobs:
buildx:
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 (build amd64)
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform linux/amd64 \
--output "type=image,push=false" \
--tag albertoxamin/bang:dev \
--file ./Dockerfile ./
-
name: Docker Buildx (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:dev \
--file ./Dockerfile ./
-
name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Docker Buildx (push)
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--platform linux/amd64,linux/arm/v7 \
--output "type=image,push=true" \
--tag albertoxamin/bang:dev \
--file ./Dockerfile ./
-
name: Inspect image
run: |
docker buildx imagetools inspect albertoxamin/bang:dev
- name: Notify discord
uses: th0th/notify-discord@v0.4.1
if: ${{ always() }}
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_JOB_NAME: "Docker image :dev"
GITHUB_JOB_STATUS: ${{ job.status }}

View File

@ -1,71 +1,16 @@
name: Docker Images CI
on:
push:
branches: main
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-platform:
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [linux/amd64, linux/arm/v7, linux/arm64/v8]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
id: cache
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Prepare Platform Tag
id: platform_tag
run: echo "platform_tag=$(echo ${{ matrix.platform }} | sed 's|/|-|g')" >> $GITHUB_ENV
- name: Build and push platform-specific image
uses: docker/build-push-action@v5
with:
context: .
platforms: ${{ matrix.platform }}
push: true
tags: albertoxamin/bang:${{ env.platform_tag }}
cache-from: type=registry,ref=albertoxamin/bang:${{ env.platform_tag }}
cache-to: type=inline
create-manifest:
runs-on: ubuntu-latest
needs: build-platform
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Create and push multi-arch manifest
run: |
docker buildx imagetools create \
--tag albertoxamin/bang:latest \
albertoxamin/bang:linux-amd64 \
albertoxamin/bang:linux-arm-v7 \
albertoxamin/bang:linux-arm64-v8
- uses: actions/checkout@v2
- name: Build the Docker image
run: cd backend;docker build . --file Dockerfile --tag bang-backend:$(date +%s)
- uses: actions/checkout@v2
- name: Build the Docker image
run: cd frontend;docker build . --file Dockerfile --tag bang-frontend:$(date +%s)

20
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Publish Docker image
on:
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to GitHub Packages
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Push to GitHub Packages
uses: docker/build-push-action@v1
with:
context: backend
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: docker.pkg.github.com
repository: albertoxamin/bang/bang-backend
tag_with_ref: true

View File

@ -1,25 +0,0 @@
name: Pylint
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
cd backend
pip install --user -r requirements.txt
pip install pylint
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py') -E

View File

@ -1,28 +0,0 @@
name: Python package
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.11]
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
run: |
python -m pytest -p no:warnings

View File

@ -1,89 +0,0 @@
name: Test Pull requests
on:
pull_request:
jobs:
build-amd64:
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 amd64)
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform linux/amd64 \
--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: |
# docker buildx build \
# --cache-from "type=local,src=/tmp/.buildx-cache" \
# --cache-to "type=local,dest=/tmp/.buildx-cache" \
# --platform linux/arm64 \
# --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 ./

7
.gitignore vendored
View File

@ -136,10 +136,3 @@ dmypy.json
# Cython debug symbols
cython_debug/
frontend/package-lock.json
bang-workspace.code-workspace
.vscode/
backend/save/*
save/

View File

@ -1,30 +0,0 @@
# build Vue frontend
FROM node:18 as builder
COPY ./frontend .
RUN npm install
RUN npm run build
# now we should have a dist folder containing the static website
FROM python:3.11.1-bullseye as pybuilder
WORKDIR /code
COPY ./backend /code/
RUN pip install --user -r requirements.txt
# We get the dependencies with the full python image so we can compile the one with missing binaries
ENV UseRobots=false
FROM python:3.11.1-slim-bullseye as app
# copy the dependencies from the pybuilder
COPY --from=pybuilder /root/.local /root/.local
# copy the backend python files from the pybuilder
COPY --from=pybuilder /code /dist
# copy the frontend static files from the builder
COPY --from=builder ./dist /dist/
WORKDIR /dist
# create dir for save
RUN mkdir save
EXPOSE 5001
ENV PATH=/root/.local/bin:${PATH}
VOLUME /dist/save
ENTRYPOINT ["python", "/dist/server.py"]

View File

@ -1,14 +1 @@
# bang
This is the repo for the PewPew! game, which is a replica of BANG!.
BANG! is a trademark owned by DVGiochi.
[Frontend Readme](./frontend/README.md)
[Backend Readme](./backend/Readme.md)
<img width="1326" alt="bang-lobby" src="https://github.com/albertoxamin/bang/assets/6067659/853a4182-aace-4f5d-92c3-ad63f309c37a">
<img width="1316" alt="bang-in-game" src="https://github.com/albertoxamin/bang/assets/6067659/fa4ad9f3-2012-4a38-b3eb-97fd0b4b8f2b">

View File

@ -2,9 +2,6 @@ FROM python:3.7-slim-stretch
WORKDIR /code
COPY * /code/
RUN RUN apt-get update && apt-get install -y \
libevent-dev \
python-all-dev
RUN pip install -r requirements.txt
EXPOSE 5001

View File

@ -1,25 +0,0 @@
# Bang Backend
How to get started
## Development
Create a virtual env
```bash
python3 -m venv PATHTOENV
```
activate the new environment
```bash
souce PATHTOENV/bin/activate
```
now with the current directory in the backend folder install the dependencies
```bash
pip install -r requirements.txt
```
then you can start the python file `__init__.py`, I recommend you use *nodemon* for that, as it will automatically reload the server on new changes. If you don't already have it, you can install nodemon with
```
npm i -g nodemon
```
then you will be able to start the server with
```
nodemon __init__.py
```

110
backend/__init__.py Normal file
View File

@ -0,0 +1,110 @@
import json
import eventlet
import socketio
from game import Game
from players import Player
sio = socketio.Server(cors_allowed_origins="*")
app = socketio.WSGIApp(sio, static_files={
'/': {'content_type': 'text/html', 'filename': 'index.html'}
})
games = []
def advertise_lobbies():
sio.emit('lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players)} for g in games if not g.started])
@sio.event
def connect(sid, environ):
print('connect ', sid)
sio.enter_room(sid, 'lobby')
@sio.event
def set_username(sid, username):
sio.save_session(sid, Player(username, sid, sio))
print(f'{sid} is now {username}')
advertise_lobbies()
@sio.event
def my_message(sid, data):
print('message ', data)
@sio.event
def disconnect(sid):
if sio.get_session(sid).disconnect():
games.pop(games.index(sio.get_session(sid).game))
print('disconnect ', sid)
advertise_lobbies()
@sio.event
def create_room(sid, room_name):
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
g = Game(room_name, sio)
g.add_player(sio.get_session(sid))
games.append(g)
print(f'{sid} created a room named {room_name}')
advertise_lobbies()
@sio.event
def join_room(sid, room_name):
print(f'{sid} joined a room named {room_name}')
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
i = [g.name for g in games].index(room_name)
games[i].add_player(sio.get_session(sid))
advertise_lobbies()
@sio.event
def chat_message(sid, msg):
ses = 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.game.start_game()
@sio.event
def set_character(sid, name):
ses = sio.get_session(sid)
ses.set_character(name)
@sio.event
def refresh(sid):
ses = sio.get_session(sid)
ses.notify_self()
@sio.event
def draw(sid):
ses = sio.get_session(sid)
ses.draw()
@sio.event
def pick(sid):
ses = sio.get_session(sid)
ses.pick()
@sio.event
def end_turn(sid):
ses = 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'])
@sio.event
def respond(sid, data):
ses = sio.get_session(sid)
ses.respond(data)
@sio.event
def choose(sid, card_index):
ses = sio.get_session(sid)
ses.choose(card_index)
if __name__ == '__main__':
eventlet.wsgi.server(eventlet.listen(('', 5001)), app)

View File

@ -1,741 +0,0 @@
from __future__ import annotations
from typing import List, Set, Dict, Tuple, Optional, TYPE_CHECKING
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
from abc import ABC, abstractmethod
from enum import IntEnum
import bang.roles as r
from globals import G
if TYPE_CHECKING:
from bang.players import Player
from bang.game import Game
class Suit(IntEnum):
"""Enum for card suits"""
DIAMONDS = 0 # ♦
CLUBS = 1 # ♣
HEARTS = 2 # ♥
SPADES = 3 # ♠
GOLD = 4 # 🤑
TRAIN = 5 # 🚂
class Card(ABC):
"""Base class for all cards"""
sym = {"A": 1, "J": 11, "Q": 12, "K": 13}
def __init__(
self,
suit: Suit,
name: str,
number,
is_equipment: bool = False,
is_weapon: bool = False,
vis_mod: int = 0,
sight_mod: int = 0,
range: int = 99,
desc: str = "",
):
super().__init__()
self.name = name
self.suit = suit
if isinstance(number, int):
self.number = number
else:
self.number = self.sym[number]
self.is_equipment = is_equipment
self.is_weapon = is_weapon
self.vis_mod = vis_mod
self.sight_mod = sight_mod
self.range = range
if self.range != 0 and self.range != 99:
self.alt_text = f"{self.range} 🔍"
self.desc = desc # deprecated, has been replaced by the card's description in the localization files (see i18n folder)
self.need_target = False # Cards that need a target like Bang
self.can_target_self = False # for example Panico and CatBalou
self.can_be_used_now = True # to check wether the green card can be used now
self.usable_next_turn = False # it will be True for Green Cards
self.need_with = (
False # it will be true for cards that require a card to be discarded with
)
self.need_with_only = "" # names of the cards allowed to be discarded with
self.must_be_used = False # used by LeggeDelWest
def __str__(self) -> str:
if str(self.suit).isnumeric():
char = ["♦️", "♣️", "♥️", "♠️", "🤑", "🚋"][int(self.suit)]
else:
char = self.suit
return f"{self.name} {char}{self.number}"
def num_suit(self) -> str:
"""Returns the card's number and suit as a string"""
return f"{['♦️', '♣️', '♥️', '♠️', '🤑'][int(self.suit)]}{self.number}"
def reset_card(self) -> None:
"""Resets the card's attributes"""
if self.usable_next_turn:
self.can_be_used_now = False
else:
self.can_be_used_now = True
if self.must_be_used:
self.must_be_used = False
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
"""Plays the card and returns True if the card was played successfully, False otherwise"""
if (
(player.game.check_event(ce.IlGiudice))
and self.usable_next_turn
and not self.can_be_used_now
):
return False
if self.is_equipment:
if (player.game.check_event(ce.IlGiudice)) or not self.can_be_used_now:
return False
if self.is_weapon:
has_weapon = False
for i, card in enumerate(player.equipment):
if card.is_weapon:
player.game.deck.scrap(card, player=player)
player.equipment[i] = self
has_weapon = True
break
if not has_weapon:
player.equipment.append(self)
elif self.name in [
c.name for c in player.equipment if not isinstance(c, Dinamite)
]:
return False
else:
player.equipment.append(self)
self.must_be_used = False
self.can_be_used_now = False
if against:
G.sio.emit(
"card_against",
room=player.game.name,
data={"player": player.name, "target": against, "card": self.__dict__},
)
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_against{'_with' if _with else ''}|{player.name}|{self.name}|{against}|{_with.name if _with else ''}",
)
else:
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card{'_with' if _with else ''}|{player.name}|{self.name}|{_with.name if _with else ''}",
)
return True
def use_card(self, player):
pass
def is_duplicate_card(self, player: Player):
"""Checks if the card is already in the player's equipment"""
return any(c.name == self.name for c in player.equipment) or any(
c.name == self.name for c in player.gold_rush_equipment
)
def check_suit(self, game: Game, accepted: List[Suit]):
"""Checks if the card's suit is in the list of accepted suits
(also checks for the events Benedizione and Maledizione)
returns True if it is, False otherwise"""
if game.check_event(ceh.Benedizione):
return Suit.HEARTS in accepted
elif game.check_event(ceh.Maledizione):
return Suit.SPADES in accepted
return self.suit in accepted
class Barile(Card):
"""Quando sei bersagliato da un Bang puoi estrarre la prima carta dalla cima del mazzo, se la carta estratta è del seme Cuori allora vale come un Mancato
When someone plays a Bang against you. You can flip the first card from the deck, if the suit is Hearts then it counts as a Missed card
"""
def __init__(self, suit, number):
super().__init__(suit, "Barile", number, is_equipment=True)
self.icon = "🛢"
self.alt_text = "♥️=😅"
class Dinamite(Card):
"""Giocando la Dinamite, posizionala davanti a te, resterà innocua per un intero giro. All'inizio del prossimo turno prima di pescare e prima di una eventuale estrazione (es. Prigione), estrai una carta dalla cima del mazzo. Se esce una carta tra il 2 il 9 di picche (compresi) allora la dinamite esplode: perdi 3 vite e scarta la carta, altrimenti passa la dinamite al giocatore successivo, il quale estrarà a sua volta dopo che tu avrai passato il tuo turno
When playing Dynamite, place it in front of you, it will remain harmless for a whole round. At the beginning of the next turn before drawing and before any card flip (eg Prison), flip a card from the top of the deck. If a card is between 2 and 9 of spades (inclusive) then the dynamite explodes: you lose 3 lives and discard the card, otherwise pass the dynamite to the next player, who will draw in turn after you have ended your turn
"""
def __init__(self, suit, number):
super().__init__(suit, "Dinamite", number, is_equipment=True)
self.icon = "🧨"
self.alt_text = "2-9♠ = 🤯"
class Mirino(Card):
"""Tu vedi gli altri giocatori a distanza -1
You see the other players at distance -1"""
def __init__(self, suit, number):
super().__init__(suit, "Mirino", number, is_equipment=True, sight_mod=1)
self.icon = "🔎"
self.alt_text = "-1"
class Mustang(Card):
"""Gli altri giocatori ti vedono a distanza +1
The other players see you at distance +1"""
def __init__(self, suit, number):
super().__init__(suit, "Mustang", number, is_equipment=True, vis_mod=1)
self.icon = "🐎"
self.alt_text = "+1"
class Prigione(Card):
"""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
Equip this card to another player, except the Sheriff. The player chosen at the beginning of his turn, must flip a card before drawing: if it's Hearts, discard this card and play the turn normally, otherwise discard this card and skip the turn
"""
def __init__(self, suit, number):
super().__init__(suit, "Prigione", number, is_equipment=True)
self.icon = ""
self.need_target = True
self.alt_text = "♥️= 🆓"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
if player.game.check_event(ce.IlGiudice):
return False
if (
against is not None
and not isinstance(player.game.get_player_named(against).role, r.Sheriff)
and not self.is_duplicate_card(player.game.get_player_named(against))
):
self.can_be_used_now = False
G.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 Remington(Card):
"""Puoi sparare a un giocatore che sia distante 3 o meno
You can shoot another player at distance 3 or less"""
def __init__(self, suit, number):
super().__init__(
suit, "Remington", number, is_equipment=True, is_weapon=True, range=3
)
self.icon = "🔫"
class RevCarabine(Card):
"""Puoi sparare a un giocatore che sia distante 4 o meno
You can shoot another player at distance 4 or less"""
def __init__(self, suit, number):
super().__init__(
suit, "Rev Carabine", number, is_equipment=True, is_weapon=True, range=4
)
self.icon = "🔫"
class Schofield(Card):
"""Puoi sparare a un giocatore che sia distante 2 o meno
You can shoot another player at distance 2 or less"""
def __init__(self, suit, number):
super().__init__(
suit, "Schofield", number, is_equipment=True, is_weapon=True, range=2
)
self.icon = "🔫"
class Volcanic(Card):
"""Puoi sparare a un giocatore che sia distante 1 o meno, tuttavia puoi giocare quanti bang vuoi
You can shoot another player at distance 1 or less, however you no longer have the limit of 1 Bang
"""
def __init__(self, suit, number):
super().__init__(
suit, "Volcanic", number, is_equipment=True, is_weapon=True, range=1
)
self.icon = "🔫"
class Winchester(Card):
"""Puoi sparare a un giocatore che sia distante 5 o meno
You can shoot another player at distance 5 or less"""
def __init__(self, suit, number):
super().__init__(
suit, "Winchester", number, is_equipment=True, is_weapon=True, range=5
)
self.icon = "🔫"
class Bang(Card):
"""Spara a un giocatore a distanza raggiungibile. Se non hai armi la distanza di default è 1
Shoot a player in sight. If you do not have weapons, your is sight is 1"""
def __init__(self, suit, number):
super().__init__(suit, "Bang!", number)
self.icon = "💥"
self.need_target = True
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
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 is not None
): # 42 gold rush:
return False
elif against is not None:
import bang.characters as chars
super().play_card(player, against=against)
if not (self.number == 42 and self.suit == Suit.GOLD): # 42 gold rush
player.bang_used += 1
player.has_played_bang = (
True
if not player.game.check_event(ceh.Sparatoria)
else player.bang_used > 1
)
if player.character.check(player.game, chars.WillyTheKid):
player.has_played_bang = False
player.game.attack(
player,
against,
double=player.character.check(player.game, chars.SlabTheKiller),
card_name=self.name,
)
return True
return False
class Birra(Card):
"""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
Play this card to regain a life point. You cannot heal more than your character's maximum limit. If you are about to lose your last life point, you can also play this card on your opponent's turn. Beer no longer takes effect if there are only two players
"""
def __init__(self, suit, number):
super().__init__(suit, "Birra", number)
self.icon = "🍺"
def play_card(self, player, against=None, _with=None, skipChecks=False):
if player.game.check_event(ceh.IlReverendo):
return False
if not skipChecks:
import bang.expansions.gold_rush.characters as grch
madamYto = [
p
for p in player.game.get_alive_players()
if p.character.check(player.game, grch.MadamYto) and self.number != 42
]
for p in madamYto:
player.game.deck.draw(True, player=p)
p.notify_self()
if "gold_rush" in player.game.expansions and self.number != 42:
player.set_choose_action(
"choose_birra_function",
[
{
"name": "Pepita",
"icon": "💵️",
"alt_text": "1",
"noDesc": True,
},
self,
],
)
player.notify_self()
return True
if (
len(player.game.get_alive_players()) != 2 or self.number == 42
) and player.lives < player.max_lives:
super().play_card(player, against=against)
player.lives = min(player.lives + 1, player.max_lives)
import bang.expansions.dodge_city.characters as chd
if player.character.check(player.game, chd.TequilaJoe):
player.lives = min(player.lives + 1, player.max_lives)
return True
elif (
len(player.game.get_alive_players()) == 2
or player.lives == player.max_lives
):
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_spilled_beer|{player.name}|{self.name}",
)
return True
return False
class CatBalou(Card):
"""Fai scartare una carta a un qualsiasi giocatore, scegli a caso dalla mano, oppure fra quelle che ha in gioco
Choose and discard a card from any other player."""
def __init__(self, suit, number):
super().__init__(suit, "Cat Balou", number)
self.icon = "💃"
self.need_target = True
self.can_target_self = True
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
if (
against is not None
and (
len(player.game.get_player_named(against).hand)
+ len(player.game.get_player_named(against).equipment)
)
> 0
and (player.name != against or len(player.equipment) > 0)
):
super().play_card(player, against=against)
player.game.steal_discard(player, against, self)
return True
return False
class Diligenza(Card):
"""Pesca 2 carte dalla cima del mazzo
Draw 2 cards from the deck."""
def __init__(self, suit, number):
super().__init__(suit, "Diligenza", number)
self.icon = "🚡"
self.alt_text = "🎴🎴"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_diligenza|{player.name}|{self.name}",
)
for i in range(2):
player.game.deck.draw(True, player)
player.game.deck.flip_wildwestshow()
return True
class Duello(Card):
def __init__(self, suit, number):
super().__init__(suit, "Duello", number)
self.need_target = True
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"
# self.desc_eng = "Play this card against any player. In turn, starting with your opponent, you can discard a Bang! Card, the first player who does not do so loses 1 life."
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
if against is not None:
super().play_card(player, against=against)
player.game.duel(player, against)
return True
return False
class Emporio(Card):
"""Scopri dal mazzo tante carte quanto il numero di giocatori vivi, a turno, partendo da te, scegliete una carta e aggiungetela alla vostra mano
Put on the table N cards from the deck, where N is the number of alive players, in turn, starting with you, choose a card and add it to your hand
"""
def __init__(self, suit, number):
super().__init__(suit, "Emporio", number)
self.icon = "🏪"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
super().play_card(player, against=against)
player.game.emporio()
return True
class Gatling(Card):
"""Spara a tutti gli altri giocatori
Shoot all the other players"""
def __init__(self, suit, number):
super().__init__(suit, "Gatling", number)
self.icon = "🛰"
self.alt_text = "👥💥"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
super().play_card(player, against=against)
player.game.attack_others(player, card_name=self.name)
return True
class Indiani(Card):
"""Tutti gli altri giocatori devono scartare un Bang! o perdere una vita
All the other players must discard a Bang! or lose 1 Health Point"""
def __init__(self, suit, number):
super().__init__(suit, "Indiani!", number)
self.icon = "🏹"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
super().play_card(player, against=against)
player.game.indian_others(player)
return True
class Mancato(Card):
"""Usa questa carta per annullare un bang
Use this card to cancel the effect of a bang"""
def __init__(self, suit, number):
super().__init__(suit, "Mancato!", number)
self.icon = "😅"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
import bang.characters as chars
if against is not 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)
):
return False
if player.game.check_event(ceh.Sermone):
return False
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_special_calamity|{player.name}|{self.name}|{against}",
)
player.bang_used += 1
player.has_played_bang = (
True
if not player.game.check_event(ceh.Sparatoria)
else player.bang_used > 1
)
player.game.attack(player, against, card_name=self.name)
return True
return False
class Panico(Card):
"""Pesca una carta da un giocatore a distanza 1, scegli a caso dalla mano, oppure fra quelle che ha in gioco
Steal a card from a player at distance 1"""
def __init__(self, suit, number):
super().__init__(suit, "Panico!", number, range=1)
self.icon = "😱"
self.need_target = True
self.can_target_self = True
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
if (
against is not None
and (
len(player.game.get_player_named(against).hand)
+ len(player.game.get_player_named(against).equipment)
)
> 0
and (player.name != against or len(player.equipment) > 0)
):
super().play_card(player, against=against)
player.game.steal_discard(player, against, self)
return True
return False
class Saloon(Card):
"""Tutti i giocatori recuperano un punto vita compreso chi gioca la carta
Everyone heals 1 Health point"""
def __init__(self, suit, number):
super().__init__(suit, "Saloon", number)
self.icon = "🍻"
self.alt_text = "👥🍺"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_saloon|{player.name}|{self.name}",
)
for p in player.game.get_alive_players():
p.lives = min(p.lives + 1, p.max_lives)
p.notify_self()
return True
class WellsFargo(Card):
"""Pesca 3 carte dalla cima del mazzo
Draw 3 cards from the deck"""
def __init__(self, suit, number):
super().__init__(suit, "WellsFargo", number)
self.icon = "💸"
self.alt_text = "🎴🎴🎴"
def play_card(
self, player: Player, against: str = None, _with: Card = None
) -> bool:
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_wellsfargo|{player.name}|{self.name}",
)
for _ in range(3):
player.game.deck.draw(True, player)
player.game.deck.flip_wildwestshow()
return True
def get_starting_deck(expansions: List[str]) -> List[Card]:
from bang.expansions import DodgeCity, TheValleyOfShadows
base_cards = [
Barile(Suit.SPADES, "Q"),
Barile(Suit.SPADES, "K"),
Dinamite(Suit.HEARTS, 2),
Mirino(Suit.SPADES, "A"),
Mustang(Suit.HEARTS, 8),
Mustang(Suit.HEARTS, 9),
Prigione(Suit.SPADES, "J"),
Prigione(Suit.HEARTS, 4),
Prigione(Suit.SPADES, 10),
Remington(Suit.CLUBS, "K"),
RevCarabine(Suit.CLUBS, "A"),
Schofield(Suit.CLUBS, "J"),
Schofield(Suit.CLUBS, "Q"),
Schofield(Suit.SPADES, "K"),
Volcanic(Suit.SPADES, 10),
Volcanic(Suit.CLUBS, 10),
Winchester(Suit.SPADES, 8),
Bang(Suit.SPADES, "A"),
Bang(Suit.DIAMONDS, 2),
Bang(Suit.DIAMONDS, 3),
Bang(Suit.DIAMONDS, 4),
Bang(Suit.DIAMONDS, 5),
Bang(Suit.DIAMONDS, 6),
Bang(Suit.DIAMONDS, 7),
Bang(Suit.DIAMONDS, 8),
Bang(Suit.DIAMONDS, 9),
Bang(Suit.DIAMONDS, 10),
Bang(Suit.DIAMONDS, "J"),
Bang(Suit.DIAMONDS, "Q"),
Bang(Suit.DIAMONDS, "K"),
Bang(Suit.DIAMONDS, "A"),
Bang(Suit.CLUBS, 2),
Bang(Suit.CLUBS, 3),
Bang(Suit.CLUBS, 4),
Bang(Suit.CLUBS, 5),
Bang(Suit.CLUBS, 6),
Bang(Suit.CLUBS, 7),
Bang(Suit.CLUBS, 8),
Bang(Suit.CLUBS, 9),
Bang(Suit.HEARTS, "Q"),
Bang(Suit.HEARTS, "K"),
Bang(Suit.HEARTS, "A"),
Birra(Suit.HEARTS, 6),
Birra(Suit.HEARTS, 7),
Birra(Suit.HEARTS, 8),
Birra(Suit.HEARTS, 9),
Birra(Suit.HEARTS, 10),
Birra(Suit.HEARTS, "J"),
CatBalou(Suit.HEARTS, "K"),
CatBalou(Suit.DIAMONDS, 9),
CatBalou(Suit.DIAMONDS, 10),
CatBalou(Suit.DIAMONDS, "J"),
Diligenza(Suit.SPADES, 9),
Diligenza(Suit.SPADES, 9),
Duello(Suit.DIAMONDS, "Q"),
Duello(Suit.SPADES, "J"),
Duello(Suit.CLUBS, 8),
Emporio(Suit.CLUBS, 9),
Emporio(Suit.SPADES, "Q"),
Gatling(Suit.HEARTS, 10),
Indiani(Suit.DIAMONDS, "K"),
Indiani(Suit.DIAMONDS, "A"),
Mancato(Suit.CLUBS, 10),
Mancato(Suit.CLUBS, "J"),
Mancato(Suit.CLUBS, "Q"),
Mancato(Suit.CLUBS, "K"),
Mancato(Suit.CLUBS, "A"),
Mancato(Suit.SPADES, 2),
Mancato(Suit.SPADES, 3),
Mancato(Suit.SPADES, 4),
Mancato(Suit.SPADES, 5),
Mancato(Suit.SPADES, 6),
Mancato(Suit.SPADES, 7),
Mancato(Suit.SPADES, 8),
Panico(Suit.HEARTS, "J"),
Panico(Suit.HEARTS, "Q"),
Panico(Suit.HEARTS, "A"),
Panico(Suit.DIAMONDS, 8),
Saloon(Suit.HEARTS, 5),
WellsFargo(Suit.HEARTS, 3),
]
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

View File

@ -1,252 +0,0 @@
from __future__ import annotations
from abc import ABC
from bang.expansions import *
from typing import List, TYPE_CHECKING
from globals import G
if TYPE_CHECKING:
from bang.players import Player
from bang.game import Game
class Character(ABC):
"""Base class for all characters"""
def __init__(
self,
name: str,
max_lives: int,
sight_mod: int = 0,
visibility_mod: int = 0,
pick_mod: int = 0,
desc: str = "",
):
super().__init__()
self.name = name
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 = "🤷‍♂️"
self.number = "".join(["❤️"] * self.max_lives)
def check(self, game: Game, character: Character):
"""Check character type and if Sbornia is active"""
import bang.expansions.high_noon.card_events as ceh
if game.check_event(ceh.Sbornia):
return False
return isinstance(self, character)
def special(self, player: Player, data):
"""Base for special actions that can be performed by a character"""
import bang.expansions.high_noon.card_events as ceh
if player.game.check_event(ceh.Sbornia):
return False
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_use_special|{player.name}|{self.name}",
)
return True
class BartCassidy(Character):
"""Ogni volta che viene ferito, pesca una carta
Each time he is hurt, he draws a card"""
def __init__(self):
super().__init__("Bart Cassidy", max_lives=4)
self.icon = "💔"
class BlackJack(Character):
"""All'inizio del suo turno, quando deve pescare, mostra a tutti la seconda carta, se è Cuori o Quadri pesca una terza carta senza farla vedere
At the beginning of his turn, when he has to draw, he shows everyone the second card, if it is Hearts or Diamonds he draws a third card without showing it
"""
def __init__(self):
super().__init__("Black Jack", max_lives=4)
self.icon = "🎰"
class CalamityJanet(Character):
"""Può usare i Mancato! come Bang! e viceversa
She can use the Missed! as Bang! and the other way around"""
def __init__(self):
super().__init__("Calamity Janet", max_lives=4)
self.icon = "🔀"
class ElGringo(Character):
"""Ogni volta che perde un punto vita pesca una carta dalla mano del giocatore responsabile ma solo se il giocatore in questione ha carte in mano (una carta per ogni punto vita)
Each time he is hurt, he draws a card from the hand of the attacking player"""
def __init__(self):
super().__init__("El Gringo", max_lives=3)
self.icon = "🤕"
# ovviamente la dinamite non è considerata danno inferto da un giocatore
class JesseJones(Character):
"""All'inizio del suo turno, quando deve pescare, può prendere la prima carta a caso dalla mano di un giocatore e la seconda dal mazzo
When he has to draw his cards, he may draw the first card from the hand of another player
"""
def __init__(self):
super().__init__("Jesse Jones", max_lives=4)
self.icon = "😜"
class Jourdonnais(Character):
"""Gioca come se avesse un Barile sempre attivo, nel caso in cui metta in gioco un Barile 'Reale' può estrarre due volte
He plays as he had a Barrel always active, if he equips another Barrel, he can flip 2 cards
"""
def __init__(self):
super().__init__("Jourdonnais", max_lives=4)
self.icon = "🛢"
class KitCarlson(Character):
"""All'inizio del suo turno, quando deve pescare, pesca tre carte, ne sceglie due da tenere in mano e la rimanente la rimette in cima la mazzo
When he has to draw, he peeks 3 cards and chooses 2, putting the other card on the top of the deck
"""
def __init__(self):
super().__init__("Kit Carlson", max_lives=4)
self.icon = "🤔"
class LuckyDuke(Character):
"""Ogni volta che deve estrarre, prende due carte dal mazzo, sceglie una delle due carte per l'estrazione, infine le scarta entrambe
Every time he has to flip a card, he can flip 2 times"""
def __init__(self):
super().__init__("Lucky Duke", max_lives=4, pick_mod=1)
self.icon = "🍀"
class PaulRegret(Character):
"""Gioca come se avesse una Mustang sempre attiva, nel caso in cui metta in gioco una Mustang 'Reale' l'effetto si somma tranquillamente
The other players see him at distance +1"""
def __init__(self):
super().__init__("Paul Regret", max_lives=3, visibility_mod=1)
self.icon = "🏇"
class PedroRamirez(Character):
"""All'inizio del suo turno, quando deve pescare, può prendere la prima carta dalla cima degli scarti e la seconda dal mazzo
When he has to draw, he may pick the first card from the discarded cards"""
def __init__(self):
super().__init__("Pedro Ramirez", max_lives=4)
self.icon = "🚮"
class RoseDoolan(Character):
"""Gioca come se avesse un Mirino sempre attivo, nel caso in cui metta in gioco una Mirino 'Reale' l'effetto si somma tranquillamente
She sees the other players at distance -1"""
def __init__(self):
super().__init__("Rose Doolan", max_lives=4, sight_mod=1)
self.icon = "🕵️‍♀️"
class SidKetchum(Character):
"""Può scartare due carte per recuperare un punto vita anche più volte di seguito a patto di avere carte da scartare, può farlo anche nel turno dell'avversario se stesse per morire
He can discard 2 cards to regain 1HP"""
def __init__(self):
super().__init__("Sid Ketchum", max_lives=4)
self.icon = "🤤"
class SlabTheKiller(Character):
"""Per evitare i suoi Bang servono due Mancato, un eventuale barile vale solo come un Mancato
To dodge his Bang! cards other players need 2 Missed!"""
def __init__(self):
super().__init__("Slab The Killer", max_lives=4)
self.icon = "🔪"
# vale per tutte le carte bang non solo per la carta che si chiama Bang!
class SuzyLafayette(Character):
"""Appena rimane senza carte in mano pesca immediatamente una carta dal mazzo
Whenever she has an empty hand, she draws a card"""
def __init__(self):
super().__init__("Suzy Lafayette", max_lives=4)
self.icon = "🔂"
class VultureSam(Character):
"""Quando un personaggio viene eliminato prendi tutte le carte di quel giocatore e aggiungile alla tua mano, sia le carte in mano che quelle in gioco
When a player dies, he gets all the cards in the dead's hand and equipments"""
def __init__(self):
super().__init__("Vulture Sam", max_lives=4)
self.icon = "🦉"
class WillyTheKid(Character):
"""Questo personaggio può giocare quanti bang vuole nel suo turno
He doesn't have limits to the amounts of bang he can use"""
def __init__(self):
super().__init__("Willy The Kid", max_lives=4)
self.icon = "🎉"
def all_characters(expansions: List[str]):
from bang.expansions import DodgeCity, TheValleyOfShadows, WildWestShow, GoldRush
base_chars = [
BartCassidy(),
BlackJack(),
CalamityJanet(),
ElGringo(),
JesseJones(),
Jourdonnais(),
KitCarlson(),
LuckyDuke(),
PaulRegret(),
PedroRamirez(),
RoseDoolan(),
SidKetchum(),
SlabTheKiller(),
SuzyLafayette(),
VultureSam(),
WillyTheKid(),
]
if "dodge_city" in expansions:
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())
if "wild_west_show" in expansions:
base_chars.extend(WildWestShow.get_characters())
return base_chars

View File

@ -1,220 +0,0 @@
from typing import List, Set, Dict, Tuple, Optional, TYPE_CHECKING
import bang.cards as cs
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
import bang.expansions.wild_west_show.card_events as cew
import bang.expansions.wild_west_show.characters as chw
import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.train_robbery.stations as trs
import bang.expansions.train_robbery.trains as trt
from globals import G
if TYPE_CHECKING:
from bang.game import Game
from bang.players import Player
class Deck:
"""Class that handles all deck dealing information"""
def __init__(self, game: "Game"):
super().__init__()
self.cards: List[cs.Card] = cs.get_starting_deck(game.expansions)
self.mancato_cards: List[str] = []
self.mancato_cards_not_green_or_blue: List[str] = []
self.green_cards: Set[str] = set()
for c in self.cards:
if isinstance(c, cs.Mancato) and c.name not in self.mancato_cards:
self.mancato_cards.append(c.name)
if not (c.usable_next_turn or c.is_equipment):
self.mancato_cards_not_green_or_blue.append(c.name)
elif c.usable_next_turn:
self.green_cards.add(c.name)
self.all_cards_str: List[str] = []
for c in self.cards:
if c.name not in self.all_cards_str:
self.all_cards_str.append(c.name)
self.game = game
self.event_cards: List[ce.CardEvent] = []
self.event_cards_wildwestshow: List[ce.CardEvent] = []
self.stations: List[trs.StationCard] = []
self.train_pile: List[trt.TrainCard] = []
self.current_train: List[trt.TrainCard] = []
endgame_cards: List[ce.CardEvent] = []
if "fistful_of_cards" in game.expansions:
self.event_cards.extend(ce.get_all_events(game.rng))
endgame_cards.append(ce.get_endgame_card())
if "high_noon" in game.expansions:
self.event_cards.extend(ceh.get_all_events(game.rng))
endgame_cards.append(ceh.get_endgame_card())
if "wild_west_show" in game.expansions:
self.event_cards_wildwestshow.extend(cew.get_all_events(game.rng))
game.rng.shuffle(self.event_cards_wildwestshow)
self.event_cards_wildwestshow.insert(0, None)
self.event_cards_wildwestshow.append(cew.get_endgame_card())
if "train_robbery" in game.expansions:
self.stations = game.rng.sample(trs.get_all_stations(), len(game.players))
self.train_pile = trt.get_all_cards(game.rng)
self.current_train = [trt.get_locomotives(game.rng)[0]] + self.train_pile[
:3
]
if len(self.event_cards) > 0:
game.rng.shuffle(self.event_cards)
self.event_cards = self.event_cards[:12]
self.event_cards.insert(0, None)
self.event_cards.insert(
0, None
) # 2 perchè iniziale, e primo flip dallo sceriffo
self.event_cards.append(game.rng.choice(endgame_cards))
game.rng.shuffle(self.cards)
self.shop_deck: List[grc.ShopCard] = []
self.shop_cards: List[grc.ShopCard] = []
if "gold_rush" in game.expansions:
self.shop_cards = [None, None, None]
self.shop_deck = grc.get_cards()
game.rng.shuffle(self.shop_deck)
self.fill_gold_rush_shop()
self.scrap_pile: List[cs.Card] = []
print(f"Deck initialized with {len(self.cards)} cards")
def flip_event(self):
"""Flip event for regular Sheriff turn (High Noon, Fistful of Cards)"""
if len(self.event_cards) > 0 and not (
isinstance(self.event_cards[0], ce.PerUnPugnoDiCarte)
or isinstance(self.event_cards[0], ceh.MezzogiornoDiFuoco)
):
self.event_cards.append(self.event_cards.pop(0))
if len(self.event_cards) > 0 and self.event_cards[0] is not None:
self.event_cards[0].on_flipped(self.game)
self.game.notify_event_card()
self.game.notify_all()
def flip_wildwestshow(self):
"""Flip event for Wild West Show only"""
if len(self.event_cards_wildwestshow) > 0 and not isinstance(
self.event_cards_wildwestshow[0], cew.WildWestShow
):
self.event_cards_wildwestshow.append(self.event_cards_wildwestshow.pop(0))
if (
len(self.event_cards_wildwestshow) > 0
and self.event_cards_wildwestshow[0] is not None
):
self.event_cards_wildwestshow[0].on_flipped(self.game)
self.game.notify_event_card_wildwestshow()
self.game.notify_all()
def fill_gold_rush_shop(self):
"""
As gold_rush shop cards are stored in a fixed 3 space array,
this function replaces the None values with new cards.
"""
if not any((c is None for c in self.shop_cards)):
return
for i in range(3):
if self.shop_cards[i] is None:
print(f"replacing gr-card {i}")
self.shop_cards[i] = self.shop_deck.pop(0)
self.shop_cards[i].reset_card()
self.game.notify_gold_rush_shop()
def move_train_forward(self):
if len(self.stations) == 0:
return
if len(self.current_train) == len(self.stations) + 4:
return
if len(self.current_train) > 0:
self.current_train.append(None)
self.game.notify_stations()
def peek(self, n_cards: int) -> list:
return self.cards[:n_cards]
def peek_scrap_pile(self, n_cards: int=1) -> List[cs.Card]:
if len(self.scrap_pile) > 0:
return self.scrap_pile[-n_cards:]
else:
return None
def pick_and_scrap(self) -> cs.Card:
card = self.cards.pop(0)
jpain = None
for p in self.game.players:
if p.character.check(self.game, chw.JohnPain) and len(p.hand) < 6:
jpain = p
break
if jpain:
jpain.hand.append(card)
jpain.notify_self()
else:
self.scrap_pile.append(card)
if len(self.cards) == 0:
self.reshuffle()
self.game.notify_scrap_pile()
return card
def put_on_top(self, card: cs.Card):
self.cards.insert(0, card)
def draw(self, ignore_event=False, player=None) -> cs.Card:
if (
self.game.check_event(ce.MinieraAbbandonata)
and len(self.scrap_pile) > 0
and not ignore_event
):
card = self.draw_from_scrap_pile()
if player is not None and self.game.replay_speed > 0:
G.sio.emit(
"card_drawn",
room=self.game.name,
data={"player": player.name, "pile": "scrap"},
)
player.hand.append(card)
return card
card = self.cards.pop(0)
if len(self.cards) == 0:
self.reshuffle()
if player is not None and self.game.replay_speed > 0:
G.sio.emit(
"card_drawn",
room=self.game.name,
data={"player": player.name, "pile": "deck"},
)
player.hand.append(card)
return card
def reshuffle(self):
self.cards = self.scrap_pile[:-1].copy()
self.game.rng.shuffle(self.cards)
self.scrap_pile = self.scrap_pile[-1:]
def draw_from_scrap_pile(self) -> cs.Card:
if len(self.scrap_pile) > 0:
card = self.scrap_pile.pop(-1)
self.game.notify_scrap_pile()
card.reset_card()
return card
else:
return self.draw()
def scrap(self, card: cs.Card, ignore_event:bool=False, player:'Player'=None):
if card.number == 42:
return
card.reset_card()
if self.game.check_event(ce.MinieraAbbandonata) and not ignore_event:
self.put_on_top(card)
else:
self.scrap_pile.append(card)
if player is not None and self.game.replay_speed > 0:
G.sio.emit(
"card_scrapped",
room=self.game.name,
data={
"player": player.name,
"card": card.__dict__,
"pile": "scrap",
},
)
G.sio.sleep(0.6)
self.game.notify_scrap_pile()
else:
self.game.notify_scrap_pile()

View File

@ -1,146 +0,0 @@
# pylint: skip-file
class DodgeCity():
def get_characters():
from bang.expansions.dodge_city import characters
return characters.all_characters()
def get_cards():
from bang.expansions.dodge_city import cards
return cards.get_starting_deck()
def get_expansion_info(self):
return {
"id": "dodge_city",
"name": "Dodge City",
"cards": [
{"type": "characters", "cards": DodgeCity.get_characters()},
{"type": "cards", "cards": DodgeCity.get_cards()}
]
}
class HighNoon():
def get_events():
from bang.expansions.high_noon import card_events
return card_events.get_all_events() + [card_events.get_endgame_card()]
def get_expansion_info(self):
return {
"id": "high_noon",
"name": "High Noon",
"cards": [
{"type": "events", "cards": HighNoon.get_events()}
]
}
class FistfulOfCards():
def get_events():
from bang.expansions.fistful_of_cards import card_events
return card_events.get_all_events() + [card_events.get_endgame_card()]
def get_expansion_info(self):
return {
"id": "fistful_of_cards",
"name": "Fistful of Cards",
"cards": [
{"type": "events", "cards": FistfulOfCards.get_events()}
]
}
class GoldRush():
def get_characters():
from bang.expansions.gold_rush import characters
return characters.all_characters()
def get_shop_cards():
from bang.expansions.gold_rush import shop_cards
return shop_cards.get_cards()
def get_expansion_info(self):
return {
"id": "gold_rush",
"name": "Gold Rush",
"cards": [
{"type": "characters", "cards": GoldRush.get_characters()},
{"type": "cards", "cards": GoldRush.get_shop_cards()}
]
}
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()
def get_expansion_info(self):
return {
"id": "the_valley_of_shadows",
"name": "The Valley of Shadows",
"cards": [
{"type": "characters", "cards": TheValleyOfShadows.get_characters()},
{"type": "cards", "cards": TheValleyOfShadows.get_cards()}
]
}
class WildWestShow():
def get_characters():
from bang.expansions.wild_west_show import characters
return characters.all_characters()
def get_events():
from bang.expansions.wild_west_show import card_events
return card_events.get_all_events() + [card_events.get_endgame_card()]
def get_expansion_info(self):
return {
"id": "wild_west_show",
"name": "Wild West Show",
"cards": [
{"type": "characters", "cards": WildWestShow.get_characters()},
{"type": "events", "cards": WildWestShow.get_events()}
]
}
class TrainRobbery():
def get_stations():
from bang.expansions.train_robbery import stations
return stations.get_all_stations()
def get_trains():
from bang.expansions.train_robbery import trains
return trains.get_all_cards() + trains.get_locomotives()
def get_expansion_info(self):
return {
"id": "train_robbery",
"name": "Train Robbery",
"cards": [
{"type": "stations", "cards": TrainRobbery.get_stations()},
{"type": "trains", "cards": TrainRobbery.get_trains()}
]
}
def get_expansion_info(expansion_id):
expansion_map = {
"dodge_city": DodgeCity(),
"high_noon": HighNoon(),
"fistful_of_cards": FistfulOfCards(),
"gold_rush": GoldRush(),
"the_valley_of_shadows": TheValleyOfShadows(),
"wild_west_show": WildWestShow(),
"train_robbery": TrainRobbery()
}
expansion_info = expansion_map[expansion_id].get_expansion_info()
for section in expansion_info["cards"]:
unique_cards = []
seen_card = set()
for card in section["cards"]:
if card.name not in seen_card:
unique_cards.append(card)
seen_card.add(card.name)
section["cards"] = unique_cards
return expansion_info

View File

@ -1,571 +0,0 @@
from bang.cards import *
import bang.expansions.fistful_of_cards.card_events as ce
from globals import G
class Binocolo(Mirino):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Binocolo"
self.icon = "🔍"
class Riparo(Mustang):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Riparo"
self.icon = ""
class Pugno(Card):
def __init__(self, suit, number):
super().__init__(suit, "Pugno!", number, range=1)
self.icon = "👊"
self.alt_text = "1🔎 💥"
# self.desc = "Spara a un giocatore a distanza 1"
# self.desc_eng = "Shoot a player at distance 1"
self.need_target = True
def play_card(self, player, against, _with=None):
if against is not None:
super().play_card(player, against=against)
player.game.attack(player, against, card_name=self.name)
return True
return False
class Schivata(Mancato):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Schivata"
self.icon = "🙅‍♂️"
# self.desc += " e poi pesca una carta"
# self.desc_eng += " and then draw a card."
self.alt_text = "😅 | 🎴"
def play_card(self, player, against, _with=None):
return False
def use_card(self, player):
player.game.deck.draw(True, player=player)
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 da un giocatore a prescindere dalla distanza"
# self.desc_eng = "Steal a card from another player at any distance"
self.need_target = True
self.need_with = True
self.alt_text = "2🃏 | 👤😱"
def play_card(self, player, against, _with):
if against is not None and _with is not None:
player.game.deck.scrap(_with)
super().play_card(player, against=against, _with=_with)
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.desc_eng = "Choose a card to discard from the hand/equipment of all the other players"
self.need_with = True
self.need_target = False
self.alt_text = "2🃏 | 👤💃"
def play_card(self, player, against, _with):
if _with is not None:
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 = []
target = player.game.get_player_named(player.name, next=True)
while target != player:
if len(target.hand) + len(target.equipment) > 0:
player.rissa_targets.append(target)
target = player.game.get_player_named(target.name, next=True)
player.game.deck.scrap(_with)
player.event_type = "rissa"
print(f"rissa targets: {player.rissa_targets}")
super().play_card(
player, against=player.rissa_targets.pop(0).name, _with=_with
)
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card|{player.name}|{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.desc_eng = "Shoot a player at any distance"
self.need_target = True
self.need_with = True
self.alt_text = "2🃏 | 👤💥"
def play_card(self, player, against, _with=None):
if against is not None and _with is not None:
player.game.deck.scrap(_with)
super().play_card(player, against=against, _with=_with)
player.game.attack(player, against, card_name=self.name)
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.desc_eng = "Heal 1 HP to a player of your choice (can be you)"
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 is not None and _with is not None:
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_for|{player.name}|{self.name}|{against}",
)
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.desc_eng = "Heal 2 HP"
self.need_with = True
self.alt_text = "2🃏 | 🍺🍺"
def play_card(self, player, against, _with=None):
if _with is not None:
super().play_card(player, against=against, _with=_with)
player.game.deck.scrap(_with)
player.lives = min(player.lives + 2, player.max_lives)
player.notify_self()
return True
return False
class Bibbia(Schivata):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Bibbia"
self.icon = "📖"
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
pass
return False
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class Cappello(Mancato):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Cappello"
self.icon = "🧢"
self.usable_next_turn = True
self.can_be_used_now = False
self.alt_text = "😅"
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
pass
return False
else:
self.reset_card()
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class PlaccaDiFerro(Cappello):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Placca Di Ferro"
self.icon = "🛡"
class Sombrero(Cappello):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Sombrero"
self.icon = "👒"
class Pugnale(Pugno):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Pugnale"
self.icon = "🗡"
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
return super().play_card(player, against=against)
else:
self.reset_card()
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class Derringer(Pugnale):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Derringer"
self.icon = "🚬"
self.alt_text += " 🎴"
# self.desc += ' e poi pesca una carta'
# self.desc_eng += ' and then draw a card.'
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
player.game.deck.draw(True, player=player)
return super().play_card(player, against=against)
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
def use_card(self, player):
player.game.deck.draw(True, player=player)
player.notify_self()
class Borraccia(Card):
def __init__(self, suit, number):
super().__init__(suit, "Borraccia", number)
self.icon = "🍼"
# self.desc = 'Recupera 1 vita'
# self.desc_eng = 'Regain 1 HP'
self.alt_text = "🍺"
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
super().play_card(player, against)
player.lives = min(player.lives + 1, player.max_lives)
player.notify_self()
return True
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class PonyExpress(WellsFargo):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Pony Express"
self.icon = "🦄"
self.alt_text = "🎴🎴🎴"
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
return super().play_card(player, against)
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class Howitzer(Gatling):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Howitzer"
self.icon = "📡"
self.alt_text = "👥💥"
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
return super().play_card(player, against)
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class CanCan(CatBalou):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Can Can"
self.icon = "👯‍♀️"
self.alt_text = "👤💃"
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
return super().play_card(player, against)
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class Conestoga(Panico):
def __init__(self, suit, number):
Card.__init__(self, suit, "Conestoga", number)
self.icon = "🏕"
# self.desc = "Ruba 1 carta da un giocatore a prescindere dalla distanza"
# self.desc_eng = "Steal a card from another player at any distance"
self.alt_text = "👤😱"
self.need_target = True
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
return super().play_card(player, against)
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class Pepperbox(Bang):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = "Pepperbox"
self.icon = "🌶"
self.alt_text = "💥"
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
if against is not None:
Card.play_card(self, player, against=against)
player.game.attack(player, against, card_name=self.name)
return True
return False
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
class FucileDaCaccia(Card):
def __init__(self, suit, number):
super().__init__(suit, "Fucile Da Caccia", number)
self.icon = "🌂"
# self.desc = "Spara a un giocatore a prescindere dalla distanza"
self.alt_text = "👤💥"
self.need_target = True
self.usable_next_turn = True
self.can_be_used_now = False
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
if against is not None:
super().play_card(player, against=against)
player.game.attack(player, against, card_name=self.name)
return True
return False
else:
if not self.is_duplicate_card(player) and not player.game.check_event(
ce.IlGiudice
):
self.reset_card()
G.sio.emit(
"chat_message",
room=player.game.name,
data=f"_play_card_green|{player.name}|{self.name}",
)
player.equipment.append(self)
return True
else:
return False
# pylint: disable=function-redefined
def get_starting_deck() -> List[Card]:
cards = [
Barile(Suit.CLUBS, "A"),
Binocolo(Suit.DIAMONDS, 10),
Dinamite(Suit.CLUBS, 10),
Mustang(Suit.HEARTS, 5),
Remington(Suit.DIAMONDS, 6),
RevCarabine(Suit.SPADES, 5),
Riparo(Suit.DIAMONDS, "K"),
Bang(Suit.SPADES, 8),
Bang(Suit.CLUBS, 5),
Bang(Suit.CLUBS, 6),
Bang(Suit.CLUBS, "K"),
Birra(Suit.HEARTS, 6),
Birra(Suit.SPADES, 6),
CatBalou(Suit.CLUBS, 8),
Emporio(Suit.SPADES, "A"),
Indiani(Suit.DIAMONDS, 5),
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"),
Bibbia(Suit.HEARTS, 10),
Cappello(Suit.DIAMONDS, "J"),
PlaccaDiFerro(Suit.DIAMONDS, "A"),
PlaccaDiFerro(Suit.SPADES, "Q"),
Sombrero(Suit.CLUBS, 7),
Pugnale(Suit.HEARTS, 8),
Derringer(Suit.SPADES, 7),
Borraccia(Suit.HEARTS, 7),
CanCan(Suit.CLUBS, "J"),
Conestoga(Suit.DIAMONDS, 9),
FucileDaCaccia(Suit.CLUBS, "Q"),
PonyExpress(Suit.DIAMONDS, "Q"),
Pepperbox(Suit.HEARTS, "A"),
Howitzer(Suit.SPADES, 9),
]
for c in cards:
c.expansion_icon = "🐄️"
c.expansion = "dodge_city"
return cards

View File

@ -1,220 +0,0 @@
from typing import List
from bang.characters import Character
from globals import PendingAction
class PixiePete(Character):
"""All'inizio del turno pesca 3 carte invece che 2
He draws 3 cards instead of 2"""
def __init__(self):
super().__init__("Pixie Pete", max_lives=3)
self.icon = "☘️"
class TequilaJoe(Character):
"""Se gioca la carta Birra recupera 2 vite invece che una sola
When he plays Beer, he regains 2 Health Points"""
def __init__(self):
super().__init__("Tequila Joe", max_lives=4)
self.icon = "🍻"
class GregDigger(Character):
"""Quando un giocatore muore, recupera fino a 2 vite
Whenever a player dies, he regains up to 2 lives"""
def __init__(self):
super().__init__("Greg Digger", max_lives=4)
self.icon = "🦴"
class HerbHunter(Character):
"""Quando un giocatore muore, pesca 2 carte
Whenever a player dies, he draws 2 cards"""
def __init__(self):
super().__init__("Herb Hunter", max_lives=4)
self.icon = "⚰️"
class ElenaFuente(Character):
"""Può usare una carta qualsiasi nella sua mano come mancato
She can use any card of her hand as missed"""
def __init__(self):
super().__init__("Elena Fuente", max_lives=3)
self.icon = "🧘‍♀️"
class BillNoface(Character):
"""All'inizio del turno pesca 1 carta + 1 carta per ogni ferita che ha
Draw 1 card + 1 card for each wound he has"""
def __init__(self):
super().__init__("Bill Noface", max_lives=4)
self.icon = "🙈"
class MollyStark(Character):
"""Quando usa volontariamente una carta che ha in mano, fuori dal suo turno, ne ottiene un'altra dal mazzo
When she uses a card from her hand outside her turn, she draws a card."""
def __init__(self):
super().__init__("Molly Stark", max_lives=4)
self.icon = "🙅‍♀️"
class ApacheKid(Character):
"""Le carte di quadri ♦️ giocate contro di lui non hanno effetto (non vale durante i duelli)
Cards of diamonds played against him, do no have effect (doesn't work in duels).
"""
def __init__(self):
super().__init__("Apache Kid", max_lives=3)
self.icon = "♦️"
class SeanMallory(Character):
"""Quando finisce il suo turno può tenere fino a 10 carte in mano
He can keep up to 10 cards in his hand when ending the turn."""
def __init__(self):
super().__init__("Sean Mallory", max_lives=3)
self.icon = "🍟"
class BelleStar(Character):
"""Nel suo turno le carte verdi degli altri giocatori non hanno effetto.
During her turn the green cards of the other players do not work."""
def __init__(self):
super().__init__("Belle Star", max_lives=4)
self.icon = ""
class VeraCuster(Character):
"""Prima di pescare le sue carte può scegliere l'abilità speciale di un altro giocatore fino al prossimo turno.
Before drawing, she may choose the special ability on another alive player. This ability is used until next turn.
"""
def __init__(self):
super().__init__("Vera Custer", max_lives=3)
self.icon = "🎭"
class ChuckWengam(Character):
"""Durante il suo turno può perdere una vita per pescare 2 carte dal mazzo.
On his turn he may decide to lose 1 HP to draw 2 cards from the deck."""
def __init__(self):
super().__init__("Chuck Wengam", max_lives=4)
self.icon = "💰"
def special(self, player, data):
if super().special(player, data):
if player.lives > 1 and player.is_my_turn:
player.lives -= 1
player.game.deck.draw(True, player=player)
player.game.deck.draw(True, player=player)
player.notify_self()
return True
return False
class PatBrennan(Character):
"""Invece di pescare può prendere una carta dall'equipaggiamento di un altro giocatore.
Instead of drawing he can steal a card from the equipment of another player."""
def __init__(self):
super().__init__("Pat Brennan", max_lives=4)
self.icon = "🤗"
class JoseDelgado(Character):
"""Può scartare una carta blu per pescare 2 carte.
He can discard a blue card to draw 2 cards."""
def __init__(self):
super().__init__("José Delgado", max_lives=4)
self.icon = "🎒"
class DocHolyday(Character):
"""Nel suo turno può scartare 2 carte per fare un bang.
He can discard 2 cards to play a bang."""
def __init__(self):
super().__init__("Doc Holyday", max_lives=4)
self.icon = "✌🏻"
def special(self, player, data):
if super().special(player, data):
if (
player.special_use_count < 1
and player.pending_action == PendingAction.PLAY
):
player.special_use_count += 1
cards = sorted(data["cards"], reverse=True)
for c in cards:
player.game.deck.scrap(player.hand.pop(c), True)
player.notify_self()
player.game.attack(player, data["against"])
return True
return False
# pylint: disable=function-redefined
def all_characters() -> List[Character]:
cards = [
PixiePete(),
TequilaJoe(),
GregDigger(),
HerbHunter(),
ElenaFuente(),
BillNoface(),
MollyStark(),
ApacheKid(),
SeanMallory(),
BelleStar(),
VeraCuster(),
ChuckWengam(),
PatBrennan(),
JoseDelgado(),
DocHolyday(),
]
for card in cards:
card.expansion_icon = "🐄️" # pylint: disable=attribute-defined-outside-init
card.expansion = "dodge_city" # pylint: disable=attribute-defined-outside-init
return cards
# Apache Kid: il suo effetto non conta nei duelli
# belle star: vale solo per le carte blu e verdi
# chuck wengam: può usarlo più volte in un turno, ma non può suicidarsi
# doc holiday: il suo effetto non conta nel limite di un bang per turno,
# se deve sparare a Apache Kid una delle due carte scartate non deve essere di quadri
# molly stark: le carte scartate che valgono sono solo quelle scartate volontariamente,
# carte scartate per colpa di can can, cat balou, rissa, panico non valgono,
# invece carte scartata per indiani, birra(in caso di morte), o un mancato valgono,
# in un duello pesca solo quando il duello è finito (una carta x ogni bang scartato)
# pat brennan: quando pesca con il suo effetto, pesca solo la carta del giocatore non anche dal mazzo
# vera custer: la scelta può essere fatta solo appena prima di pescare,
# quando inizia la partita serve farle scegliere, poi può rimanere quello finchè non decide di cambiarlo
# eventualmente fare una schermata dove vede tutti i personaggi

View File

@ -1,222 +0,0 @@
from typing import TYPE_CHECKING
from abc import ABC, abstractmethod
import random
import bang.players as players
import bang.roles as r
import bang.cards as cs
from globals import G
if TYPE_CHECKING:
from bang.game import Game
class CardEvent(ABC):
"""Base class for all event cards"""
def __init__(self, name, icon):
self.name = name
self.icon = icon
def on_flipped(self, game: "Game"):
"""Default on flipped event
Args:
game (Game): the game object
"""
print(f"{game.name}: flip new event {self.name}")
G.sio.emit(
"chat_message",
room=game.name,
data={
"color": "orange",
"text": f"_flip_event|{self.name}",
},
)
return
def on_clicked(self, game, player):
"""Default on clicked event
Args:
game (Game): the game object
player (Player): the player that clicked the card
"""
print(f"{game.name}: {player.name} clicked event {self.name}")
return
class Agguato(CardEvent):
"""La distanza base tra 2 qualsiasi giocatori è 1
The distance between 2 players is always 1"""
def __init__(self):
super().__init__("Agguato", "🛁")
class Cecchino(CardEvent):
"""Nel proprio turno i giocatori possono
scartare 2 Bang assieme per sparare un bang che necessita 2 mancato (clicca la carta)
In your turn you can discard 2 Bang together to shoot a bang that needs 2 miss (click the card)
"""
def __init__(self):
super().__init__("Cecchino", "👁")
class DeadMan(CardEvent):
"""Al proprio turno il giocatore che è morto per primo torna in vita con 2 vite e 2 carte
The first player that died returns back to life with 2 hp and 2 cards"""
def __init__(self):
super().__init__("Dead Man", "⚰️")
def on_flipped(self, game):
game.did_resuscitate_deadman = False
return super().on_flipped(game)
class FratelliDiSangue(CardEvent):
"""All'inizio del proprio turno, i giocatori possono perdere 1 vita (tranne l'ultimo) per darla a un altro giocatore"""
def __init__(self):
super().__init__("Fratelli Di Sangue", "💉")
class IlGiudice(CardEvent):
"""Non si possono equipaggiare carte a se stessi o agli altri"""
def __init__(self):
super().__init__("Il Giudice", "👨‍⚖️")
class Lazo(CardEvent):
"""Le carte equipaggiate non hanno effetto"""
def __init__(self):
super().__init__("Lazo", "📿")
class LeggeDelWest(CardEvent):
"""I giocatori mostrano la seconda carta che pescano e sono obbligati a usarla in quel turno (se possibile)"""
def __init__(self):
super().__init__("Legge Del West", "⚖️")
class LiquoreForte(CardEvent):
"""I giocatori possono evitare di pescare per recuperare 1 vita (clicca sulla carta evento per farlo)"""
def __init__(self):
super().__init__("Liquore Forte", "🥃")
class MinieraAbbandonata(CardEvent):
"""I giocatori pescano dagli scarti nella loro fase 1 e scartano in cima al mazzo nella loro fase 3 (se gli scarti finiscono, è necessario pescare e scartare in cima al mazzo)"""
def __init__(self):
super().__init__("Miniera Abbandonata", "")
class PerUnPugnoDiCarte(CardEvent):
"""All'inizio del proprio turno, il giocatore subisce tanti bang quante carte ha in mano"""
def __init__(self):
super().__init__("Per Un Pugno Di Carte", "🎴")
class Peyote(CardEvent):
"""Invece che pescare il giocatore prova a indovinare il colore del seme, se lo indovina aggiunge la carta alla mano e continua provando ad indovinare la carta successiva"""
def __init__(self):
super().__init__("Peyote", "🌵")
class Ranch(CardEvent):
"""Dopo aver pescato il giocatore può scartare quante carte vuole dalla mano e pescarne altrettante dal mazzo"""
def __init__(self):
super().__init__("Ranch", "🐮")
class Rimbalzo(CardEvent):
"""Il giocatore di turno può giocare bang contro le carte equipaggiate dagli altri giocatori, se non giocano mancato vengono scartate (clicca la carta evento)"""
def __init__(self):
super().__init__("Rimbalzo", "")
def on_clicked(self, game, player):
super().on_clicked(game, player)
if any((c.name == cs.Bang(0, 0).name for c in player.hand)):
player.available_cards = [
{
"name": p.name,
"icon": p.role.icon
if (game.initial_players == 3)
else "⭐️"
if isinstance(p.role, r.Sheriff)
else "🤠",
"is_character": True,
"avatar": p.avatar,
"is_player": True,
}
for p in game.get_alive_players()
if len(p.equipment) > 0 and p != player
]
player.available_cards.append({"icon": "", "noDesc": True})
player.choose_text = "choose_rimbalzo_player"
player.pending_action = players.PendingAction.CHOOSE
player.using_rimbalzo = 1
player.notify_self()
class RouletteRussa(CardEvent):
"""A partire dallo sceriffo, ogni giocatore scarta 1 mancato, il primo che non lo fa perde 2 vite"""
def __init__(self):
super().__init__("Roulette Russa", "🇷🇺")
# self.desc_eng = "Starting from the sheriff, every player discards 1 missed, the first one that doesn't loses 2 HP"
class Vendetta(CardEvent):
"""Alla fine del proprio turno il giocatore estrae dal mazzo, se esce ♥️ gioca un altro turno (ma non estrae di nuovo)"""
def __init__(self):
super().__init__("Vendetta", "😤")
# self.desc_eng = "When ending the turn, the player flips a card from the deck, if it's ♥️ he plays another turn (but he does not flip another card)"
def get_endgame_card():
end_game = PerUnPugnoDiCarte()
end_game.expansion = ( # pylint: disable=attribute-defined-outside-init
"fistful-of-cards"
)
return end_game
def get_all_events(rng=random):
cards = [
Agguato(),
Cecchino(),
DeadMan(),
FratelliDiSangue(),
IlGiudice(),
Lazo(),
LeggeDelWest(),
LiquoreForte(),
MinieraAbbandonata(),
Peyote(),
Ranch(),
Rimbalzo(),
RouletteRussa(),
Vendetta(),
]
rng.shuffle(cards)
for card in cards:
card.expansion = ( # pylint: disable=attribute-defined-outside-init
"fistful-of-cards"
)
return cards

View File

@ -1,113 +0,0 @@
from typing import List
from bang.characters import Character
class DonBell(Character):
def __init__(self):
super().__init__("Don Bell", max_lives=4)
# A fine turno estrae, ❤️ o ♦️ gioca di nuovo
self.icon = "🔔️"
class DutchWill(Character):
def __init__(self):
super().__init__("Dutch Will", max_lives=4)
# Pesca 2 ne scarta 1 e prende 1 pepita
self.icon = "🧐️"
class JackyMurieta(Character):
def __init__(self):
super().__init__("Jacky Murieta", max_lives=4)
# puo pagare 2 pepite per sparare 1 bang extra
self.icon = "💆‍♂️️"
def special(self, player, data):
if super().special(player, data):
if player.gold_nuggets >= 2 and player.is_my_turn:
player.gold_nuggets -= 2
player.has_played_bang = False
player.bang_used -= 1
player.notify_self()
return True
return False
class JoshMcCloud(Character):
def __init__(self):
super().__init__("Josh McCloud", max_lives=4)
# puo pagare 2 pepite per pescare il primo equipaggiamento dalla pila gold rush
self.icon = "⛅️"
def special(self, player, data):
if super().special(player, data):
if player.gold_nuggets >= 2 and player.is_my_turn:
player.gold_nuggets -= 2
card = player.game.deck.shop_deck.pop(0)
print(
f"{player.name} ha comprato usando la abilità speciale {card.name}"
)
if card.play_card(player):
player.game.deck.shop_deck.append(card)
player.notify_self()
return True
return False
class MadamYto(Character):
def __init__(self):
super().__init__("Madam Yto", max_lives=4)
# quando viene giocata 1 birra pesca 1 carta
self.icon = "💃️"
class PrettyLuzena(Character):
def __init__(self):
super().__init__("Pretty Luzena", max_lives=4)
# una volta per turno ha 1 sconto di 1 pepita sugli equipaggiamenti
self.icon = "👛️"
class RaddieSnake(Character):
def __init__(self):
super().__init__("Raddie Snake", max_lives=4)
# può scartare 1 pepita per pescare 1 carta (2 volte per turno)
self.icon = "🐍️"
def special(self, player, data):
if super().special(player, data):
if (
player.gold_nuggets >= 1
and player.is_my_turn
and player.special_use_count < 2
):
player.gold_nuggets -= 1
player.special_use_count += 1
player.game.deck.draw(True, player=player)
player.notify_self()
return True
return False
class SimeonPicos(Character):
def __init__(self):
super().__init__("Simeon Picos", max_lives=4)
# ottiene 1 pepita ogni volta che perde 1 punto vita
self.icon = "🥲"
def all_characters() -> List[Character]:
cards = [
DonBell(),
DutchWill(),
JackyMurieta(),
JoshMcCloud(),
MadamYto(),
PrettyLuzena(),
RaddieSnake(),
SimeonPicos(),
]
for c in cards:
c.expansion_icon = "🤑️"
c.expansion = "gold_rush"
return cards

View File

@ -1,265 +0,0 @@
from bang.cards import *
import bang.roles as r
import bang.players as pl
from globals import G, PendingAction
class ShopCardKind(IntEnum):
BROWN = 0 # Se lequipaggiamento ha il bordo marrone, applicane subito leffetto e poi scartalo.
BLACK = 1 # Se lequipaggiamento ha il bordo nero, tienilo scoperto di fronte a te.
class ShopCard(Card):
def __init__(self, name:str, cost:int, kind:ShopCardKind):
super().__init__(suit='💵', number=cost, name=name)
self.kind = kind
self.expansion_icon = '🤑️'
self.expansion = 'gold_rush'
self.reset_card()
def play_card(self, player, against, _with=None):
if self.kind == ShopCardKind.BROWN:
G.sio.emit('chat_message', room=player.game.name, data=f'_purchase_card|{player.name}|{self.name}')
return True
elif self.kind == ShopCardKind.BLACK: # equip it
if not self.is_duplicate_card(player):
self.reset_card()
self.can_be_used_now = True
player.gold_rush_equipment.append(self)
G.sio.emit('chat_message', room=player.game.name, data=f'_purchase_card|{player.name}|{self.name}')
return True
else:
return False
def reset_card(self):
if self.kind == ShopCardKind.BLACK:
self.can_be_used_now = False
class Bicchierino(ShopCard):
def __init__(self):
super().__init__('Bicchierino', 1, ShopCardKind.BROWN)
self.icon = '🍸️'
def play_card(self, player, against=None, _with=None):
player.available_cards = [{
'name': p.name,
'icon': p.role.icon if(player.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'alt_text': ''.join(['❤️']*p.lives)+''.join(['💀']*(p.max_lives-p.lives)),
'is_character': True,
'is_player': True
} for p in player.game.get_alive_players()]
player.choose_text = 'choose_bicchierino'
player.pending_action = PendingAction.CHOOSE
player.notify_self()
return super().play_card(player, against, _with)
class Bottiglia(ShopCard):
def __init__(self):
super().__init__('Bottiglia', 2, ShopCardKind.BROWN)
self.icon = '🍾️'
def play_card(self, player, against=None, _with=None):
# bang, birra, panico
player.available_cards = [Bang(4,42), Birra(4,42), Panico(4,42)]
if not any((player.get_sight() >= p['dist'] for p in player.game.get_visible_players(player))):
player.available_cards.pop(0)
for i in range(len(player.available_cards)):
player.available_cards[i].must_be_used = True
player.choose_text = 'choose_bottiglia'
player.pending_action = PendingAction.CHOOSE
player.notify_self()
return super().play_card(player, against, _with)
class Complice(ShopCard):
def __init__(self):
super().__init__('Complice', 2, ShopCardKind.BROWN)
self.icon = '😉️'
def play_card(self, player, against=None, _with=None):
# emporio, duello, Cat balou
player.available_cards = [Emporio(4,42), Duello(4,42), CatBalou(4,42)]
for i in range(len(player.available_cards)):
player.available_cards[i].must_be_used = True
player.choose_text = 'choose_complice'
player.pending_action = PendingAction.CHOOSE
player.notify_self()
return super().play_card(player, against, _with)
class CorsaAllOro(ShopCard):
def __init__(self):
super().__init__("Corsa All Oro_gr", 5, ShopCardKind.BROWN)
self.icon = '🤑️'
def play_card(self, player, against=None, _with=None):
player.lives = player.max_lives
player.play_turn()
return super().play_card(player, against, _with)
class Rum(ShopCard):
def __init__(self):
super().__init__("Rum", 3, ShopCardKind.BROWN)
self.icon = '🍷️'
def play_card(self, player, against=None, _with=None):
# Estrai 4 carte e ottieni 1 hp per ogni seme diverso
import bang.characters as c
suits = set()
num = 5 if player.character.check(player.game, c.LuckyDuke) else 4
for i in range(num):
c = player.game.deck.pick_and_scrap()
G.sio.emit('chat_message', room=player.game.name, data=f'_flipped|{player.name}|{c.name}|{c.num_suit()}')
suits.add(c.suit)
player.lives = min(player.lives+len(suits), player.max_lives)
return super().play_card(player, against, _with)
class UnionPacific(ShopCard):
def __init__(self):
super().__init__("Union Pacific", 4, ShopCardKind.BROWN)
self.icon = '🚆️'
def play_card(self, player, against=None, _with=None):
G.sio.emit('chat_message', room=player.game.name,
data=f'_UnionPacific|{player.name}|{self.name}')
for i in range(4):
player.game.deck.draw(True, player=player)
return super().play_card(player, against, _with)
class Calumet(ShopCard):
def __init__(self):
super().__init__("Calumet", 3, ShopCardKind.BLACK)
self.icon = '🚭️'
def play_card(self, player, against=None, _with=None):
return super().play_card(player, against, _with)
# ti rende immuni ai quadri
class Cinturone(ShopCard):
def __init__(self):
super().__init__("Cinturone", 2, ShopCardKind.BLACK)
self.icon = '🥡'
def play_card(self, player, against=None, _with=None):
return super().play_card(player, against, _with)
# max carte a fine turno 8
class FerroDiCavallo(ShopCard):
def __init__(self):
super().__init__("Ferro di Cavallo", 2, ShopCardKind.BLACK)
self.icon = '🎠'
def play_card(self, player, against=None, _with=None):
return super().play_card(player, against, _with)
# estrai come luky duke
class Piccone(ShopCard):
def __init__(self):
super().__init__("Piccone", 4, ShopCardKind.BLACK)
self.icon = '⛏️'
def play_card(self, player, against=None, _with=None):
return super().play_card(player, against, _with)
# peschi una carta in piu a inizio turno
class Ricercato(ShopCard):
def __init__(self):
super().__init__("Ricercato", 2, ShopCardKind.BLACK)
self.icon = '🤠️'
self.can_target_self = True
def play_card(self, player, against=None, _with=None):
G.sio.emit('chat_message', room=player.game.name, data=f'_purchase_card|{player.name}|{self.name}')
player.available_cards = [{
'name': p.name,
'icon': p.role.icon if(player.game.initial_players == 3) else '🤠',
'alt_text': ''.join(['❤️']*p.lives)+''.join(['💀']*(p.max_lives-p.lives)),
'is_character': True,
'is_player': True
} for p in player.game.get_alive_players() if p != player and not isinstance(p.role, r.Sheriff)]
player.available_cards.append({'name': player.name, 'number':0,'icon': 'you', 'is_character': True})
player.choose_text = 'choose_ricercato'
player.pending_action = PendingAction.CHOOSE
player.notify_self()
return True
# la giochi su un altro giocatore, ricompensa di 2 carte e 1 pepita a chi lo uccide
class Setaccio(ShopCard):
def __init__(self):
super().__init__("Setaccio", 3, ShopCardKind.BLACK)
self.icon = '🥘️'
def play_card(self, player, against=None, _with=None):
if not self.can_be_used_now:
return super().play_card(player, against, _with)
else:
if player.gold_nuggets >= 1 and player.setaccio_count < 2:
G.sio.emit('chat_message', room=player.game.name, data=f'_play_card|{player.name}|{self.name}')
player.gold_nuggets -= 1
player.setaccio_count += 1
player.game.deck.draw(True, player=player)
player.notify_self()
return True
return False
# paghi 1 pepita per pescare 1 carta durante il tuo turno (max 2 volte per turno)
class Stivali(ShopCard):
def __init__(self):
super().__init__("Stivali", 3, ShopCardKind.BLACK)
self.icon = '🥾️'
def play_card(self, player, against=None, _with=None):
return super().play_card(player, against, _with)
# peschi una carta ogni volta che vieni ferito
class Talismano(ShopCard):
def __init__(self):
super().__init__("Talismano", 3, ShopCardKind.BLACK)
self.icon = '🧿'
def play_card(self, player, against=None, _with=None):
return super().play_card(player, against, _with)
# ottieni una pepita ogni volta che vieni ferito
class Zaino(ShopCard):
def __init__(self):
super().__init__("Zaino", 3, ShopCardKind.BLACK)
self.icon = '🎒️'
def play_card(self, player, against=None, _with=None):
if not self.can_be_used_now:
return super().play_card(player, against, _with)
else:
if player.gold_nuggets >= 2:
G.sio.emit('chat_message', room=player.game.name, data=f'_play_card|{player.name}|{self.name}')
player.gold_nuggets -= 2
player.lives = min(player.lives + 1, player.max_lives)
player.notify_self()
return True
return False
# paga 2 pepite per recuperare 1 vita
def get_cards() -> List[Card]:
cards = [
Bicchierino(),
Bicchierino(),
Bicchierino(),
Bottiglia(),
Bottiglia(),
Bottiglia(),
Complice(),
Complice(),
Complice(),
CorsaAllOro(),
Rum(),
Rum(),
UnionPacific(),
Calumet(),
Cinturone(),
FerroDiCavallo(),
Piccone(),
Ricercato(),
Ricercato(),
Ricercato(),
Setaccio(),
Stivali(),
Talismano(),
Zaino(),
]
return cards

View File

@ -1,184 +0,0 @@
import random
from globals import G
from bang.expansions.fistful_of_cards.card_events import CardEvent
class Benedizione(CardEvent):
"""Tutte le carte sono considerate di cuori ♥️"""
def __init__(self):
super().__init__("Benedizione", "🙏")
# self.desc_eng = "All cards are of hearts ♥️"
class Maledizione(CardEvent):
"""Tutte le carte sono considerate di picche ♠"""
def __init__(self):
super().__init__("Maledizione", "🤬")
# self.desc_eng = "All cards are of spades ♠"
class Sbornia(CardEvent):
"""I personaggi perdono le loro abilità speciali"""
def __init__(self):
super().__init__("Sbornia", "🥴")
# self.desc_eng = "The characters lose their special abilities"
class Sete(CardEvent):
"""I giocatori pescano 1 carta in meno nella loro fase 1"""
def __init__(self):
super().__init__("Sete", "🥵")
# self.desc_eng = "Players only draw 1 card at the start of their turn"
class IlTreno(CardEvent):
"""I giocatori pescano 1 carta extra nella loro fase 1"""
def __init__(self):
super().__init__("Il Treno", "🚂")
# self.desc_eng = "Players draw 1 extra card"
class IlReverendo(CardEvent):
"""Non si possono giocare le carte Birra"""
def __init__(self):
super().__init__("Il Reverendo", "⛪️")
# self.desc_eng = "Beers can't be played"
class IlDottore(CardEvent):
"""Il/i giocatore/i con meno vite ne recupera/no una"""
def __init__(self):
super().__init__("Il Dottore", "👨‍⚕️")
# self.desc_eng = "The player with the least amount of HP gets healed 1"
def on_flipped(self, game):
super().on_flipped(game)
most_hurt = [
p.lives for p in game.players if p.lives > 0 and p.max_lives > p.lives
]
if len(most_hurt) > 0:
hurt_players = [p for p in game.players if p.lives == min(most_hurt)]
for p in hurt_players:
if p.lives != p.max_lives:
p.lives += 1
G.sio.emit(
"chat_message",
room=game.name,
data=f"_doctor_heal|{p.name}",
)
p.notify_self()
return
class Sermone(CardEvent):
"""I giocatori non possono giocare Bang! durante il loro turno"""
def __init__(self):
super().__init__("Sermone", "✝️")
# self.desc_eng = "Players can't play Bang! during their turn"
class Sparatoria(CardEvent):
"""Il limite di Bang! per turno è 2 invece che 1"""
def __init__(self):
super().__init__("Sparatoria", "🔫🔫")
# self.desc_eng = "The turn Bang! limit is 2"
class CorsaAllOro(CardEvent):
"""Si gioca per un intero giro in senso antiorario, tuttavia gli effetti delle carte rimangono invariati"""
def __init__(self):
super().__init__("Corsa All Oro", "🌟")
# self.desc_eng = "Turns are played counter clockwise"
class IDalton(CardEvent):
"""Chi ha carte blu in gioco ne scarta 1 a sua scelta"""
def __init__(self):
super().__init__("I Dalton", "🙇‍♂️")
# self.desc_eng = "Players that have blue cards equipped, discard 1 of those card of their choice"
def on_flipped(self, game):
game.waiting_for = 0
game.ready_count = 0
game.dalton_on = True
for p in game.players:
if p.get_dalton():
game.waiting_for += 1
p.notify_self()
if game.waiting_for != 0:
return
game.dalton_on = False
return super().on_flipped(game)
class Manette(CardEvent):
"""Dopo aver pescato in fase 1, il giocatore di turno dichiara un seme: potrà usare solamente carte di quel seme nel suo turno"""
def __init__(self):
super().__init__("Manette", "🔗")
# self.desc_eng = "After drawing in phase 1, the player declares a suit. He will be able to use only cards of that suit for that turn"
class NuovaIdentita(CardEvent):
"""All'inizio del proprio turno, ogni giocatore potrà decidere se sostituire il suo personaggio attuale con quello era stato proposto ad inizio partita, se lo fa riparte con 2 punti vita"""
def __init__(self):
super().__init__("Nuova Identita", "🕶")
# self.desc_eng = "At the beginning of their turn, each player can choose to change its character with the other shown at the game start. If he does so he restarts from 2 HP."
class CittaFantasma(CardEvent):
"""Tutti i giocatori morti tornano in vita al proprio turno, non possono morire e pescano 3 carte invece che 2. Quando terminano il turno tornano morti."""
def __init__(self):
super().__init__("Città Fantasma", "👻")
# self.desc_eng = "All dead players come back to life in their turn, they can't die and draw 3 cards instead of 2. When they end their turn the die."
class MezzogiornoDiFuoco(CardEvent):
"""Ogni giocatore perde 1 punto vita all'inizio del turno"""
def __init__(self):
super().__init__("Mezzogiorno di Fuoco", "🔥")
# self.desc_eng = "Every player loses 1 HP when their turn starts"
def get_endgame_card():
end_game = MezzogiornoDiFuoco()
end_game.expansion = "high-noon" # pylint: disable=attribute-defined-outside-init
return end_game
def get_all_events(rng=random):
cards = [
Benedizione(),
Maledizione(),
CittaFantasma(),
CorsaAllOro(),
IDalton(),
IlDottore(),
IlReverendo(),
IlTreno(),
Sbornia(),
Sermone(),
Sete(),
Sparatoria(),
Manette(),
NuovaIdentita(),
]
rng.shuffle(cards)
for c in cards:
c.expansion = "high-noon" # pylint: disable=attribute-defined-outside-init
return cards

View File

@ -1,303 +0,0 @@
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
from globals import G, PendingAction
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)) or not self.can_be_used_now:
return False
if len(player.game.get_dead_players(include_ghosts=False)) > 0:
player.pending_action = 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,
"is_player": True,
}
for p in player.game.get_dead_players(include_ghosts=False)
]
self.can_be_used_now = 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, conta per il conteggio dei bang per turno
def play_card(self, player, against, _with=None):
if not self.can_be_used_now and player.game.check_event(ce.Lazo):
return False
if self.can_be_used_now:
if not super().play_card(player, against, _with):
return False
self.can_be_used_now = False
return True
elif not player.has_played_bang and any(
(
player.get_sight() >= p["dist"]
for p in player.game.get_visible_players(player)
)
):
player.set_choose_action("choose_play_as_bang", player.hand.copy())
player.notify_self()
return False
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)) or not self.can_be_used_now:
return False
if against is not None:
self.can_be_used_now = False
G.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)) or not self.can_be_used_now:
return False
if against is not None:
self.can_be_used_now = False
G.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 is not 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):
super().play_card(player, against=against)
player.game.discard_others(player, card_name=self.name)
return True
class Sventagliata(
Bang
): # : conta come un normale BANG! del turno. Il BANG! secondario è obbligatorio ed è sparato anche se il primo viene annullato, se si può, tu sei escluso come target
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 is not None:
if player.has_played_bang:
return False
t = player.game.get_player_named(against)
player.available_cards = [
dict(p, **{"original_target": against})
for p in player.game.get_visible_players(t)
if p["name"] != player.name and p["name"] != t.name and p["dist"]
]
if len(player.available_cards) > 0:
player.pending_action = PendingAction.CHOOSE
player.choose_text = "choose_sventagliata"
else:
player.available_cards = []
super().play_card(player, against=against)
return True
return False
class Salvo(Card): # puoi anche prevenire un danno inferto da te, duello?
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 is not 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.need_with_only = "Bang"
self.alt_text = "💥🃏💔💔"
self.need_target = True
self.need_with = True
def play_card(self, player, against, _with=None):
if against is not None and _with is not None and _with.name == "Bang!":
super().play_card(player, against=against, _with=_with)
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):
super().play_card(player, against=against)
player.game.discard_others(player, card_name=self.name)
return True
class Fuga(
Card
): # comprende indiani gatling etc, ma solo se carte marroni, le carte verdi valgono, attenzione alla classi ereditate
def __init__(self, suit, number):
super().__init__(suit, "Fuga", number)
self.icon = "🏃🏻"
self.alt_text = ""
def play_card(self, player, against, _with=None):
return False
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):
super().play_card(player, against=against)
player.game.discard_others(player, card_name=self.name)
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
RitornoDiFiamma(Suit.CLUBS, "Q"), # un mancato che fa bang
Tornado(Suit.CLUBS, "A"),
]
for c in cards:
c.expansion_icon = "👻️"
return cards

View File

@ -1,125 +0,0 @@
from typing import List
from bang.characters import Character
import bang.cards as cs
class BlackFlower(Character):
"""Una volta nel tuo turno, puoi usare una carta di fiori per sparare un BANG! extra."""
def __init__(self):
super().__init__("Black Flower", max_lives=4)
self.icon = "🥀"
def special(self, player, data): # fiori = suit.Clubs
if player.special_use_count > 0 or not any(
(c.suit == cs.Suit.CLUBS for c in player.hand)
):
return False
if any(
(
player.get_sight() >= p["dist"]
for p in player.game.get_visible_players(player)
)
) and super().special(player, data):
player.special_use_count += 1
player.set_choose_action(
"choose_play_as_bang",
[c for c in player.hand if c.suit == cs.Suit.CLUBS],
)
player.notify_self()
class ColoradoBill(Character):
"""Ogni volta che giochi una carta BANG!, "estrai!": se è Picche, il colpo non può essere evitato.
Whenever you play a BANG! card, "draw!": if it's a Spade, the shot can't be avoided.
"""
def __init__(self):
super().__init__("Colorado Bill", max_lives=4)
self.icon = "♠️"
class DerSpotBurstRinger(Character):
"""Una volta nel tuo turno, puoi usare una carta BANG! come Gatling.
Once per turn, you can use a BANG! card as a Gatling."""
def __init__(self):
super().__init__("Der Spot Burst Ringer", max_lives=4)
self.icon = "🫧"
def special(self, player, data):
if (
player.special_use_count == 0
and any((c.name == "Bang!" for c in player.hand))
and super().special(player, data)
):
player.special_use_count += 1
# get cards from hand of type Bang and sort them by suit
cards = sorted(
[c for c in player.hand if c.name == "Bang!"], key=lambda c: c.suit
)
player.hand.remove(cards[0])
player.game.deck.scrap(cards[0], True, player=player)
player.notify_self()
player.game.attack_others(player, cs.Gatling(0, 0).name)
class EvelynShebang(Character):
"""Puoi rinunciare a pescare carte nella tua fase di pesca. Per ogni carta non pescata, spari un BANG! a distanza raggiungibile, a un diverso bersaglio."""
def __init__(self):
super().__init__("Evelyn Shebang", max_lives=4)
self.icon = "📵"
class HenryBlock(Character):
"""Chiunque peschi o scarti una tua cartain gioco o in mano) è bersaglio di un BANG!"""
def __init__(self):
super().__init__("Henry Block", max_lives=4)
self.icon = "🚯"
class LemonadeJim(Character):
"""Ogni volta che un altro giocatore gioca una Birra, puoi scartare una carta dalla mano per riguadagnare anche tu 1 punto vita."""
def __init__(self):
super().__init__("Lemonade Jim", max_lives=4)
self.icon = "🍋"
class MickDefender(Character):
"""Se sei bersaglio di una carta marrone (non BANG!), puoi usare una carta Mancato! evitarne 1 gli effetti."""
def __init__(self):
super().__init__("Mick Defender", max_lives=4)
self.icon = ""
class TucoFranziskaner(Character):
"""Durante la tua fase di pesca, se non hai carte blu in gioco, pesca 2 carte extra."""
def __init__(self):
super().__init__("Tuco Franziskaner", max_lives=4)
self.icon = "🥬"
def all_characters() -> List[Character]:
cards = [
BlackFlower(),
ColoradoBill(),
DerSpotBurstRinger(),
# EvelynShebang(),
HenryBlock(),
# LemonadeJim(),
MickDefender(),
TucoFranziskaner(),
]
for card in cards:
card.expansion_icon = "👻️" # pylint: disable=attribute-defined-outside-init
card.expansion = ( # pylint: disable=attribute-defined-outside-init
"the_valley_of_shadows"
)
return cards

View File

@ -1,315 +0,0 @@
from typing import TYPE_CHECKING
import bang.cards as cs
from globals import PendingAction
if TYPE_CHECKING:
from bang.players import Player
class StationCard:
def __init__(self, name: str):
self.name = name
self.expansion = "train_robbery"
self.price: list[dict] = []
self.attached_train = None
def discard_and_buy_train(self, player: "Player", card_index: int):
"""Discard the card and buy the train"""
if self.attached_train is None:
return
card = player.available_cards.pop(card_index)
for i, card in enumerate(player.hand):
if card == self:
player.hand.pop(i)
break
else:
player.lives -= 1
card = player.hand.pop(card_index)
player.game.deck.scrap(card, True, player=player)
player.equipment.append(self.attached_train)
self.attached_train = None
player.pending_action = PendingAction.PLAY
def check_price(self, player: "Player") -> bool:
"""Check if the card can be used to rob the train"""
return len(player.hand) > 0
class BoomTown(StationCard):
"""Discard a Bang! to rob the train"""
def __init__(self):
super().__init__("Boom Town")
self.price = [cs.Bang(0, 0).__dict__]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
not isinstance(c, cs.Bang) for c in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if isinstance(c, cs.Bang)],
self.discard_and_buy_train,
)
return True
class Caticor(StationCard):
"""Discard a Cat Balou or Panico to rob the train"""
def __init__(self):
super().__init__("Caticor")
self.price = [cs.CatBalou(0, 0).__dict__, cs.Panico(0, 0).__dict__]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
not (isinstance(card, cs.CatBalou) or isinstance(card, cs.Panico))
for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[
c
for c in player.hand
if isinstance(c, cs.CatBalou) or isinstance(c, cs.Panico)
],
self.discard_and_buy_train,
)
return True
class CreepyCreek(StationCard):
"""Discard a card of spades to rob the train"""
def __init__(self):
super().__init__("Creepy Creek")
self.price = [{"icon": "♠️"}]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
card.suit != cs.Suit.SPADES for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if c.suit == cs.Suit.SPADES],
self.discard_and_buy_train,
)
return True
class CrownsHole(StationCard):
"""Discard a beer to rob the train"""
def __init__(self):
super().__init__("Crowns Hole")
self.price = [cs.Birra(0, 0).__dict__]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
not isinstance(card, cs.Birra) for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if isinstance(c, cs.Birra)],
self.discard_and_buy_train,
)
return True
class Deadwood(StationCard):
"""Discard an equipment card to rob the train"""
def __init__(self):
super().__init__("Deadwood")
self.price = [{"is_equipment": True}]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
not card.is_equipment for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if c.is_equipment],
self.discard_and_buy_train,
)
return True
class Dodgeville(StationCard):
"""Discard a Missed! to rob the train"""
def __init__(self):
super().__init__("Dodgeville")
self.price = [cs.Mancato(0, 0).__dict__]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
not isinstance(card, cs.Mancato) for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if isinstance(c, cs.Mancato)],
self.discard_and_buy_train,
)
return True
class FortWorth(StationCard):
"""Discard a card with number 10, J, Q, K, A to rob the train"""
def __init__(self):
super().__init__("Fort Worth")
self.price = [{"icon": "10\nJ\nQ\nK\nA"}]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
card.number not in {1, 10, 11, 12, 13} for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if c.number in {1, 10, 11, 12, 13}],
self.discard_and_buy_train,
)
return True
class Frisco(StationCard):
"""Discard a card of clubs to rob the train"""
def __init__(self):
super().__init__("Frisco")
self.price = [{"icon": "♣️"}]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
card.suit != cs.Suit.CLUBS for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if c.suit == cs.Suit.CLUBS],
self.discard_and_buy_train,
)
return True
class MinersOath(StationCard):
"""Discard a card of diamonds to rob the train"""
def __init__(self):
super().__init__("Miners Oath")
self.price = [{"icon": "♦️"}]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
card.suit != cs.Suit.DIAMONDS for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if c.suit == cs.Suit.DIAMONDS],
self.discard_and_buy_train,
)
return True
class SanTafe(StationCard):
"""Discard a card of hearts to rob the train"""
def __init__(self):
super().__init__("San Tafe")
self.price = [{"icon": "♥️"}]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
card.suit != cs.Suit.HEARTS for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if c.suit == cs.Suit.HEARTS],
self.discard_and_buy_train,
)
return True
class Tombrock(StationCard):
"""Lose 1 life point to rob the train"""
def __init__(self):
super().__init__("Tombrock")
self.price = [{"icon": "💔"}]
def check_price(self, player: "Player"):
if player.lives <= 1:
return False
player.set_choose_action(
"choose_buy_train",
[{"icon": "💔"}],
self.discard_and_buy_train,
)
return True
class Yooma(StationCard):
"""Discard a card with number between 2 and 9 to rob the train"""
def __init__(self):
super().__init__("Yooma")
self.price = [{"icon": "2-9"}]
def check_price(self, player: "Player"):
if super().check_price(player) and all(
not (2 <= card.number <= 9) for card in player.hand
):
return False
player.set_choose_action(
"choose_buy_train",
[c for c in player.hand if 2 <= c.number <= 9],
self.discard_and_buy_train,
)
return True
class VirginiaTown(StationCard):
"""Discard two cards to rob the train"""
def __init__(self):
super().__init__("Virginia Town")
self.price = [{}, {}]
def check_price(self, player: "Player"):
if super().check_price(player) and len(player.hand) < 2:
return False
player.set_choose_action(
"choose_buy_train",
player.hand.copy(),
self.discard_and_buy_train,
)
return True
def get_all_stations():
"""Return a list of all the station cards"""
return [
BoomTown(),
Caticor(),
CreepyCreek(),
CrownsHole(),
Deadwood(),
Dodgeville(),
FortWorth(),
Frisco(),
MinersOath(),
SanTafe(),
Tombrock(),
Yooma(),
VirginiaTown(),
]

View File

@ -1,406 +0,0 @@
import random
from bang.cards import Card, Bang, Panico, CatBalou, Mancato
from typing import TYPE_CHECKING
from globals import G, PendingAction
if TYPE_CHECKING:
from bang.players import Player
class TrainCard(Card):
def __init__(self, name: str, is_locomotive: bool = False):
super().__init__(suit=5, number=0, name=name)
self.expansion_icon = "🚂"
self.is_equipment = True
self.is_locomotive = is_locomotive
self.expansion = "train_robbery"
self.type = "train"
self.implemented = True
# Circus Wagon: gli altri giocatori
# scartano una carta, in senso orario, a
# partire dal giocatore alla tua sinistra.
# Express Car: non puoi svolgere
# un altro turno extra dopo quello
# ottenuto da questo effetto, anche se
# riesci a giocare di nuovo Express Car.
# Ghost Car: giocabile su un
# qualsiasi giocatore, anche se già
# eliminato, te compreso. Non può
# essere giocato sullo Sceriffo.
# Se quel giocatore è/viene eliminato,
# invece ritorna/resta in gioco, senza
# punti vita. Non può guadagnare né
# perdere punti vita, e viene considerato
# un personaggio in gioco per tutti gli
# effetti (condizioni di vittoria, distanza
# tra giocatori, abilità dei personaggi,
# ecc.). Non avendo punti vita, deve
# scartare la sua intera mano alla fine
# del turno, ma può tenere qualsiasi
# carta in gioco di fronte a sé, incluso
# Ghost Car. Tuttavia, è eliminato
# dal gioco non appena Ghost Car
# viene scartato: nessuna ricompensa
# viene assegnata in questo caso se il
# giocatore è un Fuorilegge, e le abilità
# dei personaggi (ad es. Vulture Sam)
# non si attivano
# Lounge Car: i vagoni che peschi
# non contano per il normale limite di
# acquisizione di 1 vagone per turno. Se
# sei lo Sceriffo e peschi Ghost Car, devi
# darlo a un altro giocatore.
# Lumber Flatcar: gioca su un
# qualsiasi giocatore (compreso te).
# Finché questa carta è in gioco, quel
# giocatore vede gli altri giocatori a
# distanza aumentata di 1.
# Private Car: questo effetto non
# ti protegge da Gatling, Knife Revolver,
# labilità di Evan Babbit, e così via.
# Sleeper Car: puoi anche usare
# leffetto una volta per turno con
# Indiani!, Duello, ecc.
class Ironhorse(TrainCard):
"""LOCOMOTIVA:
Ogni giocatore, incluso colui che ha attivato l'effetto, è bersaglio di un BANG!
Nessun giocatore è responsabile dell'eventuale perdita di punti vita.
Se tutti i giocatori vengono eliminati allo stesso tempo, i Fuorilegge vincono.
"""
def __init__(self):
super().__init__("Ironhorse", is_locomotive=True)
self.icon = "🚂"
def play_card(self, player, against=None, _with=None) -> bool:
player.game.attack(player, player.name, card_name=self.name)
player.game.attack_others(player, card_name=self.name)
return True
class Leland(TrainCard):
"""
LOCOMOTIVA: svolgi l'effetto dell'Emporio, cominciando dal giocatore di turno e procedendo in senso orario.
"""
def __init__(self):
super().__init__("Leland", is_locomotive=True)
self.icon = "🚂"
def play_card(self, player, against=None, _with=None) -> bool:
player.game.emporio(player)
return True
class BaggageCar(TrainCard):
"""Scartalo: ottieni l'effetto di un Mancato!, Panico!, Cat Balou o di un BANG! extra.
Discard this for a Missed! Panic!, Cat Balou, or an extra BANG!"""
def __init__(self):
super().__init__("Baggage Car")
self.icon = "🚋🛄"
def choose_callback(self, player: 'Player', card_index):
player.hand.append(player.available_cards[card_index])
player.pending_action = PendingAction.PLAY
def play_card(self, player, against=None, _with=None) -> bool:
player.set_choose_action(
"choose_baggage_car",
[Bang(4, 42), Mancato(4, 42), CatBalou(4, 42), Panico(4, 42)],
self.choose_callback,
)
return True
class Caboose(TrainCard):
"""Puoi scartare un altra tua carta bordo blu incuso un vagone come se fosse un Mancato!"""
def __init__(self):
super().__init__("Caboose")
self.icon = "🚋"
def play_card(self, player, against=None, _with=None) -> bool:
return False
class CattleTruck(TrainCard):
"""Scartalo: guarda le 3 carte in cima agli scarti e pescane I"""
def __init__(self):
super().__init__("Cattle Truck")
self.icon = "🚋🐄"
def choose_card_callback(self, player: 'Player', card_index):
chosen_card = player.available_cards.pop(card_index)
player.game.deck.scrap_pile.pop(-card_index)
player.hand.append(chosen_card)
player.pending_action = PendingAction.PLAY
player.notify_self()
def play_card(self, player, against=None, _with=None) -> bool:
drawn_cards = player.game.deck.peek_scrap_pile(n_cards=3)
player.set_choose_action(
"choose_cattle_truck",
drawn_cards,
self.choose_card_callback,
)
return True
class CircusWagon(TrainCard):
"""Scartalo: ogni altro giocatore deve scartare una carta che ha in gioco."""
def __init__(self):
super().__init__("Circus Wagon", is_locomotive=True)
self.icon = "🚋🎪"
def play_card(self, player, against=None, _with=None) -> bool:
player.game.discard_others(player, card_name=self.name)
return True
@classmethod
def choose_circus_wagon(cls, player: 'Player', card_index):
player.game.deck.scrap(player.hand.pop(card_index), player=player)
player.pending_action = PendingAction.WAIT
player.game.responders_did_respond_resume_turn()
player.notify_self()
class CoalHopper(TrainCard):
"""Scartalo: pesca una carta e scarta un vagone in gioco davanti a un giocatore a tua scelta."""
def __init__(self):
super().__init__("Coal Hopper")
self.icon = "🚋🔥"
self.need_target = True
def play_card(self, player, against=None, _with=None) -> bool:
if against is not None and len(player.game.get_player_named(against).equipment) > 0:
player.game.steal_discard(player, against, self)
return True
class DiningCar(TrainCard):
"""A inizio turno, "estrai!": se è Cuori, recuperi I punto vita."""
def __init__(self):
super().__init__("Dining Car")
self.icon = "🚋🍽"
def play_card(self, player, against=None, _with=None) -> bool:
return False
class ExpressCar(TrainCard):
"""Scarta tutte le carte in mano, poi gioca un altro turno"""
def __init__(self):
super().__init__("Express Car")
self.icon = "🚋⚡"
def play_card(self, player, against=None, _with=None) -> bool:
while len(player.hand) > 0:
player.game.deck.scrap(player.hand.pop(0), player=player)
player.notify_self()
player.play_turn()
return True
class GhostCar(TrainCard):
"""Giocalo su chiunque tranne lo Sceritfo. Se vieni eliminato, invece resta in gioco, ma non puo guadagnare ne perdere punti vita."""
def __init__(self):
super().__init__("Ghost Car")
self.icon = "🚋👻"
self.implemented = False
def play_card(self, player, against=None, _with=None) -> bool:
return False
class LoungeCar(TrainCard):
"""Scartalo: pesca 2 vagoni dal mazzo, mettine I in gioco di fronte a te e 1 di fronte a un altro giocatore."""
def __init__(self):
super().__init__("Lounge Car")
self.icon = "🚋🛋"
self.implemented = False
def play_card(self, player, against=None, _with=None) -> bool:
return True
class LumberFlatcar(TrainCard):
"""Giocalo su un qualsiasi giocatore (compreso te). Finché questa carta è in gioco, quel giocatore vede gli altri giocatori a distanza aumentata di 1."""
def __init__(self):
super().__init__("Lumber Flatcar")
self.icon = "🚋🪵"
self.sight_mod = -1
def play_card(self, player, against=None, _with=None) -> bool:
return False
class MailCar(TrainCard):
"""Scartalo: pesca 3 carte e dai 1 di esse a un altro giocatore a tua scelta."""
def __init__(self):
super().__init__("Mail Car")
self.icon = "🚋📮"
def choose_card_callback(self, player: 'Player', card_index):
chosen_card = player.available_cards.pop(card_index)
player.hand.extend(player.available_cards)
player.set_choose_action(
"choose_other_player",
player.game.get_other_players(player),
lambda p, other_player_index: self.choose_player_callback(p, other_player_index, chosen_card)
)
def choose_player_callback(self, player: 'Player', other_player_index, chosen_card):
pl_name = player.game.get_other_players(player)[other_player_index]["name"]
other_player = player.game.get_player_named(pl_name)
other_player.hand.append(chosen_card)
G.sio.emit(
"card_drawn",
room=player.game.name,
data={"player": pl_name, "pile": player.name},
)
other_player.notify_self()
player.pending_action = PendingAction.PLAY
def play_card(self, player, against=None, _with=None) -> bool:
drawn_cards = [player.game.deck.draw(player=player) for _ in range(3)]
player.set_choose_action(
"choose_mail_car",
drawn_cards,
self.choose_card_callback,
)
return True
class ObservationCar(TrainCard):
"""Tu vedi gli altri a distanza -1. Gli altri a vedono a distanza +1."""
def __init__(self):
super().__init__("Observation Car")
self.icon = "🚋👀"
self.sight_mod = 1
self.vis_mod = 1
def play_card(self, player, against=None, _with=None) -> bool:
return False
class PassengerCar(TrainCard):
"""Scartalo: pesca una carta (o in mano o in gioco) da un altro giocatore"""
def __init__(self):
super().__init__("Passenger Car")
self.icon = "🚋🚶"
self.range = 99
self.need_target = True
def play_card(self, player, against=None, _with=None) -> bool:
if (
against is not None
and (len(player.equipment) > 0 or len(player.equipment) > 0)
):
player.game.steal_discard(player, against, self)
return True
return False
class PrisonerCar(TrainCard):
"""Le carte Duello e Indiani! giocate dagli altri giocatori non hanno effetto su di te."""
def __init__(self):
super().__init__("Prisoner Car")
self.icon = "🚋👮🏻‍♂️"
def play_card(self, player, against=None, _with=None) -> bool:
return False
class PrivateCar(TrainCard):
"""Se non hai carte in mano, non puoi essere bersaglio di carte BANG"""
def __init__(self):
super().__init__("Private Car")
self.icon = "🚋💁🏻"
def play_card(self, player, against=None, _with=None) -> bool:
return False
class SleeperCar(TrainCard):
"""Una volta per turno, puoi scartare un'altra tua carta a bordo blu incluso."""
def __init__(self):
super().__init__("Sleeper Car")
self.icon = "🚋🛌"
def choose_card_callback(self, player: 'Player', card_index):
player.game.deck.scrap(player.equipment.pop(card_index), player=player)
player.pending_action = PendingAction.PLAY
self.usable_next_turn = True
self.can_be_used_now = False
player.notify_self()
def play_card(self, player, against=None, _with=None) -> bool:
if not self.can_be_used_now:
return False
player.set_choose_action(
"choose_sleeper_car",
player.equipment,
self.choose_card_callback,
)
return False
def get_all_cards(rng=random):
"""Return a list of all train cards in the expansion"""
cars = [
BaggageCar(),
Caboose(),
CattleTruck(),
CircusWagon(),
CoalHopper(),
DiningCar(),
ExpressCar(),
GhostCar(),
LoungeCar(),
LumberFlatcar(),
MailCar(),
ObservationCar(),
PassengerCar(),
PrisonerCar(),
PrivateCar(),
SleeperCar(),
]
cars = [c for c in cars if c.implemented]
rng.shuffle(cars)
return cars
def get_locomotives(rng=random):
"""Return a list of all locomotive cards in the expansion"""
locs = [
Ironhorse(),
Leland(),
]
rng.shuffle(locs)
return locs

View File

@ -1,178 +0,0 @@
import random
import bang.cards as cs
import bang.roles as roles
import bang.players as players
from globals import G
from bang.expansions.fistful_of_cards.card_events import CardEvent
class WildWestShowCardEvent(CardEvent):
"""
Base class for all card events in the Wild West Show expansion
"""
def __init__(self, name, icon):
super().__init__(name, icon)
self.expansion = "wild-west-show"
# class Bavaglio(CardEvent):
# def __init__(self):
# super().__init__("Bavaglio", "🤐")
# # I giocatori non possono parlare (ma possono gesticolare, mugugnare...). Chi parla perde 1 punto vita.
# # NOT IMPLEMENTED
class Camposanto(WildWestShowCardEvent):
"""
All'inizio del proprio turno, ogni giocatore eliminato torna in gioco con 1 punto vita. Pesca il ruolo a caso fra quelli dei giocatori eliminati.
"""
def __init__(self):
super().__init__("Camposanto", "")
class DarlingValentine(WildWestShowCardEvent):
"""
All'inizio del proprio turno, ogni giocatore scarta le carte in mano e ne pesca dal mazzo altrettante.
"""
def __init__(self):
super().__init__("Darling Valentine", "💋")
class DorothyRage(WildWestShowCardEvent):
"""
Nel proprio turno, ogni giocatore può obbligarne un altro a giocare una carta.
"""
def __init__(self):
super().__init__("Dorothy Rage", "👩‍⚖️")
class HelenaZontero(WildWestShowCardEvent):
"""
Quando Helena entra in gioco, "estrai!": se esce Cuori o Quadri, rimescola i ruoli attivi tranne lo Sceriffo, e ridistribuiscili a caso.
"""
def __init__(self):
super().__init__("Helena Zontero", "💞")
def on_flipped(self, game):
c = game.deck.pick_and_scrap()
G.sio.emit(
"chat_message",
room=game.name,
data=f"_flipped|Helena Zontero|{c.name}|{c.num_suit()}",
)
if c.check_suit(game, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]):
G.sio.emit(
"chat_message",
room=game.name,
data=f"_swapped_roles|Helena Zontero|{c.name}|{c.num_suit()}",
)
pls = [p for p in game.players if not isinstance(p.role, roles.Sheriff)]
newroles = [p.role for p in pls]
game.rng.shuffle(newroles)
for p in pls:
p.set_role(newroles.pop(game.rng.randint(0, len(newroles) - 1)))
return super().on_flipped(game)
class LadyRosaDelTexas(WildWestShowCardEvent):
"""
Nel proprio turno, ogni giocatore può scambiarsi di posto con quello alla sua destra, il quale salta il prossimo turno.
"""
def __init__(self):
super().__init__("Lady Rosa del Texas", "🩰")
def on_clicked(self, game, player):
super().on_clicked(game, player)
nextp = game.next_player()
i, j = game.players_map[player.name], game.players_map[nextp.name]
game.players[i], game.players[j] = nextp, player
game.players_map[player.name], game.players_map[nextp.name] = j, i
game.turn = j
game.notify_all()
class MissSusanna(WildWestShowCardEvent):
"""
Nel proprio turno ogni giocatore deve giocare almeno 3 carte. Se non lo fa, perde 1 punto vita.
"""
def __init__(self):
super().__init__("Miss Susanna", "👩‍🎤")
class RegolamentoDiConti(WildWestShowCardEvent):
"""
Tutte le carte possono essere giocate come se fossero BANG!. Le carte BANG! come se fossero Mancato!
"""
def __init__(self):
super().__init__("Regolamento di Conti", "🤠")
def on_clicked(self, game, player):
super().on_clicked(game, player)
if (
len(player.hand) > 0
and not player.has_played_bang
and any(
(
player.get_sight() >= p["dist"]
for p in game.get_visible_players(player)
)
)
):
player.available_cards = player.hand.copy()
player.pending_action = players.PendingAction.CHOOSE
player.choose_text = "choose_play_as_bang"
player.notify_self()
class Sacagaway(WildWestShowCardEvent):
"""
Tutti i giocatori giocano a carte scoperte (tranne il ruolo!).
"""
def __init__(self):
super().__init__("Sacagaway", "🌄")
class WildWestShow(WildWestShowCardEvent):
"""
L'obiettivo di ogni giocatore diventa: "Rimani l'ultimo in gioco!"
"""
def __init__(self):
super().__init__("Wild West Show", "🎪")
def on_flipped(self, game):
for player in game.players:
player.set_role(roles.Renegade())
return super().on_flipped(game)
def get_endgame_card():
"""Return the endgame card for this expansion"""
return WildWestShow()
def get_all_events(rng=random):
"""Return all the events for this expansion shuffled, excluding the endgame card"""
cards = [
Camposanto(),
DarlingValentine(),
# DorothyRage(),
HelenaZontero(),
LadyRosaDelTexas(),
MissSusanna(),
RegolamentoDiConti(),
Sacagaway(),
]
rng.shuffle(cards)
return cards

View File

@ -1,144 +0,0 @@
from typing import List
import bang.cards as cs
from bang.characters import Character
class BigSpencer(Character):
"""
Inizia con 5 carte. Non può giocare Mancato!
"""
def __init__(self):
super().__init__("Big Spencer", max_lives=9)
self.icon = "🫘"
class FlintWestwood(Character):
"""
Nel suo turno può scambiare una carta dalla mano con 2 carte a caso dalla mano di un altro giocatore.
> NOTE: La carta dalla tua mano è a scelta, non a caso. Se il giocatore bersaglio ha una sola carta, ne ricevi solo una.
"""
def __init__(self):
super().__init__("Flint Westwood", max_lives=4)
self.icon = "🔫"
def special(self, player, data):
if (
not player.is_my_turn
or not any((len(p.hand) > 0 for p in player.game.get_alive_players()))
or not super().special(player, data)
):
return False
import bang.players as pls
player.available_cards = player.hand.copy()
player.choose_text = "choose_flint_special"
player.pending_action = pls.PendingAction.CHOOSE
player.special_use_count += 1
player.notify_self()
class GaryLooter(Character):
"""
Pesca tutte le carte in eccesso scartate dagli altri giocatori a fine turno.
"""
def __init__(self):
super().__init__("Gary Looter", max_lives=5)
self.icon = "🥲"
class GreygoryDeckard(Character):
"""
All'inizio del suo turno può pescare 2 personaggi a caso. Ha tutte le abilità dei personaggi pescati.
"""
def __init__(self):
super().__init__("Greygory Deckard", max_lives=4)
self.icon = "👨‍🦳"
class JohnPain(Character):
"""
Se ha meno di 6 carte in mano, quando un giocatore "estrae!" John aggiunge alla mano la carta appena estratta.
"""
def __init__(self):
super().__init__("John Pain", max_lives=4)
self.icon = "🤕"
class LeeVanKliff(Character):
"""
Nel suo turno, può scartare un BANG! per ripetere l'effetto di una carta a bordo marrone che ha appena giocato.
"""
def __init__(self):
super().__init__("Lee Van Kliff", max_lives=4)
self.icon = "👨‍🦲"
def special(self, player, data):
if player.last_played_card is None:
return False
if (
player.last_played_card.is_equipment
or player.last_played_card.usable_next_turn
or player.last_played_card.number == 42
or not any(isinstance(c, cs.Bang) for c in player.hand)
or not super().special(player, data)
):
return False
bang_index = next(
(i for i, card in enumerate(player.hand) if isinstance(card, cs.Bang)), -1
)
bang_card = player.hand.pop(bang_index)
print(f"{bang_card=}")
player.game.deck.scrap(bang_card, player=player)
player.last_played_card.must_be_used = True
player.last_played_card.number = 42
player.hand.append(player.last_played_card)
print(f"{player.hand=}")
player.notify_self()
class TerenKill(Character):
"""
Ogni volta che sarebbe eliminato "estrai!": se non è Picche, Teren resta a 1 punto vita e pesca 1 carta.
"""
def __init__(self):
super().__init__("Teren Kill", max_lives=3)
self.icon = "👨‍🦰"
class YoulGrinner(Character):
"""
Prima di pescare, i giocatori con più carte in mano di lui devono dargli una carta a scelta.
"""
def __init__(self):
super().__init__("Youl Grinner", max_lives=4)
self.icon = "🤡"
def all_characters() -> List[Character]:
"""
Returns a list of all characters in this expansion.
"""
cards = [
BigSpencer(),
FlintWestwood(),
GaryLooter(),
# GreygoryDeckard(),
JohnPain(),
LeeVanKliff(),
TerenKill(),
YoulGrinner(),
]
for card in cards:
card.expansion_icon = "🎪" # pylint: disable=attribute-defined-outside-init
card.expansion = ( # pylint: disable=attribute-defined-outside-init
"wild_west_show"
)
return cards

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,90 +0,0 @@
from abc import ABC, abstractmethod
class Role(ABC):
def __init__(self, name: str, goal: str, health_mod: int = 0):
super().__init__()
self.name = name
self.goal = goal
self.health_mod = health_mod
@abstractmethod
def on_player_death(self, alive_players: list, initial_players: int, dead_role=None, attacker_role=None):
pass
class Sheriff(Role):
def __init__(self):
super().__init__("Sceriffo", "Elimina tutti i Fuorilegge e il Rinnegato!", health_mod=+1)
self.goal_eng = "Kill the Outlaws and the Renegade!"
self.max_players = 1
self.icon = '⭐️'
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)):
print("The Sheriff won!")
return True
return False
class Vice(Role):
def __init__(self, alternative_goal=None, alternative_goal_eng=None):
super().__init__("Vice", "Proteggi lo Sceriffo! Elimina tutti i Fuorilegge e il Rinnegato!")
self.goal_eng = "Protect the Sheriff! Kill the Outlaws and the Renegade!"
if alternative_goal:
self.goal = alternative_goal
self.goal_eng = alternative_goal_eng
self.max_players = 2
self.icon = '🎖'
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 attacker_role is not 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)):
print("The Vice won!")
return True
return False
class Outlaw(Role):
def __init__(self, alternative_goal=None, alternative_goal_eng=None):
super().__init__("Fuorilegge", "Elimina lo Sceriffo!")
self.goal_eng = "Kill the Sheriff!"
if alternative_goal:
self.goal = alternative_goal
self.goal_eng = alternative_goal_eng
self.max_players = 3
self.icon = '🐺'
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 attacker_role is not 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)):
print("The Outlaw won!")
return True
return False
class Renegade(Role):
def __init__(self, alternative_goal=None, alternative_goal_eng=None):
super().__init__("Rinnegato", "Rimani l'ultimo personaggio in gioco!")
self.goal_eng = "Be the last man standing!"
if alternative_goal:
self.goal = alternative_goal
self.goal_eng = alternative_goal_eng
self.max_players = 1
self.icon = '🦅'
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 attacker_role is not 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):
print("The Renegade won!")
return True
return False

252
backend/cards.py Normal file
View File

@ -0,0 +1,252 @@
from typing import List, Set, Dict, Tuple, Optional
from abc import ABC, abstractmethod
from enum import IntEnum
class Suit(IntEnum):
DIAMONDS = 0 # ♦
CLUBS = 1 # ♣
HEARTS = 2 # ♥
SPADES = 3 # ♠
class Card(ABC):
sym = {
'A': 1,
'J': 11,
'Q': 12,
'K': 13
}
def __init__(self, suit: Suit, name: str, number, is_equipment:bool=False, is_weapon:bool=False, vis_mod:int=0, sight_mod:int=0, range:int=99, desc:str=''):
super().__init__()
self.name = name
self.suit = suit
if type(number) == int:
self.number = number
else:
self.number = self.sym[number]
self.is_equipment = is_equipment
self.is_weapon = is_weapon
self.vis_mod = vis_mod
self.sight_mod = sight_mod
self.range = range
self.desc = desc
self.need_target = False
def __str__(self):
char = ['♦️','♣️','♥️','♠️'][int(self.suit)]
return f'{self.name} {char}{self.number}'
return super().__str__()
class Barile(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Barile', number, is_equipment=True)
self.icon = '🛢'
self.desc = "Quando sei bersagliato da un Bang puoi estrarre la prima carta dalla cima del mazzo, se la carta estratta è del seme Cuori allora vale come un Mancato"
class Dinamite(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Dinamite', number, is_equipment=True)
self.icon = '🧨'
self.desc = "Giocando la Dinamite, posizionala davanti a te, resterà innocua per un intero giro. All'inizio del prossimo turno prima di pescare e prima di una eventuale estrazione (es. Prigione), estrai una carta dalla cima del mazzo. Se esce una carta tra il 2 il 9 di picche (compresi) allora la dinamite esplode: perdi 3 vite e scarta la carta, altrimenti passa la dinamite al giocatore successivo, il quale estrarà a sua volta dopo che tu avrai passato il tuo turno"
class Mirino(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Mirino', number, is_equipment=True, sight_mod=1)
self.icon = '🔎'
self.desc = "Tu vedi gli altri giocatori a distanza -1"
class Mustang(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Mustang', number, is_equipment=True, vis_mod=1)
self.icon = '🐎'
self.desc = "Gli altri giocatori ti vedono a distanza +1"
class Prigione(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Prigione', number, is_equipment=True)
self.icon = ''
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
class Remington(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Remington', number, is_equipment=True, is_weapon=True, range=3)
self.icon = '🔫'
self.desc = "Puoi sparare a un giocatore che sia distante 3 o meno"
class RevCarabine(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Rev. Carabine', number, is_equipment=True, is_weapon=True, range=4)
self.icon = '🔫'
self.desc = "Puoi sparare a un giocatore che sia distante 4 o meno"
class Schofield(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Schofield', number, is_equipment=True, is_weapon=True, range=2)
self.icon = '🔫'
self.desc = "Puoi sparare a un giocatore che sia distante 2 o meno"
class Volcanic(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Volcanic', number, is_equipment=True, is_weapon=True, range=1)
self.icon = '🔫'
self.desc = "Puoi sparare a un giocatore che sia distante 1 o meno, tuttavia puoi giocare quanti bang vuoi"
class Winchester(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Winchester', number, is_equipment=True, is_weapon=True, range=5)
self.icon = '🔫'
self.desc = "Puoi sparare a un giocatore che sia distante 5 o meno"
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.need_target = True
class Birra(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Birra', number)
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"
class CatBalou(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Cat Balou', number)
self.icon = '💃'
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
class Diligenza(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Diligenza', number)
self.icon = '🚡'
self.desc = "Pesca 2 carte dalla cima del mazzo"
class Duello(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Duello', number)
self.need_target = True
self.icon = '⚔️'
class Emporio(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Emporio', number)
self.icon = '🏪'
class Gatling(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Gatling', number)
self.icon = '🛰'
class Indiani(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Indiani!', number)
self.icon = '🏹'
class Mancato(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Mancato!', number)
self.icon = '😅'
class Panico(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Panico!', number, range=1)
self.icon = '😱'
self.need_target = True
class Saloon(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Saloon', number)
self.desc = "Tutti i giocatori recuperano un punto vita compreso chi gioca la carta"
self.icon = '🍻'
class WellsFargo(Card):
def __init__(self, suit, number):
super().__init__(suit, 'WellsFargo', number)
self.desc = "Pesca 3 carte dalla cima del mazzo"
self.icon = '💸'
def get_starting_deck() -> List[Card]:
return [
Barile(Suit.SPADES, 'Q'),
Barile(Suit.SPADES, 'K'),
Dinamite(Suit.HEARTS, 2),
Mirino(Suit.SPADES, 'A'),
Mustang(Suit.HEARTS, 8),
Mustang(Suit.HEARTS, 9),
Prigione(Suit.SPADES, 'J'),
Prigione(Suit.HEARTS, 4),
Prigione(Suit.SPADES, 10),
Remington(Suit.CLUBS, 'K'),
RevCarabine(Suit.CLUBS, 'A'),
Schofield(Suit.CLUBS, 'J'),
Schofield(Suit.CLUBS, 'Q'),
Schofield(Suit.SPADES, 'K'),
Volcanic(Suit.SPADES, 10),
Volcanic(Suit.CLUBS, 10),
Winchester(Suit.SPADES, 8),
Bang(Suit.SPADES, 'A'),
Bang(Suit.DIAMONDS, 2),
Bang(Suit.DIAMONDS, 3),
Bang(Suit.DIAMONDS, 4),
Bang(Suit.DIAMONDS, 5),
Bang(Suit.DIAMONDS, 6),
Bang(Suit.DIAMONDS, 7),
Bang(Suit.DIAMONDS, 8),
Bang(Suit.DIAMONDS, 9),
Bang(Suit.DIAMONDS, 10),
Bang(Suit.DIAMONDS, 'J'),
Bang(Suit.DIAMONDS, 'Q'),
Bang(Suit.DIAMONDS, 'K'),
Bang(Suit.DIAMONDS, 'A'),
Bang(Suit.CLUBS, 2),
Bang(Suit.CLUBS, 3),
Bang(Suit.CLUBS, 4),
Bang(Suit.CLUBS, 5),
Bang(Suit.CLUBS, 6),
Bang(Suit.CLUBS, 7),
Bang(Suit.CLUBS, 8),
Bang(Suit.CLUBS, 9),
Bang(Suit.HEARTS, 'Q'),
Bang(Suit.HEARTS, 'K'),
Bang(Suit.HEARTS, 'A'),
Birra(Suit.HEARTS, 6),
Birra(Suit.HEARTS, 7),
Birra(Suit.HEARTS, 8),
Birra(Suit.HEARTS, 9),
Birra(Suit.HEARTS, 10),
Birra(Suit.HEARTS, 'J'),
CatBalou(Suit.HEARTS, 'K'),
CatBalou(Suit.DIAMONDS, 9),
CatBalou(Suit.DIAMONDS, 10),
CatBalou(Suit.DIAMONDS, 'J'),
Diligenza(Suit.SPADES, 9),
Diligenza(Suit.SPADES, 9),
Duello(Suit.DIAMONDS, 'Q'),
Duello(Suit.SPADES, 'J'),
Duello(Suit.CLUBS, 8),
Emporio(Suit.CLUBS, 9),
Emporio(Suit.SPADES, 'Q'),
Gatling(Suit.HEARTS, 10),
Indiani(Suit.DIAMONDS, 'K'),
Indiani(Suit.DIAMONDS, 'A'),
Mancato(Suit.CLUBS, 10),
Mancato(Suit.CLUBS, 'J'),
Mancato(Suit.CLUBS, 'Q'),
Mancato(Suit.CLUBS, 'K'),
Mancato(Suit.CLUBS, 'A'),
Mancato(Suit.SPADES, 2),
Mancato(Suit.SPADES, 3),
Mancato(Suit.SPADES, 4),
Mancato(Suit.SPADES, 5),
Mancato(Suit.SPADES, 6),
Mancato(Suit.SPADES, 7),
Mancato(Suit.SPADES, 8),
Panico(Suit.HEARTS, 'J'),
Panico(Suit.HEARTS, 'Q'),
Panico(Suit.HEARTS, 'A'),
Panico(Suit.DIAMONDS, 8),
Saloon(Suit.HEARTS, 5),
WellsFargo(Suit.HEARTS, 3),
]

118
backend/characters.py Normal file
View File

@ -0,0 +1,118 @@
from abc import ABC, abstractmethod
class Character(ABC):
def __init__(self, name: str, max_lives: int, sight_mod: int = 0, visibility_mod: int = 0, pick_mod: int = 0, desc: str = ''):
super().__init__()
self.name = name
self.max_lives = max_lives
self.sight_mod = 0
self.visibility_mod = 0
self.pick_mod = 0
self.desc = desc
self.icon = '🤷‍♂️'
self.number = ''.join(['❤️']*self.max_lives)
# @abstractmethod
# def on_hurt(self, dmg: int):
# pass
# @abstractmethod
# def on_pick(self, card): # tipo dinamite e prigione
# pass
# @abstractmethod
# def on_empty_hand(self):
# pass
# @abstractmethod
# def on_empty_hand(self):
# pass
class BartCassidy(Character):
def __init__(self):
super().__init__("Bart Cassidy", max_lives=4, desc='Ogni volta che viene ferito, pesca una carta.')
def on_hurt(self, dmg):
pass
class BlackJack(Character):
def __init__(self):
super().__init__("Black Jack", max_lives=4)
class CalamityJanet(Character):
def __init__(self):
super().__init__("Calamity Janet", max_lives=4)
self.icon = '🤷‍♀️'
class ElGringo(Character):
def __init__(self):
super().__init__("El Gringo", max_lives=3)
class JesseJones(Character):
def __init__(self):
super().__init__("Jesse Jones", max_lives=4)
class Jourdonnais(Character):
def __init__(self):
super().__init__("Jourdonnais", max_lives=4)
class KitCarlson(Character):
def __init__(self):
super().__init__("Kit Carlson", max_lives=4)
class LuckyDuke(Character):
def __init__(self):
super().__init__("Lucky Duke", max_lives=4, pick_mod=1)
class PaulRegret(Character):
def __init__(self):
super().__init__("Paul Regret", max_lives=3)
class PedroRamirez(Character):
def __init__(self):
super().__init__("Pedro Ramirez", max_lives=4)
class RoseDoolan(Character):
def __init__(self):
super().__init__("Rose Doolan", max_lives=4)
self.icon = '🤷‍♀️'
class SidKetchum(Character):
def __init__(self):
super().__init__("Sid Ketchum", max_lives=4)
class SlabTheKiller(Character):
def __init__(self):
super().__init__("Slab The Killer", max_lives=4)
class SuzyLafayette(Character):
def __init__(self):
super().__init__("Suzy Lafayette", max_lives=4)
class VultureSam(Character):
def __init__(self):
super().__init__("Vulture Sam", max_lives=4)
class WillyTheKid(Character):
def __init__(self):
super().__init__("Willy The Kid", max_lives=4)
def all_characters():
return [
BartCassidy(),
BlackJack(),
CalamityJanet(),
ElGringo(),
JesseJones(),
Jourdonnais(),
KitCarlson(),
LuckyDuke(),
PaulRegret(),
PedroRamirez(),
RoseDoolan(),
SidKetchum(),
SlabTheKiller(),
SuzyLafayette(),
VultureSam(),
WillyTheKid(),
]

46
backend/deck.py Normal file
View File

@ -0,0 +1,46 @@
from typing import List, Set, Dict, Tuple, Optional
import random
from cards import Card, get_starting_deck
class Deck:
def __init__(self, game):
super().__init__()
self.cards: List[Card] = get_starting_deck()
self.game = game
random.shuffle(self.cards)
self.scrap_pile: List[Card] = []
print(f'Deck initialized with {len(self.cards)} cards')
def peek(self, n_cards: int) -> list:
return self.cards[:n_cards]
def peek_scrap_pile(self) -> Card:
if len(self.scrap_pile) > 0:
return self.scrap_pile[-1]
else:
return None
def pick_and_scrap(self) -> Card:
card = self.cards.pop(0)
self.scrap_pile.append(card)
self.game.notify_scrap_pile()
return card
def draw(self) -> Card:
card = self.cards.pop(0)
if len(self.cards) == 0:
self.cards = self.scrap_pile[:-1].copy()
random.shuffle(self.cards)
self.scrap_pile = self.scrap_pile[-1:]
return card
def draw_from_scrap_pile(self) -> Card:
if len(self.scrap_pile) > 0:
return self.scrap_pile.pop(0)
self.game.notify_scrap_pile()
else:
return self.draw()
def scrap(self, card: Card):
self.scrap_pile.append(card)
self.game.notify_scrap_pile()

170
backend/game.py Normal file
View File

@ -0,0 +1,170 @@
from typing import List, Set, Dict, Tuple, Optional
import random
import socketio
import players
from characters import all_characters
from deck import Deck
from players import Player
import roles
class Game:
def __init__(self, name, sio:socketio):
super().__init__()
self.sio = sio
self.name = name
self.players: List[players.Player] = []
self.deck: Deck = None
self.started = False
self.turn = 0
self.readyCount = 0
def handle_disconnect(self, player: players.Player):
print(f'player {player.name} left the game {self.name}')
index = self.players.index(player)
for c in player.hand:
self.deck.scrap(c)
for c in player.equipment:
self.deck.scrap(c)
died_in_his_turn = self.started and index == self.turn
if self.started and index < self.turn:
self.turn -= 1
self.players.pop(index)
if len(self.players) == 0:
print(f'no players left in game {self.name}')
return True
self.sio.emit('room', room=self.name, data={'name': self.name, 'started': self.started, 'players': [p.name for p in self.players]})
self.sio.emit('chat_message', room=self.name, data=f'{player.name} si è disconnesso.')
self.players_map = {c.name: i for i, c in enumerate(self.players)}
if died_in_his_turn:
self.next_turn()
return False
def add_player(self, player: players.Player):
if player in self.players:
return
player.join_game(self)
self.players.append(player)
print(f'Added player {player.name} to game')
self.sio.emit('room', room=self.name, data={'name': self.name, 'started': self.started, 'players': [p.name for p in self.players]})
self.sio.emit('chat_message', room=self.name, data=f'{player.name} è entrato nella lobby.')
def notify_character_selection(self):
self.readyCount += 1
if self.readyCount == len(self.players):
self.distribute_roles()
def choose_characters(self):
char_cards = random.sample(all_characters(), len(self.players)*2)
for i in range(len(self.players)):
self.players[i].set_available_character(char_cards[i * 2 : i * 2 + 2])
def start_game(self):
print('GAME IS STARING')
if self.started:
return
self.players_map = {c.name: i for i, c in enumerate(self.players)}
self.sio.emit('chat_message', room=self.name, data=f'La partita sta iniziando...')
self.sio.emit('start', room=self.name)
self.started = True
self.deck = Deck(self)
self.choose_characters()
def distribute_roles(self):
available_roles: List[roles.Role] = []
if len(self.players) == 3:
available_roles = [roles.Sheriff(), roles.Renegade(), roles.Outlaw()]
random.shuffle(available_roles)
for i in range(len(self.players)):
self.players[i].set_role(available_roles[i])
if type(available_roles[i]) == roles.Sheriff:
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 get_visible_players(self, player):
i = self.players.index(player)
sight = player.get_sight()
return [{
'name': self.players[j].name,
'dist': min(abs(i - j), abs(i - len(self.players) - j)) + self.players[j].get_visibility(),
'lives': self.players[j].lives,
'max_lives': self.players[j].max_lives,
} for j in range(len(self.players)) if i != j]
def attack(self, attacker:Player, target_username:str):
self.sio.emit('chat_message', room=self.name, data=f'{attacker.name} ha fatto Bang contro {target_username}.')
if self.players[self.players_map[target_username]].get_banged():
attacker.pending_action = players.PendingAction.WAIT
attacker.notify_self()
def get_player_named(self, name:str):
return self.players[self.players_map[name]]
def responders_did_respond(self):
self.players[self.turn].pending_action = players.PendingAction.PLAY
self.players[self.turn].notify_self()
def next_player(self):
return self.players[(self.turn + 1) % len(self.players)]
def play_turn(self):
self.players[self.turn].play_turn()
def next_turn(self):
self.turn = (self.turn + 1) % len(self.players)
self.play_turn()
def notify_scrap_pile(self):
print('scrap')
self.sio.emit('scrap', room=self.name, data=self.deck.peek_scrap_pile().__dict__)
def player_death(self, player: players.Player):
print(f'player {player.name} died')
for c in player.hand:
self.deck.scrap(c)
for c in player.equipment:
self.deck.scrap(c)
index = self.players.index(player)
died_in_his_turn = index == self.turn
if index <= self.turn:
self.turn -= 1
self.players.pop(index)
if len(self.players) == 0:
print(f'no players left in game {self.name}')
return True
self.sio.emit('room', room=self.name, data={'name': self.name, 'started': self.started, 'players': [p.name for p in self.players]})
self.sio.emit('chat_message', room=self.name, data=f'{player.name} è morto.')
for p in self.players:
p.notify_self()
self.players_map = {c.name: i for i, c in enumerate(self.players)}
if died_in_his_turn:
self.next_turn()
def notify_all(self):
data = [{
'name': p.name,
'ncards': len(p.hand),
'equipment': [e.__dict__ for e in p.equipment],
'lives': p.lives,
'max_lives': p.max_lives,
'is_sheriff': isinstance(p.role, roles.Sheriff),
'is_my_turn': p.is_my_turn,
} for p in self.players]
self.sio.emit('players_update', room=self.name, data=data)
# game = Game()
# p1 = players.Player('p1')
# game.add_player(p1)
# p2 = players.Player('p2')
# game.add_player(p2)
# p3 = players.Player('p3')
# game.add_player(p3)
# game.start_game()
# for p in game.players:
# p.set_character(random.choice(p.available_characters))
# game.distribute_roles()

View File

@ -1,17 +0,0 @@
from enum import IntEnum
class G:
sio = None
def __init__(self):
pass
class PendingAction(IntEnum):
PICK = 0
DRAW = 1
PLAY = 2
RESPOND = 3
WAIT = 4
CHOOSE = 5

View File

@ -1,28 +0,0 @@
import os
import time
from datadog import initialize, api
class Metrics:
send_metrics = False
@classmethod
def init(cls):
if "DATADOG_API_KEY" in os.environ and "DATADOG_APP_KEY" in os.environ and "HOST" in os.environ:
Metrics.send_metrics = True
initialize()
api.Event.create(title="Backend start", text="", tags=["server:backend", f"host:{os.environ['HOST']}"], alert_type="info")
else:
print("Datadog not configured")
@classmethod
def send_metric(cls, metric_name, **kwargs):
if Metrics.send_metrics:
kwargs['tags'] = ["server:backend", f"host:{os.environ['HOST']}"] + kwargs.get('tags', [])
kwargs['points'][0] = (int(time.time()) , kwargs['points'][0])
api.Metric.send(metric=metric_name, **kwargs)
@classmethod
def send_event(cls, event_name, event_data, **kwargs):
if Metrics.send_metrics:
kwargs['tags'] = ["server:backend", f"host:{os.environ['HOST']}"] + kwargs.get('tags', [])
api.Event.create(title=event_name, text=event_data, tags=kwargs['tags'], alert_type=kwargs.get('alert_type', "info"))

339
backend/players.py Normal file
View File

@ -0,0 +1,339 @@
from enum import IntEnum
import json
import socketio
from cards import Mancato
import roles
import cards
import characters
class PendingAction(IntEnum):
PICK = 0
DRAW = 1
PLAY = 2
RESPOND = 3
WAIT = 4
CHOOSE = 5
class Player:
def __init__(self, name, sid, sio):
super().__init__()
self.name = name
self.sid = sid
self.sio = sio
self.hand: cards.Card = []
self.equipment: cards.Card = []
self.role: roles.Role = None
self.character: characters.Character = None
self.lives = 0
self.max_lives = 0
self.game = None
self.is_my_turn = False
self.is_waiting_for_action = True
self.has_played_bang = False
self.pending_action: PendingAction = None
self.available_characters = []
self.was_shot = False
self.on_pick_cb = None
self.on_response_cb = None
self.expected_response = None
self.target_p: str = None
def join_game(self, game):
self.game = game
print(f'I {self.name} joined {self.game}')
def disconnect(self):
return self.game.handle_disconnect(self)
def set_role(self, role: roles.Role):
self.role = role
print(f'I {self.name} am a {role.name}, my goal is "{role.goal}"')
self.sio.emit('role', room=self.sid, data=json.dumps(role, default=lambda o: o.__dict__))
def set_character(self, character: str):
print(self.available_characters, character)
self.character = next(x for x in self.available_characters if x.name==character)
self.available_characters = []
print(f'I {self.name} chose character {self.character.name}')
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha scelto il personaggio.')
self.game.notify_character_selection()
def prepare(self):
self.max_lives = self.character.max_lives + self.role.health_mod
self.lives = self.max_lives
self.hand = []
self.equipment = []
def set_available_character(self, available):
self.available_characters = available
print(f'I {self.name} have to choose between {available}')
self.sio.emit('characters', room=self.sid, data=json.dumps(available, default=lambda o: o.__dict__))
def notify_self(self):
if self.lives <= 0 and self.max_lives > 0:
self.game.player_death(self)
ser = self.__dict__.copy()
ser.pop('game')
ser.pop('sio')
ser.pop('sid')
ser.pop('on_pick_cb')
ser.pop('on_response_cb')
ser.pop('expected_response')
self.sio.emit('self', room=self.sid, data=json.dumps(ser, default=lambda o: o.__dict__))
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()
def play_turn(self):
if self.lives == 0:
self.end_turn(forced=True)
self.sio.emit('chat_message', room=self.game.name, data=f'È il turno di {self.name}.')
print(f'I {self.name} was notified that it is my turn')
self.was_shot = False
self.is_my_turn = True
self.is_waiting_for_action = True
self.has_played_bang = False
if any([isinstance(c, cards.Dinamite) or isinstance(c, cards.Prigione) for c in self.equipment]):
self.pending_action = PendingAction.PICK
else:
self.pending_action = PendingAction.DRAW
self.notify_self()
def draw(self):
if self.pending_action != PendingAction.DRAW:
return
for i in range(2):
self.hand.append(self.game.deck.draw())
self.pending_action = PendingAction.PLAY
self.notify_self()
def pick(self):
if self.pending_action != PendingAction.PICK:
return
pickable_cards = 1 + self.character.pick_mod
if self.is_my_turn:
for i in range(len(self.equipment)):
if isinstance(self.equipment[i], cards.Dinamite):
while pickable_cards > 0:
pickable_cards -= 1
picked: cards.Card = self.game.deck.pick_and_scrap()
print(f'Did pick {picked}')
if picked.suit == cards.Suit.SPADES and 2 <= picked.number <= 9 and pickable_cards == 0:
self.lives -= 3
self.game.deck.scrap(self.equipment.pop(i))
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha fatto esplodere la dinamite.')
print(f'{self.name} Boom, -3 hp')
else:
self.game.next_player().equipment.append(self.equipment.pop(i))
self.game.next_player().notify_self()
if any([isinstance(c, cards.Dinamite) or isinstance(c, cards.Prigione) for c in self.equipment]):
self.notify_self()
return
for i in range(len(self.equipment)):
if isinstance(self.equipment[i], cards.Prigione):
while pickable_cards > 0:
pickable_cards -= 1
picked: cards.Card = self.game.deck.pick_and_scrap()
print(f'Did pick {picked}')
if picked.suit != cards.Suit.HEARTS and pickable_cards == 0:
self.game.deck.scrap(self.equipment.pop(i))
self.end_turn(forced=True)
return
else:
self.game.deck.scrap(self.equipment.pop(i))
break
break
self.pending_action = PendingAction.DRAW
self.notify_self()
else:
self.pending_action = PendingAction.WAIT
self.on_pick_cb()
def get_playable_cards(self):
playable_cards = []
for i in range(len(self.hand)):
card = self.hand[i]
if isinstance(card, cards.Bang) and self.has_played_bang and not any([isinstance(c, cards.Volcanic) for c in self.equipment]):
continue
elif isinstance(card, cards.Birra) and self.lives >= self.max_lives:
continue
else:
playable_cards.append(i)
return playable_cards
def get_public_description(self):
s = f"{self.name} {'Sheriff ⭐️' if isinstance(self.role, roles.Sheriff) else ''} ({self.lives}/{self.max_lives} ⁍) {len(self.hand)} Cards in hand, "
s += f"equipment {[str(c) for c in self.equipment]}"
return s
def play_card(self, hand_index: int, againts=None):
if not (0 <= hand_index < len(self.hand)):
print('illegal')
return
card: cards.Card = self.hand.pop(hand_index)
print(self.name, 'is playing ', card, ' against:', againts)
if isinstance(card, cards.Prigione) and not isinstance(self.game.get_player_named(againts).role, roles.Sheriff):
self.game.get_player_named(againts).equipment.append(card)
self.game.get_player_named(againts).notify_self()
elif card.is_equipment and card.name not in [c.name for c in self.equipment]:
if card.is_weapon:
has_weapon = False
for i in range(len(self.equipment)):
if self.equipment[i].is_weapon:
self.game.deck.scrap(self.equipment[i])
self.equipment[i] = card
has_weapon = True
break
if not has_weapon:
self.equipment.append(card)
else:
self.equipment.append(card)
else:
if isinstance(card, cards.Bang) and self.has_played_bang and not any([isinstance(c, cards.Volcanic) for c in self.equipment]) and againts != None:
self.hand.insert(hand_index, card)
return
if isinstance(card, cards.Bang) and againts != None:
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.')
self.has_played_bang = True
self.game.attack(self, againts)
if isinstance(card, cards.Birra) and len(self.game.players) != 2:
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato una {card.name}.')
self.lives = min(self.lives+1, self.max_lives)
if isinstance(card, cards.CatBalou) and againts != None:
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.')
self.pending_action = PendingAction.CHOOSE
self.choose_action = 'discard'
self.target_p = againts
print('choose now')
if isinstance(card, cards.Diligenza):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} e ha pescato 2 carte.')
for i in range(2):
self.hand.append(self.game.deck.draw())
if isinstance(card, cards.Duello):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.')
pass
if isinstance(card, cards.Emporio):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name}.')
pass
if isinstance(card, cards.Gatling):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name}.')
for p in self.game.players:
if p != self:
self.game.attack(self, p)
pass
if isinstance(card, cards.Indiani):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name}.')
pass
if isinstance(card, cards.Mancato):
pass
if isinstance(card, cards.Panico):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} contro {againts}.')
self.pending_action = PendingAction.CHOOSE
self.choose_action = 'steal'
self.target_p = againts
print('choose now')
if isinstance(card, cards.Saloon):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} e ha curato 1 punto vita a tutti.')
for p in self.game.players:
p.lives = min(p.lives+1, p.max_lives)
p.notify_self()
if isinstance(card, cards.WellsFargo):
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha giocato {card.name} e ha pescato 3 carte.')
for i in range(3):
self.hand.append(self.game.deck.draw())
self.game.deck.scrap(card)
self.notify_self()
def choose(self, card_index):
if self.pending_action != PendingAction.CHOOSE:
return
target = self.game.get_player_named(self.target_p)
card = None
if card_index >= len(target.hand):
card = target.equipment.pop(card_index - len(target.hand))
else:
card = target.hand.pop(card_index)
target.notify_self()
if self.choose_action == 'steal':
self.hand.append(card)
else:
self.game.deck.scrap(card)
self.target_p = ''
self.choose_action = ''
self.pending_action = PendingAction.PLAY
self.notify_self()
def barrel_pick(self):
pickable_cards = 1 + self.character.pick_mod
while pickable_cards > 0:
pickable_cards -= 1
picked: cards.Card = self.game.deck.pick_and_scrap()
print(f'Did pick {picked}')
if picked.suit == cards.Suit.HEARTS:
self.notify_self()
self.game.responders_did_respond()
return
if len([c for c in self.hand if isinstance(c, cards.Mancato)]) == 0:
self.take_damage_response()
self.game.responders_did_respond()
else:
self.pending_action = PendingAction.RESPOND
self.expected_response = cards.Mancato
self.on_response_cb = self.take_damage_response
self.notify_self()
def get_banged(self):
if len([c for c in self.hand if isinstance(c, cards.Mancato)]) == 0 and len([c for c in self.equipment if isinstance(c, cards.Barile)]) == 0:
print('Cant defend')
self.take_damage_response()
return False
else:
if len([c for c in self.equipment if isinstance(c, cards.Barile)]) > 0:
print('has barrel')
self.pending_action = PendingAction.PICK
self.on_pick_cb = self.barrel_pick
else:
print('has mancato')
self.pending_action = PendingAction.RESPOND
self.expected_response = cards.Mancato
self.on_response_cb = self.take_damage_response
self.notify_self()
return True
def take_damage_response(self):
self.lives -= 1
self.notify_self()
def respond(self, hand_index):
self.pending_action = PendingAction.WAIT
if hand_index != -1 and isinstance(self.hand[hand_index], self.expected_response):
self.game.deck.scrap(self.hand.pop(hand_index))
self.notify_self()
self.game.responders_did_respond()
else:
self.on_response_cb()
self.game.responders_did_respond()
def get_sight(self):
aim = 0
for card in self.equipment:
aim += card.sight_mod
if card.is_weapon:
aim += card.range
return 1 + self.character.sight_mod + aim
def get_visibility(self):
covers = 0
for card in self.equipment:
covers += card.vis_mod
return self.character.visibility_mod + covers
def end_turn(self, forced=False):
if not self.is_my_turn: return
if len(self.hand) > self.max_lives and not forced:
print(f"I {self.name} have to many cards in my hand and I can't end the turn")
else:
self.is_my_turn = False
self.pending_action = PendingAction.WAIT
self.notify_self()
self.game.next_turn()

View File

@ -1,10 +1,7 @@
certifi==2022.12.7
dnspython==2.3.0
eventlet==0.35.2
python-engineio==4.3.4
python-socketio==5.8.0
six==1.16.0
pytest==7.2.2
requests==2.32.0
discord-webhook==1.1.0
datadog==0.45.0
certifi==2020.11.8
dnspython==2.0.0
eventlet==0.29.1
greenlet==0.4.17
python-engineio==3.13.2
python-socketio==4.6.0
six==1.15.0

57
backend/roles.py Normal file
View File

@ -0,0 +1,57 @@
from abc import ABC, abstractmethod
class Role(ABC):
def __init__(self, name: str, goal: str, health_mod: int = 0):
super().__init__()
self.name = name
self.goal = goal
self.health_mod = health_mod
@abstractmethod
def on_player_death(self, alive_players: list):
pass
class Sheriff(Role):
def __init__(self):
super().__init__("Sceriffo", "Elimina tutti i Fuorilegge e il Rinnegato!", health_mod=+1)
self.max_players = 1
self.icon = '⭐️'
def on_player_death(self, alive_players: list):
if not any([isinstance(p.role) == Outlaw or isinstance(p.role) == Renegade for p in alive_players]):
print("The Sheriff won!")
pass
class Vice(Role):
def __init__(self):
super().__init__("Vice", "Proteggi lo Sceriffo! Elimina tutti i Fuorilegge e il Rinnegato!")
self.max_players = 2
self.icon = '🎖'
def on_player_death(self, alive_players: list):
if not any([isinstance(p.role) == Outlaw or isinstance(p.role) == Renegade for p in alive_players]):
print("The Vice won!")
pass
class Outlaw(Role):
def __init__(self):
super().__init__("Fuorilegge", "Elimina lo Sceriffo!")
self.max_players = 3
self.icon = '🐺'
def on_player_death(self, alive_players: list):
if not any([isinstance(p.role) == Sheriff for p in alive_players]):
print("The Outlaw won!")
pass
class Renegade(Role):
def __init__(self):
super().__init__("Rinnegato", "Rimani l'ultimo personaggio in gioco!")
self.max_players = 1
self.icon = '🦅'
def on_player_death(self, alive_players: list):
if len(alive_players) == 1 and isinstance(alive_players[0]) == Renegade:
print("The Renegade won!")
pass

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +0,0 @@
from typing import Any, List
import pytest
from bang.characters import Character
from bang.game import Game
from bang.players import Player
from tests.dummy_socket import DummySocket
from globals import G
G.sio = DummySocket()
def started_game(expansions=[], players=4, character=Character("test_char", 4)) -> Game:
g = Game("test")
g.expansions = expansions
ps = [Player(f"p{i}", f"p{i}") for i in range(players)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [character]
if "high_noon" in expansions:
p.available_characters.append(Character("test_char2", 4))
p.set_character(p.available_characters[0].name)
return g
def set_events(g: Game, event_cards) -> None:
g.deck.event_cards = event_cards
def current_player(g: Game) -> Player:
return g.players[g.turn]
def next_player(g: Game) -> Player:
return g.players[(g.turn + 1) % len(g.players)]
def current_player_with_cards(g: Game, cards: List[Any]) -> Player:
p = current_player(g)
p.draw("")
p.hand = cards
return p

View File

@ -1,495 +0,0 @@
from random import randint
from bang.characters import Character
from bang.cards import *
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
from globals import PendingAction
# test card Barile
def test_barile():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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', 2)]
p.set_character(p.available_characters[0].name)
barrel_guy = g.players[g.turn]
barrel_guy.draw('')
barrel_guy.hand = [Barile(0,0)]
barrel_guy.play_card(0)
assert isinstance(barrel_guy.equipment[0], Barile)
barrel_guy.end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Volcanic(0,0), Bang(0,0), Bang(0,0)]
g.players[g.turn].play_card(0)
g.players[g.turn].play_card(0, barrel_guy.name)
assert g.players[g.turn].pending_action == PendingAction.WAIT
assert barrel_guy.pending_action == PendingAction.PICK
g.deck.cards[0] = Bang(Suit.HEARTS, 5)
barrel_guy.pick()
assert barrel_guy.pending_action == PendingAction.WAIT
assert barrel_guy.lives == barrel_guy.max_lives
assert g.players[g.turn].pending_action == PendingAction.PLAY
g.players[g.turn].play_card(0, barrel_guy.name)
g.deck.cards[0] = Bang(Suit.SPADES, 5)
barrel_guy.pick()
assert barrel_guy.pending_action == PendingAction.WAIT
assert barrel_guy.lives == barrel_guy.max_lives - 1
assert g.players[g.turn].pending_action == PendingAction.PLAY
#test card Volcanic
def test_volcanic():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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', 3)]
p.set_character(p.available_characters[0].name)
for p in ps:
p.hand = []
volcanic_guy = g.players[g.turn]
volcanic_guy.draw('')
volcanic_guy.hand = [Volcanic(0,0), Bang(0,0), Bang(0,0)]
volcanic_guy.play_card(0)
assert isinstance(volcanic_guy.equipment[0], Volcanic)
assert volcanic_guy.get_sight() == 1
volcanic_guy.play_card(0, g.players[(g.turn+1)%3].name)
assert len(volcanic_guy.hand) == 1
volcanic_guy.play_card(0, g.players[(g.turn+1)%3].name)
assert len(volcanic_guy.hand) == 0
# test card Dinamite
def test_dinamite():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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)
for p in ps:
p.hand = []
dinamite_guy = g.players[g.turn]
dinamite_guy.draw('')
dinamite_guy.hand = [Dinamite(0,0)]
dinamite_guy.play_card(0)
assert isinstance(dinamite_guy.equipment[0], Dinamite)
dinamite_guy.end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.deck.cards.insert(0, Dinamite(Suit.HEARTS, 5))
dinamite_guy.pick()
assert len(dinamite_guy.equipment) == 0
dinamite_guy.draw('')
dinamite_guy.end_turn()
assert len(g.players[g.turn].equipment) == 1
g.deck.cards.insert(0, Dinamite(Suit.SPADES, 5))
g.players[g.turn].pick()
assert len(g.players[g.turn].equipment) == 0
assert g.players[g.turn].lives == 1
# test mirino
def test_mirino():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(4)]
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)
mirino_guy = g.players[g.turn]
mirino_guy.draw('')
mirino_guy.hand = [Mirino(0,0)]
assert mirino_guy.get_sight(countWeapon=False) == 1
mirino_guy.play_card(0)
assert mirino_guy.get_sight(countWeapon=False) == 2
# test mustang
def test_mustang():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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)
mustang_guy = g.players[g.turn]
mustang_guy.draw('')
mustang_guy.hand = [Mustang(0,0)]
assert mustang_guy.get_visibility() == 0
mustang_guy.play_card(0)
assert mustang_guy.get_visibility() == 1
# test Prigione
def test_prigione():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(4)]
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)
sheriff = g.players[g.turn]
sheriff.draw('')
sheriff.hand = [Prigione(0,0)]
sheriff.play_card(0, g.players[(g.turn+1)%4].name)
assert len(sheriff.hand) == 0
sheriff.end_turn()
g.deck.cards.insert(0, Prigione(Suit.CLUBS, 5))
skip_check = g.turn
g.players[g.turn].pick()
assert g.turn != skip_check
g.players[g.turn].draw('')
g.players[g.turn].hand = [Prigione(0,0)]
g.players[g.turn].play_card(0, sheriff.name)
assert len(g.players[g.turn].hand) == 1
g.players[g.turn].play_card(0, g.players[(g.turn+1)%4].name)
g.players[g.turn].end_turn()
g.deck.cards.insert(0, Prigione(Suit.HEARTS, 5))
skip_check = g.turn
g.players[g.turn].pick()
assert g.turn == skip_check
# test all weapons ranges
def test_all_weapons():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(4)]
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)
g.players[g.turn].draw('')
g.players[g.turn].hand = [Volcanic(0,0), Schofield(0,0), Remington(0,0), RevCarabine(0,0), Winchester(0,0)]
g.players[g.turn].play_card(0)
assert g.players[g.turn].get_sight() == 1
g.players[g.turn].play_card(0)
assert g.players[g.turn].get_sight() == 2
g.players[g.turn].play_card(0)
assert g.players[g.turn].get_sight() == 3
g.players[g.turn].play_card(0)
assert g.players[g.turn].get_sight() == 4
g.players[g.turn].play_card(0)
assert g.players[g.turn].get_sight() == 5
# test bang
def test_bang():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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)
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0), Bang(0,0)]
assert len(g.players[g.turn].hand) == 2
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert len(g.players[g.turn].hand) == 1
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert len(g.players[g.turn].hand) == 1
# test birra
def test_birra_2p():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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)
g.players[g.turn].draw('')
g.players[g.turn].hand = [Birra(0,0)]
g.players[g.turn].lives = 1
g.players[g.turn].play_card(0)
assert g.players[g.turn].lives == 1
# test birra
def test_birra_3p():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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)
initial_p = g.players[g.turn]
g.players[g.turn].draw('')
g.players[g.turn].hand = [Birra(0,0)]
g.players[g.turn].lives = 1
g.players[g.turn].play_card(0)
assert g.players[g.turn].lives == 2
# test beer save
g.players[g.turn].hand = [Birra(0,0)]
g.players[g.turn].lives = 1
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
g.players[g.turn].play_card(0, initial_p.name)
assert initial_p.lives == 1
# test non overflow
g.players[g.turn].lives = g.players[g.turn].max_lives
g.players[g.turn].hand = [Birra(0,0)]
g.players[g.turn].play_card(0)
assert g.players[g.turn].lives == g.players[g.turn].max_lives
# test CatBalou
def test_catbalou():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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)
g.players[g.turn].draw('')
g.players[g.turn].hand = [CatBalou(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
g.players[g.turn].choose(0)
assert len(g.players[g.turn].hand) == 0
assert len(g.deck.scrap_pile) == 2
assert len(g.players[(g.turn+1)%2].hand) == 3
# test Diligenza
def test_diligenza():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(4)]
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)
g.players[g.turn].draw('')
g.players[g.turn].hand = [Diligenza(0,0)]
g.players[g.turn].play_card(0)
assert len(g.players[g.turn].hand) == 2
# test Duello
def test_duello():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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)
for p in ps:
p.hand = []
g.players[g.turn].draw('')
# winning duello
g.players[g.turn].hand = [Duello(0,0), Duello(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert len(g.players[g.turn].hand) == 1
assert g.players[(g.turn+1)%2].lives == g.players[(g.turn+1)%2].max_lives - 1
# losing duello
g.players[(g.turn+1)%2].hand = [Bang(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[g.turn].pending_action == PendingAction.WAIT
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.WAIT
assert g.players[g.turn].lives == g.players[g.turn].max_lives - 1
# test Emporio
def test_emporio():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(7)]
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)
for p in ps:
p.hand = []
g.players[g.turn].draw('')
g.players[g.turn].hand = [Emporio(0,0)]
g.players[g.turn].play_card(0)
assert g.players[g.turn].pending_action == PendingAction.CHOOSE
g.players[g.turn].choose(0)
print(g.players[g.turn].name)
for i in range(1, len(g.players)-1):
assert g.players[(g.turn+i)%7].pending_action == PendingAction.CHOOSE
g.players[(g.turn+i)%7].choose(0)
for p in ps:
assert len(p.hand) == 1
assert g.players[g.turn].pending_action == PendingAction.PLAY
# test Gatling
def test_gatling():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(7)]
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)
# test lose gatling
for p in ps:
p.hand = []
g.players[g.turn].draw('')
g.players[g.turn].hand = [Gatling(0,0), Gatling(0,0)]
g.players[g.turn].play_card(0)
for p in ps:
if p != g.players[g.turn]:
assert p.lives == p.max_lives - 1
# test win gatling
for p in ps:
if p != g.players[g.turn]:
p.hand = [Mancato(0,0)]
g.players[g.turn].play_card(0)
assert g.players[g.turn].pending_action == PendingAction.WAIT
for p in ps:
if p != g.players[g.turn]:
p.respond(0)
assert g.players[g.turn].pending_action == PendingAction.PLAY
for p in ps:
if p != g.players[g.turn]:
assert p.lives == p.max_lives - 1
# test Indiani
def test_indiani():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(7)]
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)
# test lose indiani
for p in ps:
p.hand = []
g.players[g.turn].draw('')
g.players[g.turn].hand = [Indiani(0,0), Indiani(0,0)]
g.players[g.turn].play_card(0)
for p in ps:
if p != g.players[g.turn]:
assert p.lives == p.max_lives - 1
# test win indiani
for p in ps:
if p != g.players[g.turn]:
p.hand = [Bang(0,0)]
g.players[g.turn].play_card(0)
assert g.players[g.turn].pending_action == PendingAction.WAIT
for p in ps:
if p != g.players[g.turn]:
p.respond(0)
assert g.players[g.turn].pending_action == PendingAction.PLAY
for p in ps:
if p != g.players[g.turn]:
assert p.lives == p.max_lives - 1
# test Mancato
def test_mancato():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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)
for p in ps:
p.hand = [Mancato(0,0)]
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
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].lives == g.players[(g.turn+1)%2].max_lives
assert g.players[(g.turn+1)%2].pending_action == PendingAction.WAIT
assert g.players[g.turn].pending_action == PendingAction.PLAY
# test Panico
def test_panico():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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)
g.players[g.turn].draw('')
g.players[g.turn].hand = [Panico(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
g.players[g.turn].choose(0)
assert len(g.players[g.turn].hand) == 1
assert len(g.deck.scrap_pile) == 1
assert len(g.players[(g.turn+1)%2].hand) == 3
# test Saloon
def test_saloon():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(8)]
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)
for p in ps:
p.lives = randint(p.max_lives-1, p.max_lives)
g.players[g.turn].draw('')
g.players[g.turn].hand = [Saloon(0,0)]
g.players[g.turn].play_card(0)
for p in ps:
assert p.lives == p.max_lives
# test WellsFargo
def test_wells_fargo():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(4)]
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)
g.players[g.turn].draw('')
g.players[g.turn].hand = [WellsFargo(0,0)]
g.players[g.turn].play_card(0)
assert len(g.players[g.turn].hand) == 3

View File

@ -1,365 +0,0 @@
from random import randint
from bang.characters import *
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
from globals import PendingAction
from bang.cards import *
def test_bartcassidy():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 2), BartCassidy()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if isinstance(g.players[g.turn].character, BartCassidy):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[(g.turn+1)%2].hand = []
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
assert len(g.players[(g.turn+1)%2].hand) == 0
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert len(g.players[(g.turn+1)%2].hand) == 1
def test_blackjack():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(1)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [BlackJack()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
g.players[g.turn].hand = []
g.deck.cards.insert(1, Bang(Suit.HEARTS, 1))
g.players[g.turn].draw('')
assert len(g.players[g.turn].hand) == 3
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.deck.cards.insert(1, Bang(Suit.CLUBS, 1))
g.players[g.turn].draw('')
assert len(g.players[g.turn].hand) == 2
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.deck.cards.insert(1, Bang(Suit.DIAMONDS, 1))
g.players[g.turn].draw('')
assert len(g.players[g.turn].hand) == 3
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.deck.cards.insert(1, Bang(Suit.SPADES, 1))
g.players[g.turn].draw('')
assert len(g.players[g.turn].hand) == 2
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
def test_calamityjanet():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 2), CalamityJanet()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if isinstance(g.players[g.turn].character, CalamityJanet):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[(g.turn+1)%2].hand = [Bang(0,0)]
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
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)
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Mancato(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].lives == 1
def test_ElGringo():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 2), ElGringo()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if isinstance(g.players[g.turn].character, ElGringo):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[(g.turn+1)%2].hand = []
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0), Bang(0,0)]
assert len(g.players[(g.turn+1)%2].hand) == 0
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert len(g.players[(g.turn+1)%2].hand) == 1
assert len(g.players[g.turn].hand) == 0
def test_JesseJones():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 2), JesseJones()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if not isinstance(g.players[g.turn].character, JesseJones):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[(g.turn+1)%2].hand = [Bang(0,0)]
g.players[g.turn].draw('p1' if g.turn == 0 else 'p0')
g.players[g.turn].hand = [Bang(0,0), Bang(0,0)]
assert len(g.players[(g.turn+1)%2].hand) == 0
assert len(g.players[g.turn].hand) == 2
def test_Jourdonnais():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 2), Jourdonnais()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if isinstance(g.players[g.turn].character, Jourdonnais):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].pending_action == PendingAction.PICK
def test_KitCarlson():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), KitCarlson()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if not isinstance(g.players[g.turn].character, KitCarlson):
g.players[g.turn].draw('')
g.players[g.turn].hand = [Mancato(0,0)]
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
assert g.players[g.turn].pending_action == PendingAction.CHOOSE
assert len(g.players[g.turn].available_cards) == 3
g.players[g.turn].choose(0)
assert len(g.players[g.turn].available_cards) == 2
g.players[g.turn].choose(1)
assert g.players[g.turn].pending_action == PendingAction.PLAY
def test_LuckyDuke():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [LuckyDuke(), LuckyDuke()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
g.players[0].equipment = [Prigione(0,0)]
g.players[1].equipment = [Prigione(0,0)]
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
assert g.players[g.turn].pending_action == PendingAction.PICK
g.deck.cards.insert(0, Bang(Suit.SPADES,0))
g.deck.cards.insert(1, Bang(Suit.HEARTS,0))
g.players[g.turn].pick()
assert g.players[g.turn].pending_action == PendingAction.DRAW
def test_PaulRegret():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 2), PaulRegret()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
pls = g.get_visible_players(g.players[0])
assert len(pls) == 1
assert pls[0]['name'] == g.players[1].name
assert pls[0]['dist'] > g.players[0].get_sight()
def test_PedroRamirez():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), PedroRamirez()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if not isinstance(g.players[g.turn].character, PedroRamirez):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.deck.scrap_pile.append(Bang(0,0))
g.players[g.turn].hand = []
g.players[g.turn].draw('scrap')
assert len(g.players[g.turn].hand) == 2
assert g.players[g.turn].hand[0].number == 0
assert g.players[g.turn].hand[0].suit == 0
assert isinstance(g.players[g.turn].hand[0], Bang)
def test_RoseDoolan():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 2), RoseDoolan()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
g.players[0].equipment = [Mustang(0,0)]
g.players[0].notify_self()
pls = g.get_visible_players(g.players[1])
print(pls)
assert len(pls) == 1
assert pls[0]['name'] != g.players[1].name
assert pls[0]['dist'] <= g.players[1].get_sight()
def test_SidKetchum():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), SidKetchum()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if not isinstance(g.players[g.turn].character, SidKetchum):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].lives = 1
g.players[g.turn].scrap(0)
assert g.players[g.turn].lives == 1
g.players[g.turn].scrap(0)
assert g.players[g.turn].lives == 2
def test_SlabTheKiller():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), SlabTheKiller()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if not isinstance(g.players[g.turn].character, SlabTheKiller):
g.players[g.turn].draw('')
g.players[g.turn].hand = [Mancato(0,0)]
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
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.WAIT
assert g.players[(g.turn+1)%2].lives == 3
def test_SuzyLafayette():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), SuzyLafayette()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
g.players[1].hand = []
assert len(g.players[1].hand) == 0
g.players[1].notify_self()
assert len(g.players[1].hand) == 1
g.players[g.turn].end_turn()
def test_VultureSam():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), Character('test_char', 4), VultureSam()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if isinstance(g.players[g.turn].character, VultureSam):
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0), Bang(0,0), Bang(0,0), Bang(0,0)]
g.players[g.turn].end_turn()
while not isinstance(g.players[g.turn].character, VultureSam):
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0), Bang(0,0), Bang(0,0), Bang(0,0)]
g.players[g.turn].lives = 0
g.players[g.turn].notify_self()
assert len(g.players[2].hand) == 8
return
def test_WillyTheKid():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), WillyTheKid()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if not isinstance(g.players[g.turn].character, WillyTheKid):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0), Bang(0,0), Bang(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].lives == 3
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].lives == 2
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].lives == 1

View File

@ -1,37 +0,0 @@
from tests.dummy_socket import DummySocket
from bang.deck import Deck
from bang.game import Game
def test_card_flip():
g = Game('test')
g.deck = Deck(g)
l = len(g.deck.cards)
assert g.deck.pick_and_scrap() is not None
assert len(g.deck.cards) == l - 1
assert len(g.deck.scrap_pile) == 1
def test_draw():
g = Game('test')
g.deck = Deck(g)
l = len(g.deck.cards)
assert g.deck.draw(True) is not None
assert len(g.deck.cards) == l - 1
assert len(g.deck.scrap_pile) == 0
def test_reshuffle():
g = Game('test')
g.deck = Deck(g)
l = len(g.deck.cards)
for i in range(80):
assert g.deck.pick_and_scrap() is not None
assert len(g.deck.cards) == 79
assert len(g.deck.scrap_pile) == 1
def test_draw_from_scrap():
g = Game('test')
g.deck = Deck(g)
l = len(g.deck.cards)
assert g.deck.pick_and_scrap() is not None
assert g.deck.draw_from_scrap_pile() is not None
assert len(g.deck.cards) == 79
assert len(g.deck.scrap_pile) == 0

View File

@ -1,40 +0,0 @@
from random import randint
from bang.characters import Character
from bang.expansions.dodge_city.cards import *
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
import bang.cards as cs
# test Borraccia
def test_Borraccia():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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)
borraccia_guy = g.players[g.turn]
borraccia_guy.draw('')
borraccia_guy.lives = 3
borraccia_guy.hand = [Borraccia(0,0)]
assert len(borraccia_guy.hand) == 1
borraccia_guy.play_card(0)
assert len(borraccia_guy.hand) == 0
assert len(borraccia_guy.equipment) == 1
assert not borraccia_guy.equipment[0].can_be_used_now
borraccia_guy.play_card(0)
assert len(borraccia_guy.hand) == 0
assert len(borraccia_guy.equipment) == 1
borraccia_guy.end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
borraccia_guy.draw('')
assert borraccia_guy.equipment[0].can_be_used_now
borraccia_guy.hand = []
borraccia_guy.play_card(0)
assert len(borraccia_guy.equipment) == 0
assert borraccia_guy.lives == 4

View File

@ -1,16 +0,0 @@
class DummySocket():
def __init__(self, sio=None):
self.true_sio = sio
def emit(self, event, data=None, to=None, room=None, skip_sid=None, namespace=None, callback=None, **kwargs):
# print(f'event: {event}, data: {data}, to: {to}, room: {room}')
if self.true_sio and event == 'chat_message':
self.true_sio.emit(event, data, to, room, skip_sid, namespace, callback, **kwargs)
elif event == 'chat_message':
print(f'event: {event}, data: {data}, to: {to}, room: {room}')
return True
def sleep(self, seconds):
return seconds
is_fake = True

View File

@ -1,103 +0,0 @@
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
from bang.roles import *
from bang.cards import *
from globals import PendingAction
from tests import started_game
# test that game can start
def test_game_start():
g = Game("test")
p1 = Player("p1", "p1")
g.add_player(p1)
p2 = Player("p2", "p2")
g.add_player(p2)
p3 = Player("p3", "p3")
g.add_player(p3)
assert p1.role is None
assert p2.role is None
assert p3.role is None
assert not g.started
g.start_game()
assert g.started
assert p1.role is not None
assert p2.role is not None
assert p3.role is not None
assert len(p1.available_characters) == g.characters_to_distribute
assert len(p2.available_characters) == g.characters_to_distribute
assert len(p3.available_characters) == g.characters_to_distribute
p1.set_character(p1.available_characters[0].name)
assert p1.character is not None
p2.set_character(p2.available_characters[0].name)
assert p2.character is not None
p3.set_character(p3.available_characters[0].name)
assert p3.character is not None
assert g.players[g.turn].pending_action == PendingAction.DRAW
# test that dodge_city is added to games with more than 8 players
def test_dodge_city():
g = Game("test")
for i in range(9):
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
assert "dodge_city" in g.expansions
# test that a game with 2 players has only renegade as role
def test_renegade_only():
g = Game("test")
p1 = Player("p1", "p1")
g.add_player(p1)
p2 = Player("p2", "p2")
g.add_player(p2)
g.start_game()
assert isinstance(g.players[0].role, Renegade)
assert isinstance(g.players[1].role, Renegade)
# test that a game with 3 player has Renegade, Vice and Outlaw as roles
def test_renegade_vice_outlaw():
g = Game("test")
for i in range(3):
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
g.start_game()
roles = {p.role.name for p in g.players}
assert len(roles) == 3
# test that a game with 4 players has all roles except the deputy
def test_4_players_roles():
g = Game("test")
for i in range(4):
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
g.start_game()
roles = {p.role.name for p in g.players}
assert len(roles) == 3
# test that a game with 5 players has all roles
def test_5_players_roles():
g = Game("test")
for i in range(5):
p = Player(f"p{i}", f"p{i}")
g.add_player(p)
g.start_game()
roles = {p.role.name for p in g.players}
assert len(roles) == 4
def test_expansions():
started_game(
[
"high_noon",
"dodge_city",
"gold_rush",
"the_valley_of_shadows",
"wild_west_show",
]
)

View File

@ -1,206 +0,0 @@
from bang.characters import Character
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
from globals import PendingAction
from bang.roles import *
from bang.cards import *
# test that a game with 3 player the deputy kills renegade and wins
def test_3p_deputy_win():
g = Game('test')
for i in range(3):
p = Player(f'p{i}', f'p{i}')
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 3
assert isinstance(g.players[g.turn].role, Vice)
for i in range(3):
g.players[i].lives = 1
g.players[i].hand = []
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
g.players[g.turn].play_card(0, against=g.players[roles['Rinnegato']].name)
assert (hasattr(g.players[g.turn], 'win_status') and g.players[g.turn].win_status)
assert not (hasattr(g.players[roles['Rinnegato']], 'win_status') and g.players[roles['Rinnegato']].win_status)
assert not (hasattr(g.players[roles['Fuorilegge']], 'win_status') and g.players[roles['Fuorilegge']].win_status)
# test that a game with 3 player the renegade kills the outlaw and wins
def test_3p_renegade_win():
g = Game('test')
for i in range(3):
p = Player(f'p{i}', f'p{i}')
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 3
assert isinstance(g.players[g.turn].role, Vice)
for i in range(3):
g.players[i].lives = 1
g.players[i].hand = []
g.turn = roles['Rinnegato']
g.play_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
g.players[g.turn].play_card(0, against=g.players[roles['Fuorilegge']].name)
assert (hasattr(g.players[g.turn], 'win_status') and g.players[g.turn].win_status)
assert not (hasattr(g.players[roles['Vice']], 'win_status') and g.players[roles['Vice']].win_status)
assert not (hasattr(g.players[roles['Fuorilegge']], 'win_status') and g.players[roles['Fuorilegge']].win_status)
# test that a game with 3 player the outlaw kills the deputy and wins
def test_3p_outlaw_win():
g = Game('test')
for i in range(3):
p = Player(f'p{i}', f'p{i}')
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 3
assert isinstance(g.players[g.turn].role, Vice)
for i in range(3):
g.players[i].lives = 1
g.players[i].hand = []
g.turn = roles['Fuorilegge']
g.play_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
g.players[g.turn].play_card(0, against=g.players[roles['Vice']].name)
assert (hasattr(g.players[g.turn], 'win_status') and g.players[g.turn].win_status)
assert not (hasattr(g.players[roles['Vice']], 'win_status') and g.players[roles['Vice']].win_status)
assert not (hasattr(g.players[roles['Rinnegato']], 'win_status') and g.players[roles['Rinnegato']].win_status)
# test that a game with 4 player the outlaw kills the sheriff and win
def test_4p_outlaw_win():
g = Game('test')
for i in range(4):
p = Player(f'p{i}', f'p{i}')
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 3
assert isinstance(g.players[g.turn].role, Sheriff)
for i in range(4):
g.players[i].lives = 1
g.players[i].hand = []
g.turn = roles['Fuorilegge']
g.play_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0)]
g.players[g.turn].play_card(0, against=g.players[roles['Sceriffo']].name)
for i in range(4):
if isinstance(g.players[i].role, Outlaw):
assert (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
else:
assert not (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
# test that a game with 5 player the renegade kills all the other players and wins
def test_5p_renegade_gatling_win():
g = Game('test')
for i in range(5):
p = Player(f'p{i}', f'p{i}')
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 4
assert isinstance(g.players[g.turn].role, Sheriff)
g.players[g.turn].is_my_turn = False
for i in range(len(g.players)):
g.players[i].lives = 1
g.players[i].hand = []
g.turn = roles['Rinnegato']
g.play_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Gatling(0,0)]
g.players[g.turn].play_card(0)
for i in range(len(g.players)):
if isinstance(g.players[i].role, Renegade):
print (g.players[i].role.name, 'win_status:', hasattr(g.players[i], 'win_status') and g.players[i].win_status)
assert (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
else:
print(g.players[i].role.name, 'win_status:', (hasattr(g.players[i], 'win_status') and g.players[i].win_status))
assert not (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
# test that a game with 5 player the renegade kills all the other players and wins
def test_5p_renegade_indiani_win():
g = Game('test')
for i in range(5):
p = Player(f'p{i}', f'p{i}')
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 4
assert isinstance(g.players[g.turn].role, Sheriff)
g.players[g.turn].is_my_turn = False
for i in range(len(g.players)):
g.players[i].lives = 1
g.players[i].hand = []
g.turn = roles['Rinnegato']
g.play_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Indiani(0,0)]
g.players[g.turn].play_card(0)
for i in range(len(g.players)):
if isinstance(g.players[i].role, Renegade):
print (g.players[i].role.name, 'win_status:', hasattr(g.players[i], 'win_status') and g.players[i].win_status)
assert (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
else:
print(g.players[i].role.name, 'win_status:', (hasattr(g.players[i], 'win_status') and g.players[i].win_status))
assert not (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
# test that a game with 5 player the renegade kills the sheriff but it isn't the last alive player and the outlaws wins
def test_5p_outlaw_death_win():
g = Game('test')
for i in range(5):
p = Player(f'p{i}', f'p{i}')
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 4
assert isinstance(g.players[g.turn].role, Sheriff)
g.players[g.turn].is_my_turn = False
for i in range(len(g.players)):
g.players[i].lives = 1
g.players[i].hand = []
g.players[roles['Vice']].lives = 2
g.turn = roles['Rinnegato']
g.play_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Gatling(0,0)]
g.players[g.turn].play_card(0)
for i in range(len(g.players)):
if isinstance(g.players[i].role, Outlaw):
print (g.players[i].role.name, 'win_status:', hasattr(g.players[i], 'win_status') and g.players[i].win_status)
assert (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
assert (hasattr(g.players[i], 'is_dead') and g.players[i].is_dead)
else:
print(g.players[i].role.name, 'win_status:', (hasattr(g.players[i], 'win_status') and g.players[i].win_status))
assert not (hasattr(g.players[i], 'win_status') and g.players[i].win_status)

View File

@ -1,24 +0,0 @@
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
from bang.expansions.fistful_of_cards.card_events import *
import bang.cards as cs
def test_miniera_abbandonata():
g = started_game(['fistful_of_cards'])
set_events(g, [MinieraAbbandonata()])
p = current_player(g)
starting_cards = len(p.hand)
g.deck.scrap_pile = [
cs.Bang(0, 0),
cs.Bang(0, 1),
cs.Bang(0, 2),
cs.Bang(0, 3),
]
p.draw("")
assert len(p.hand) == starting_cards + 2
# check the last two cards are the ones from the scrap pile
assert p.hand[-2].name == cs.Bang(0, 0).name
assert p.hand[-2].number == 3
assert p.hand[-1].name == cs.Bang(0, 0).name
assert p.hand[-1].number == 2

View File

@ -1,24 +0,0 @@
from random import randint
from bang.characters import Character
from bang.expansions.train_robbery.trains import *
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
import bang.cards as cs
from globals import PendingAction
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
def test_cattle_truck():
g = started_game()
g.deck.scrap_pile = [cs.CatBalou(0,1), cs.CatBalou(0,2), cs.CatBalou(0,3)]
p = current_player_with_cards(g, [CattleTruck()])
p.play_card(0)
assert p.pending_action == PendingAction.CHOOSE
p.choose(0)
assert p.pending_action == PendingAction.PLAY
assert len(p.hand) == 1
assert len(g.deck.scrap_pile) == 2

View File

@ -1,72 +0,0 @@
from random import randint
from bang.characters import Character
from bang.expansions.the_valley_of_shadows.characters import *
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
import bang.cards as cs
from globals import PendingAction
# test TucoFranziskaner
def test_TucoFranziskaner():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [TucoFranziskaner()]
p.set_character(p.available_characters[0].name)
p = g.players[g.turn]
p.hand = []
p.draw('')
assert len(p.hand) == 4
p.end_turn()
p = g.players[g.turn]
p.hand = []
p.equipment = [cs.Barile(0,0)]
p.draw('')
assert len(p.hand) == 2
# test ColoradoBill
def test_ColoradoBill():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [ColoradoBill()]
p.set_character(p.available_characters[0].name)
p = g.players[g.turn]
p1 = g.players[(g.turn+1)%2]
p.draw('')
p.hand = [cs.Volcanic(0,0), cs.Bang(0,0), cs.Bang(0,0)]
p.play_card(0)
g.deck.cards.insert(0, cs.Bang(cs.Suit.SPADES,0))
g.deck.cards.insert(1, cs.Bang(cs.Suit.HEARTS,0))
p1.hand = [cs.Mancato(0,0)]
p.play_card(0, p1.name)
assert len(p1.hand) == 1
assert p1.lives == 3
p.play_card(0, p1.name)
assert p1.pending_action == PendingAction.RESPOND
# test BlackFlower
def test_BlackFlower():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [BlackFlower()]
p.set_character(p.available_characters[0].name)
p = g.players[g.turn]
p.draw('')
p.hand = [cs.Volcanic(cs.Suit.DIAMONDS,0)]
p.special('')
assert p.pending_action == PendingAction.PLAY
p.hand = [cs.Volcanic(cs.Suit.CLUBS,0)]
p.special('')
assert p.pending_action == PendingAction.CHOOSE

View File

@ -1,366 +0,0 @@
from random import randint
from bang.characters import Character
from bang.expansions.the_valley_of_shadows.cards import *
from bang.deck import Deck
from bang.game import Game
from bang.players import Player
import bang.cards as cs
from globals import PendingAction
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
# test UltimoGiro
def test_ultimo_giro():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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():
g = Game('test')
g.expansions = ['the_valley_of_shadows']
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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()
assert p1.is_my_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
# test RitornoDiFiamma with gatling
def test_ritorno_di_fiamma_gatling():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(3)]
g.expansions = ['the_valley_of_shadows']
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]
p2 = g.players[(g.turn+2)%3]
p.draw('')
p.hand = [cs.Gatling(1, 1), Mancato(0,0)]
p1.hand = [RitornoDiFiamma(0,0)]
p2.hand = [Mancato(0,0)]
p.play_card(0)
assert len(p.hand) == 1
assert p1.pending_action == PendingAction.RESPOND
assert p2.pending_action == PendingAction.RESPOND
p1.respond(0)
assert p2.pending_action == PendingAction.RESPOND
assert p.pending_action == PendingAction.WAIT
p2.respond(0)
# end of gatling
assert p.pending_action == PendingAction.RESPOND
p.respond(0)
assert len(p.hand) == 0
assert p.pending_action == PendingAction.PLAY
# test Taglia
def test_taglia():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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 = [Taglia(0,0), Bang(1, 1)]
p1.hand = []
p.play_card(0, p1.name)
assert len(p.hand) == 1
assert len(p1.equipment) == 1
assert len(p1.hand) == 0
p.play_card(0, p1.name)
assert p1.lives == 3
assert len(p.hand) == 1
# test Bandidos
def test_bandidos():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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 = [Bandidos(0,0), Bandidos(0,0)]
p.play_card(0)
assert len(p.hand) == 1
assert p.pending_action == PendingAction.WAIT
assert p1.pending_action == PendingAction.CHOOSE
p1.choose(len(p1.hand))
assert p1.lives == 3
assert p.pending_action == PendingAction.PLAY
p.play_card(0)
assert len(p.hand) == 0
assert p.pending_action == PendingAction.WAIT
assert p1.pending_action == PendingAction.CHOOSE
p1.choose(0)
assert p1.pending_action == PendingAction.CHOOSE
p1.choose(0)
assert p1.pending_action == PendingAction.WAIT
assert p.pending_action == PendingAction.PLAY
def test_bandidos_with_gold_rush():
g = Game('test')
g.expansions = ['gold_rush']
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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 = [Bandidos(0,0), Bandidos(0,0)]
p.play_card(0)
assert len(p.hand) == 1
assert p.pending_action == PendingAction.WAIT
assert p1.pending_action == PendingAction.CHOOSE
p1.choose(len(p1.hand))
assert p1.lives == 3
assert p.pending_action == PendingAction.PLAY
assert p.gold_nuggets == 1
# test Poker
def test_poker():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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 = [Poker(0,0), Poker(0,0)]
p1.hand = [Bang(1, 1), Bang(2, 2)]
p.play_card(0)
assert len(p.hand) == 1
assert p.pending_action == PendingAction.WAIT
assert p1.pending_action == PendingAction.CHOOSE
p1.choose(0)
assert p.pending_action == PendingAction.PLAY
p.play_card(0)
assert p.pending_action == PendingAction.WAIT
assert p1.pending_action == PendingAction.CHOOSE
p1.choose(0)
assert p.pending_action == PendingAction.CHOOSE
p.choose(0)
assert p1.pending_action == PendingAction.WAIT
assert p.pending_action == PendingAction.PLAY
assert len(p.hand) == 1
# test Tornado
def test_tornado():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') for i in range(2)]
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 = [Tornado(0,0), Bang(1, 1)]
p1.hand = [Bang(2, 2)]
p.play_card(0)
assert len(p.hand) == 1
assert p.pending_action == PendingAction.CHOOSE
assert p1.pending_action == PendingAction.CHOOSE
p.choose(0)
p1.choose(0)
assert p.pending_action == PendingAction.PLAY
assert len(p.hand) == 2
assert len(p1.hand) == 2
def test_sventagliata():
g = Game('test')
ps = [Player(f'p{i}', f'p{i}') 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]
p2 = g.players[(g.turn + 2) % 3]
p.draw('')
p.hand = [Sventagliata('Hearts', 10), Bang('Hearts', 10)]
p1.hand = [Mancato('Spades', 2)]
p2.hand = [Mancato('Clubs', 5)]
# Play Sventagliata
p.play_card(0, against=p1.name)
assert p.pending_action == PendingAction.CHOOSE
assert len(p.available_cards) > 0 # Ensure there are available targets
# Simulate choosing a secondary target
secondary_target = p.available_cards[0]['name']
assert secondary_target != p.name and secondary_target != p1.name # Ensure the secondary target is correct
p.choose(0) # Choose the first available target
assert p.pending_action == PendingAction.WAIT
assert p1.pending_action == PendingAction.RESPOND
# Simulate p1 responding to the Bang
p1.respond(0) # Assuming p1 plays a Mancato card in response
assert p1.pending_action == PendingAction.WAIT
assert p.pending_action == PendingAction.WAIT
p2.respond(0) # Assuming p2 plays a Mancato card in response
assert p2.pending_action == PendingAction.WAIT
assert p.pending_action == PendingAction.PLAY
# check bang cannot be played
assert len(p.hand) == 1
p.play_card(0, against=p2.name)
assert p.pending_action == PendingAction.PLAY
assert len(p.hand) == 1
def test_mira():
g = started_game(['the_valley_of_shadows'])
p = current_player(g)
p.draw('')
p.hand = [Mira(0, 0), Bang(0, 0)]
target = next_player(g)
target.hand = []
target_health = target.lives
p.play_card(0, against=target.name, _with=1)
assert target.lives == target_health - 2

View File

@ -1,34 +0,0 @@
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
from bang.expansions.wild_west_show.characters import *
from bang.cards import Card, Suit
import bang.roles as roles
from globals import PendingAction
# test TerenKill
def test_TerenKill():
g = started_game(['wild_west_show'], 4, TerenKill())
p = current_player_with_cards(g, [])
p.lives = 0
g.deck.cards = [Card(Suit.HEARTS, 'card', 0), Card(Suit.HEARTS, 'card', 0)]
p.notify_self()
assert p.lives == 1
assert len(p.hand) == 1
p.lives = 0
g.deck.cards = [Card(Suit.SPADES, 'card', 0), Card(Suit.HEARTS, 'card', 0)]
p.notify_self()
assert p.lives == 0
# test YoulGrinner
def test_YoulGrinner():
g = started_game(['wild_west_show'], 4, YoulGrinner())
p = current_player(g)
p.hand = []
p.draw('')
assert len(p.hand) == 5
for pl in g.players:
if pl != p:
assert len(pl.hand) == 3

View File

@ -1,98 +0,0 @@
from tests import started_game, set_events, current_player, next_player, current_player_with_cards
from bang.expansions.wild_west_show.card_events import *
from bang.cards import Card, Suit
import bang.roles as roles
from globals import PendingAction
# test Camposanto
def test_camposanto():
g = started_game(['wild_west_show'], 4)
set_events(g, [Camposanto()])
current_player_with_cards(g, []).end_turn()
p = current_player_with_cards(g, [])
p.lives = 0
p.notify_self()
p1 = current_player_with_cards(g, [])
p1.lives = 0
p1.notify_self()
current_player_with_cards(g, []).end_turn()
current_player_with_cards(g, []).end_turn()
assert p.is_my_turn
assert p.lives == 1
current_player_with_cards(g, []).end_turn()
assert p1.is_my_turn
assert p1.lives == 1
# test DarlingValentine
def test_darling_valentine():
g = started_game(['wild_west_show'], 4)
set_events(g, [DarlingValentine()])
p = next_player(g)
hand = p.hand.copy()
current_player_with_cards(g, []).end_turn()
assert hand != current_player(g).hand
# test DorothyRage
# test HelenaZontero
def test_helena_zontero():
g = started_game(['wild_west_show'], 8)
set_events(g, [None, HelenaZontero()])
roles = [p.role.name for p in g.players]
for i in range(len(g.players)-1):
current_player_with_cards(g, []).end_turn()
g.deck.cards = [Card(Suit.HEARTS, 'card', 0)]*5
current_player_with_cards(g, []).end_turn()
roles2 = [p.role.name for p in g.players]
assert roles != roles2
# test LadyRosaDelTexas
def test_LadyRosaDelTexas():
g = started_game(['wild_west_show'], 4)
set_events(g, [LadyRosaDelTexas()])
p = current_player_with_cards(g, [Card(0,'card',0)]*4)
t = g.turn
p.draw('event')
assert g.turn == (t+1)%len(g.players)
# test MissSusanna
def test_miss_suzanna():
g = started_game(['wild_west_show'], 4)
set_events(g, [MissSusanna()])
p = current_player_with_cards(g, [])
p.end_turn()
assert p.lives == 4 # sceriffo 5-1
p = current_player_with_cards(g, [Card(0,'card',0)]*4)
p.play_card(0)
p.play_card(0)
p.play_card(0)
p.end_turn()
assert p.lives == 4
p = current_player_with_cards(g, [])
p.end_turn()
assert p.lives == 3
# test RegolamentoDiConti
def test_RegolamentoDiConti():
g = started_game(['wild_west_show'], 4)
set_events(g, [RegolamentoDiConti()])
p = current_player_with_cards(g, [Card(0,'card',0)]*4)
p.draw('event')
assert p.pending_action == PendingAction.CHOOSE
p.choose(0)
# test WildWestShow
def test_WildWestShow():
g = started_game(['wild_west_show'], 8)
set_events(g, [None, WildWestShow()])
for i in range(len(g.players)):
current_player_with_cards(g, []).end_turn()
for p in g.players:
assert isinstance(p.role, roles.Renegade)

View File

@ -1,4 +1,4 @@
# Bang Frontend
# frontend
## Project setup
```

5
frontend/babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -1,35 +0,0 @@
<!DOCTYPE html>
<html lang="en" translate="no" class="notranslate" data-build-timestamp-utc="<%= new Date().toISOString() %>">
<head>
<meta name="google" content="notranslate" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="keywords" content="bang, bang online, bang game, bang multiplayer, bang card game, bang card game online, Bang! card game rules online, play Bang! online, bang online with friends, high noon, dodge city, gold rush"/>
<link rel="icon" href="/favicon.ico">
<meta name="theme-color" content="black">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="PewPew!">
<meta name="description" content="Wanna play Bang with your friends but you cannot meet in person? Play Bang! the online multiplayer card game. It includes expansion like Gold Rush, Dodge City, Fistful of Cards and High Noon">
<title>PewPew! The online Bang card game!</title>
<!-- Twitter Card data -->
<meta name="twitter:card" value="Wanna play Bang with your friends but you cannot meet in person? Play Bang! the online multiplayer card game. It includes expansion like Gold Rush, Dodge City, Fistful of Cards and High Noon">
<!-- Open Graph data -->
<meta property="og:title" content="PewPew! The online bang card game!" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://bang.xamin.it/" />
<!-- <meta property="og:image" content="http://example.com/image.jpg" /> -->
<meta property="og:description" content="Wanna play Bang with your friends but you cannot meet in person? Play Bang! the online multiplayer card game. It includes expansion like Gold Rush, Dodge City, Fistful of Cards and High Noon" />
<script type="module" src="/src/main.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but to play Bang! you need JavaScript enabqled. Please enable it to play.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,32 +1,26 @@
{
"name": "bang-frontend",
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vite",
"build": "vite build",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@datadog/browser-rum": "^4.36.0",
"bang-vue-socket.io": "^4.0.0",
"caniuse-lite": "^1.0.30001470",
"pretty-checkbox-vue": "^1.1.9",
"register-service-worker": "^1.7.2",
"vue": "^2.7.16",
"vue-clipboard2": "^0.3.3",
"vue-i18n": "^8.28.2",
"vue-json-viewer": "^2.2.22",
"vue-router": "^3.6.5"
"core-js": "^3.6.5",
"socket.io-client": "^3.0.3",
"vue": "^2.6.11",
"vue-socket.io": "^3.0.10"
},
"devDependencies": {
"eslint": "^8.36.0",
"eslint-plugin-vue": "^9.10.0",
"vite": "^4.5.3",
"vite-plugin-html": "^3.2.0",
"vite-plugin-pwa": "^0.14.6",
"vite-plugin-vue2": "^2.0.3",
"vue-template-compiler": "^2.7.14"
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
@ -37,6 +31,9 @@
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [

BIN
frontend/public/favicon.ico Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 215 B

Some files were not shown because too many files have changed in this diff Show More