Compare commits
No commits in common. "main" and "0.1.0b" have entirely different histories.
@ -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"
|
||||
}
|
25
.github/dependabot.yml
vendored
@ -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"
|
72
.github/workflows/dev-image.yml
vendored
@ -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 }}
|
79
.github/workflows/docker-image.yml
vendored
@ -1,71 +1,20 @@
|
||||
name: Docker Images CI
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
|
||||
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 Unified Docker image
|
||||
run: docker build . --file Dockerfile --tag albertoxamin/bang:latest
|
||||
- name: Build the Backend Docker image
|
||||
run: cd backend;docker build . --file Dockerfile --tag albertoxamin/bang-backend:latest
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build the Frontend Docker image
|
||||
run: cd frontend;docker build . --file Dockerfile --tag albertoxamin/bang-frontend:latest
|
||||
- name: Log into registry
|
||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
- name: Push images
|
||||
run: docker push albertoxamin/bang:latest; docker push albertoxamin/bang-backend:latest; docker push albertoxamin/bang-frontend:latest
|
||||
|
25
.github/workflows/pylint.yml
vendored
@ -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
|
28
.github/workflows/test-backend.yml
vendored
@ -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
|
89
.github/workflows/tests.yaml
vendored
@ -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
@ -136,10 +136,3 @@ dmypy.json
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
frontend/package-lock.json
|
||||
bang-workspace.code-workspace
|
||||
|
||||
.vscode/
|
||||
backend/save/*
|
||||
save/
|
29
Dockerfile
@ -1,30 +1,11 @@
|
||||
# build Vue frontend
|
||||
FROM node:18 as builder
|
||||
FROM node:lts-alpine 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
|
||||
FROM python:3.7-slim-stretch
|
||||
COPY --from=builder ./dist /dist/
|
||||
COPY ./backend /dist/
|
||||
WORKDIR /dist
|
||||
# create dir for save
|
||||
RUN mkdir save
|
||||
RUN pip install -r requirements.txt
|
||||
EXPOSE 5001
|
||||
|
||||
ENV PATH=/root/.local/bin:${PATH}
|
||||
VOLUME /dist/save
|
||||
|
||||
ENTRYPOINT ["python", "/dist/server.py"]
|
||||
ENTRYPOINT ["python", "/dist/__init__.py"]
|
13
README.md
@ -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">
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
```
|
131
backend/__init__.py
Normal file
@ -0,0 +1,131 @@
|
||||
import json
|
||||
from typing import List
|
||||
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'},
|
||||
'/css': './css',
|
||||
'/js': './js',
|
||||
})
|
||||
|
||||
games: List[Game] = []
|
||||
online_players = 0
|
||||
|
||||
def advertise_lobbies():
|
||||
sio.emit('lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players)} for g in games if not g.started and len(g.players) < 7])
|
||||
|
||||
@sio.event
|
||||
def connect(sid, environ):
|
||||
print('connect ', sid)
|
||||
sio.enter_room(sid, 'lobby')
|
||||
sio.emit('players', room='lobby', data=online_players)
|
||||
|
||||
@sio.event
|
||||
def set_username(sid, username):
|
||||
global online_players
|
||||
online_players += 1
|
||||
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):
|
||||
global online_players
|
||||
if sio.get_session(sid):
|
||||
online_players -= 1
|
||||
sio.emit('players', room='lobby', data=online_players)
|
||||
if sio.get_session(sid).game and sio.get_session(sid).disconnect():
|
||||
sio.close_room(sio.get_session(sid).game.name)
|
||||
games.pop(games.index(sio.get_session(sid).game))
|
||||
print('disconnect ', sid)
|
||||
advertise_lobbies()
|
||||
|
||||
@sio.event
|
||||
def create_room(sid, room_name):
|
||||
while len([g for g in games if g.name == room_name]):
|
||||
room_name += '_1'
|
||||
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)
|
||||
while len([p for p in games[i].players if p.name == sio.get_session(sid).name]):
|
||||
sio.get_session(sid).name += '_1'
|
||||
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, pile):
|
||||
ses = sio.get_session(sid)
|
||||
ses.draw(pile)
|
||||
|
||||
@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)
|
||||
|
||||
@sio.event
|
||||
def scrap(sid, card_index):
|
||||
ses = sio.get_session(sid)
|
||||
ses.scrap(card_index)
|
||||
|
||||
if __name__ == '__main__':
|
||||
eventlet.wsgi.server(eventlet.listen(('', 5001)), app)
|
@ -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
|
@ -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
|
@ -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()
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 l’equipaggiamento ha il bordo marrone, applicane subito l’effetto e poi scartalo.
|
||||
BLACK = 1 # Se l’equipaggiamento 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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(),
|
||||
]
|
@ -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,
|
||||
# l’abilità di Evan Babbit, e così via.
|
||||
# Sleeper Car: puoi anche usare
|
||||
# l’effetto 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
|
@ -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
|
@ -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
|
1312
backend/bang/game.py
397
backend/cards.py
Normal file
@ -0,0 +1,397 @@
|
||||
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
|
||||
if self.range != 0 and self.range != 99:
|
||||
self.alt_text = f'{self.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__()
|
||||
|
||||
def play_card(self, player, against):
|
||||
contro = f'contro {against}' if against else ''
|
||||
player.sio.emit('chat_message', room=player.game.name,
|
||||
data=f'{player.name} ha giocato {self.name}{contro}.')
|
||||
return True
|
||||
|
||||
|
||||
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
|
||||
|
||||
def play_card(self, player, against):
|
||||
if player.has_played_bang and not any([isinstance(c, Volcanic) for c in player.equipment]) and against != None:
|
||||
return False
|
||||
elif against != None:
|
||||
import characters as chars
|
||||
super().play_card(player, against=against)
|
||||
player.has_played_bang = not isinstance(
|
||||
player.character, chars.WillyTheKid)
|
||||
player.game.attack(player, against)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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"
|
||||
|
||||
def play_card(self, player, against):
|
||||
if len(player.game.players) != 2 and player.lives != player.max_lives:
|
||||
super().play_card(player, against=against)
|
||||
player.lives = min(player.lives+1, player.max_lives)
|
||||
return True
|
||||
elif len(player.game.players) == 2:
|
||||
player.sio.emit('chat_message', room=player.game.name,
|
||||
data=f'{player.name} ha rovesciato una {self.name}.')
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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
|
||||
|
||||
def play_card(self, player, against):
|
||||
if against != None:
|
||||
super().play_card(player, against=against)
|
||||
from players import PendingAction
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.choose_action = 'discard'
|
||||
player.target_p = against
|
||||
print('choose now')
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Diligenza(Card):
|
||||
def __init__(self, suit, number):
|
||||
super().__init__(suit, 'Diligenza', number)
|
||||
self.icon = '🚡'
|
||||
self.desc = "Pesca 2 carte dalla cima del mazzo"
|
||||
|
||||
def play_card(self, player, against):
|
||||
super().play_card(player, against=against)
|
||||
player.sio.emit('chat_message', room=player.game.name,
|
||||
data=f'{player.name} ha giocato {self.name} e ha pescato 2 carte.')
|
||||
for i in range(2):
|
||||
player.hand.append(player.game.deck.draw())
|
||||
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"
|
||||
|
||||
def play_card(self, player, against):
|
||||
if against != None:
|
||||
super().play_card(player, against=against)
|
||||
player.game.duel(player, against)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Emporio(Card):
|
||||
def __init__(self, suit, number):
|
||||
super().__init__(suit, 'Emporio', number)
|
||||
self.icon = '🏪'
|
||||
self.desc = "Scopri dal mazzo tante carte quanto il numero di giocatori, a turno, partendo da te, scegliete una carta e aggiungetela alla vostra mano"
|
||||
|
||||
def play_card(self, player, against):
|
||||
super().play_card(player, against=against)
|
||||
player.game.emporio()
|
||||
return True
|
||||
|
||||
|
||||
class Gatling(Card):
|
||||
def __init__(self, suit, number):
|
||||
super().__init__(suit, 'Gatling', number)
|
||||
self.icon = '🛰'
|
||||
self.desc = "Spara a tutti gli altri giocatori"
|
||||
|
||||
def play_card(self, player, against):
|
||||
super().play_card(player, against=against)
|
||||
player.game.attack_others(player)
|
||||
return True
|
||||
|
||||
|
||||
class Indiani(Card):
|
||||
def __init__(self, suit, number):
|
||||
super().__init__(suit, 'Indiani!', number)
|
||||
self.icon = '🏹'
|
||||
self.desc = "Tutti gli altri giocatori devono scartare un Bang! o perdere una vita"
|
||||
|
||||
def play_card(self, player, against):
|
||||
super().play_card(player, against=against)
|
||||
player.game.indian_others(player)
|
||||
return True
|
||||
|
||||
|
||||
class Mancato(Card):
|
||||
def __init__(self, suit, number):
|
||||
super().__init__(suit, 'Mancato!', number)
|
||||
self.icon = '😅'
|
||||
self.desc = "Usa questa carta per annullare un bang"
|
||||
|
||||
def play_card(self, player, against):
|
||||
import characters as chars
|
||||
if (not player.has_played_bang and against != None and isinstance(player.character, chars.CalamityJanet)):
|
||||
player.sio.emit('chat_message', room=player.game.name,
|
||||
data=f'{player.name} ha giocato {self.name} come un BANG! contro {against}.')
|
||||
player.has_played_bang = True
|
||||
player.game.attack(player, against)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Panico(Card):
|
||||
def __init__(self, suit, number):
|
||||
super().__init__(suit, 'Panico!', number, range=1)
|
||||
self.icon = '😱'
|
||||
self.need_target = True
|
||||
self.desc = "Pesca una carta da un giocatore a distanza 1, scegli a caso dalla mano, oppure fra quelle che ha in gioco"
|
||||
|
||||
def play_card(self, player, against):
|
||||
if against != None:
|
||||
super().play_card(player, against=against)
|
||||
from players import PendingAction
|
||||
player.pending_action = PendingAction.CHOOSE
|
||||
player.choose_action = 'steal'
|
||||
player.target_p = against
|
||||
print('choose now')
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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 = '🍻'
|
||||
|
||||
def play_card(self, player, against):
|
||||
player.sio.emit('chat_message', room=player.game.name,
|
||||
data=f'{player.name} ha giocato {self.name} e ha curato 1 punto vita a tutti.')
|
||||
for p in player.game.players:
|
||||
p.lives = min(p.lives+1, p.max_lives)
|
||||
p.notify_self()
|
||||
return True
|
||||
|
||||
|
||||
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 play_card(self, player, against):
|
||||
player.sio.emit('chat_message', room=player.game.name,
|
||||
data=f'{player.name} ha giocato {self.name} e ha pescato 3 carte.')
|
||||
for i in range(3):
|
||||
player.hand.append(player.game.deck.draw())
|
||||
return True
|
||||
|
||||
|
||||
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),
|
||||
]
|
154
backend/characters.py
Normal file
@ -0,0 +1,154 @@
|
||||
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 = sight_mod
|
||||
self.visibility_mod = visibility_mod
|
||||
self.pick_mod = pick_mod
|
||||
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)
|
||||
self.desc = "Ogni volta che viene ferito, pesca una carta"
|
||||
self.icon = '💔'
|
||||
#una sola carta per ogni gruppo di danni. es: dinamite -> 1 carta
|
||||
|
||||
def on_hurt(self, dmg):
|
||||
pass
|
||||
|
||||
class BlackJack(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Black Jack", max_lives=4)
|
||||
self.desc = "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"
|
||||
self.icon = '🎰'
|
||||
|
||||
class CalamityJanet(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Calamity Janet", max_lives=4)
|
||||
self.icon = '🔀'
|
||||
self.desc = "Può usare i Mancato! come Bang! e viceversa"
|
||||
#TODO: gestire bene la scelta multipla in ogni iterazione con la carta bang e mancato
|
||||
# vale anche per le carte indiani e duello
|
||||
# se usa un mancato come bang ovviamente non ne può usare altri lo stesso turno se non ha una volcanic
|
||||
|
||||
class ElGringo(Character):
|
||||
def __init__(self):
|
||||
super().__init__("El Gringo", max_lives=3)
|
||||
self.desc = "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)"
|
||||
self.icon = '🤕'
|
||||
# ovviamente la dinamite non è considerata danno inferto da un giocatore
|
||||
|
||||
class JesseJones(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Jesse Jones", max_lives=4)
|
||||
self.desc = "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"
|
||||
self.icon = '😜'
|
||||
|
||||
class Jourdonnais(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Jourdonnais", max_lives=4)
|
||||
self.desc = "Gioca come se avesse un Barile sempre attivo, nel caso in cui metta in gioco un Barile 'Reale' può estrarre due volte"
|
||||
self.icon = '🛢'
|
||||
|
||||
class KitCarlson(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Kit Carlson", max_lives=4)
|
||||
self.desc = "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"
|
||||
self.icon = '🤔'
|
||||
|
||||
class LuckyDuke(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Lucky Duke", max_lives=4, pick_mod=1)
|
||||
self.desc = "Ogni volta che deve estrarre, prende due carte dal mazzo, sceglie una delle due carte per l'estrazione, infine le scarta entrambe"
|
||||
self.icon = '🍀'
|
||||
|
||||
class PaulRegret(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Paul Regret", max_lives=3, visibility_mod=1)
|
||||
self.desc = "Gioca come se avesse una Mustang sempre attiva, nel caso in cui metta in gioco una Mustang 'Reale' l'effetto si somma tranquillamente"
|
||||
self.icon = '🏇'
|
||||
|
||||
class PedroRamirez(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Pedro Ramirez", max_lives=4)
|
||||
self.desc = "All'inizio del suo turno, quando deve pescare, può prendere la prima carta dalla cima degli scarti e la seconda dal mazzo"
|
||||
self.icon = '🚮'
|
||||
|
||||
class RoseDoolan(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Rose Doolan", max_lives=4, sight_mod=1)
|
||||
self.icon = '🕵️♀️'
|
||||
self.desc = "Gioca come se avesse un Mirino sempre attivo, nel caso in cui metta in gioco una Mirino 'Reale' l'effetto si somma tranquillamente"
|
||||
|
||||
class SidKetchum(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Sid Ketchum", max_lives=4)
|
||||
self.desc = "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 starebbe per morire"
|
||||
self.icon = '🤤'
|
||||
|
||||
class SlabTheKiller(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Slab The Killer", max_lives=4)
|
||||
self.desc = "Per evitare i suoi Bang servono due Mancato, un eventuale barile vale solo come un Mancato"
|
||||
self.icon = '🔪'
|
||||
#vale per tutte le carte bang non solo per la carta che si chiama Bang!
|
||||
|
||||
class SuzyLafayette(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Suzy Lafayette", max_lives=4)
|
||||
self.desc = "Appena rimane senza carte in mano pesca immediatamente una carta dal mazzo"
|
||||
self.icon = '🔂'
|
||||
|
||||
class VultureSam(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Vulture Sam", max_lives=4)
|
||||
self.desc = "Quando un personaggio viene eliminato prendi tutte le carte di quel giocatore e aggiungile alla tua mano, sia le carte in mano che quelle in gioco"
|
||||
self.icon = '💰'
|
||||
|
||||
class WillyTheKid(Character):
|
||||
def __init__(self):
|
||||
super().__init__("Willy The Kid", max_lives=4)
|
||||
self.desc = "Questo personaggio può giocare quanti bang vuole nel suo turno"
|
||||
self.icon = '🎉'
|
||||
|
||||
def all_characters():
|
||||
return [
|
||||
BartCassidy(),
|
||||
BlackJack(),
|
||||
CalamityJanet(),
|
||||
ElGringo(),
|
||||
JesseJones(),
|
||||
Jourdonnais(),
|
||||
KitCarlson(),
|
||||
LuckyDuke(),
|
||||
PaulRegret(),
|
||||
PedroRamirez(),
|
||||
RoseDoolan(),
|
||||
SidKetchum(),
|
||||
SlabTheKiller(),
|
||||
SuzyLafayette(),
|
||||
VultureSam(),
|
||||
WillyTheKid(),
|
||||
]
|
50
backend/deck.py
Normal file
@ -0,0 +1,50 @@
|
||||
from typing import List, Set, Dict, Tuple, Optional
|
||||
import random
|
||||
import cards as cs
|
||||
|
||||
class Deck:
|
||||
def __init__(self, game):
|
||||
super().__init__()
|
||||
self.cards: List[cs.Card] = cs.get_starting_deck()
|
||||
self.game = game
|
||||
random.shuffle(self.cards)
|
||||
self.scrap_pile: List[cs.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) -> cs.Card:
|
||||
if len(self.scrap_pile) > 0:
|
||||
return self.scrap_pile[-1]
|
||||
else:
|
||||
return None
|
||||
|
||||
def pick_and_scrap(self) -> cs.Card:
|
||||
card = self.cards.pop(0)
|
||||
self.scrap_pile.append(card)
|
||||
self.game.notify_scrap_pile()
|
||||
return card
|
||||
|
||||
def put_on_top(self, card: cs.Card):
|
||||
self.cards.insert(0, card)
|
||||
|
||||
def draw(self) -> cs.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) -> cs.Card:
|
||||
if len(self.scrap_pile) > 0:
|
||||
card = self.scrap_pile.pop(-1)
|
||||
self.game.notify_scrap_pile()
|
||||
return card
|
||||
else:
|
||||
return self.draw()
|
||||
|
||||
def scrap(self, card: cs.Card):
|
||||
self.scrap_pile.append(card)
|
||||
self.game.notify_scrap_pile()
|
257
backend/game.py
Normal file
@ -0,0 +1,257 @@
|
||||
import cards
|
||||
from typing import List, Set, Dict, Tuple, Optional
|
||||
import random
|
||||
import socketio
|
||||
from cards import Bang
|
||||
import characters
|
||||
import players
|
||||
from characters import all_characters
|
||||
from deck import Deck
|
||||
from players import PendingAction, 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
|
||||
self.waiting_for = 0
|
||||
self.initial_players = 0
|
||||
|
||||
def add_player(self, player: players.Player):
|
||||
if player in self.players or len(self.players) >= 7:
|
||||
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.initial_players = len(self.players)
|
||||
self.choose_characters()
|
||||
|
||||
def distribute_roles(self):
|
||||
available_roles: List[roles.Role] = []
|
||||
if len(self.players) == 3:
|
||||
available_roles = [
|
||||
roles.Vice('Elimina il Rinnegato 🦅, se non lo elimini tu elimina anche il Fuorilegge'),
|
||||
roles.Renegade('Elimina il Fuorilegge 🐺, se non lo elimini tu elimina anche il Vice'),
|
||||
roles.Outlaw('Elimina il Vice 🎖, se non lo elimini tu elimina anche il Rinnegato')
|
||||
]
|
||||
elif len(self.players) >= 4:
|
||||
available_roles = [roles.Sheriff(), roles.Renegade(), roles.Outlaw(), roles.Outlaw(), roles.Vice(), roles.Outlaw(), roles.Vice()]
|
||||
available_roles = available_roles[:len(self.players)]
|
||||
random.shuffle(available_roles)
|
||||
for i in range(len(self.players)):
|
||||
self.sio.emit('chat_message', room=self.name, data=f'{self.players[i].name} ha come personaggio {self.players[i].character.name}, la sua abilità speciale è: {self.players[i].character.desc}')
|
||||
self.players[i].set_role(available_roles[i])
|
||||
if isinstance(available_roles[i], roles.Sheriff) or (len(available_roles) == 3 and isinstance(available_roles[i], roles.Vice)):
|
||||
if isinstance(available_roles[i], roles.Sheriff):
|
||||
self.sio.emit('chat_message', room=self.name, data=f'{self.players[i].name} È lo sceriffo')
|
||||
self.turn = i
|
||||
self.players[i].prepare()
|
||||
for k in range(self.players[i].max_lives):
|
||||
self.players[i].hand.append(self.deck.draw())
|
||||
self.players[i].notify_self()
|
||||
self.play_turn()
|
||||
|
||||
def attack_others(self, attacker:Player):
|
||||
attacker.pending_action = players.PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.waiting_for = 0
|
||||
self.readyCount = 0
|
||||
for p in self.players:
|
||||
if p != attacker:
|
||||
if p.get_banged(attacker=attacker):
|
||||
self.waiting_for += 1
|
||||
if self.waiting_for == 0:
|
||||
attacker.pending_action = players.PendingAction.PLAY
|
||||
attacker.notify_self()
|
||||
|
||||
def indian_others(self, attacker:Player):
|
||||
attacker.pending_action = players.PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
self.waiting_for = 0
|
||||
self.readyCount = 0
|
||||
for p in self.players:
|
||||
if p != attacker:
|
||||
if p.get_indians(attacker=attacker):
|
||||
self.waiting_for += 1
|
||||
if self.waiting_for == 0:
|
||||
attacker.pending_action = players.PendingAction.PLAY
|
||||
attacker.notify_self()
|
||||
|
||||
def attack(self, attacker:Player, target_username:str):
|
||||
if self.players[self.players_map[target_username]].get_banged(attacker=attacker, double=isinstance(attacker.character, characters.SlabTheKiller)):
|
||||
self.readyCount = 0
|
||||
self.waiting_for = 1
|
||||
attacker.pending_action = players.PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
|
||||
def duel(self, attacker:Player, target_username:str):
|
||||
if self.players[self.players_map[target_username]].get_dueled(attacker=attacker):
|
||||
self.readyCount = 0
|
||||
self.waiting_for = 1
|
||||
attacker.pending_action = players.PendingAction.WAIT
|
||||
attacker.notify_self()
|
||||
|
||||
def emporio(self):
|
||||
self.available_cards = [self.deck.draw() for i in range(len(self.players))]
|
||||
self.players[self.turn].pending_action = players.PendingAction.CHOOSE
|
||||
self.players[self.turn].available_cards = self.available_cards
|
||||
self.players[self.turn].notify_self()
|
||||
|
||||
def respond_emporio(self, player, i):
|
||||
player.hand.append(self.available_cards.pop(i))
|
||||
player.available_cards = []
|
||||
player.pending_action = players.PendingAction.WAIT
|
||||
player.notify_self()
|
||||
nextPlayer = self.players[(self.turn + (len(self.players)-len(self.available_cards))) % len(self.players)]
|
||||
if nextPlayer == self.players[self.turn]:
|
||||
self.players[self.turn].pending_action = players.PendingAction.PLAY
|
||||
self.players[self.turn].notify_self()
|
||||
else:
|
||||
nextPlayer.pending_action = players.PendingAction.CHOOSE
|
||||
nextPlayer.available_cards = self.available_cards
|
||||
nextPlayer.notify_self()
|
||||
|
||||
def get_player_named(self, name:str):
|
||||
return self.players[self.players_map[name]]
|
||||
|
||||
def responders_did_respond_resume_turn(self):
|
||||
self.readyCount += 1
|
||||
if self.readyCount == self.waiting_for:
|
||||
self.waiting_for = 0
|
||||
self.readyCount = 0
|
||||
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')
|
||||
if self.deck.peek_scrap_pile():
|
||||
self.sio.emit('scrap', room=self.name, data=self.deck.peek_scrap_pile().__dict__)
|
||||
else:
|
||||
self.sio.emit('scrap', room=self.name, data=None)
|
||||
|
||||
def handle_disconnect(self, player: players.Player):
|
||||
print(f'player {player.name} left the game {self.name}')
|
||||
self.player_death(player=player)
|
||||
if len(self.players) == 0:
|
||||
print(f'no players left in game {self.name}')
|
||||
return True
|
||||
else: return False
|
||||
|
||||
def player_death(self, player: players.Player):
|
||||
print(player.attacker)
|
||||
if player.attacker and isinstance(player.attacker, roles.Sheriff) and isinstance(player.role, roles.Vice):
|
||||
for i in range(len(player.attacker.hand)):
|
||||
self.deck.scrap(player.attacker.hand.pop())
|
||||
for i in range(len(player.attacker.equipment)):
|
||||
self.deck.scrap(player.attacker.equipment.pop())
|
||||
player.attacker.notify_self()
|
||||
elif player.attacker and (isinstance(player.role, roles.Outlaw) or self.initial_players == 3):
|
||||
for i in range(3):
|
||||
player.attacker.hand.append(self.deck.draw())
|
||||
player.attacker.notify_self()
|
||||
print(f'player {player.name} died')
|
||||
if (self.waiting_for > 0):
|
||||
self.responders_did_respond_resume_turn()
|
||||
vulture = [p for p in self.players if isinstance(p.character, characters.VultureSam)]
|
||||
if len(vulture) == 0:
|
||||
for i in range(len(player.hand)):
|
||||
self.deck.scrap(player.hand.pop())
|
||||
for i in range(len(player.equipment)):
|
||||
self.deck.scrap(player.equipment.pop())
|
||||
else:
|
||||
for i in range(len(player.hand)):
|
||||
vulture[0].hand.append(player.hand.pop())
|
||||
for i in range(len(player.equipment)):
|
||||
vulture[0].hand.append(player.equipment.pop())
|
||||
index = self.players.index(player)
|
||||
died_in_his_turn = self.started and index == self.turn
|
||||
if self.started and index <= self.turn:
|
||||
self.turn -= 1
|
||||
self.players.pop(index)
|
||||
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.')
|
||||
if self.started:
|
||||
self.sio.emit('chat_message', room=self.name, data=f'{player.name} era {player.role.name}!')
|
||||
for p in self.players:
|
||||
p.notify_self()
|
||||
self.players_map = {c.name: i for i, c in enumerate(self.players)}
|
||||
if self.started:
|
||||
print('Check win status')
|
||||
attacker_role = None
|
||||
if player.attacker:
|
||||
attacker_role = player.attacker.role
|
||||
winners = [p for p in self.players if p.role != None and p.role.on_player_death(self.players, initial_players=self.initial_players, dead_role=player.role, attacker_role=attacker_role)]
|
||||
if len(winners) > 0:
|
||||
print('WE HAVE A WINNER')
|
||||
for p in self.players:
|
||||
p.win_status = p in winners
|
||||
p.notify_self()
|
||||
return
|
||||
|
||||
if died_in_his_turn:
|
||||
self.next_turn()
|
||||
|
||||
def get_visible_players(self, player:Player):
|
||||
i = self.players.index(player)
|
||||
sight = player.get_sight()
|
||||
return [{
|
||||
'name': self.players[j].name,
|
||||
'dist': min(abs(i - j), (i+ abs(j-len(self.players))), (j+ abs(i-len(self.players)))) + self.players[j].get_visibility() - (player.get_sight(countWeapon=False)-1),
|
||||
'lives': self.players[j].lives,
|
||||
'max_lives': self.players[j].max_lives,
|
||||
'is_sheriff': isinstance(self.players[j].role, roles.Sheriff),
|
||||
} for j in range(len(self.players)) if i != j]
|
||||
|
||||
def notify_all(self):
|
||||
if self.started:
|
||||
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,
|
||||
'pending_action': p.pending_action,
|
||||
'character': p.character.__dict__ if p.character else None,
|
||||
'icon': p.role.icon if self.initial_players == 3 and p.role else '🤠'
|
||||
} for p in self.players]
|
||||
self.sio.emit('players_update', room=self.name, data=data)
|
@ -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
|
@ -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"))
|
469
backend/players.py
Normal file
@ -0,0 +1,469 @@
|
||||
import deck
|
||||
from enum import IntEnum
|
||||
import json
|
||||
from random import randrange
|
||||
import socketio
|
||||
|
||||
import roles as r
|
||||
import cards as cs
|
||||
import characters as chars
|
||||
|
||||
class PendingAction(IntEnum):
|
||||
PICK = 0
|
||||
DRAW = 1
|
||||
PLAY = 2
|
||||
RESPOND = 3
|
||||
WAIT = 4
|
||||
CHOOSE = 5
|
||||
|
||||
|
||||
class Player:
|
||||
import game as g
|
||||
|
||||
def __init__(self, name, sid, sio):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.sid = sid
|
||||
self.sio = sio
|
||||
self.hand: cs.Card = []
|
||||
self.equipment: cs.Card = []
|
||||
self.role: r.Role = None
|
||||
self.character: chars.Character = None
|
||||
self.lives = 0
|
||||
self.max_lives = 0
|
||||
self.game: g = 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_failed_response_cb = None
|
||||
self.event_type: str = None
|
||||
self.expected_response = None
|
||||
self.attacker = None
|
||||
self.target_p: str = None
|
||||
self.is_drawing = False
|
||||
self.mancato_needed = 0
|
||||
|
||||
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: r.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 = []
|
||||
self.pending_action = PendingAction.WAIT
|
||||
|
||||
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_card(self, player, card):
|
||||
mess = {
|
||||
'player': player.name,
|
||||
'card': card.__dict__
|
||||
}
|
||||
print('notifying card')
|
||||
self.sio.emit('notify_card', room=self.sid, data=mess)
|
||||
|
||||
def notify_self(self):
|
||||
if isinstance(self.character, chars.CalamityJanet):
|
||||
self.expected_response = [
|
||||
cs.Mancato(0, 0).name, cs.Bang(0, 0).name]
|
||||
elif isinstance(self.character, chars.SuzyLafayette) and len(self.hand) == 0:
|
||||
self.hand.append(self.game.deck.draw())
|
||||
ser = self.__dict__.copy()
|
||||
ser.pop('game')
|
||||
ser.pop('sio')
|
||||
ser.pop('sid')
|
||||
ser.pop('on_pick_cb')
|
||||
ser.pop('on_failed_response_cb')
|
||||
# ser.pop('expected_response')
|
||||
ser.pop('attacker')
|
||||
if self.attacker:
|
||||
ser['attacker'] = self.attacker.name
|
||||
ser['sight'] = self.get_sight()
|
||||
ser['lives'] = max(ser['lives'], 0)
|
||||
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__))
|
||||
if self.lives <= 0 and self.max_lives > 0:
|
||||
print('dying, attacker', self.attacker)
|
||||
if isinstance(self.character, chars.SidKetchum) and len(self.hand) > 1:
|
||||
self.lives += 1
|
||||
self.game.deck.scrap(self.hand.pop(
|
||||
randrange(0, len(self.hand))))
|
||||
self.game.deck.scrap(self.hand.pop(
|
||||
randrange(0, len(self.hand))))
|
||||
self.game.player_death(self)
|
||||
self.game.notify_all()
|
||||
|
||||
def play_turn(self):
|
||||
if self.lives == 0:
|
||||
return self.end_turn(forced=True)
|
||||
self.scrapped_cards = 0
|
||||
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, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]):
|
||||
self.pending_action = PendingAction.PICK
|
||||
else:
|
||||
self.pending_action = PendingAction.DRAW
|
||||
self.notify_self()
|
||||
|
||||
def draw(self, pile):
|
||||
if self.pending_action != PendingAction.DRAW:
|
||||
return
|
||||
if isinstance(self.character, chars.KitCarlson):
|
||||
self.is_drawing = True
|
||||
self.available_cards = [self.game.deck.draw() for i in range(3)]
|
||||
self.pending_action = PendingAction.CHOOSE
|
||||
self.notify_self()
|
||||
else:
|
||||
self.pending_action = PendingAction.PLAY
|
||||
if pile == 'scrap' and isinstance(self.character, chars.PedroRamirez):
|
||||
self.hand.append(self.game.deck.draw_from_scrap_pile())
|
||||
self.hand.append(self.game.deck.draw())
|
||||
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha ha pescato la prima carta dall pila delle carte scartate.')
|
||||
elif type(pile) == str and pile != self.name and pile in self.game.players_map and isinstance(self.character, chars.JesseJones) and len(self.game.get_player_named(pile).hand) > 0:
|
||||
self.hand.append(self.game.get_player_named(pile).hand.pop(
|
||||
randrange(0, len(self.game.get_player_named(pile).hand))))
|
||||
self.game.get_player_named(pile).notify_self()
|
||||
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha pescato la prima carta dalla mano di {self.attacker.name}.')
|
||||
self.hand.append(self.game.deck.draw())
|
||||
else:
|
||||
for i in range(2):
|
||||
card: cs.Card = self.game.deck.draw()
|
||||
self.hand.append(card)
|
||||
if i == 1 and isinstance(self.character, chars.BlackJack):
|
||||
for p in self.game.players:
|
||||
if p != self:
|
||||
p.notify_card(self, card)
|
||||
if card.suit == cs.Suit.HEARTS or card.suit == cs.Suit.DIAMONDS:
|
||||
self.hand.append(self.game.deck.draw())
|
||||
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], cs.Dinamite):
|
||||
while pickable_cards > 0:
|
||||
pickable_cards -= 1
|
||||
picked: cs.Card = self.game.deck.pick_and_scrap()
|
||||
print(f'Did pick {picked}')
|
||||
self.sio.emit('chat_message', room=self.game.name,
|
||||
data=f'{self.name} ha estratto {picked}.')
|
||||
if picked.suit == cs.Suit.SPADES and 2 <= picked.number <= 9 and pickable_cards == 0:
|
||||
self.lives -= 3
|
||||
self.game.deck.scrap(self.equipment.pop(i))
|
||||
if isinstance(self.character, chars.BartCassidy):
|
||||
self.hand.append(self.game.deck.draw())
|
||||
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, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]):
|
||||
self.notify_self()
|
||||
return
|
||||
for i in range(len(self.equipment)):
|
||||
if isinstance(self.equipment[i], cs.Prigione):
|
||||
while pickable_cards > 0:
|
||||
pickable_cards -= 1
|
||||
picked: cs.Card = self.game.deck.pick_and_scrap()
|
||||
print(f'Did pick {picked}')
|
||||
self.sio.emit('chat_message', room=self.game.name,
|
||||
data=f'{self.name} ha estratto {picked}.')
|
||||
if picked.suit != cs.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
|
||||
if any([isinstance(c, cs.Prigione) for c in self.equipment]):
|
||||
self.notify_self()
|
||||
return
|
||||
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, cs.Bang) and self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment]):
|
||||
continue
|
||||
elif isinstance(card, cs.Birra) and self.lives >= self.max_lives:
|
||||
continue
|
||||
else:
|
||||
playable_cards.append(i)
|
||||
return playable_cards
|
||||
|
||||
def get_public_description(self):
|
||||
s = f"{self.name} {'Sheriff ⭐️' if isinstance(self.role, r.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, against=None):
|
||||
if not (0 <= hand_index < len(self.hand)):
|
||||
print('illegal')
|
||||
return
|
||||
card: cs.Card = self.hand.pop(hand_index)
|
||||
print(self.name, 'is playing ', card, ' against:', against)
|
||||
if isinstance(card, cs.Prigione) and not isinstance(self.game.get_player_named(against).role, r.Sheriff):
|
||||
self.sio.emit('chat_message', room=self.game.name,
|
||||
data=f'{self.name} ha giocato {card.name} contro {against}.')
|
||||
self.game.get_player_named(against).equipment.append(card)
|
||||
self.game.get_player_named(against).notify_self()
|
||||
elif card.is_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)
|
||||
elif card.name in [c.name for c in self.equipment if not isinstance(c, cs.Dinamite)]:
|
||||
for i in range(len(self.equipment)):
|
||||
if type(self.equipment[i]) == type(card):
|
||||
self.game.deck.scrap(self.equipment[i])
|
||||
self.equipment[i] = card
|
||||
break
|
||||
else:
|
||||
self.equipment.append(card)
|
||||
else:
|
||||
did_play_card = card.play_card(self, against)
|
||||
if did_play_card:
|
||||
self.game.deck.scrap(card)
|
||||
else:
|
||||
self.hand.insert(hand_index, card)
|
||||
self.notify_self()
|
||||
|
||||
def choose(self, card_index):
|
||||
if self.pending_action != PendingAction.CHOOSE:
|
||||
return
|
||||
if self.target_p and self.target_p != '': # panico, cat balou
|
||||
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()
|
||||
# specifico per personaggio
|
||||
elif self.is_drawing and isinstance(self.character, chars.KitCarlson):
|
||||
self.hand.append(self.available_cards.pop(card_index))
|
||||
if len(self.available_cards) == 1:
|
||||
self.game.deck.put_on_top(self.available_cards.pop())
|
||||
self.is_drawing = False
|
||||
self.pending_action = PendingAction.PLAY
|
||||
self.notify_self()
|
||||
else: # emporio
|
||||
self.game.respond_emporio(self, card_index)
|
||||
|
||||
def barrel_pick(self):
|
||||
pickable_cards = 1 + self.character.pick_mod
|
||||
if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and isinstance(self.character, chars.Jourdonnais):
|
||||
pickable_cards = 2
|
||||
while pickable_cards > 0:
|
||||
pickable_cards -= 1
|
||||
picked: cs.Card = self.game.deck.pick_and_scrap()
|
||||
print(f'Did pick {picked}')
|
||||
self.sio.emit('chat_message', room=self.game.name,
|
||||
data=f'{self.name} ha estratto {picked}.')
|
||||
if picked.suit == cs.Suit.HEARTS:
|
||||
self.mancato_needed -= 1
|
||||
self.notify_self()
|
||||
if self.mancato_needed <= 0:
|
||||
self.game.responders_did_respond_resume_turn()
|
||||
return
|
||||
if len([c for c in self.hand if isinstance(c, cs.Mancato) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Bang))]) == 0:
|
||||
self.take_damage_response()
|
||||
self.game.responders_did_respond_resume_turn()
|
||||
else:
|
||||
self.pending_action = PendingAction.RESPOND
|
||||
self.expected_response = [cs.Mancato(0, 0).name]
|
||||
self.on_failed_response_cb = self.take_damage_response
|
||||
self.notify_self()
|
||||
|
||||
def get_banged(self, attacker, double=False):
|
||||
self.attacker = attacker
|
||||
self.mancato_needed = 1 if not double else 2
|
||||
if len([c for c in self.hand if isinstance(c, cs.Mancato) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Bang))]) == 0 and len([c for c in self.equipment if isinstance(c, cs.Barile)]) == 0 and not isinstance(self.character, chars.Jourdonnais):
|
||||
print('Cant defend')
|
||||
self.take_damage_response()
|
||||
return False
|
||||
else:
|
||||
if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 or isinstance(self.character, chars.Jourdonnais):
|
||||
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 = [cs.Mancato(0, 0).name]
|
||||
self.on_failed_response_cb = self.take_damage_response
|
||||
self.notify_self()
|
||||
return True
|
||||
|
||||
def get_indians(self, attacker):
|
||||
self.attacker = attacker
|
||||
if len([c for c in self.hand if isinstance(c, cs.Bang) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0:
|
||||
print('Cant defend')
|
||||
self.take_damage_response()
|
||||
return False
|
||||
else:
|
||||
print('has bang')
|
||||
self.pending_action = PendingAction.RESPOND
|
||||
self.expected_response = [cs.Bang(0, 0).name]
|
||||
self.event_type = 'indians'
|
||||
self.on_failed_response_cb = self.take_damage_response
|
||||
self.notify_self()
|
||||
return True
|
||||
|
||||
def get_dueled(self, attacker):
|
||||
self.attacker = attacker
|
||||
if len([c for c in self.hand if isinstance(c, cs.Bang) or (isinstance(self.character, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0:
|
||||
print('Cant defend')
|
||||
self.take_damage_response()
|
||||
self.game.responders_did_respond_resume_turn()
|
||||
return False
|
||||
else:
|
||||
self.pending_action = PendingAction.RESPOND
|
||||
self.expected_response = [cs.Bang(0, 0).name]
|
||||
self.event_type = 'duel'
|
||||
self.on_failed_response_cb = self.take_damage_response
|
||||
self.notify_self()
|
||||
return True
|
||||
|
||||
def take_damage_response(self):
|
||||
self.lives -= 1
|
||||
if self.lives > 0:
|
||||
if isinstance(self.character, chars.BartCassidy):
|
||||
self.hand.append(self.game.deck.draw())
|
||||
elif isinstance(self.character, chars.ElGringo) and self.attacker and len(self.attacker.hand) > 0:
|
||||
self.hand.append(self.attacker.hand.pop(
|
||||
randrange(0, len(self.attacker.hand))))
|
||||
self.sio.emit('chat_message', room=self.game.name, data=f'{self.name} ha rubato una carta a {self.attacker.name} mentre veniva colpito.')
|
||||
self.attacker.notify_self()
|
||||
while self.lives <= 0 and len(self.game.players) > 2 and len([c for c in self.hand if isinstance(c, cs.Birra)]) > 0:
|
||||
for i in range(len(self.hand)):
|
||||
if isinstance(self.hand[i], cs.Birra):
|
||||
self.lives += 1
|
||||
self.game.deck.scrap(self.hand.pop(i))
|
||||
self.sio.emit('chat_message', room=self.game.name,
|
||||
data=f'{self.name} ha usato una birra per recuperare una vita.')
|
||||
break
|
||||
self.mancato_needed = 0
|
||||
self.notify_self()
|
||||
self.attacker = None
|
||||
|
||||
def respond(self, hand_index):
|
||||
self.pending_action = PendingAction.WAIT
|
||||
if hand_index != -1 and self.hand[hand_index].name in self.expected_response:
|
||||
self.game.deck.scrap(self.hand.pop(hand_index))
|
||||
self.notify_self()
|
||||
self.mancato_needed -= 1
|
||||
if self.mancato_needed <= 0:
|
||||
if self.event_type == 'duel':
|
||||
self.game.duel(self, self.attacker.name)
|
||||
else:
|
||||
self.game.responders_did_respond_resume_turn()
|
||||
self.event_type = ''
|
||||
else:
|
||||
self.pending_action = PendingAction.RESPOND
|
||||
self.notify_self()
|
||||
else:
|
||||
self.on_failed_response_cb()
|
||||
self.game.responders_did_respond_resume_turn()
|
||||
self.attacker = None
|
||||
|
||||
def get_sight(self, countWeapon=True):
|
||||
if not self.character:
|
||||
return 0
|
||||
aim = 0
|
||||
range = 0
|
||||
for card in self.equipment:
|
||||
if card.is_weapon and countWeapon:
|
||||
range += card.range
|
||||
else:
|
||||
aim += card.sight_mod
|
||||
return max(1, range) + aim + self.character.sight_mod
|
||||
|
||||
def get_visibility(self):
|
||||
if not self.character:
|
||||
return 0
|
||||
covers = 0
|
||||
for card in self.equipment:
|
||||
covers += card.vis_mod
|
||||
return self.character.visibility_mod + covers
|
||||
|
||||
def scrap(self, card_index):
|
||||
if self.is_my_turn or isinstance(self.character, chars.SidKetchum):
|
||||
self.scrapped_cards += 1
|
||||
if isinstance(self.character, chars.SidKetchum) and self.scrapped_cards == 2:
|
||||
self.scrapped_cards = 0
|
||||
self.lives = min(self.lives+1, self.max_lives)
|
||||
self.game.deck.scrap(self.hand.pop(card_index))
|
||||
self.notify_self()
|
||||
|
||||
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()
|
@ -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
|
||||
|
@ -14,77 +14,68 @@ class Role(ABC):
|
||||
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)):
|
||||
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):
|
||||
def __init__(self, alternative_goal=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:
|
||||
elif initial_players == 3 and attacker_role != None:
|
||||
return isinstance(dead_role, Renegade) and isinstance(attacker_role, Vice)
|
||||
elif initial_players != 3 and not any((isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players)):
|
||||
elif initial_players != 3 and not any([isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players]):
|
||||
print("The Vice won!")
|
||||
return True
|
||||
return False
|
||||
|
||||
class Outlaw(Role):
|
||||
def __init__(self, alternative_goal=None, alternative_goal_eng=None):
|
||||
def __init__(self, alternative_goal=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:
|
||||
elif initial_players == 3 and attacker_role != None:
|
||||
return isinstance(dead_role, Vice) and isinstance(attacker_role, Outlaw)
|
||||
elif (initial_players != 3 and (not any((isinstance(p.role, Sheriff) for p in alive_players)))
|
||||
and (any((isinstance(p.role, Outlaw) for p in alive_players))
|
||||
or any((isinstance(p.role, Renegade) for p in alive_players)) and len(alive_players) > 1)):
|
||||
elif initial_players != 3 and not any([isinstance(p.role, Sheriff) for p in alive_players]):
|
||||
print("The Outlaw won!")
|
||||
return True
|
||||
return False
|
||||
|
||||
class Renegade(Role):
|
||||
def __init__(self, alternative_goal=None, alternative_goal_eng=None):
|
||||
def __init__(self, alternative_goal=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:
|
||||
elif initial_players == 3 and attacker_role != None:
|
||||
return isinstance(dead_role, Outlaw) and isinstance(attacker_role, Renegade)
|
||||
elif initial_players != 3 and len(alive_players) == 1 and isinstance(alive_players[0].role, Renegade):
|
||||
elif initial_players != 3 and len(alive_players) == 1 and isinstance(alive_players[0], Renegade):
|
||||
print("The Renegade won!")
|
||||
return True
|
||||
return False
|
1421
backend/server.py
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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",
|
||||
]
|
||||
)
|
@ -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)
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
@ -1,4 +1,4 @@
|
||||
# Bang Frontend
|
||||
# frontend
|
||||
|
||||
## Project setup
|
||||
```
|
||||
|
5
frontend/babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
@ -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>
|
@ -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": [
|
||||
|
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 527 B |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 527 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 527 B |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 17 KiB |
@ -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 |
18
frontend/public/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<script src="https://twemoji.maxcdn.com/v/latest/twemoji.min.js" crossorigin="anonymous"></script>
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
@ -1,2 +0,0 @@
|
||||
User-agent: *
|
||||
Allow: /
|
@ -1,216 +1,134 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div id="app" class="dark-mode">
|
||||
<div v-if="!isInLobby" id="logo" class="center-stuff" style="margin-bottom:10pt;">
|
||||
<h1 style="margin-bottom:0pt;">PewPew!</h1>
|
||||
<i style="font-size: x-small;">Bang! è un marchio registrato DVGiochi</i>
|
||||
</div>
|
||||
<div v-if="isConnected">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<div v-else class="center-stuff">
|
||||
<h2>{{$t("warning")}}</h2>
|
||||
<p>{{$t("connection_error")}}</p>
|
||||
<ul v-if="shouldShowBackendSuggestions">
|
||||
Connect to one of these backends:
|
||||
<li v-for="suggestion in backendSuggestions" :key="suggestion.name" @click="changeBackend(suggestion)">
|
||||
{{suggestion.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<help :inGame="true" v-if="showHelp"/>
|
||||
<div style="position:fixed;bottom:4pt;right:4pt;display:flex;z-index:10">
|
||||
<input v-if="connect_dev" type=button class="btn" style="min-width:28pt;cursor:pointer;" @click="resetConnection" :value="'💚'+connect_dev" />
|
||||
<input type=button class="btn" style="min-width:28pt;cursor:pointer;" @click="()=>{sending_report = true}" :value=" $t('report') " />
|
||||
<input type="button" class="btn" value="" style="min-width:28pt;cursor:pointer;background-position:center;background-image:url('https://img.icons8.com/color/48/discord-logo.png');background-size:1.5em;background-repeat: no-repeat;" @click="joinDiscord"/>
|
||||
<input type="button" class="btn" :value="(showHelp?'X':'?')" style="min-width:28pt;border-radius:100%;cursor:pointer;" @click="getHelp"/>
|
||||
<select id="theme" class="btn" v-model="theme">
|
||||
<option
|
||||
v-for="(theme, i) in ['light.☀️', 'dark.🌙️', 'sepia.🌇️', 'grayscale.📰️', 'black.⬛']"
|
||||
:key="`theme-${i}`"
|
||||
:value="theme.split('.')[0]">
|
||||
{{theme.split('.')[1]}} {{$t(`theme.${theme.split('.')[0]}`)}}
|
||||
</option>
|
||||
</select>
|
||||
<select id="lang" class="btn" v-model="$i18n.locale" @change="storeLangPref">
|
||||
<option
|
||||
v-for="(lang, i) in ['it.🇮🇹.Italiano', 'en.🇬🇧.English', 'cs.🇨🇿.Čeština', 'fr.🇫🇷.Français', 'es.🇪🇸.Español', 'ru.🇷🇺.Русский']"
|
||||
:key="`lang-${i}`"
|
||||
:value="lang.split('.')[0]">
|
||||
{{lang.split('.')[1]}} {{lang.split('.')[2]}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="lang" style="opacity:0" >Language</label>
|
||||
<div v-if="showUpdateUI" style="position: fixed;bottom: 0;z-index: 1;background: rgba(0,0,0,0.5);padding: 20pt;" class="center-stuff">
|
||||
<p class="update-dialog__content">
|
||||
A new version is available. Refresh to load it?
|
||||
</p>
|
||||
<div class="update-dialog__actions">
|
||||
<button @click="update">Update</button>
|
||||
<button @click="showUpdateUI = false">Cancel</button>
|
||||
<div v-if="!didSetUsername">
|
||||
<p>Scegli un username:</p>
|
||||
<form @submit="setUsername">
|
||||
<input v-model="username" />
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<p>Giocatori online: {{onlinePlayers}}</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="!isInLobby" >
|
||||
<p>Giocatori online: {{onlinePlayers}}</p>
|
||||
<Card :card="getSelfCard" style="position:absolute; bottom:10pt; right: 10pt;"/>
|
||||
<h2>Lobby disponibili:</h2>
|
||||
<div style="display: flex">
|
||||
<Card v-for="lobby in openLobbies" v-bind:key="lobby.name" :card="getLobbyCard(lobby)" @click.native="joinLobby(lobby)"/>
|
||||
<p v-if="noLobbyAvailable">Nessuna lobby disponibile</p>
|
||||
</div>
|
||||
<form @submit="createLobby">
|
||||
<h2>Crea una lobby:</h2>
|
||||
<p>Nome:</p>
|
||||
<input v-model="lobbyName"/>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
</div>
|
||||
<Lobby v-show="isInLobby" :username="username" />
|
||||
</div>
|
||||
</div>
|
||||
<transition name="bounce">
|
||||
<full-screen-input v-if="sending_report" :defaultValue="''" :text="$t('report_bug')" :val="report" :cancel="cancelReport" :send="sendReport" :canCancel="true"/>
|
||||
</transition>
|
||||
<div v-else class="center-stuff">
|
||||
<h2>Attenzione!</h2>
|
||||
<p>Connessione al server assente.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FullScreenInput from './components/FullScreenInput.vue'
|
||||
import Help from './components/Help.vue';
|
||||
import Vue from 'vue'
|
||||
import { datadogRum } from '@datadog/browser-rum';
|
||||
import Card from './components/Card.vue'
|
||||
import Lobby from './components/Lobby.vue'
|
||||
|
||||
export default {
|
||||
components: { Help, FullScreenInput },
|
||||
name: 'App',
|
||||
components: {
|
||||
Card,
|
||||
Lobby,
|
||||
},
|
||||
data: () => ({
|
||||
isConnected: false,
|
||||
c: false,
|
||||
registration: null,
|
||||
showUpdateUI: false,
|
||||
showHelp:false,
|
||||
theme: 'light',
|
||||
report: '',
|
||||
sending_report: false,
|
||||
connect_dev: undefined,
|
||||
backendSuggestions: [
|
||||
{ name: 'Bang Xamin', url: 'https://bang.xamin.it' },
|
||||
{ name: 'Bang Miga', url: 'https://bang.migani.synology.me/' },
|
||||
{ name: 'Localhost', url: 'http://localhost:5001' },
|
||||
],
|
||||
didSetUsername: false,
|
||||
username: '',
|
||||
openLobbies: [],
|
||||
lobbyName: '',
|
||||
isInLobby: false,
|
||||
onlinePlayers: 0,
|
||||
}),
|
||||
computed: {
|
||||
shouldShowBackendSuggestions() {
|
||||
return window.location.origin.indexOf('vercel') !== -1 || window.location.origin.indexOf('localhost') !== -1
|
||||
noLobbyAvailable() {
|
||||
return this.openLobbies && this.openLobbies.length == 0
|
||||
},
|
||||
getSelfCard() {
|
||||
return {
|
||||
name: this.username,
|
||||
number: 'YOU',
|
||||
icon: '🤠',
|
||||
is_character: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
sockets: {
|
||||
connect() {
|
||||
this.isConnected = true;
|
||||
document.title = 'PewPew!'
|
||||
if (Vue.config.devtools) {
|
||||
setTimeout(function(){
|
||||
this.username =(1+Math.random() * 100 % 100).toFixed(2).toString();
|
||||
this.setUsername();
|
||||
}.bind(this), 1000)
|
||||
}
|
||||
},
|
||||
disconnect() {
|
||||
this.isConnected = false;
|
||||
},
|
||||
room(data) {
|
||||
this.isInLobby = true;
|
||||
if (data.password)
|
||||
this.$router.replace({path:'game', query: { code: data.name, pwd: data.password }}).catch(()=>{});
|
||||
else
|
||||
this.$router.replace({path:'game', query: { code: data.name }}).catch(()=>{});
|
||||
lobbies(data) {
|
||||
this.openLobbies = data;
|
||||
},
|
||||
room() {
|
||||
this.isInLobby = true;
|
||||
},
|
||||
players(num) {
|
||||
this.onlinePlayers = num;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getHelp() {
|
||||
this.showHelp = !this.showHelp
|
||||
// window.open(`${window.location.origin}/help`, '_blank')
|
||||
},
|
||||
storeLangPref() {
|
||||
localStorage.setItem('lang', this.$i18n.locale);
|
||||
document.documentElement.lang = this.$i18n.locale;
|
||||
},
|
||||
update() {
|
||||
this.showUpdateUI = false;
|
||||
// Make sure we only send a 'skip waiting' message if the SW is waiting
|
||||
if (!this.registration || !this.registration.waiting) return
|
||||
// Send message to SW to skip the waiting and activate the new SW
|
||||
this.registration.waiting.postMessage({ type: 'SKIP_WAITING' })
|
||||
},
|
||||
changeBackend(suggestion) {
|
||||
this.$socket.disconnect();
|
||||
window.localStorage.setItem('connect-dev', suggestion.url);
|
||||
window.location.reload();
|
||||
},
|
||||
resetConnection() {
|
||||
this.$socket.disconnect();
|
||||
window.localStorage.removeItem('connect-dev');
|
||||
window.navigation.reload();
|
||||
},
|
||||
detectColorScheme() {
|
||||
if(localStorage.getItem("theme")){
|
||||
this.theme = localStorage.getItem("theme")
|
||||
console.log("Found theme preference: " + this.theme)
|
||||
} else if(!window.matchMedia) {
|
||||
console.log("Auto theme not supported")
|
||||
} else if(window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
console.log("Prefers dark mode")
|
||||
this.theme = "dark";
|
||||
}
|
||||
var style = getComputedStyle(document.body);
|
||||
document.querySelector("meta[name='theme-color']").setAttribute("content", style.getPropertyValue('--bg-color'));
|
||||
},
|
||||
joinDiscord() {
|
||||
window.open('https://discord.gg/Dr58dZ2na8', '_blank');
|
||||
},
|
||||
cancelReport(){
|
||||
this.sending_report = false
|
||||
},
|
||||
sendReport(text){
|
||||
if (text.trim().length > 0){
|
||||
this.sending_report = false
|
||||
this.$socket.emit('report', text)
|
||||
setUsername(e){
|
||||
if (this.username.trim().length > 0){
|
||||
this.didSetUsername = true
|
||||
this.$socket.emit('set_username', this.username)
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
updateAvailable(event) {
|
||||
this.registration = event.detail
|
||||
this.showUpdateUI = true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
theme() {
|
||||
document.documentElement.setAttribute("data-theme", this.theme);
|
||||
localStorage.setItem('theme', this.theme);
|
||||
var style = getComputedStyle(document.body);
|
||||
document.querySelector("meta[name='theme-color']").setAttribute("content", style.getPropertyValue('--bg-color'));
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (localStorage.getItem('lang')) {
|
||||
this.$i18n.locale = localStorage.getItem('lang');
|
||||
document.documentElement.lang = this.$i18n.locale;
|
||||
} else {
|
||||
let userLang = navigator.language || navigator.userLanguage;
|
||||
if (['it', 'en'].indexOf(userLang) == -1)
|
||||
userLang = 'en';
|
||||
this.$i18n.locale = userLang.split('-')[0]
|
||||
}
|
||||
if (window.localStorage.getItem('connect-dev')) {
|
||||
this.connect_dev = window.localStorage.getItem('connect-dev')
|
||||
}
|
||||
this.detectColorScheme()
|
||||
if (window.location.origin.indexOf('localhost') !== -1) return;
|
||||
datadogRum.init({
|
||||
applicationId: '076b1a5e-16a9-44eb-b320-27afd32c57a5',
|
||||
clientToken: 'pub1cc4d0d6ea0a7235aa1eab86e7a192d4',
|
||||
site: 'datadoghq.com',
|
||||
version: document.getElementsByTagName("html")[0].getAttribute("data-build-timestamp-utc").replace(/[-|:|T]/g,'.').substring(0,16),
|
||||
service:'bang-frontend',
|
||||
sessionSampleRate: 100,
|
||||
sessionReplaySampleRate: 100,
|
||||
trackUserInteractions: true,
|
||||
trackResources: true, //non so cosa faccia, proviamo
|
||||
trackLongTasks: true, //non so cosa faccia, proviamo
|
||||
defaultPrivacyLevel: 'allow',
|
||||
enableExperimentalFeatures: ['clickmap'],
|
||||
proxyUrl: (Vue.config.devtools ? `http://${window.location.hostname}:5001` : window.location.origin) + '/ddproxy',
|
||||
});
|
||||
datadogRum.setUser({name: localStorage.getItem('username')})
|
||||
datadogRum.startSessionReplayRecording();
|
||||
},
|
||||
created() {
|
||||
document.addEventListener('swUpdated', this.updateAvailable, { once: true })
|
||||
if (this.$workbox) {
|
||||
this.$workbox.addEventListener("waiting", () => {
|
||||
this.showUpdateUI = true;
|
||||
});
|
||||
}
|
||||
getLobbyCard(lobby) {
|
||||
return {
|
||||
name: lobby.name,
|
||||
icon: "💥",
|
||||
number: `${lobby.players}🤠`,
|
||||
is_equipment: true,
|
||||
}
|
||||
},
|
||||
createLobby(e) {
|
||||
if (this.lobbyName.trim().length > 0) {
|
||||
this.$socket.emit('create_room', this.lobbyName)
|
||||
}
|
||||
e.preventDefault();
|
||||
},
|
||||
joinLobby(lobby) {
|
||||
this.$socket.emit('join_room', lobby.name)
|
||||
},
|
||||
init() {
|
||||
location.reload();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '../node_modules/pretty-checkbox/dist/pretty-checkbox.css';
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
#app {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@ -221,12 +139,6 @@ html {
|
||||
-ms-user-select: none; /* Internet Explorer 10 and later */
|
||||
user-select: none; /* Likely future */
|
||||
}
|
||||
.selectable {
|
||||
-webkit-user-select: text !important; /* Chrome all and Safari all */
|
||||
-moz-user-select: text !important; /* Firefox all */
|
||||
-ms-user-select: text !important; /* Internet Explorer 10 and later */
|
||||
user-select: text !important; /* Likely future */
|
||||
}
|
||||
#logo {
|
||||
margin-top: 60pt;
|
||||
margin-bottom: 60pt !important;
|
||||
@ -235,17 +147,10 @@ html {
|
||||
#app {
|
||||
margin: 4pt;
|
||||
margin-top: -16pt;
|
||||
zoom: 0.75;
|
||||
}
|
||||
#lang,#theme{
|
||||
max-width: 26pt;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
padding-left: 4px;
|
||||
word-spacing: 20pt;
|
||||
zoom: 0.8;
|
||||
}
|
||||
}
|
||||
h1,h2,h3,h4,p,span,b,label{
|
||||
h1,h2,h3,h4,p,span{
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.center-stuff {
|
||||
@ -256,105 +161,62 @@ h1,h2,h3,h4,p,span,b,label{
|
||||
text-align: center;
|
||||
}
|
||||
.list-enter-active, .list-leave-active {
|
||||
transition: all 0.5s;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.25s ease-out;
|
||||
transition: opacity 0.25s ease-out;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.bounce-enter-active, .bounce-leave-active {
|
||||
animation: bounce-in .5s;
|
||||
animation: bounce-in .5s;
|
||||
}
|
||||
|
||||
.fade-enter, .bounce-leave-to {
|
||||
animation: bounce-out .5s;
|
||||
animation: bounce-out .5s;
|
||||
}
|
||||
@keyframes bounce-in {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes bounce-out {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
input, select {
|
||||
border: 2px solid;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
margin: 0.25rem;
|
||||
min-width: 125px;
|
||||
padding: 0.5rem;
|
||||
transition: border-color 0.5s ease-out;
|
||||
input {
|
||||
border: 2px solid;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
margin: 0.25rem;
|
||||
min-width: 125px;
|
||||
padding: 0.5rem;
|
||||
transition: border-color 0.5s ease-out;
|
||||
}
|
||||
input:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
:root {
|
||||
--font-color: #2c3e50;
|
||||
--bg-color: white;
|
||||
--muted-color: #ccc;
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
--font-color: rgb(174, 194, 211);
|
||||
--bg-color: #181a1b;
|
||||
}
|
||||
[data-theme="black"] {
|
||||
--font-color: rgb(174, 194, 211);
|
||||
--bg-color: #000000;
|
||||
}
|
||||
[data-theme="sepia"] {
|
||||
--font-color: rgb(54, 43, 33);
|
||||
--bg-color: #e7d6bb;
|
||||
--muted-color: rgba(54, 43, 33, 0.5);
|
||||
}
|
||||
[data-theme="grayscale"] {
|
||||
--font-color: rgb(66, 66, 66);
|
||||
--bg-color: #e2e0e0;
|
||||
--muted-color: rgba(66, 66, 66, 0.5);
|
||||
}
|
||||
html, #app, input, select {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--font-color);
|
||||
border: 2px solid var(--font-color);
|
||||
border-radius: 12pt;
|
||||
cursor: pointer;
|
||||
transition: all 0.13s ease-in-out;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
.btn:hover:not([disabled]) {
|
||||
background-color: var(--font-color); /* Green */
|
||||
color: var(--bg-color);
|
||||
}
|
||||
.btn:disabled {
|
||||
cursor: not-allowed;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root, #app, input {
|
||||
background-color: #181a1b;
|
||||
color: rgb(174, 194, 211);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|