Merge pull request #179 from albertoxamin/dev

Add gold rush and replay capabilities
This commit is contained in:
Alberto Xamin 2022-03-10 13:46:42 +01:00 committed by GitHub
commit ae941ba2af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 6260 additions and 4565 deletions

View File

@ -9,7 +9,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: crazy-max/ghaction-docker-buildx@v3
@ -23,12 +23,32 @@ jobs:
restore-keys: |
${{ runner.os }}-buildx-
-
name: Docker Buildx (build)
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,linux/arm/v7,linux/arm64 \
--platform linux/amd64 \
--output "type=image,push=false" \
--tag albertoxamin/bang:dev \
--file ./Dockerfile ./
-
name: Docker Buildx (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: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 ./
@ -51,3 +71,12 @@ jobs:
name: Inspect image
run: |
docker buildx imagetools inspect albertoxamin/bang:dev
- name: Notify discord
uses: th0th/notify-discord@v0.4.1
if: ${{ always() }}
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_JOB_NAME: "Docker image :dev"
GITHUB_JOB_STATUS: ${{ job.status }}

View File

@ -9,7 +9,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: crazy-max/ghaction-docker-buildx@v3
@ -23,12 +23,22 @@ jobs:
restore-keys: |
${{ runner.os }}-buildx-
-
name: Docker Buildx (build)
name: Docker Buildx (build amd64 arm64)
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform linux/amd64,linux/arm/v7 \
--platform linux/amd64,linux/arm64 \
--output "type=image,push=false" \
--tag albertoxamin/bang:latest \
--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:latest \
--file ./Dockerfile ./
@ -39,11 +49,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Docker Buildx (push)
name: Docker Buildx (push all)
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--platform linux/amd64,linux/arm/v7 \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
--output "type=image,push=true" \
--tag albertoxamin/bang:latest \
--file ./Dockerfile ./
@ -51,3 +61,12 @@ jobs:
name: Inspect image
run: |
docker buildx imagetools inspect albertoxamin/bang:latest
- 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 main :latest"
GITHUB_JOB_STATUS: ${{ job.status }}

37
.github/workflows/test-backend.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: Python package
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7]
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
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
- 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: "Backend tests"
GITHUB_JOB_STATUS: ${{ job.status }}

View File

@ -1,12 +1,53 @@
name: Test Pull requests
on:
pull_request:
jobs:
test_build:
buildx:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: '1'
steps:
- uses: actions/checkout@v2
- name: Build the Unified Docker image
run: docker build . --file Dockerfile
-
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@v2
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/arm64 \
--output "type=image,push=false" \
--tag albertoxamin/bang:test \
--file ./Dockerfile ./
-
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 ./
-
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 ./

View File

@ -1,11 +1,25 @@
# build Vue frontend
FROM node:lts-alpine as builder
COPY ./frontend .
RUN npm install
RUN npm run build
FROM python:3.7.10-stretch
# now we should have a dist folder containing the static website
FROM python:3.7.10-stretch 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.7.10-slim-stretch as app
# copy the dependencies from the pybuilder
COPY --from=pybuilder /root/.local /root/.local
# copy the backend python files from the pybuilder
COPY --from=pybuilder /code /dist
# copy the frontend static files from the builder
COPY --from=builder ./dist /dist/
COPY ./backend /dist/
WORKDIR /dist
RUN pip install -r requirements.txt
EXPOSE 5001
ENTRYPOINT ["python", "/dist/__init__.py"]
ENTRYPOINT ["python", "/dist/server.py"]

View File

@ -1 +1,8 @@
# 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)

View File

@ -1,494 +0,0 @@
import os
import json
import random
from typing import List
import eventlet
import socketio
from bang.game import Game
from bang.players import Player, PendingAction
import sys
sys.setrecursionlimit(10**6) # this should prevents bots from stopping
sio = socketio.Server(cors_allowed_origins="*")
static_files={
'/': {'content_type': 'text/html', 'filename': 'index.html'},
'/game': {'content_type': 'text/html', 'filename': 'index.html'},
'/help': {'content_type': 'text/html', 'filename': 'index.html'},
'/status': {'content_type': 'text/html', 'filename': 'index.html'},
# '/robots.txt': {'content_type': 'text/html', 'filename': 'robots.txt'},
'/favicon.ico': {'filename': 'favicon.ico'},
'/img/icons': './img/icons',
'/manifest.json': {'filename': 'manifest.json'},
'/css': './css',
'/media': './media',
'/js': './js',
}
for file in [f for f in os.listdir('.') if '.js' in f or '.map' in f or '.html' in f]:
static_files[f'/{file}'] = f'./{file}'
app = socketio.WSGIApp(sio, static_files=static_files)
games: List[Game] = []
online_players = 0
blacklist: List[str] = []
def advertise_lobbies():
sio.emit('lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'password': g.password} for g in games if not g.started and len(g.players) < 10 and not g.is_hidden])
sio.emit('spectate_lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if g.started])
@sio.event
def connect(sid, environ):
global online_players
online_players += 1
print('connect ', sid)
sio.enter_room(sid, 'lobby')
sio.emit('players', room='lobby', data=online_players)
@sio.event
def get_online_players(sid):
global online_players
sio.emit('players', room='lobby', data=online_players)
@sio.event
def set_username(sid, username):
ses = sio.get_session(sid)
if not isinstance(ses, Player):
sio.save_session(sid, Player(username, sid, sio))
print(f'{sid} is now {username}')
advertise_lobbies()
elif ses.game == None or not ses.game.started:
print(f'{sid} changed username to {username}')
prev = ses.name
if len([p for p in ses.game.players if p.name == username]) > 0:
ses.name = f"{username}_{random.randint(0,100)}"
else:
ses.name = username
sio.emit('chat_message', room=ses.game.name, data=f'_change_username|{prev}|{ses.name}')
sio.emit('me', data=ses.name, room=sid)
ses.game.notify_room()
@sio.event
def get_me(sid, room):
if isinstance(sio.get_session(sid), Player):
sio.emit('me', data=sio.get_session(sid).name, room=sid)
if sio.get_session(sid).game:
sio.get_session(sid).game.notify_room()
else:
sio.save_session(sid, Player('player', sid, sio))
de_games = [g for g in games if g.name == room['name']]
if len(de_games) == 1 and not de_games[0].started:
join_room(sid, room)
elif len(de_games) == 1 and de_games[0].started:
print('room exists')
if room['username'] != None and any([p.name == room['username'] for p in de_games[0].players if p.is_bot]):
print('getting inside the bot')
bot = [p for p in de_games[0].players if p.is_bot and p.name == room['username'] ][0]
bot.sid = sid
bot.is_bot = False
sio.enter_room(sid, de_games[0].name)
sio.save_session(sid, bot)
de_games[0].notify_room(sid)
eventlet.sleep(0.1)
de_games[0].notify_all()
sio.emit('role', room=sid, data=json.dumps(bot.role, default=lambda o: o.__dict__))
bot.notify_self()
if len(bot.available_characters) > 0:
bot.set_available_character(bot.available_characters)
else: #spectate
de_games[0].spectators.append(sio.get_session(sid))
sio.get_session(sid).game = de_games[0]
sio.enter_room(sid, de_games[0].name)
de_games[0].notify_room(sid)
de_games[0].notify_event_card(sid)
de_games[0].notify_scrap_pile(sid)
de_games[0].notify_all()
de_games[0].notify_event_card()
else:
create_room(sid, room['name'])
if sio.get_session(sid).game == None:
sio.emit('me', data={'error':'Wrong password/Cannot connect'}, room=sid)
else:
sio.emit('me', data=sio.get_session(sid).name, room=sid)
if room['username'] == None or any([p.name == room['username'] for p in sio.get_session(sid).game.players]):
sio.emit('change_username', room=sid)
else:
sio.emit('chat_message', room=sio.get_session(sid).game.name, data=f"_change_username|{sio.get_session(sid).name}|{room['username']}")
sio.get_session(sid).name = room['username']
sio.emit('me', data=sio.get_session(sid).name, room=sid)
if not sio.get_session(sid).game.started:
sio.get_session(sid).game.notify_room()
@sio.event
def disconnect(sid):
global online_players
online_players -= 1
if sio.get_session(sid):
sio.emit('players', room='lobby', data=online_players)
if sio.get_session(sid).game and sio.get_session(sid).disconnect():
sio.close_room(sio.get_session(sid).game.name)
games.pop(games.index(sio.get_session(sid).game))
print('disconnect ', sid)
advertise_lobbies()
@sio.event
def create_room(sid, room_name):
if sio.get_session(sid).game == None:
while len([g for g in games if g.name == room_name]):
room_name += f'_{random.randint(0,100)}'
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
g = Game(room_name, sio)
g.add_player(sio.get_session(sid))
if room_name in blacklist:
g.is_hidden = True
games.append(g)
print(f'{sid} created a room named {room_name}')
advertise_lobbies()
@sio.event
def private(sid):
g = sio.get_session(sid).game
g.set_private()
advertise_lobbies()
@sio.event
def toggle_expansion(sid, expansion_name):
g = sio.get_session(sid).game
g.toggle_expansion(expansion_name)
@sio.event
def toggle_comp(sid):
sio.get_session(sid).game.toggle_competitive()
@sio.event
def toggle_replace_with_bot(sid):
sio.get_session(sid).game.toggle_disconnect_bot()
@sio.event
def join_room(sid, room):
room_name = room['name']
i = [g.name for g in games].index(room_name)
if games[i].password != '' and games[i].password != room['password'].upper():
return
if not games[i].started:
print(f'{sid} joined a room named {room_name}')
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
while len([p for p in games[i].players if p.name == sio.get_session(sid).name]):
sio.get_session(sid).name += f'_{random.randint(0,100)}'
sio.emit('me', data=sio.get_session(sid).name, room=sid)
games[i].add_player(sio.get_session(sid))
advertise_lobbies()
else:
games[i].spectators.append(sio.get_session(sid))
sio.get_session(sid).game = games[i]
sio.get_session(sid).pending_action = PendingAction.WAIT
sio.enter_room(sid, games[0].name)
games[i].notify_room(sid)
eventlet.sleep(0.5)
games[i].notify_room(sid)
games[i].notify_all()
@sio.event
def chat_message(sid, msg):
ses: Player = sio.get_session(sid)
if len(msg) > 0:
if msg[0] == '/':
if '/addbot' in msg and not ses.game.started:
if len(msg.split()) > 1:
# for _ in range(int(msg.split()[1])):
# ses.game.add_player(Player(f'AI_{random.randint(0,1000)}', 'bot', sio, bot=True))
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'Only 1 bot at the time'})
else:
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
while any([p for p in ses.game.players if p.name == bot.name]):
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
ses.game.add_player(bot)
bot.bot_spin()
return
elif '/removebot' in msg and not ses.game.started:
if any([p.is_bot for p in ses.game.players]):
[p for p in ses.game.players if p.is_bot][-1].disconnect()
return
elif '/togglecomp' in msg and ses.game:
ses.game.toggle_competitive()
return
if '/debug' in msg:
cmd = msg.split()
if len(cmd) == 2 and 'DEPLOY_KEY' in os.environ and cmd[1] == os.environ['DEPLOY_KEY']: # solo chi ha la deploy key può attivare la modalità debug
ses.game.debug = not ses.game.debug
ses.game.notify_room()
elif ses == ses.game.players[0]: # solo l'owner può attivare la modalità debug
ses.game.debug = not ses.game.debug
ses.game.notify_room()
if ses.game.debug:
sio.emit('chat_message', room=sid, data={'color': f'red','text':f'debug mode is now active, only the owner of the room can disable it with /debug'})
return
if not ses.game.debug:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'debug mode is not active, only the owner of the room can enable it with /debug'})
elif '/set_chars' in msg and not ses.game.started:
cmd = msg.split()
if len(cmd) == 2 and int(cmd[1]) > 0:
ses.game.characters_to_distribute = int(cmd[1])
elif '/suicide' in msg and ses.game.started and ses.lives > 0:
ses.lives = 0
ses.notify_self()
elif '/nextevent' in msg and ses.game.started:
ses.game.deck.flip_event()
elif '/notify' in msg and ses.game.started:
cmd = msg.split()
if len(cmd) >= 3:
if cmd[1] in ses.game.players_map:
ses.game.get_player_named(cmd[1]).notify_card(ses, {
'name': ' '.join(cmd[2:]),
'icon': '🚨',
'suit': 4,
'number': ' '.join(cmd[2:])
})
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/show_cards' in msg and ses.game.started:
cmd = msg.split()
if len(cmd) == 2:
if cmd[1] in ses.game.players_map:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and is looking at {cmd[1]} hand'})
for c in ses.game.get_player_named(cmd[1]).hand:
ses.notify_card(ses, c)
eventlet.sleep(0.3)
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/ddc' in msg and ses.game.started: # debug destroy cards usage: [/ddc *] [/ddc username]
cmd = msg.split()
if len(cmd) == 2:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode destroyed {cmd[1]} cards'})
if cmd[1] == "*":
for p in ses.game.players_map:
ses.game.get_player_named(p).hand = []
ses.game.get_player_named(p).equipment = []
ses.game.get_player_named(p).notify_self()
elif cmd[1] in ses.game.players_map:
ses.game.get_player_named(cmd[1]).hand = []
ses.game.get_player_named(cmd[1]).equipment = []
ses.game.get_player_named(cmd[1]).notify_self()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/dsh' in msg and ses.game.started: #debug set health usage [/dsh * hp] [/dsh username hp]
cmd = msg.split()
if len(cmd) == 3:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and is changing {cmd[1]} health'})
if cmd[1] == "*":
for p in ses.game.players_map:
ses.game.get_player_named(p).lives = int(cmd[2])
ses.game.get_player_named(p).notify_self()
elif cmd[1] in ses.game.players_map:
ses.game.get_player_named(cmd[1]).lives = int(cmd[2])
ses.game.get_player_named(cmd[1]).notify_self()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/togglebot' in msg and ses.game:
ses.game.toggle_disconnect_bot()
elif '/cancelgame' in msg and ses.game.started:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} stopped the current game'})
ses.game.reset()
elif '/startgame' in msg and not ses.game.started:
ses.game.start_game()
elif '/setbotspeed' in msg:
ses.game.bot_speed = float(msg.split()[1])
elif '/addex' in msg and not ses.game.started:
cmd = msg.split()
if len(cmd) == 2:
cmd[1] = cmd[1].replace('foc', 'fistful_of_cards')
if cmd[1] not in ses.game.available_expansions:
ses.game.available_expansions.append(cmd[1])
ses.game.notify_room()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/setcharacter' in msg:
import bang.characters as characters
cmd = msg.split()
if len(cmd) >= 2:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and changed character'})
chs = characters.all_characters(ses.game.expansions)
ses.character = [c for c in chs if c.name == ' '.join(cmd[1:])][0]
ses.real_character = ses.character
ses.notify_self()
elif '/setevent' in msg and ses.game and ses.game.deck: #add event before the position /setevent (position) 0 (name) Peyote
cmd = msg.split()
if len(cmd) >= 3:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and changed event'})
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
chs = []
chs.extend(ce.get_all_events())
chs.append(ce.get_endgame_card())
chs.extend(ceh.get_all_events())
chs.append(ceh.get_endgame_card())
ses.game.deck.event_cards.insert(int(cmd[1]), [c for c in chs if c!=None and c.name == ' '.join(cmd[2:])][0])
ses.game.notify_event_card()
elif '/removecard' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and removed a card'})
cmd = msg.split()
if len(cmd) == 2:
if int(cmd[1]) < len(ses.hand):
ses.hand.pop(int(cmd[1]))
else:
ses.equipment.pop(int(cmd[1])-len(ses.hand))
ses.notify_self()
elif '/getcard' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got a card'})
import bang.cards as cs
cmd = msg.split()
if len(cmd) >= 2:
cards = cs.get_starting_deck(ses.game.expansions)
card_names = ' '.join(cmd[1:]).split(',')
for cn in card_names:
ses.hand.append([c for c in cards if c.name == cn][0])
ses.notify_self()
elif '/gameinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.game.__dict__}'})
elif '/meinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.__dict__}'})
elif '/mebot' in msg:
ses.is_bot = not ses.is_bot
ses.bot_spin()
elif '/arcadekick' in msg and ses.game.started:
if len([p for p in ses.game.players if p.pending_action != PendingAction.WAIT]) == 0:
sio.emit('chat_message', room=ses.game.name, data={'color': f'','text':f'KICKING THE ARCADE CABINET'})
ses.game.next_turn()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} COMMAND NOT FOUND'})
else:
color = sid.encode('utf-8').hex()[-3:]
sio.emit('chat_message', room=ses.game.name, data={'color': f'#{color}','text':f'[{ses.name}]: {msg}'})
@sio.event
def get_all_rooms(sid, deploy_key):
if 'DEPLOY_KEY' in os.environ and deploy_key == os.environ['DEPLOY_KEY']:
sio.emit('all_rooms', room=sid, data=[{
'name': g.name,
'hidden': g.is_hidden,
'players': [{'name':p.name, 'bot': p.is_bot, 'health': p.lives, 'sid': p.sid} for p in g.players],
'password': g.password,
'expansions': g.expansions,
'started': g.started,
'current_turn': g.turn,
'incremental_turn': g.incremental_turn,
'debug': g.debug,
'spectators': len(g.spectators)
} for g in games])
@sio.event
def kick(sid, data):
if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']:
sio.emit('kicked', room=data['sid'])
@sio.event
def hide_toogle(sid, data):
if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']:
game = [g for g in games if g.name==data['room']]
if len(games) > 0:
game[0].is_hidden = not game[0].is_hidden
if game[0].is_hidden:
if not data['room'] in blacklist:
blacklist.append(data['room'])
elif data['room'] in blacklist:
blacklist.remove(data['room'])
advertise_lobbies()
@sio.event
def start_game(sid):
ses: Player = sio.get_session(sid)
ses.game.start_game()
advertise_lobbies()
@sio.event
def set_character(sid, name):
ses: Player = sio.get_session(sid)
ses.set_character(name)
@sio.event
def refresh(sid):
ses: Player = sio.get_session(sid)
ses.notify_self()
@sio.event
def draw(sid, pile):
ses: Player = sio.get_session(sid)
ses.draw(pile)
@sio.event
def pick(sid):
ses: Player = sio.get_session(sid)
ses.pick()
@sio.event
def end_turn(sid):
ses: Player = sio.get_session(sid)
ses.end_turn()
@sio.event
def play_card(sid, data):
ses: Player = sio.get_session(sid)
ses.play_card(data['index'], data['against'], data['with'])
@sio.event
def respond(sid, data):
ses: Player = sio.get_session(sid)
ses.respond(data)
@sio.event
def choose(sid, card_index):
ses: Player = sio.get_session(sid)
ses.choose(card_index)
@sio.event
def scrap(sid, card_index):
ses: Player = sio.get_session(sid)
ses.scrap(card_index)
@sio.event
def chuck_lose_hp_draw(sid):
ses: Player = sio.get_session(sid)
ses.chuck_lose_hp_draw()
@sio.event
def holyday_special(sid, data):
ses: Player = sio.get_session(sid)
ses.holyday_special(data)
@sio.event
def get_cards(sid):
import bang.cards as c
cards = c.get_starting_deck(['dodge_city'])
cards_dict = {}
for ca in cards:
if ca.name not in cards_dict:
cards_dict[ca.name] = ca
cards = [cards_dict[i] for i in cards_dict]
sio.emit('cards_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event
def get_characters(sid):
import bang.characters as ch
cards = ch.all_characters(['dodge_city'])
sio.emit('characters_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event
def get_highnooncards(sid):
import bang.expansions.high_noon.card_events as ceh
chs = []
chs.extend(ceh.get_all_events())
chs.append(ceh.get_endgame_card())
sio.emit('highnooncards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
@sio.event
def get_foccards(sid):
import bang.expansions.fistful_of_cards.card_events as ce
chs = []
chs.extend(ce.get_all_events())
chs.append(ce.get_endgame_card())
sio.emit('foccards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
if __name__ == '__main__':
eventlet.wsgi.server(eventlet.listen(('', 5001)), app)

View File

@ -42,7 +42,10 @@ class Card(ABC):
self.must_be_used = False
def __str__(self):
if str(self.suit).isnumeric():
char = ['♦️', '♣️', '♥️', '♠️'][int(self.suit)]
else:
char = self.suit
return f'{self.name} {char}{self.number}'
return super().__str__()
@ -71,6 +74,7 @@ class Card(ABC):
return False
else:
player.equipment.append(self)
self.can_be_used_now = False
if against:
player.sio.emit('chat_message', room=player.game.name,
data=f'_play_card_against|{player.name}|{self.name}|{against}')
@ -84,7 +88,7 @@ class Card(ABC):
pass
def is_duplicate_card(self, player):
return self.name in [c.name for c in player.equipment]
return self.name in [c.name for c in player.equipment] or self.name in [c.name for c in player.gold_rush_equipment]
def check_suit(self, game, accepted):
import bang.expansions.high_noon.card_events as ceh
@ -207,13 +211,14 @@ class Bang(Card):
def play_card(self, player, against, _with=None):
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
if player.game.check_event(ceh.Sermone):
if player.game.check_event(ceh.Sermone) and not self.number == 42: # 42 gold rush
return False
if player.has_played_bang and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)) and against != None:
if ((player.has_played_bang and not self.number == 42) and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)) and against != None): # 42 gold rush:
return False
elif against != None:
import bang.characters as chars
super().play_card(player, against=against)
if not self.number == 42: # 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):
@ -230,18 +235,36 @@ class Birra(Card):
# 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"
# self.desc_eng = "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 play_card(self, player, against, _with=None):
def play_card(self, player, against=None, _with=None, skipChecks=False):
import bang.expansions.high_noon.card_events as ceh
if player.game.check_event(ceh.IlReverendo):
return False
if len(player.game.get_alive_players()) != 2:
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:
p.hand.append(player.game.deck.draw(True))
p.notify_self()
if 'gold_rush' in player.game.expansions and self.number != 42:
from bang.players import PendingAction
player.available_cards = [{
'name': 'Pepita',
'icon': '💵️',
'alt_text': '1',
'noDesc': True
}, self]
player.choose_text = 'choose_birra_function'
player.pending_action = PendingAction.CHOOSE
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:
elif len(player.game.get_alive_players()) == 2 or player.lives == player.max_lives:
player.sio.emit('chat_message', room=player.game.name,
data=f'_spilled_beer|{player.name}|{self.name}')
return True
@ -255,9 +278,10 @@ class CatBalou(Card):
# self.desc = "Fai scartare una carta a un qualsiasi giocatore, scegli a caso dalla mano, oppure fra quelle che ha in gioco"
# self.desc_eng = "Choose and discard a card from any other player."
self.need_target = True
self.can_target_self = True
def play_card(self, player, against, _with=None):
if against != None and (len(player.game.get_player_named(against).hand) + len(player.game.get_player_named(against).equipment)) > 0:
if against != 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):
if self.name == 'Cat Balou':
super().play_card(player, against=against)
from bang.players import PendingAction
@ -281,7 +305,7 @@ class Diligenza(Card):
player.sio.emit('chat_message', room=player.game.name,
data=f'_diligenza|{player.name}|{self.name}')
for i in range(2):
player.hand.append(player.game.deck.draw())
player.hand.append(player.game.deck.draw(True))
return True
@ -371,11 +395,12 @@ class Panico(Card):
super().__init__(suit, 'Panico!', number, range=1)
self.icon = '😱'
self.need_target = True
self.can_target_self = True
# self.desc = "Pesca una carta da un giocatore a distanza 1, scegli a caso dalla mano, oppure fra quelle che ha in gioco"
# self.desc_eng = "Steal a card from a player at distance 1"
def play_card(self, player, against, _with=None):
if against != None and (len(player.game.get_player_named(against).hand) + len(player.game.get_player_named(against).equipment)) > 0:
if against != 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)
from bang.players import PendingAction
player.pending_action = PendingAction.CHOOSE
@ -415,7 +440,7 @@ class WellsFargo(Card):
player.sio.emit('chat_message', room=player.game.name,
data=f'_wellsfargo|{player.name}|{self.name}')
for i in range(3):
player.hand.append(player.game.deck.draw())
player.hand.append(player.game.deck.draw(True))
return True

View File

@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from bang.expansions import *
from typing import List
class Character(ABC):
@ -19,6 +20,13 @@ class Character(ABC):
return False
return isinstance(self, character)
def special(self, player, data):
import bang.expansions.high_noon.card_events as ceh
if player.game.check_event(ceh.Sbornia):
return False
player.sio.emit('chat_message', room=player.game.name, data=f'_use_special|{player.name}|{self.name}')
return True
class BartCassidy(Character):
def __init__(self):
super().__init__("Bart Cassidy", max_lives=4)
@ -158,4 +166,6 @@ def all_characters(expansions: List[str]):
]
if 'dodge_city' in expansions:
base_chars.extend(DodgeCity.get_characters())
if 'gold_rush' in expansions:
base_chars.extend(GoldRush.get_characters())
return base_chars

View File

@ -3,6 +3,7 @@ import random
import bang.cards as cs
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
import bang.expansions.gold_rush.shop_cards as grc
class Deck:
def __init__(self, game):
@ -21,20 +22,27 @@ class Deck:
self.all_cards_str.append(c.name)
self.game = game
self.event_cards: List[ce.CardEvent] = []
endgame_cards = []
endgame_cards: List[ce.CardEvent] = []
if 'fistful_of_cards' in game.expansions:
self.event_cards.extend(ce.get_all_events())
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())
self.event_cards.extend(ceh.get_all_events(game.rng))
endgame_cards.append(ceh.get_endgame_card())
if len(self.event_cards) > 0:
random.shuffle(self.event_cards)
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(random.choice(endgame_cards))
random.shuffle(self.cards)
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')
@ -43,6 +51,16 @@ class Deck:
self.event_cards.append(self.event_cards.pop(0))
self.game.notify_event_card()
def fill_gold_rush_shop(self):
if not any([c == None for c in self.shop_cards]):
return
for i in range(3):
if self.shop_cards[i] == 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 peek(self, n_cards: int) -> list:
return self.cards[:n_cards]
@ -73,7 +91,7 @@ class Deck:
def reshuffle(self):
self.cards = self.scrap_pile[:-1].copy()
random.shuffle(self.cards)
self.game.rng.shuffle(self.cards)
self.scrap_pile = self.scrap_pile[-1:]
def draw_from_scrap_pile(self) -> cs.Card:
@ -86,6 +104,7 @@ class Deck:
return self.draw()
def scrap(self, card: cs.Card, ignore_event = False):
if card.number == 42: return
card.reset_card()
if self.game.check_event(ce.MinieraAbbandonata) and not ignore_event:
self.put_on_top(card)

View File

@ -7,3 +7,8 @@ class DodgeCity():
def get_cards():
from bang.expansions.dodge_city import cards
return cards.get_starting_deck()
class GoldRush():
def get_characters():
from bang.expansions.gold_rush import characters
return characters.all_characters()

View File

@ -41,7 +41,7 @@ class Schivata(Mancato):
return False
def use_card(self, player):
player.hand.append(player.game.deck.draw())
player.hand.append(player.game.deck.draw(True))
player.notify_self()
class RagTime(Panico):
@ -225,7 +225,7 @@ class Derringer(Pugnale):
def play_card(self, player, against, _with=None):
if self.can_be_used_now:
player.hand.append(player.game.deck.draw())
player.hand.append(player.game.deck.draw(True))
return super().play_card(player, against=against)
else:
if not self.is_duplicate_card(player):
@ -236,7 +236,7 @@ class Derringer(Pugnale):
return False
def use_card(self, player):
player.hand.append(player.game.deck.draw())
player.hand.append(player.game.deck.draw(True))
player.notify_self()
class Borraccia(Card):
@ -440,4 +440,5 @@ def get_starting_deck() -> List[Card]:
]
for c in cards:
c.expansion_icon = '🐄️'
c.expansion = 'dodge_city'
return cards

View File

@ -85,6 +85,21 @@ class ChuckWengam(Character):
# self.desc_eng = "On his turn he may decide to lose 1 HP to draw 2 cards from the deck."
self.icon = '💰'
def special(self, player, data):
if super().special(player, data):
if player.lives > 1 and player.is_my_turn:
import bang.expansions.gold_rush.shop_cards as grc
player.lives -= 1
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
player.gold_nuggets += 1
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
player.hand.append(player.game.deck.draw(True))
player.hand.append(player.game.deck.draw(True))
player.hand.append(player.game.deck.draw(True))
player.notify_self()
return True
return False
class PatBrennan(Character):
def __init__(self):
super().__init__("Pat Brennan", max_lives=4)
@ -106,6 +121,19 @@ class DocHolyday(Character):
# self.desc_eng = "He can discard 2 cards to play a bang."
self.icon = '✌🏻'
def special(self, player, data):
if super().special(player, data):
from bang.players import PendingAction
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
def all_characters() -> List[Character]:
cards = [
PixiePete(),
@ -126,6 +154,7 @@ def all_characters() -> List[Character]:
]
for c in cards:
c.expansion_icon = '🐄️'
c.expansion = 'dodge_city'
return cards
#Apache Kid: il suo effetto non conta nei duelli

View File

@ -101,7 +101,7 @@ def get_endgame_card():
end_game.expansion = 'fistful-of-cards'
return end_game
def get_all_events():
def get_all_events(rng=random):
cards = [
Agguato(),
Cecchino(),
@ -118,7 +118,7 @@ def get_all_events():
RouletteRussa(),
Vendetta(),
]
random.shuffle(cards)
rng.shuffle(cards)
for c in cards:
c.expansion = 'fistful-of-cards'
return cards

View File

@ -0,0 +1,98 @@
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)
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.hand.append(player.game.deck.draw(True))
player.notify_self()
return True
return False
class SimeonPicos(Character):
def __init__(self):
super().__init__("Simeon Picos", max_lives=4)
# ottiene 1 pepita ogni volta che perde 1 punto vita
self.icon = '🏇️'
def all_characters() -> List[Character]:
cards = [
DonBell(),
DutchWill(),
JackyMurieta(),
JoshMcCloud(),
MadamYto(),
PrettyLuzena(),
RaddieSnake(),
SimeonPicos(),
]
for c in cards:
c.expansion_icon = '🤑️'
c.expansion = 'gold_rush'
return cards

View File

@ -0,0 +1,261 @@
from bang.cards import *
import bang.roles as r
import bang.players as pl
class ShopCardKind(IntEnum):
BROWN = 0 # Se lequipaggiamento ha il bordo marrone, applicane subito leffetto e poi scartalo.
BLACK = 1 # Se lequipaggiamento ha il bordo nero, tienilo scoperto di fronte a te.
class ShopCard(Card):
def __init__(self, name:str, cost:int, kind:ShopCardKind):
super().__init__(suit='💵', number=cost, name=name)
self.kind = kind
self.expansion_icon = '🤑️'
self.expansion = 'gold_rush'
def play_card(self, player, against, _with=None):
if self.kind == ShopCardKind.BROWN:
player.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)
player.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,
'noDesc': True
} for p in player.game.get_alive_players()]
player.choose_text = 'choose_bicchierino'
player.pending_action = pl.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(1,42), Birra(1,42), Panico(1,42)]
for i in range(len(player.available_cards)):
player.available_cards[i].must_be_used = True
player.choose_text = 'choose_bottiglia'
player.pending_action = pl.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(1,42), Duello(1,42), CatBalou(1,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 = pl.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()
player.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):
player.sio.emit('chat_message', room=player.game.name,
data=f'_UnionPacific|{player.name}|{self.name}')
for i in range(4):
player.hand.append(player.game.deck.draw(True))
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):
player.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,
'noDesc': 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 = pl.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:
player.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.hand.append(player.game.deck.draw(True))
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:
player.sio.emit('chat_message', room=player.game.name, data=f'_play_card|{player.name}|{self.name}')
player.gold_nuggets -= 2
player.lives = min(player.lives + 1, player.max_lives)
player.notify_self()
return True
return False
# paga 2 pepite per recuperare 1 vita
def get_cards() -> List[Card]:
cards = [
Bicchierino(),
Bicchierino(),
Bicchierino(),
Bottiglia(),
Bottiglia(),
Bottiglia(),
Complice(),
Complice(),
Complice(),
CorsaAllOro(),
Rum(),
Rum(),
UnionPacific(),
Calumet(),
Cinturone(),
FerroDiCavallo(),
Piccone(),
Ricercato(),
Ricercato(),
Ricercato(),
Setaccio(),
Stivali(),
Talismano(),
Zaino(),
]
return cards

View File

@ -96,7 +96,7 @@ def get_endgame_card():
end_game.expansion = 'high-noon'
return end_game
def get_all_events():
def get_all_events(rng=random):
cards = [
Benedizione(),
Maledizione(),
@ -113,7 +113,7 @@ def get_all_events():
Manette(),
NuovaIdentita(),
]
random.shuffle(cards)
rng.shuffle(cards)
for c in cards:
c.expansion = 'high-noon'
return cards

View File

@ -7,11 +7,13 @@ import eventlet
import bang.players as pl
import bang.characters as characters
import bang.expansions.dodge_city.characters as chd
from bang.deck import Deck
import bang.roles as roles
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch
class Game:
def __init__(self, name, sio:socketio):
super().__init__()
@ -26,8 +28,8 @@ class Game:
self.waiting_for = 0
self.initial_players = 0
self.password = ''
self.expansions = []
self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon']
self.expansions: List[str] = []
self.available_expansions = ['dodge_city', 'fistful_of_cards', 'high_noon', 'gold_rush']
self.shutting_down = False
self.is_competitive = False
self.disconnect_bot = True
@ -38,22 +40,28 @@ class Game:
self.incremental_turn = 0
self.did_resuscitate_deadman = False
self.is_handling_death = False
self.pending_winners = []
self.pending_winners: List[pl.Player] = []
self.someone_won = False
self.attack_in_progress = False
self.characters_to_distribute = 2 # personaggi da dare a inizio partita
self.debug = self.name == 'debug'
self.is_changing_pwd = False
self.is_hidden = False
self.rng = random.Random()
self.rpc_log = []
self.is_replay = False
def reset(self):
print('resetting lobby')
print(f'{self.name}: resetting lobby')
self.players.extend(self.spectators)
self.spectators = []
for bot in [p for p in self.players if p.is_bot]:
if bot.was_player:
bot.is_bot = False
else:
bot.game = None
self.players = [p for p in self.players if not p.is_bot]
print(self.players)
print(f'{self.name}: players: {self.players}')
self.started = False
self.is_handling_death = False
self.waiting_for = 0
@ -66,6 +74,54 @@ class Game:
eventlet.sleep(0.5)
self.notify_room()
def replay(self, log):
from tests.dummy_socket import DummySocket
self.players = []
self.is_hidden = True
self.is_replay = True
self.replay_speed = 1
for i in range(len(log)):
print('replay:', i, 'of', len(log))
cmd = log[i].split(';')
if cmd[1] == 'players':
self.expansions = json.loads(cmd[4].replace("'",'"'))
pnames = json.loads(cmd[3].replace("'",'"'))
for p in pnames:
self.add_player(pl.Player(p, p, DummySocket(self.sio), bot=False))
continue
if cmd[1] == 'start_game':
self.start_game(int(cmd[2]))
continue
player = [p for p in self.players if p.name == cmd[0]][0]
if cmd[1] == 'set_character':
if player.character != None and isinstance(player.real_character, chd.VeraCuster):
player.set_available_character([p.character for p in self.get_alive_players() if p != player])
player.set_character(cmd[2])
if cmd[1] == 'draw':
player.draw(cmd[2])
if cmd[1] == 'pick':
player.pick()
if cmd[1] == 'end_turn':
player.end_turn()
if cmd[1] == 'play_card':
data = json.loads(cmd[2])
player.play_card(data['index'], data['against'], data['with'])
if cmd[1] == 'respond':
player.respond(int(cmd[2]))
if cmd[1] == 'choose':
player.choose(int(cmd[2]))
if cmd[1] == 'scrap':
player.scrap(int(cmd[2]))
if cmd[1] == 'special':
player.special(json.loads(cmd[2]))
if cmd[1] == 'gold_rush_discard':
player.gold_rush_discard()
if cmd[1] == 'buy_gold_rush_card':
player.buy_gold_rush_card(int(cmd[2]))
# if cmd[1] == 'chat_message':
# chat_message(None, cmd[2], player)
eventlet.sleep(max(self.replay_speed, 0.1))
def notify_room(self, sid=None):
if len([p for p in self.players if p.character == None]) != 0 or sid:
self.sio.emit('room', room=self.name if not sid else sid, data={
@ -80,15 +136,37 @@ class Game:
})
self.sio.emit('debug', room=self.name, data=self.debug)
if self.debug:
commands = ['/debug', '/set_chars', '/suicide', '/nextevent', '/notify', '/show_cards', '/ddc', '/dsh', '/togglebot', '/cancelgame', '/startgame', '/setbotspeed', '/addex', '/setcharacter', '/setevent', '/removecard', '/getcard', '/meinfo', '/gameinfo', '/mebot']
commands = [
{'cmd':'/debug', 'help':'Toggles the debug mode'},
{'cmd':'/set_chars', 'help':'Set how many characters to distribute - sample /set_chars 3'},
{'cmd':'/suicide', 'help':'Kills you'},
{'cmd':'/nextevent', 'help':'Flip the next event card'},
{'cmd':'/notify', 'help':'Send a message to a player - sample /notify player hi!'},
{'cmd':'/show_cards', 'help':'View the hand of another - sample /show_cards player'},
{'cmd':'/ddc', 'help':'Destroy all cards - sample /ddc player'},
{'cmd':'/dsh', 'help':'Set health - sample /dsh player'},
# {'cmd':'/togglebot', 'help':''},
{'cmd':'/cancelgame', 'help':'Stops the current game'},
{'cmd':'/startgame', 'help':'Force starts the game'},
{'cmd':'/setbotspeed', 'help':'Changes the bot response time - sample /setbotspeed 0.5'},
# {'cmd':'/addex', 'help':''},
{'cmd':'/setcharacter', 'help':'Changes your current character - sample /setcharacter Willy The Kid'},
{'cmd':'/setevent', 'help':'Changes the event deck - sample /setevent 0 Manette'},
{'cmd':'/removecard', 'help':'Remove a card from hand/equip - sample /removecard 0'},
{'cmd':'/getcard', 'help':'Get a brand new card - sample /getcard Birra'},
{'cmd':'/meinfo', 'help':'Get player data'},
{'cmd':'/gameinfo', 'help':'Get game data'},
{'cmd':'/mebot', 'help':'Toggles bot mode'},
{'cmd':'/getnuggets', 'help':'Adds nuggets to yourself - sample /getnuggets 5'},
{'cmd':'/startwithseed', 'help':'start the game with custom seed'}]
self.sio.emit('commands', room=self.name, data=commands)
else:
self.sio.emit('commands', room=self.name, data=['/debug'])
self.sio.emit('commands', room=self.name, data=[{'cmd':'/debug', 'help':'Toggles the debug mode'}])
self.sio.emit('spectators', room=self.name, data=len(self.spectators))
def toggle_expansion(self, expansion_name):
if not self.started:
print('toggling', expansion_name)
print(f'{self.name}: toggling', expansion_name)
if expansion_name in self.expansions:
self.expansions.remove(expansion_name)
else:
@ -113,7 +191,7 @@ class Game:
self.expansions.append('dodge_city')
player.join_game(self)
self.players.append(player)
print(f'Added player {player.name} to game')
print(f'{self.name}: Added player {player.name} to game')
self.notify_room()
self.sio.emit('chat_message', room=self.name, data=f'_joined|{player.name}')
@ -133,16 +211,14 @@ class Game:
self.notify_room()
if len([p for p in self.players if p.character == None]) == 0:
for i in range(len(self.players)):
print(self.name)
print(self.players[i].name)
print(self.players[i].character)
print(self.name, self.players[i].name, self.players[i].character)
self.sio.emit('chat_message', room=self.name, data=f'_choose_character|{self.players[i].name}|{self.players[i].character.name}')
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()
current_roles = [x.role.name for x in self.players]
random.shuffle(current_roles)
self.rng.shuffle(current_roles)
cr = ''
for x in current_roles:
if (x not in cr):
@ -152,14 +228,21 @@ class Game:
def choose_characters(self):
n = self.characters_to_distribute
char_cards = random.sample(characters.all_characters(self.expansions), len(self.players)*n)
char_cards = self.rng.sample(characters.all_characters(self.expansions), len(self.players)*n)
for i in range(len(self.players)):
self.players[i].set_available_character(char_cards[i * n : i * n + n])
def start_game(self):
print('GAME IS STARING')
def start_game(self, SEED=None):
if self.started:
return
print(f'{self.name}: GAME IS STARING')
if SEED == None:
import time
SEED = int(time.time())
print(f'{self.name}: SEED IS {SEED}')
self.SEED = SEED
self.rpc_log = [f';players;{len(self.players)};{[p.name for p in self.players]};{self.expansions}', f';start_game;{SEED}']
self.rng = random.Random(SEED)
self.players_map = {c.name: i for i, c in enumerate(self.players)}
self.sio.emit('chat_message', room=self.name, data=f'_starting')
self.sio.emit('start', room=self.name)
@ -170,6 +253,8 @@ class Game:
self.initial_players = len(self.players)
self.distribute_roles()
self.choose_characters()
if 'gold_rush' in self.expansions:
self.notify_gold_rush_shop()
def distribute_roles(self):
available_roles: List[roles.Role] = []
@ -184,7 +269,7 @@ class Game:
available_roles = available_roles[:len(self.players)]
else:
available_roles = [roles.Renegade(), roles.Renegade()]
random.shuffle(available_roles)
self.rng.shuffle(available_roles)
for i in range(len(self.players)):
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)):
@ -271,7 +356,13 @@ class Game:
player.notify_self()
pls = self.get_alive_players()
nextPlayer = pls[(pls.index(self.players[self.turn])+(len(pls)-len(self.available_cards))) % len(pls)]
if nextPlayer == self.players[self.turn]:
if len(self.available_cards) == 1:
nextPlayer.hand.append(self.available_cards.pop())
nextPlayer.notify_self()
self.sio.emit('emporio', room=self.name, data='{"name":"","cards":[]}')
self.players[self.turn].pending_action = pl.PendingAction.PLAY
self.players[self.turn].notify_self()
elif nextPlayer == self.players[self.turn]:
self.sio.emit('emporio', room=self.name, data='{"name":"","cards":[]}')
self.players[self.turn].pending_action = pl.PendingAction.PLAY
self.players[self.turn].notify_self()
@ -287,32 +378,38 @@ class Game:
return self.players[self.players_map[name]]
def responders_did_respond_resume_turn(self, did_lose=False):
print('did_lose', did_lose)
print(f'{self.name}: did_lose', did_lose)
if self.player_bangs > 0 and self.check_event(ce.PerUnPugnoDiCarte):
self.player_bangs -= 1
if self.player_bangs >= 1:
print('bang again')
print(f'{self.name}: bang again')
if self.players[self.turn].get_banged(self.deck.event_cards[0]):
self.players[self.turn].notify_self()
else:
self.responders_did_respond_resume_turn()
else:
print('ok play turn now')
print(f'{self.name}: ok play turn now')
self.player_bangs = 0
self.players[self.turn].play_turn()
elif self.is_russian_roulette_on and self.check_event(ce.RouletteRussa):
pls = self.get_alive_players()
if did_lose:
target_pl = pls[(pls.index(self.players[self.turn]) + self.player_bangs) % len(pls)]
print('stop roulette')
print(f'{self.name}: stop roulette')
target_pl.lives -= 1
if len([c for c in target_pl.equipment if isinstance(c, grc.Talismano)]) > 0:
target_pl.gold_nuggets += 1
if target_pl.character.check(self, grch.SimeonPicos):
target_pl.gold_nuggets += 1
if len([c for c in target_pl.equipment if isinstance(c, grc.Stivali)]) > 0:
target_pl.hand.append(self.deck.draw(True))
target_pl.notify_self()
self.is_russian_roulette_on = False
self.players[self.turn].play_turn()
else:
self.player_bangs += 1
target_pl = pls[(pls.index(self.players[self.turn]) + self.player_bangs) % len(pls)]
print(f'next in line {target_pl.name}')
print(f'{self.name}: next in line {target_pl.name}')
if target_pl.get_banged(self.deck.event_cards[0]):
target_pl.notify_self()
else:
@ -322,11 +419,12 @@ class Game:
if self.ready_count == self.waiting_for:
self.waiting_for = 0
self.ready_count = 0
self.attack_in_progress = False
if self.pending_winners and not self.someone_won:
return self.announces_winners()
if self.dalton_on:
self.dalton_on = False
print(f'notifying {self.players[self.turn].name} about his turn')
print(f'{self.name}: notifying {self.players[self.turn].name} about his turn')
self.players[self.turn].play_turn()
else:
self.players[self.turn].pending_action = pl.PendingAction.PLAY
@ -334,19 +432,22 @@ class Game:
def announces_winners(self, winners=None):
if (winners is None):
print('WE HAVE A WINNER - pending winners')
print(f'{self.name}: WE HAVE A WINNER - pending winners')
else:
print('WE HAVE A WINNER')
for p in self.get_alive_players():
print(f'{self.name}: WE HAVE A WINNER')
for p in self.players:
if winners is None:
p.win_status = p in self.pending_winners
else:
p.win_status = p in winners
if p.win_status:
if p.win_status and not (isinstance(p.role, roles.Renegade) and p.is_dead):
if not self.someone_won:
self.someone_won = True
self.sio.emit('chat_message', room=self.name, data=f'_won|{p.name}')
self.sio.emit('chat_message', room=self.name, data=f'_won|{p.name}|{p.role.name}')
p.notify_self()
if hasattr(self.sio, 'is_fake'):
print('announces_winners(): Running for tests, you will have to call reset manually!')
return
for i in range(5):
self.sio.emit('chat_message', room=self.name, data=f'_lobby_reset|{5-i}')
eventlet.sleep(1)
@ -361,7 +462,7 @@ class Game:
if self.players[self.turn].is_dead:
pl = sorted(self.get_dead_players(), key=lambda x:x.death_turn)[0]
if self.check_event(ce.DeadMan) and not self.did_resuscitate_deadman and pl == self.players[self.turn]:
print(f'{self.players[self.turn]} is dead, revive')
print(f'{self.name}: {self.players[self.turn]} is dead, revive')
self.did_resuscitate_deadman = True
pl.is_dead = False
pl.is_ghost = False
@ -370,16 +471,16 @@ class Game:
pl.hand.append(self.deck.draw())
pl.notify_self()
elif self.check_event(ceh.CittaFantasma):
print(f'{self.players[self.turn]} is dead, event ghost')
print(f'{self.name}: {self.players[self.turn]} is dead, event ghost')
self.players[self.turn].is_ghost = True
else:
print(f'{self.players[self.turn]} is dead, next turn')
print(f'{self.name}: {self.players[self.turn]} is dead, next turn')
return self.next_turn()
self.player_bangs = 0
if isinstance(self.players[self.turn].role, roles.Sheriff) or ((self.initial_players == 3 and isinstance(self.players[self.turn].role, roles.Vice) and not self.players[self.turn].is_ghost) or (self.initial_players == 3 and any([p for p in self.players if p.is_dead and p.role.name == 'Vice']) and isinstance(self.players[self.turn].role, roles.Renegade))):
self.deck.flip_event()
if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] != None:
print(f'flip new event {self.deck.event_cards[0].name}')
print(f'{self.name}: flip new event {self.deck.event_cards[0].name}')
self.sio.emit('chat_message', room=self.name, data={'color': f'orange','text':f'_flip_event|{self.deck.event_cards[0].name}'})
if self.check_event(ce.DeadMan):
self.did_resuscitate_deadman = False
@ -395,6 +496,7 @@ class Game:
if len(most_hurt) > 0:
hurt_players = [p for p in self.players if p.lives == min(most_hurt)]
for p in hurt_players:
if p.lives != p.max_lives:
p.lives += 1
self.sio.emit('chat_message', room=self.name, data=f'_doctor_heal|{p.name}')
p.notify_self()
@ -417,12 +519,12 @@ class Game:
else:
self.responders_did_respond_resume_turn()
else:
print(f'notifying {self.players[self.turn].name} about his turn')
print(f'{self.name}: notifying {self.players[self.turn].name} about his turn')
self.players[self.turn].play_turn()
def next_turn(self):
if self.shutting_down: return
print(f'{self.players[self.turn].name} invoked next turn')
print(f'{self.name}: {self.players[self.turn].name} invoked next turn')
if self.pending_winners and not self.someone_won:
return self.announces_winners()
pls = self.get_alive_players()
@ -441,8 +543,14 @@ class Game:
else:
self.sio.emit('event_card', room=room, data=None)
def notify_gold_rush_shop(self, sid=None):
if 'gold_rush' in self.expansions and self.deck and self.deck.shop_cards and len(self.deck.shop_cards) > 0:
room = self.name if sid == None else sid
print(f'{self.name}: gold_rush_shop room={room}, data={self.deck.shop_cards}')
self.sio.emit('gold_rush_shop', room=room, data=json.dumps(self.deck.shop_cards, default=lambda o: o.__dict__))
def notify_scrap_pile(self, sid=None):
print('scrap')
print(f'{self.name}: scrap')
room = self.name if sid == None else sid
if self.deck.peek_scrap_pile():
self.sio.emit('scrap', room=room, data=self.deck.peek_scrap_pile().__dict__)
@ -450,7 +558,7 @@ class Game:
self.sio.emit('scrap', room=room, data=None)
def handle_disconnect(self, player: pl.Player):
print(f'player {player.name} left the game {self.name}')
print(f'{self.name}: player {player.name} left the game')
if player in self.spectators:
self.spectators.remove(player)
self.sio.emit('spectators', room=self.name, data=len(self.spectators))
@ -462,7 +570,7 @@ class Game:
if len([p for p in self.players if not p.is_bot]) == 0:
eventlet.sleep(5)
if len([p for p in self.players if not p.is_bot]) == 0:
print(f'no players left in game {self.name}, shutting down')
print(f'{self.name}: no players left in game, shutting down')
self.shutting_down = True
self.players = []
self.spectators = []
@ -470,6 +578,7 @@ class Game:
return True
eventlet.sleep(15) # he may reconnect
if player.is_bot:
player.was_player = False
if len(player.available_characters) > 0:
player.set_available_character(player.available_characters)
player.bot_spin()
@ -479,7 +588,7 @@ class Game:
# player.lives = 0
# self.players.remove(player)
if len([p for p in self.players if not p.is_bot]) == 0:
print(f'no players left in game {self.name}, shutting down')
print(f'{self.name}: no players left in game, shutting down')
self.shutting_down = True
self.players = []
self.spectators = []
@ -491,18 +600,26 @@ class Game:
if not player in self.players or player.is_ghost: return
self.is_handling_death = True
import bang.expansions.dodge_city.characters as chd
print(player.attacker)
print(f'{self.name}: the killer is {player.attacker}')
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Ricercato)]) > 0 and player.attacker and player.attacker in self.players:
player.attacker.gold_nuggets += 1
player.attacker.hand.append(self.deck.draw(True))
player.attacker.hand.append(self.deck.draw(True))
player.attacker.notify_self()
# se lo sceriffo uccide il proprio vice
if player.attacker and player.attacker in self.players and isinstance(player.attacker.role, roles.Sheriff) and isinstance(player.role, roles.Vice):
for i in range(len(player.attacker.hand)):
self.deck.scrap(player.attacker.hand.pop(), True)
for i in range(len(player.attacker.equipment)):
self.deck.scrap(player.attacker.equipment.pop(), True)
for i in range(len(player.attacker.gold_rush_equipment)):
self.deck.shop_deck.append(player.attacker.gold_rush_equipment.pop())
player.attacker.notify_self()
elif player.attacker and player.attacker in self.players and (isinstance(player.role, roles.Outlaw) or self.initial_players == 3):
for i in range(3):
player.attacker.hand.append(self.deck.draw(True))
player.attacker.notify_self()
print(f'player {player.name} died')
print(f'{self.name}: player {player.name} died')
if self.waiting_for > 0 and player.pending_action == pl.PendingAction.RESPOND:
self.responders_did_respond_resume_turn()
player.pending_action = pl.PendingAction.WAIT
@ -529,17 +646,22 @@ class Game:
p.notify_self()
# self.players_map = {c.name: i for i, c in enumerate(self.players)}
if self.started:
print('Check win status')
print(f'{self.name}: Check win status')
attacker_role = None
if player.attacker and player.attacker in self.players:
attacker_role = player.attacker.role
winners = [p for p in self.players if p.role != None and p.role.on_player_death(self.get_alive_players(), initial_players=self.initial_players, dead_role=player.role, attacker_role=attacker_role)]
#print(f'win check: ready-{self.ready_count} waiting-{self.waiting_for} winners:{len(winners)}')
if not self.attack_in_progress and len(winners) > 0 and not self.someone_won:
return self.announces_winners(winners)
elif len(winners) > 0 and not self.someone_won: # non tutti hanno risposto, ma ci sono vincitori.
self.pending_winners = winners
for i in range(len(player.gold_rush_equipment)):
self.deck.shop_deck.append(player.gold_rush_equipment.pop()) # vulture sam doesnt get these cards
#il giocatore quando muore perde tutte le pepite se non è pistolero ombra
player.gold_nuggets = 0
vulture = [p for p in self.get_alive_players() if p.character.check(self, characters.VultureSam)]
if len(vulture) == 0:
for i in range(len(player.hand)):
@ -578,26 +700,25 @@ class Game:
herb[i].hand.append(self.deck.draw(True))
herb[i].hand.append(self.deck.draw(True))
herb[i].notify_self()
self.is_handling_death = False
if corpse.is_my_turn:
corpse.is_my_turn = False
corpse.notify_self()
self.next_turn()
def check_event(self, ev):
if self.deck == None or len(self.deck.event_cards) == 0: return False
return isinstance(self.deck.event_cards[0], ev)
def get_visible_players(self, player: pl.Player):
def get_visible_players(self, player: pl.Player): # returns a dictionary because we need to add the distance
pls = self.get_alive_players()
if len(pls) == 0 or player not in pls: return []
i = pls.index(player)
sight = player.get_sight()
mindist = 99 if not self.check_event(ce.Agguato) else 1
return [{
'name': pls[j].name,
'dist': min([abs(i - j), (i+ abs(j-len(pls))), (j+ abs(i-len(pls))), mindist]) + pls[j].get_visibility() - (player.get_sight(countWeapon=False)-1),
'dist': min([abs(i - j), (i+ abs(j-len(pls))), (j+ abs(i-len(pls))), mindist]) + pls[j].get_visibility(),
'lives': pls[j].lives,
'max_lives': pls[j].max_lives,
'is_sheriff': isinstance(pls[j].role, roles.Sheriff),
@ -620,8 +741,10 @@ class Game:
'name': p.name,
'ncards': len(p.hand),
'equipment': [e.__dict__ for e in p.equipment],
'gold_rush_equipment': [e.__dict__ for e in p.gold_rush_equipment],
'lives': p.lives,
'max_lives': p.max_lives,
'gold_nuggets': p.gold_nuggets,
'is_sheriff': isinstance(p.role, roles.Sheriff),
'is_my_turn': p.is_my_turn,
'pending_action': p.pending_action,

View File

@ -10,7 +10,10 @@ import bang.characters as chars
import bang.expansions.dodge_city.characters as chd
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch
import eventlet
from typing import List
class PendingAction(IntEnum):
PICK = 0
@ -33,8 +36,8 @@ class Player:
self.reset()
def reset(self):
self.hand: cs.Card = []
self.equipment: cs.Card = []
self.hand: List[cs.Card] = []
self.equipment: List[cs.Card] = []
self.role: r.Role = None
self.character: chars.Character = None
self.real_character: chars.Character = None
@ -47,7 +50,7 @@ class Player:
self.can_play_ranch = True
self.is_playing_ranch = False
self.pending_action: PendingAction = None
self.available_characters = []
self.available_characters: List[chars.Character] = []
self.was_shot = False
self.on_pick_cb = None
self.on_failed_response_cb = None
@ -70,10 +73,15 @@ class Player:
self.death_turn = 0
self.noStar = False
self.can_play_vendetta = True
self.can_play_again_don_bell = True
self.is_giving_life = False
self.choose_text = 'choose_card_to_get'
self.using_rimbalzo = 0 # 0 no, 1 scegli giocatore, 2 scegli carta
self.bang_used = 0
self.gold_nuggets = 0
self.gold_rush_equipment: List[grc.ShopCard] = []
self.was_player = False
self.setaccio_count = 0
def join_game(self, game):
self.game = game
@ -91,7 +99,11 @@ class Player:
def set_character(self, character: str):
print(self.available_characters, character)
if self.character == None:
try:
self.character = next(x for x in self.available_characters if x.name == character)
except:
# fix for wrong character encoding in the first part of some characters like Jose delgrado
self.character = next(x for x in self.available_characters if x.name.split()[1] == character.split()[1])
if 'high_noon' in self.game.expansions:
# questo viene utilizzato per la carta nuova identità
self.not_chosen_character = next(x for x in self.available_characters if x.name != character)
@ -128,7 +140,9 @@ class Player:
self.sio.emit('characters', room=self.sid, data=json.dumps(
available, default=lambda o: o.__dict__))
else:
self.set_character(available[randrange(0, len(available))].name)
char_name = available[randrange(0, len(available))].name
self.game.rpc_log.append(f'{self.name};set_character;{char_name}')
self.set_character(char_name)
def notify_card(self, player, card, message=''):
try:
@ -169,13 +183,21 @@ class Player:
self.hand.append(self.game.deck.draw(True))
if self.lives <= 0 and self.max_lives > 0 and not self.is_dead:
print('dying, attacker', self.attacker)
if self.character.check(self.game, chars.SidKetchum) and len(self.hand) > 1:
if self.gold_nuggets >= 2 and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Zaino)]) > 0:
for i in range(len(self.gold_rush_equipment)):
if isinstance(self.gold_rush_equipment[i], grc.Zaino):
self.gold_rush_equipment[i].play_card(self, None)
return # play card will notify the player
if self.character.check(self.game, chars.SidKetchum) and len(self.hand) > 1 and self.lives == 0:
if self.game.players[self.game.turn] != self:
self.game.players[self.game.turn].pending_action = PendingAction.WAIT
self.game.players[self.game.turn].notify_self()
self.scrapped_cards = 0
self.previous_pending_action = self.pending_action
self.pending_action = PendingAction.CHOOSE
self.choose_text = 'choose_sid_scrap'
self.available_cards = self.hand
self.lives += 1
#TODO Sid dovrebbe poter decidere cosa scartare
self.game.deck.scrap(self.hand.pop(
randrange(0, len(self.hand))), True)
self.game.deck.scrap(self.hand.pop(
randrange(0, len(self.hand))), True)
ser = self.__dict__.copy()
ser.pop('game')
ser.pop('sio')
@ -186,6 +208,9 @@ class Player:
if self.attacker:
ser['attacker'] = self.attacker.name
ser['sight'] = self.get_sight()
ser['can_gold_rush_discard'] = len([p for p in self.game.get_alive_players() if p != self and len([e for e in p.gold_rush_equipment if e.number <= self.gold_nuggets + 1]) > 0]) > 0
if self.character:
ser['gold_rush_discount'] = 1 if self.character.check(self.game, grch.PrettyLuzena) and self.special_use_count < 1 else 0
ser['lives'] = max(ser['lives'], 0)
if self.lives <= 0 and self.max_lives > 0 and not self.is_dead:
@ -215,32 +240,49 @@ class Player:
else:
return
if self.pending_action == PendingAction.PICK:
self.game.rpc_log.append(f'{self.name};pick;')
self.pick()
elif self.pending_action == PendingAction.DRAW:
self.game.rpc_log.append(f'{self.name};draw;')
self.draw('')
elif self.pending_action == PendingAction.PLAY:
non_blocked_cards = [card for card in self.hand if (not self.game.check_event(ceh.Manette) or card.suit == self.committed_suit_manette)]
equippables = [c for c in non_blocked_cards if (c.is_equipment or c.usable_next_turn) and not isinstance(c, cs.Prigione) and not any([type(c) == type(x) for x in self.equipment])]
misc = [c for c in non_blocked_cards if (isinstance(c, cs.WellsFargo) or isinstance(c, cs.Indiani) or isinstance(c, cs.Gatling) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or (isinstance(c, cs.Birra) and self.lives < self.max_lives and not self.game.check_event(ceh.IlReverendo)) or (c.need_with and len(self.hand) > 1 and not c.need_target and not (isinstance(c, csd.Whisky) and self.lives == self.max_lives)))
and not (not c.can_be_used_now and self.game.check_event(ce.IlGiudice))]
need_target = [c for c in non_blocked_cards if c.need_target and c.can_be_used_now and not (c.need_with and len(self.hand) < 2) and not (
(self.game.check_event(ceh.Sermone) or self.has_played_bang and not (any([isinstance(c, cs.Volcanic) for c in self.equipment]) and type(c) == type(cs.Bang)
) and not self.game.check_event(ce.Lazo))) and not ( isinstance(c, cs.Prigione) and self.game.check_event(ce.IlGiudice))]
equippables = [c for c in non_blocked_cards if (c.is_equipment or c.usable_next_turn) and not isinstance(c, cs.Prigione) and not any([type(c) == type(x) and not (c.is_weapon and c.must_be_used) for x in self.equipment])]
misc = [c for c in non_blocked_cards if not c.need_target and (isinstance(c, cs.WellsFargo) or isinstance(c, cs.Indiani) or isinstance(c, cs.Gatling) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or ((isinstance(c, cs.Birra) and self.lives < self.max_lives or c.must_be_used) and not self.game.check_event(ceh.IlReverendo)) or (c.need_with and len(self.hand) > 1 and not (isinstance(c, csd.Whisky) and self.lives == self.max_lives)))
and not (not c.can_be_used_now and self.game.check_event(ce.IlGiudice)) and not c.is_equipment]
need_target = [c for c in non_blocked_cards if c.need_target and c.can_be_used_now and not (c.need_with and len(self.hand) < 2) and not (type(c) == type(cs.Bang) and (self.game.check_event(ceh.Sermone) or (self.has_played_bang and (not any([isinstance(c, cs.Volcanic) for c in self.equipment]) or self.game.check_event(ce.Lazo))))) and not (isinstance(c, cs.Prigione) and self.game.check_event(ce.IlGiudice)) or isinstance(c, cs.Duello) or isinstance(c, cs.CatBalou) or isinstance(c, csd.Pugno)]
green_cards = [c for c in self.equipment if not self.game.check_event(ce.Lazo) and not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now]
if self.game.debug:
print(f'hand: {self.hand}')
print(f'non_blocked: {non_blocked_cards}')
print(f'equippables: {equippables}')
print(f'misc: {misc}')
print(f'need_target: {need_target}')
print(f'green_cards: {green_cards}')
if self.gold_nuggets > 0 and any([c.number <= self.gold_nuggets for c in self.game.deck.shop_cards]):
for i in range(len(self.game.deck.shop_cards)):
if self.game.deck.shop_cards[i].number <= self.gold_nuggets:
self.game.rpc_log.append(f'{self.name};buy_gold_rush_card;{i}')
self.buy_gold_rush_card(i)
return
if len(equippables) > 0 and not self.game.check_event(ce.IlGiudice):
for c in equippables:
if self.play_card(self.hand.index(c)):
return
elif len(misc) > 0:
for c in misc:
if c.need_with and self.play_card(self.hand.index(c), _with=sample([j for j in range(len(self.hand)) if j != self.hand.index(c)], 1)[0]):
if c.need_with and len(self.hand) > 1 and self.play_card(self.hand.index(c), _with=sample([j for j in range(len(self.hand)) if j != self.hand.index(c)], 1)[0]):
return
elif self.play_card(self.hand.index(c)):
return
elif len(need_target) > 0:
for c in need_target:
_range = self.get_sight() if c.name == 'Bang!' or c.name == "Pepperbox" else c.range
others = [p for p in self.game.get_visible_players(self) if _range >= p['dist'] and not (isinstance(self.role, r.Vice) and p['is_sheriff']) and p['lives'] > 0 and not ((isinstance(c, cs.CatBalou) or isinstance(c, cs.Panico)) and p['cards'] == 0) and not (p['is_sheriff'] and isinstance(c, cs.Prigione))]
others = [p for p in self.game.get_visible_players(self) if _range >= p['dist'] and not (isinstance(self.role, r.Vice) and p['is_sheriff'] and not c.must_be_used) and p['lives'] > 0 and not
((isinstance(c, cs.CatBalou) or isinstance(c, cs.Panico)) and p['cards'] == 0)
and not (p['is_sheriff'] and isinstance(c, cs.Prigione))]
if (isinstance(c, cs.Panico) or isinstance(c, cs.Panico))and len(self.equipment) > 0:
others.append({'name': self.name, 'is_sheriff': isinstance(self.role, r.Sheriff)})
if len(others) == 0 or c not in self.hand:
continue
target = others[randrange(0, len(others))]
@ -249,7 +291,7 @@ class Player:
if not c.need_with:
if self.play_card(self.hand.index(c), against=target['name']):
return
else:
elif len(self.hand) > 1:
if self.play_card(self.hand.index(c), against=target['name'], _with=sample([j for j in range(len(self.hand)) if j != self.hand.index(c)], 1)[0]):
return
elif len(green_cards) > 0:
@ -270,42 +312,60 @@ class Player:
return
break
maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10
if maxcards == self.lives and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Cinturone)]) > 0:
maxcards = 8
if len(self.hand) > maxcards:
self.game.rpc_log.append(f'{self.name};scrap;{0}')
self.scrap(0)
else:
self.game.rpc_log.append(f'{self.name};end_turn')
self.end_turn()
elif self.pending_action == PendingAction.RESPOND:
did_respond = False
for i in range(len(self.hand)):
if self.hand[i].can_be_used_now and (self.hand[i].name in self.expected_response or self.character.check(self.game, chd.ElenaFuente)):
self.game.rpc_log.append(f'{self.name};respond;{i}')
self.respond(i)
did_respond = True
break
for i in range(len(self.equipment)):
if not self.game.check_event(ce.Lazo) and self.equipment[i].name in self.expected_response:
self.game.rpc_log.append(f'{self.name};respond;{len(self.hand)+i}')
self.respond(len(self.hand)+i)
did_respond = True
break
if not did_respond:
self.game.rpc_log.append(f'{self.name};respond;{-1}')
self.respond(-1)
elif self.pending_action == PendingAction.CHOOSE:
if not self.target_p:
self.choose(randrange(0, len(self.available_cards)))
card_index = randrange(0, len(self.available_cards))
self.game.rpc_log.append(f'{self.name};choose;{card_index}')
self.choose(card_index)
else:
target = self.game.get_player_named(self.target_p)
if len(target.hand)+len(target.equipment) == 0:
self.pending_action = PendingAction.PLAY
self.notify_self()
else:
self.choose(randrange(0, len(target.hand)+len(target.equipment)))
try:
card_index = randrange(0, len(target.hand)+len(target.equipment))
self.game.rpc_log.append(f'{self.name};choose;{card_index}')
self.choose(card_index)
except:
self.game.rpc_log.append(f'{self.name};choose;{0}')
self.choose(0)
def play_turn(self, can_play_vendetta = True, again = False):
def play_turn(self, can_play_vendetta = True, again = False, can_play_again_don_bell=True):
if (self.lives == 0 or self.is_dead) and not self.is_ghost:
return self.end_turn(forced=True)
self.scrapped_cards = 0
self.setaccio_count = 0
self.can_play_ranch = True
self.is_playing_ranch = False
self.can_play_vendetta = can_play_vendetta
self.can_play_again_don_bell = can_play_again_don_bell
if not again:
self.sio.emit('chat_message', room=self.game.name,
data=f'_turn|{self.name}')
@ -319,6 +379,12 @@ class Player:
if self.game.check_event(ceh.MezzogiornoDiFuoco):
self.attacker = None
self.lives -= 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
self.hand.append(self.game.deck.draw(True))
if self.character.check(self.game, chars.BartCassidy) and self.lives > 0:
self.hand.append(self.game.deck.draw(True))
self.sio.emit('chat_message', room=self.game.name, data=f'_special_bart_cassidy|{self.name}')
@ -332,6 +398,7 @@ class Player:
'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'alt_text': ''.join(['❤️']*p.lives)+''.join(['💀']*(p.max_lives-p.lives)),
'is_character': True,
'noDesc': True
} for p in self.game.get_alive_players() if p != self and p.lives < p.max_lives]
self.available_cards.append({'icon': '', 'noDesc': True})
@ -360,6 +427,7 @@ class Player:
'name': p['name'],
'icon': p['role'].icon if(self.game.initial_players == 3) else '⭐️' if p['is_sheriff'] else '🤠',
'alt_text': ''.join(['❤️']*p['lives'])+''.join(['💀']*(p['max_lives']-p['lives'])),
'is_character': True,
'desc': p['name']
} for p in self.game.get_visible_players(self) if p['dist'] <= self.get_sight()]
self.available_cards.append({'icon': '', 'noDesc': True})
@ -370,6 +438,7 @@ class Player:
self.available_cards = [{
'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'is_character': True,
'noDesc': True
} for p in self.game.get_alive_players() if len(p.equipment) > 0 and p != self]
self.available_cards.append({'icon': '', 'noDesc': True})
@ -389,6 +458,12 @@ class Player:
self.choose_text = 'choose_card_to_get'
self.pending_action = PendingAction.CHOOSE
self.notify_self()
elif self.character.check(self.game, grch.DutchWill):
self.is_drawing = True
self.available_cards = [self.game.deck.draw() for i in range(2)]
self.choose_text = 'choose_card_to_get'
self.pending_action = PendingAction.CHOOSE
self.notify_self()
elif self.character.check(self.game, chd.PatBrennan) and type(pile) == str and pile != self.name and pile in self.game.players_map and len(self.game.get_player_named(pile).equipment) > 0:
self.is_drawing = True
self.available_cards = self.game.get_player_named(pile).equipment
@ -398,42 +473,31 @@ class Player:
self.notify_self()
else:
self.pending_action = PendingAction.PLAY
if pile == 'scrap' and self.character.check(self.game, chars.PedroRamirez):
num = 2 if not self.character.check(self.game, chd.BillNoface) else self.max_lives-self.lives+1
if self.character.check(self.game, chd.PixiePete): num += 1
if (len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0): num += 1
if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.check_event(ceh.CittaFantasma)): num += 1
elif self.game.check_event(ceh.Sete): num -= 1
for i in range(num):
if i == 0 and pile == 'scrap' and self.character.check(self.game, chars.PedroRamirez):
self.hand.append(self.game.deck.draw_from_scrap_pile())
if not self.game.check_event(ceh.Sete):
self.hand.append(self.game.deck.draw())
self.sio.emit('chat_message', room=self.game.name,
data=f'_draw_from_scrap|{self.name}')
elif type(pile) == str and pile != self.name and pile in self.game.players_map and self.character.check(self.game, 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.sio.emit('chat_message', room=self.game.name, data=f'_draw_from_scrap|{self.name}')
elif i == 0 and type(pile) == str and pile != self.name and pile in self.game.players_map and self.character.check(self.game, 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'_draw_from_player|{self.name}|{pile}')
if not self.game.check_event(ceh.Sete):
self.hand.append(self.game.deck.draw())
elif self.character.check(self.game, chd.BillNoface):
self.hand.append(self.game.deck.draw())
if not self.game.check_event(ceh.Sete):
for i in range(self.max_lives-self.lives):
self.hand.append(self.game.deck.draw())
else:
if self.character.check(self.game, chd.PixiePete):
self.hand.append(self.game.deck.draw())
for i in range(2):
self.sio.emit('chat_message', room=self.game.name, data=f'_draw_from_player|{self.name}|{pile}')
elif i == 1:
card: cs.Card = self.game.deck.draw()
self.hand.append(card)
if i == 1 and (self.character.check(self.game, chars.BlackJack) or self.game.check_event(ce.LeggeDelWest)):
if (self.character.check(self.game, chars.BlackJack) or self.game.check_event(ce.LeggeDelWest)):
for p in self.game.get_alive_players():
if p != self:
p.notify_card(self, card, 'blackjack_special' if self.character.check(self.game, chars.BlackJack) else 'foc.leggedelwest')
if self.game.check_event(ce.LeggeDelWest):
card.must_be_used = True
if card.check_suit(self.game, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]) and self.character.check(self.game, chars.BlackJack):
if self.character.check(self.game, chars.BlackJack) and card.check_suit(self.game, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]):
self.hand.append(self.game.deck.draw())
if self.game.check_event(ceh.Sete):
return self.notify_self()
if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.check_event(ceh.CittaFantasma)):
self.hand.append(card)
else:
self.hand.append(self.game.deck.draw())
self.manette()
self.notify_self()
@ -453,6 +517,8 @@ class Player:
if self.pending_action != PendingAction.PICK:
return
pickable_cards = 1 + self.character.pick_mod
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0:
pickable_cards += 1
if self.is_my_turn:
for i in range(len(self.equipment)):
if i < len(self.equipment) and isinstance(self.equipment[i], cs.Dinamite):
@ -464,6 +530,14 @@ class Player:
data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
if picked.check_suit(self.game, [cs.Suit.SPADES]) and 2 <= picked.number <= 9 and pickable_cards == 0:
self.lives -= 3
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
self.gold_nuggets += 3
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 3
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw())
self.attacker = None
self.game.deck.scrap(self.equipment.pop(i), True)
self.sio.emit('chat_message', room=self.game.name, data=f'_explode|{self.name}')
@ -524,10 +598,24 @@ class Player:
return playable_cards
def play_card(self, hand_index: int, against=None, _with=None):
if self.is_bot:
data = {
"index": hand_index,
"against": against,
"with": _with
}
self.game.rpc_log.append(f'{self.name};play_card;{json.dumps(data)}')
print(self.name, 'wants to play card ', hand_index, ' against:', against, ' with:', _with)
if not self.is_my_turn or self.pending_action != PendingAction.PLAY or self.game.is_handling_death:
print('but cannot')
return
if not (0 <= hand_index < len(self.hand) + len(self.equipment)):
if not (0 <= hand_index < len(self.hand) + len(self.equipment) + len(self.gold_rush_equipment)):
print('but the card index is out of range')
return
elif len(self.hand) + len(self.equipment) <= hand_index < len(self.hand) + len(self.equipment) + len(self.gold_rush_equipment) and len(self.gold_rush_equipment):
print('which is a gold rush black card')
card: grc.ShopCard = self.gold_rush_equipment[hand_index - len(self.hand) - len(self.equipment)]
return card.play_card(self)
card: cs.Card = self.hand.pop(hand_index) if hand_index < len(self.hand) else self.equipment.pop(hand_index-len(self.hand))
withCard: cs.Card = None
if _with != None:
@ -535,19 +623,20 @@ class Player:
print(self.name, 'is playing ', card, ' against:', against, ' with:', _with)
did_play_card = False
event_blocks_card = (self.game.check_event(ce.IlGiudice) and (card.is_equipment or (card.usable_next_turn and not card.can_be_used_now))) or (self.game.check_event(ce.Lazo) and card.usable_next_turn and card.can_be_used_now) or (self.game.check_event(ceh.Manette) and card.suit != self.committed_suit_manette and not (card.usable_next_turn and card.can_be_used_now))
if not(against != None and isinstance(self.game.get_player_named(against).character, chd.ApacheKid) and card.check_suit(self.game, [cs.Suit.DIAMONDS])) and not event_blocks_card:
if against == self.name and not isinstance(card, csd.Tequila):
if not(against != None and (self.game.get_player_named(against).character.check(self.game, chd.ApacheKid) or len([c for c in self.game.get_player_named(against).gold_rush_equipment if isinstance(c, grc.Calumet)]) > 0) and card.check_suit(self.game, [cs.Suit.DIAMONDS])) or (isinstance(card, grc.ShopCard) and card.kind == grc.ShopCardKind.BLACK) and not event_blocks_card:
if against == self.name and not isinstance(card, csd.Tequila) and not isinstance(card, cs.Panico) and not isinstance(card, cs.CatBalou):
did_play_card = False
else:
did_play_card = card.play_card(self, against, withCard)
if not card.is_equipment and not card.usable_next_turn or event_blocks_card:
if not card.is_equipment and not card.usable_next_turn and not (isinstance(card, grc.ShopCard) and card.kind == grc.ShopCardKind.BLACK) or event_blocks_card:
if did_play_card:
self.game.deck.scrap(card, True)
else:
self.hand.insert(hand_index, card)
if withCard:
self.hand.insert(_with, withCard)
elif card.usable_next_turn and card.can_be_used_now:
self.sio.emit('cant_play_card', room=self.sid)
elif (card.usable_next_turn and card.can_be_used_now) or (isinstance(card, grc.ShopCard) and card.kind == grc.ShopCardKind.BLACK):
if did_play_card:
self.game.deck.scrap(card, True)
else:
@ -568,7 +657,9 @@ class Player:
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):
if (target.name == self.name):
card = self.equipment.pop(card_index)
elif card_index >= len(target.hand):
card = target.equipment.pop(card_index - len(target.hand))
else:
card = target.hand.pop(card_index)
@ -588,6 +679,74 @@ class Player:
while self.target_p == self.name or len(self.game.players[self.game.players_map[self.target_p]].hand) + len(self.game.players[self.game.players_map[self.target_p]].equipment) == 0:
self.target_p = self.game.players[(self.game.players_map[self.target_p]+1)%len(self.game.players)].name
self.notify_self()
elif self.choose_text == 'choose_ricercato':
player = self.game.get_player_named(self.available_cards[card_index]['name'])
player.sio.emit('chat_message', room=player.game.name, data=f'_play_card_against|{self.name}|Ricercato|{player.name}')
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Ricercato)]) > 0:
self.game.deck.shop_deck.append(grc.Ricercato())
else:
player.gold_rush_equipment.append(grc.Ricercato())
player.notify_self()
self.pending_action = PendingAction.PLAY
self.notify_self()
elif self.choose_text == 'choose_sid_scrap':
self.scrapped_cards += 1
self.game.deck.scrap(self.hand.pop(card_index), True)
if self.scrapped_cards == 2:
self.available_cards = []
self.pending_action = self.previous_pending_action
if self.game.players[self.game.turn] != self:
self.game.players[self.game.turn].pending_action = PendingAction.PLAY
self.game.players[self.game.turn].notify_self()
self.notify_self()
elif self.choose_text == 'choose_bicchierino':
player = self.game.get_player_named(self.available_cards[card_index]['name'])
self.sio.emit('chat_message', room=self.game.name, data=f'_play_card_for|{self.name}|{"Bicchierino"}|{player.name}')
player.lives = min(player.lives+1, player.max_lives)
self.pending_action = PendingAction.PLAY
self.notify_self()
elif self.choose_text == 'choose_birra_function':
if card_index == 0:
self.gold_nuggets += 1
self.sio.emit('chat_message', room=self.game.name, data=f'_get_nugget|{self.name}')
else:
cs.Birra(1,1).play_card(self, skipChecks=True)
self.pending_action = PendingAction.PLAY
self.notify_self()
elif self.choose_text == 'choose_bottiglia':
self.sio.emit('chat_message', room=self.game.name, data=f'_play_card|{self.name}|{"Bottiglia"}')
if isinstance(self.available_cards[card_index], cs.Birra):
self.lives = min(self.lives+1, self.max_lives)
else:
self.hand.append(self.available_cards[card_index])
self.pending_action = PendingAction.PLAY
self.notify_self()
elif self.choose_text == 'choose_complice':
self.sio.emit('chat_message', room=self.game.name, data=f'_play_card|{self.name}|{"Bottiglia"}')
self.hand.append(self.available_cards[card_index])
self.pending_action = PendingAction.PLAY
self.notify_self()
elif self.choose_text == 'gold_rush_discard':
if card_index == len(self.available_cards) - 1:
self.pending_action = PendingAction.PLAY
else:
player = self.game.get_player_named(self.available_cards[card_index]['name'])
self.available_cards = [c for c in player.gold_rush_equipment if c.number+1 <= self.gold_nuggets]
self.available_cards.append({'icon': '', 'noDesc': True})
self.choose_text = 'gold_rush_discard_2|' + player.name
self.notify_self()
elif 'gold_rush_discard_2' in self.choose_text:
if card_index == len(self.available_cards) - 1:
self.pending_action = PendingAction.PLAY
else:
self.gold_nuggets -= self.available_cards[card_index].number + 1
player = self.game.get_player_named(self.choose_text.split('|')[1])
player.gold_rush_equipment.remove(self.available_cards[card_index])
self.game.deck.shop_deck.append(self.available_cards[card_index])
self.sio.emit('chat_message', room=self.game.name, data=f'_gold_rush_pay_discard|{self.name}|{player.name}|{self.available_cards[card_index].name}')
player.notify_self()
self.pending_action = PendingAction.PLAY
self.notify_self()
elif self.game.check_event(ceh.NuovaIdentita) and self.choose_text == 'choose_nuova_identita':
if card_index == 1: # the other character
self.character = self.not_chosen_character
@ -606,6 +765,12 @@ class Player:
player = self.game.get_player_named(self.available_cards[card_index]['name'])
player.lives += 1
self.lives -= 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
self.hand.append(self.game.deck.draw())
player.notify_self()
self.sio.emit('chat_message', room=self.game.name, data=f'_fratelli_sangue|{self.name}|{player.name}')
except: pass
@ -677,19 +842,47 @@ class Player:
self.notify_self()
# specifico per personaggio
elif self.is_drawing and self.character.check(self.game, chars.KitCarlson):
self.hand.append(self.available_cards.pop(card_index))
card: cs.Card = self.available_cards.pop(card_index)
if len(self.available_cards) == 1: #ho pescato la seconda carta
if self.game.check_event(ce.LeggeDelWest):
card.must_be_used = True
self.hand.append(card)
pickable_stop = 1
if self.game.check_event(ceh.Sete): pickable_stop = 2
if self.game.check_event(ceh.IlTreno): pickable_stop = 0
if self.game.check_event(ceh.Sete): pickable_stop += 1
if self.game.check_event(ceh.IlTreno) or len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0:
pickable_stop -= 1
if len(self.available_cards) == pickable_stop:
if len(self.available_cards) > 0:
if len(self.available_cards) > 0: #la carta non scelta la rimettiamo in cima al mazzo
self.game.deck.put_on_top(self.available_cards.pop())
if len(self.available_cards) > 0: #se sono rimaste carte le scartiamo
self.game.deck.scrap(self.available_cards.pop())
#se c'è sia treno che piccone pesco un'altra carta
if self.game.check_event(ceh.IlTreno) and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0:
self.hand.append(self.game.deck.draw())
self.is_drawing = False
self.pending_action = PendingAction.PLAY
self.manette()
self.notify_self()
# specifico per personaggio
elif self.is_drawing and self.character.check(self.game, grch.DutchWill):
if not self.game.check_event(ceh.Sete):
self.hand.append(self.available_cards.pop(card_index)) #prendo la carta scelta
else:
self.game.deck.scrap(self.available_cards.pop(0), True) #non pesco carte
self.game.deck.scrap(self.available_cards.pop(0), True) #scarto l'altra
#legge del west non si applica perchè la seconda carta viene scartata
if self.game.check_event(ceh.IlTreno):
self.hand.append(self.game.deck.draw())
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0:
self.hand.append(self.game.deck.draw())
self.gold_nuggets += 1
self.is_drawing = False
self.pending_action = PendingAction.PLAY
self.manette()
self.notify_self()
# specifico per personaggio
elif self.is_drawing and self.character.check(self.game, chd.PatBrennan):
#non pesca per niente dal mazzo
self.is_drawing = False
card = self.available_cards.pop(card_index)
card.reset_card()
@ -704,6 +897,8 @@ class Player:
def barrel_pick(self):
pickable_cards = 1 + self.character.pick_mod
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0:
pickable_cards += 1
if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais):
pickable_cards = 2
while pickable_cards > 0:
@ -734,6 +929,8 @@ class Player:
pickable_cards = 1 + self.character.pick_mod
if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais):
pickable_cards = 2
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0:
pickable_cards += 1
while pickable_cards > 0:
pickable_cards -= 1
picked: cs.Card = self.game.deck.pick_and_scrap()
@ -812,7 +1009,7 @@ class Player:
def get_indians(self, attacker):
self.attacker = attacker
if self.character.check(self.game, chd.ApacheKid): return False
if self.character.check(self.game, chd.ApacheKid) or len([c for c in self.gold_rush_equipment if isinstance(c, grc.Calumet)]) > 0: return False
if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0:
print('Cant defend')
self.take_damage_response()
@ -850,6 +1047,7 @@ class Player:
if self.character.check(self.game, chd.MollyStark) and not self.is_my_turn:
self.hand.append(self.game.deck.draw(True))
self.lives += 1 if not self.character.check(self.game, chd.TequilaJoe) else 2
self.lives = min(self.lives, self.max_lives)
self.game.deck.scrap(self.hand.pop(i), True)
self.sio.emit('chat_message', room=self.game.name,
data=f'_beer_save|{self.name}')
@ -868,6 +1066,16 @@ class Player:
self.sio.emit('chat_message', room=self.game.name,
data=f'_special_el_gringo|{self.name}|{self.attacker.name}')
self.attacker.notify_self()
if self.attacker and 'gold_rush' in self.game.expansions:
if (isinstance(self.attacker, Player)):
self.attacker.gold_nuggets += 1
self.attacker.notify_self()
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0:
self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0:
self.hand.append(self.game.deck.draw(True))
self.heal_if_needed()
self.mancato_needed = 0
self.expected_response = []
@ -894,11 +1102,12 @@ class Player:
card = self.hand.pop(hand_index) if hand_index < len(self.hand) else self.equipment.pop(hand_index-len(self.hand))
#hand_index < len(self.hand) with the '<=' due to the hand.pop
if self.character.check(self.game, chd.MollyStark) and hand_index <= len(self.hand) and not self.is_my_turn and self.event_type != 'duel':
if self.attacker.character.check(self.game, chars.SlabTheKiller) and isinstance(self.hand[hand_index], cs.Mancato):
if hasattr(self.attacker,'character') and self.attacker.character.check(self.game, chars.SlabTheKiller) and isinstance(self.hand[hand_index], cs.Mancato):
self.molly_discarded_cards += 1
else:
self.hand.append(self.game.deck.draw(True))
card.use_card(self)
print(f'{self.game.name}: {self.name} responded with {card.name}')
self.sio.emit('chat_message', room=self.game.name, data=f'_respond|{self.name}|{card.name}')
self.game.deck.scrap(card, True)
self.notify_self()
@ -936,9 +1145,11 @@ class Player:
if self.mancato_needed <= 0:
self.attacker = None
def get_sight(self, countWeapon=True):
def get_sight(self, countWeapon=True): #come vedo io gli altri
if not self.character:
return 0
if self.game.check_event(ce.Lazo):
return 1 + self.character.sight_mod
aim = 0
range = 0
for card in self.equipment:
@ -946,19 +1157,18 @@ class Player:
range += card.range
else:
aim += card.sight_mod
if self.game.check_event(ce.Lazo):
return 1 + self.character.sight_mod
return max(1, range) + aim + self.character.sight_mod
return max(1,range) + aim + (self.character.sight_mod if not self.game.check_event(ceh.Sbornia) else 0)
def get_visibility(self):
def get_visibility(self): #come mi vedono gli altri
if not self.character or not self.game or not self.game.players[self.game.turn].character:
return 0
covers = 0
ch_vis_mod = self.character.visibility_mod if not self.game.check_event(ceh.Sbornia) else 0
if self.game.check_event(ce.Lazo) or self.game.players[self.game.turn].character.check(self.game, chd.BelleStar):
return self.character.visibility_mod
return ch_vis_mod
for card in self.equipment:
covers += card.vis_mod
return self.character.visibility_mod + covers
return ch_vis_mod + covers
def scrap(self, card_index):
if self.is_my_turn or self.character.check(self.game, chars.SidKetchum):
@ -974,51 +1184,105 @@ class Player:
self.game.deck.scrap(card)
self.notify_self()
def holyday_special(self, data):
if self.character.check(self.game, chd.DocHolyday) and self.special_use_count < 1 and self.pending_action == PendingAction.PLAY:
self.special_use_count += 1
cards = sorted(data['cards'], reverse=True)
for c in cards:
self.game.deck.scrap(self.hand.pop(c), True)
self.notify_self()
self.game.attack(self, data['against'])
def special(self, data):
self.character.special(self, data)
def chuck_lose_hp_draw(self):
if self.character.check(self.game, chd.ChuckWengam) and self.lives > 1 and self.is_my_turn:
self.lives -= 1
self.hand.append(self.game.deck.draw(True))
self.hand.append(self.game.deck.draw(True))
def gold_rush_discard(self):
self.available_cards = [{
'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'is_character': True,
'alt_text': ''.join(['🎴️'] * len(p.gold_rush_equipment)),
'noDesc': True
} for p in self.game.get_alive_players() if p != self and len([e for e in p.gold_rush_equipment if e.number + 1 <= self.gold_nuggets]) > 0]
self.available_cards.append({'icon': '', 'noDesc': True})
self.choose_text = 'gold_rush_discard'
self.pending_action = PendingAction.CHOOSE
self.notify_self()
def buy_gold_rush_card(self, index):
print(f'{self.name} wants to buy gr-card index {index} in room {self.game.name}')
card: cs.Card = self.game.deck.shop_cards[index]
discount = 0
if self.character.check(self.game, grch.PrettyLuzena) and self.special_use_count < 1:
discount = 1
if self.pending_action == PendingAction.PLAY and self.gold_nuggets >= card.number - discount:
self.gold_nuggets -= card.number - discount
if self.character.check(self.game, grch.PrettyLuzena) and self.special_use_count < 1:
self.special_use_count += 1
if card.play_card(self):
self.game.deck.shop_deck.append(card)
self.game.deck.shop_cards[index] = None
self.game.deck.fill_gold_rush_shop()
self.notify_self()
def check_can_end_turn(self):
must_be_used_cards = [c for c in self.hand if c.must_be_used]
if self.game.check_event(ce.LeggeDelWest) and len(must_be_used_cards) > 0:
card = must_be_used_cards[0]
print(f'Legge del west card: {card.name}')
print(self.has_played_bang and not (any([isinstance(c, cs.Volcanic) for c in self.equipment]) and type(card) == type(cs.Bang)))
if card.suit == cs.Suit.DIAMONDS and card.need_target and len([p for p in self.game.get_alive_players() if (not p.character.check(self.game, chd.ApacheKid) and not any([isinstance(c, grc.Calumet) for c in p.gold_rush_equipment]))]) == 0:
if isinstance(card, cs.Bang):
return True
else:
return len(self.equipment) == 0 # se non ho carte equipaggiamento
elif (isinstance(card, cs.Bang) or (isinstance(card, cs.Mancato) and self.character.check(self.game, chars.CalamityJanet))) and self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment]) or len([p for p in self.game.get_visible_players(self) if self.get_sight() >= p['dist']]) == 0:
return True
elif isinstance(card, cs.Mancato) or (card.need_with and len(self.hand) < 2):
return True
elif isinstance(card, cs.Panico) and len([p for p in self.game.get_visible_players(self) if self.get_sight(False) >= p['dist']]) == 0 and len(self.equipment) == 0:
return True
elif isinstance(card, csd.Pugno) and len([p for p in self.game.get_visible_players(self) if self.get_sight(False) >= p['dist']]) == 0:
return True
elif isinstance(card, cs.Prigione) and len([p for p in self.game.get_visible_players(self) if not p['is_sheriff']]) == 0:
return True
elif not card.is_weapon and len([c for c in self.equipment if c.name == card.name]) > 0:
return True
return False
return True
def end_turn(self, forced=False):
print(f"{self.name} wants to end his turn")
if not self.is_my_turn and not forced:
return
maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10
if maxcards == self.lives and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Cinturone)]) > 0:
maxcards = 8
if len(self.hand) > maxcards and not forced:
print(
f"{self.name}: I have to many cards in my hand and I can't end the turn")
print(f"{self.name}: I have to many cards in my hand and I can't end the turn")
elif not self.check_can_end_turn():
print(f"{self.name}: I must play the legge del west card")
elif self.pending_action == PendingAction.PLAY or forced:
if not forced and self.game.check_event(ce.Vendetta) and self.can_play_vendetta:
picked: cs.Card = self.game.deck.pick_and_scrap()
self.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
if picked.check_suit(self.game, [cs.Suit.HEARTS]):
self.play_turn(can_play_vendetta=False)
return
self.is_my_turn = False
self.has_played_bang = False
for i in range(len(self.equipment)):
if self.equipment[i].usable_next_turn and not self.equipment[i].can_be_used_now:
self.equipment[i].can_be_used_now = True
for i in range(len(self.hand)):
if self.hand[i].must_be_used:
self.hand[i].must_be_used = False
self.has_played_bang = False
##Vendetta##
if not forced and self.game.check_event(ce.Vendetta) and self.can_play_vendetta:
picked: cs.Card = self.game.deck.pick_and_scrap()
self.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
if picked.check_suit(self.game, [cs.Suit.HEARTS]):
self.play_turn(can_play_vendetta=False)
return
##Don Bell##
if not forced and self.character.check(self.game, grch.DonBell) and self.can_play_again_don_bell:
picked: cs.Card = self.game.deck.pick_and_scrap()
self.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
if picked.check_suit(self.game, [cs.Suit.HEARTS, cs.Suit.DIAMONDS]):
self.play_turn(can_play_vendetta=False, can_play_again_don_bell=False)
return
##Ghost##
if self.is_dead and self.is_ghost and self.game.check_event(ceh.CittaFantasma):
self.is_ghost = False
for i in range(len(self.hand)):
self.game.deck.scrap(self.hand.pop(), True)
for i in range(len(self.equipment)):
self.game.deck.scrap(self.equipment.pop(), True)
self.is_my_turn = False
self.committed_suit_manette = None
self.pending_action = PendingAction.WAIT
self.notify_self()

View File

@ -62,7 +62,9 @@ class Outlaw(Role):
return True
elif initial_players == 3 and attacker_role != None:
return isinstance(dead_role, Vice) and isinstance(attacker_role, Outlaw)
elif initial_players != 3 and not any([isinstance(p.role, Sheriff) for p in alive_players]):
elif (initial_players != 3 and (not any([isinstance(p.role, Sheriff) for p in alive_players]))
and (any([isinstance(p.role, Outlaw) for p in alive_players])
or any([isinstance(p.role, Renegade) for p in alive_players]) and len(alive_players) > 1)):
print("The Outlaw won!")
return True
return False
@ -82,7 +84,7 @@ class Renegade(Role):
return True
elif initial_players == 3 and attacker_role != None:
return isinstance(dead_role, Outlaw) and isinstance(attacker_role, Renegade)
elif initial_players != 3 and len(alive_players) == 1 and alive_players[0].role == self:
elif initial_players != 3 and len(alive_players) == 1 and isinstance(alive_players[0].role, Renegade):
print("The Renegade won!")
return True
return False

View File

@ -1,7 +1,10 @@
certifi==2021.5.30
dnspython==1.16.0
eventlet==0.31.0
greenlet==1.1.0
certifi==2021.10.8
dnspython==2.2.1
eventlet==0.33.0
greenlet==1.1.2
python-engineio==3.14.2
python-socketio==4.6.1
six==1.16.0
pytest==7.0.1
requests==2.27.1
discord-webhook==0.15.0

584
backend/server.py Normal file
View File

@ -0,0 +1,584 @@
import os
import json
import random
from typing import List
import eventlet
import socketio
from bang.game import Game
from bang.players import Player, PendingAction
import requests
from discord_webhook import DiscordWebhook
import sys
sys.setrecursionlimit(10**6) # this should prevents bots from stopping
sio = socketio.Server(cors_allowed_origins="*")
static_files={
'/': {'content_type': 'text/html', 'filename': 'index.html'},
'/game': {'content_type': 'text/html', 'filename': 'index.html'},
'/help': {'content_type': 'text/html', 'filename': 'index.html'},
'/status': {'content_type': 'text/html', 'filename': 'index.html'},
# '/robots.txt': {'content_type': 'text/html', 'filename': 'robots.txt'},
'/favicon.ico': {'filename': 'favicon.ico'},
'/img/icons': './img/icons',
'/manifest.json': {'filename': 'manifest.json'},
'/css': './css',
'/media': './media',
'/js': './js',
}
if "UseRobots" in os.environ and os.environ['UseRobots'].upper() == "TRUE":
static_files['/robots.txt'] = {'content_type': 'text/html', 'filename': 'robots.txt'}
for file in [f for f in os.listdir('.') if '.js' in f or '.map' in f or '.html' in f]:
static_files[f'/{file}'] = f'./{file}'
app = socketio.WSGIApp(sio, static_files=static_files)
games: List[Game] = []
online_players = 0
blacklist: List[str] = []
def advertise_lobbies():
sio.emit('lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if not g.started and len(g.players) < 10 and not g.is_hidden])
sio.emit('spectate_lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if g.started])
@sio.event
def connect(sid, environ):
global online_players
online_players += 1
print('connect ', sid)
sio.enter_room(sid, 'lobby')
sio.emit('players', room='lobby', data=online_players)
@sio.event
def get_online_players(sid):
global online_players
sio.emit('players', room='lobby', data=online_players)
@sio.event
def set_username(sid, username):
ses = sio.get_session(sid)
if not isinstance(ses, Player):
sio.save_session(sid, Player(username, sid, sio))
print(f'{sid} is now {username}')
advertise_lobbies()
elif ses.game == None or not ses.game.started:
print(f'{sid} changed username to {username}')
prev = ses.name
if len([p for p in ses.game.players if p.name == username]) > 0:
ses.name = f"{username}_{random.randint(0,100)}"
else:
ses.name = username
sio.emit('chat_message', room=ses.game.name, data=f'_change_username|{prev}|{ses.name}')
sio.emit('me', data=ses.name, room=sid)
ses.game.notify_room()
@sio.event
def get_me(sid, room):
if isinstance(sio.get_session(sid), Player):
sio.emit('me', data=sio.get_session(sid).name, room=sid)
if sio.get_session(sid).game:
sio.get_session(sid).game.notify_room()
else:
sio.save_session(sid, Player('player', sid, sio))
de_games = [g for g in games if g.name == room['name']]
if len(de_games) == 1 and not de_games[0].started:
join_room(sid, room)
elif len(de_games) == 1 and de_games[0].started:
print('room exists')
if room['username'] != None and any([p.name == room['username'] for p in de_games[0].players if p.is_bot]):
print('getting inside the bot')
bot = [p for p in de_games[0].players if p.is_bot and p.name == room['username'] ][0]
bot.sid = sid
bot.is_bot = False
sio.enter_room(sid, de_games[0].name)
sio.save_session(sid, bot)
de_games[0].notify_room(sid)
eventlet.sleep(0.1)
de_games[0].notify_all()
sio.emit('role', room=sid, data=json.dumps(bot.role, default=lambda o: o.__dict__))
bot.notify_self()
if len(bot.available_characters) > 0:
bot.set_available_character(bot.available_characters)
else: #spectate
de_games[0].spectators.append(sio.get_session(sid))
sio.get_session(sid).game = de_games[0]
sio.enter_room(sid, de_games[0].name)
de_games[0].notify_room(sid)
de_games[0].notify_event_card(sid)
de_games[0].notify_scrap_pile(sid)
de_games[0].notify_all()
de_games[0].notify_gold_rush_shop()
de_games[0].notify_event_card()
else:
create_room(sid, room['name'])
if sio.get_session(sid).game == None:
sio.emit('me', data={'error':'Wrong password/Cannot connect'}, room=sid)
else:
sio.emit('me', data=sio.get_session(sid).name, room=sid)
if room['username'] == None or any([p.name == room['username'] for p in sio.get_session(sid).game.players]):
sio.emit('change_username', room=sid)
else:
sio.emit('chat_message', room=sio.get_session(sid).game.name, data=f"_change_username|{sio.get_session(sid).name}|{room['username']}")
sio.get_session(sid).name = room['username']
sio.emit('me', data=sio.get_session(sid).name, room=sid)
if not sio.get_session(sid).game.started:
sio.get_session(sid).game.notify_room()
@sio.event
def disconnect(sid):
global online_players
online_players -= 1
if sio.get_session(sid):
sio.emit('players', room='lobby', data=online_players)
if sio.get_session(sid).game and sio.get_session(sid).disconnect():
sio.close_room(sio.get_session(sid).game.name)
games.pop(games.index(sio.get_session(sid).game))
print('disconnect ', sid)
advertise_lobbies()
@sio.event
def create_room(sid, room_name):
if sio.get_session(sid).game == None:
while len([g for g in games if g.name == room_name]):
room_name += f'_{random.randint(0,100)}'
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
g = Game(room_name, sio)
g.add_player(sio.get_session(sid))
if room_name in blacklist:
g.is_hidden = True
games.append(g)
print(f'{sid} created a room named {room_name}')
advertise_lobbies()
@sio.event
def private(sid):
g = sio.get_session(sid).game
g.set_private()
advertise_lobbies()
@sio.event
def toggle_expansion(sid, expansion_name):
g = sio.get_session(sid).game
g.toggle_expansion(expansion_name)
@sio.event
def toggle_comp(sid):
sio.get_session(sid).game.toggle_competitive()
@sio.event
def toggle_replace_with_bot(sid):
sio.get_session(sid).game.toggle_disconnect_bot()
@sio.event
def join_room(sid, room):
room_name = room['name']
i = [g.name for g in games].index(room_name)
if games[i].password != '' and games[i].password != room['password'].upper():
return
if not games[i].started:
print(f'{sid} joined a room named {room_name}')
sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name)
while len([p for p in games[i].players if p.name == sio.get_session(sid).name]):
sio.get_session(sid).name += f'_{random.randint(0,100)}'
sio.emit('me', data=sio.get_session(sid).name, room=sid)
games[i].add_player(sio.get_session(sid))
advertise_lobbies()
else:
games[i].spectators.append(sio.get_session(sid))
sio.get_session(sid).game = games[i]
sio.get_session(sid).pending_action = PendingAction.WAIT
sio.enter_room(sid, games[0].name)
games[i].notify_room(sid)
eventlet.sleep(0.5)
games[i].notify_room(sid)
games[i].notify_all()
"""
Sockets for the status page
"""
@sio.event
def get_all_rooms(sid, deploy_key):
if 'DEPLOY_KEY' in os.environ and deploy_key == os.environ['DEPLOY_KEY']:
sio.emit('all_rooms', room=sid, data=[{
'name': g.name,
'hidden': g.is_hidden,
'players': [{'name':p.name, 'bot': p.is_bot, 'health': p.lives, 'sid': p.sid} for p in g.players],
'password': g.password,
'expansions': g.expansions,
'started': g.started,
'current_turn': g.turn,
'incremental_turn': g.incremental_turn,
'debug': g.debug,
'spectators': len(g.spectators)
} for g in games])
@sio.event
def kick(sid, data):
if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']:
sio.emit('kicked', room=data['sid'])
@sio.event
def hide_toogle(sid, data):
if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']:
game = [g for g in games if g.name==data['room']]
if len(games) > 0:
game[0].is_hidden = not game[0].is_hidden
if game[0].is_hidden:
if not data['room'] in blacklist:
blacklist.append(data['room'])
elif data['room'] in blacklist:
blacklist.remove(data['room'])
advertise_lobbies()
"""
Sockets for the game
"""
@sio.event
def start_game(sid):
ses: Player = sio.get_session(sid)
ses.game.start_game()
advertise_lobbies()
@sio.event
def set_character(sid, name):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};set_character;{name}')
ses.set_character(name)
@sio.event
def refresh(sid):
ses: Player = sio.get_session(sid)
ses.notify_self()
@sio.event
def draw(sid, pile):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};draw;{pile}')
ses.draw(pile)
@sio.event
def pick(sid):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};pick')
ses.pick()
@sio.event
def end_turn(sid):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};end_turn')
ses.end_turn()
@sio.event
def play_card(sid, data):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};play_card;{json.dumps(data)}')
ses.play_card(data['index'], data['against'], data['with'])
@sio.event
def respond(sid, card_index):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};respond;{card_index}')
ses.respond(card_index)
@sio.event
def choose(sid, card_index):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};choose;{card_index}')
ses.choose(card_index)
@sio.event
def scrap(sid, card_index):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};scrap;{card_index}')
ses.scrap(card_index)
@sio.event
def special(sid, data):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};play_card;{json.dumps(data)}')
ses.special(data)
@sio.event
def gold_rush_discard(sid):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};gold_rush_discard;')
ses.gold_rush_discard()
@sio.event
def buy_gold_rush_card(sid, data:int):
ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};buy_gold_rush_card;{data}')
ses.buy_gold_rush_card(data)
@sio.event
def chat_message(sid, msg, pl=None):
ses: Player = sio.get_session(sid) if pl is None else pl
ses.game.rpc_log.append(f'{ses.name};chat_message;{msg}')
if len(msg) > 0:
if msg[0] == '/':
commands = msg.split(';')
for msg in commands:
if '/addbot' in msg and not ses.game.started:
if len(msg.split()) > 1:
# for _ in range(int(msg.split()[1])):
# ses.game.add_player(Player(f'AI_{random.randint(0,1000)}', 'bot', sio, bot=True))
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'Only 1 bot at the time'})
else:
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
while any([p for p in ses.game.players if p.name == bot.name]):
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
ses.game.add_player(bot)
bot.bot_spin()
return
if '/report' in msg and not ses.game.is_replay:
data = "\n".join(ses.game.rpc_log[:-1]).strip()
response = requests.post("https://www.toptal.com/developers/hastebin/documents", data)
key = json.loads(response.text).get('key')
if "DISCORD_WEBHOOK" in os.environ and len(os.environ['DISCORD_WEBHOOK']) > 0:
webhook = DiscordWebhook(url=os.environ['DISCORD_WEBHOOK'], content=f'New bug report, replay at https://www.toptal.com/developers/hastebin/{key}')
response = webhook.execute()
sio.emit('chat_message', room=sid, data={'color': f'green','text':f'Report OK'})
else:
print("WARNING: DISCORD_WEBHOOK not found")
print(f'New bug report, replay at https://www.toptal.com/developers/hastebin/{key}')
return
if '/replay' in msg and not '/replayspeed' in msg:
_cmd = msg.split()
if len(_cmd) == 2:
replay_id = _cmd[1]
response = requests.get(f"https://www.toptal.com/developers/hastebin/raw/{replay_id}")
log = response.text.splitlines()
ses.game.spectators.append(ses)
ses.game.replay(log)
return
if '/replayspeed' in msg:
_cmd = msg.split()
if len(_cmd) == 2:
ses.game.replay_speed = float(_cmd[1])
return
if '/startwithseed' in msg and not ses.game.started:
if len(msg.split()) > 1:
ses.game.start_game(int(msg.split()[1]))
return
elif '/removebot' in msg and not ses.game.started:
if any([p.is_bot for p in ses.game.players]):
[p for p in ses.game.players if p.is_bot][-1].disconnect()
return
elif '/togglecomp' in msg and ses.game:
ses.game.toggle_competitive()
return
if '/debug' in msg:
cmd = msg.split()
if len(cmd) == 2 and 'DEPLOY_KEY' in os.environ and cmd[1] == os.environ['DEPLOY_KEY']: # solo chi ha la deploy key può attivare la modalità debug
ses.game.debug = not ses.game.debug
ses.game.notify_room()
elif ses == ses.game.players[0]: # solo l'owner può attivare la modalità debug
ses.game.debug = not ses.game.debug
ses.game.notify_room()
if ses.game.debug:
sio.emit('chat_message', room=sid, data={'color': f'red','text':f'debug mode is now active, only the owner of the room can disable it with /debug'})
return
if not ses.game.debug:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'debug mode is not active, only the owner of the room can enable it with /debug'})
elif '/set_chars' in msg and not ses.game.started:
cmd = msg.split()
if len(cmd) == 2 and int(cmd[1]) > 0:
ses.game.characters_to_distribute = int(cmd[1])
elif '/suicide' in msg and ses.game.started and ses.lives > 0:
ses.lives = 0
ses.notify_self()
elif '/nextevent' in msg and ses.game.started:
ses.game.deck.flip_event()
elif '/notify' in msg and ses.game.started:
cmd = msg.split()
if len(cmd) >= 3:
if cmd[1] in ses.game.players_map:
ses.game.get_player_named(cmd[1]).notify_card(ses, {
'name': ' '.join(cmd[2:]),
'icon': '🚨',
'suit': 4,
'number': ' '.join(cmd[2:])
})
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/show_cards' in msg and ses.game.started:
cmd = msg.split()
if len(cmd) == 2:
if cmd[1] in ses.game.players_map:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and is looking at {cmd[1]} hand'})
for c in ses.game.get_player_named(cmd[1]).hand:
ses.notify_card(ses, c)
eventlet.sleep(0.3)
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/ddc' in msg and ses.game.started: # debug destroy cards usage: [/ddc *] [/ddc username]
cmd = msg.split()
if len(cmd) == 2:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode destroyed {cmd[1]} cards'})
if cmd[1] == "*":
for p in ses.game.players_map:
ses.game.get_player_named(p).hand = []
ses.game.get_player_named(p).equipment = []
ses.game.get_player_named(p).notify_self()
elif cmd[1] in ses.game.players_map:
ses.game.get_player_named(cmd[1]).hand = []
ses.game.get_player_named(cmd[1]).equipment = []
ses.game.get_player_named(cmd[1]).notify_self()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/dsh' in msg and ses.game.started: #debug set health usage [/dsh * hp] [/dsh username hp]
cmd = msg.split()
if len(cmd) == 3:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and is changing {cmd[1]} health'})
if cmd[1] == "*":
for p in ses.game.players_map:
ses.game.get_player_named(p).lives = int(cmd[2])
ses.game.get_player_named(p).notify_self()
elif cmd[1] in ses.game.players_map:
ses.game.get_player_named(cmd[1]).lives = int(cmd[2])
ses.game.get_player_named(cmd[1]).notify_self()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/togglebot' in msg and ses.game:
ses.game.toggle_disconnect_bot()
elif '/cancelgame' in msg and ses.game.started:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} stopped the current game'})
ses.game.reset()
elif '/startgame' in msg and not ses.game.started:
ses.game.start_game()
elif '/setbotspeed' in msg:
ses.game.bot_speed = float(msg.split()[1])
elif '/addex' in msg and not ses.game.started:
cmd = msg.split()
if len(cmd) == 2:
cmd[1] = cmd[1].replace('foc', 'fistful_of_cards')
if cmd[1] not in ses.game.available_expansions:
ses.game.available_expansions.append(cmd[1])
ses.game.notify_room()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'})
elif '/setcharacter' in msg:
import bang.characters as characters
cmd = msg.split()
if len(cmd) >= 2:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and changed character'})
chs = characters.all_characters(ses.game.expansions)
ses.character = [c for c in chs if c.name == ' '.join(cmd[1:])][0]
ses.real_character = ses.character
ses.notify_self()
elif '/setevent' in msg and ses.game and ses.game.deck: #add event before the position /setevent (position) 0 (name) Peyote
cmd = msg.split()
if len(cmd) >= 3:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and changed event'})
import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh
chs = []
chs.extend(ce.get_all_events())
chs.append(ce.get_endgame_card())
chs.extend(ceh.get_all_events())
chs.append(ceh.get_endgame_card())
ses.game.deck.event_cards.insert(int(cmd[1]), [c for c in chs if c!=None and c.name == ' '.join(cmd[2:])][0])
ses.game.notify_event_card()
elif '/removecard' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and removed a card'})
cmd = msg.split()
if len(cmd) == 2:
if int(cmd[1]) < len(ses.hand):
ses.hand.pop(int(cmd[1]))
else:
ses.equipment.pop(int(cmd[1])-len(ses.hand))
ses.notify_self()
elif '/getcard' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got a card'})
import bang.cards as cs
cmd = msg.split()
if len(cmd) >= 2:
cards = cs.get_starting_deck(ses.game.expansions)
card_names = ' '.join(cmd[1:]).split(',')
for cn in card_names:
ses.hand.append([c for c in cards if c.name == cn][0])
ses.notify_self()
elif '/getnuggets' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got nuggets'})
import bang.cards as cs
cmd = msg.split()
if len(cmd) == 2:
ses.gold_nuggets += int(cmd[1])
ses.notify_self()
elif '/gameinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {dict(filter(lambda x:x[0] != "rpc_log",ses.game.__dict__.items()))}'})
elif '/meinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.__dict__}'})
elif '/mebot' in msg:
ses.is_bot = not ses.is_bot
if (ses.is_bot):
ses.was_player = True
ses.bot_spin()
elif '/arcadekick' in msg and ses.game.started:
if len([p for p in ses.game.players if p.pending_action != PendingAction.WAIT]) == 0:
sio.emit('chat_message', room=ses.game.name, data={'color': f'','text':f'KICKING THE ARCADE CABINET'})
ses.game.next_turn()
else:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} COMMAND NOT FOUND'})
else:
color = sid.encode('utf-8').hex()[-3:]
sio.emit('chat_message', room=ses.game.name, data={'color': f'#{color}','text':f'[{ses.name}]: {msg}'})
"""
Sockets for the help screen
"""
@sio.event
def get_cards(sid):
import bang.cards as c
cards = c.get_starting_deck(['dodge_city'])
cards_dict = {}
for ca in cards:
if ca.name not in cards_dict:
cards_dict[ca.name] = ca
cards = [cards_dict[i] for i in cards_dict]
sio.emit('cards_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event
def get_characters(sid):
import bang.characters as ch
cards = ch.all_characters(['dodge_city', 'gold_rush'])
sio.emit('characters_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event
def get_highnooncards(sid):
import bang.expansions.high_noon.card_events as ceh
chs = []
chs.extend(ceh.get_all_events())
chs.append(ceh.get_endgame_card())
sio.emit('highnooncards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
@sio.event
def get_foccards(sid):
import bang.expansions.fistful_of_cards.card_events as ce
chs = []
chs.extend(ce.get_all_events())
chs.append(ce.get_endgame_card())
sio.emit('foccards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
@sio.event
def get_goldrushcards(sid):
import bang.expansions.gold_rush.shop_cards as grc
cards = grc.get_cards()
cards_dict = {}
for ca in cards:
if ca.name not in cards_dict:
cards_dict[ca.name] = ca
cards = [cards_dict[i] for i in cards_dict]
sio.emit('goldrushcards_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
if __name__ == '__main__':
eventlet.wsgi.server(eventlet.listen(('', 5001)), app)

View File

495
backend/tests/cards_test.py Normal file
View File

@ -0,0 +1,495 @@
from random import randint
from bang.characters import Character
from bang.cards import *
from tests.dummy_socket import DummySocket
from bang.deck import Deck
from bang.game import Game
from bang.players import Player, PendingAction
# test card Barile
def test_barile():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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_diligenza():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(4)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
g.players[g.turn].draw('')
g.players[g.turn].hand = [WellsFargo(0,0)]
g.players[g.turn].play_card(0)
assert len(g.players[g.turn].hand) == 3

View File

@ -0,0 +1,367 @@
from random import randint
from bang.characters import *
from tests.dummy_socket import DummySocket
from bang.deck import Deck
from bang.game import Game
from bang.players import Player, PendingAction
from bang.cards import *
def test_bartcassidy():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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.RESPOND
g.players[(g.turn+1)%2].respond(-1)
assert g.players[(g.turn+1)%2].pending_action == PendingAction.WAIT
assert g.players[(g.turn+1)%2].lives == 3
def test_SuzyLafayette():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) 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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
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():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(2)]
for p in ps:
g.add_player(p)
g.start_game()
test_chars = [Character('test_char', 4), WillyTheKid()]
for p in ps:
p.available_characters = [test_chars.pop(0)]
p.set_character(p.available_characters[0].name)
if not isinstance(g.players[g.turn].character, WillyTheKid):
g.players[g.turn].draw('')
g.players[g.turn].hand = []
g.players[g.turn].end_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Bang(0,0), Bang(0,0), Bang(0,0)]
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].lives == 3
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].lives == 2
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].lives == 1

View File

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

View File

@ -0,0 +1,11 @@
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)
return True
is_fake = True

View File

@ -0,0 +1,91 @@
from tests.dummy_socket import DummySocket
from bang.deck import Deck
from bang.game import Game
from bang.players import Player, PendingAction
from bang.roles import *
from bang.cards import *
# test that game can start
def test_game_start():
sio = DummySocket()
g = Game('test', sio)
p1 = Player('p1', 'p1', sio)
g.add_player(p1)
p2 = Player('p2', 'p2', sio)
g.add_player(p2)
p3 = Player('p3', 'p3', sio)
g.add_player(p3)
assert p1.role == None
assert p2.role == None
assert p3.role == None
assert not g.started
g.start_game()
assert g.started
assert p1.role != None
assert p2.role != None
assert p3.role != 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 != None
p2.set_character(p2.available_characters[0].name)
assert p2.character != None
p3.set_character(p3.available_characters[0].name)
assert p3.character != 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():
sio = DummySocket()
g = Game('test', sio)
for i in range(9):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
p1 = Player('p1', 'p1', sio)
g.add_player(p1)
p2 = Player('p2', 'p2', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(3):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(4):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(5):
p = Player(f'p{i}', f'p{i}', sio)
g.add_player(p)
g.start_game()
roles = {p.role.name for p in g.players}
assert len(roles) == 4

213
backend/tests/roles_test.py Normal file
View File

@ -0,0 +1,213 @@
from bang.characters import Character
from tests.dummy_socket import DummySocket
from bang.deck import Deck
from bang.game import Game
from bang.players import Player, 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():
sio = DummySocket()
g = Game('test', sio)
for i in range(3):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(3):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(3):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(4):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(5):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(5):
p = Player(f'p{i}', f'p{i}', sio)
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():
sio = DummySocket()
g = Game('test', sio)
for i in range(5):
p = Player(f'p{i}', f'p{i}', sio)
g.add_player(p)
g.start_game()
for p in g.players:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
roles = {g.players[i].role.name:i for i in range(len(g.players))}
print(roles)
assert len(roles) == 4
assert isinstance(g.players[g.turn].role, Sheriff)
g.players[g.turn].is_my_turn = False
for i in range(len(g.players)):
g.players[i].lives = 1
g.players[i].hand = []
g.players[roles['Vice']].lives = 2
g.turn = roles['Rinnegato']
g.play_turn()
g.players[g.turn].draw('')
g.players[g.turn].hand = [Gatling(0,0)]
g.players[g.turn].play_card(0)
for i in range(len(g.players)):
if isinstance(g.players[i].role, Outlaw):
print (g.players[i].role.name, 'win_status:', hasattr(g.players[i], 'win_status') and g.players[i].win_status)
assert (hasattr(g.players[i], 'win_status') and g.players[i].win_status)
assert (hasattr(g.players[i], 'is_dead') and g.players[i].is_dead)
else:
print(g.players[i].role.name, 'win_status:', (hasattr(g.players[i], 'win_status') and g.players[i].win_status))
assert not (hasattr(g.players[i], 'win_status') and g.players[i].win_status)

View File

@ -8,27 +8,28 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.15.1",
"caniuse-lite": "^1.0.30001313",
"core-js": "^3.16.2",
"pretty-checkbox-vue": "^1.1.9",
"register-service-worker": "^1.7.2",
"socket.io-client": "^4.1.2",
"socket.io-client": "^4.4.1",
"vue": "^2.6.14",
"vue-clipboard2": "^0.3.1",
"vue-i18n": "^8.24.5",
"vue-router": "^3.5.2",
"vue-clipboard2": "^0.3.3",
"vue-i18n": "^8.27.0",
"vue-router": "^3.5.3",
"vue-socket.io": "^3.0.10"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.13",
"@vue/cli-plugin-eslint": "~4.5.13",
"@vue/cli-plugin-pwa": "~4.5.13",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.13",
"@vue/cli-plugin-babel": "~4.5.15",
"@vue/cli-plugin-eslint": "~4.5.15",
"@vue/cli-plugin-pwa": "~4.5.15",
"@vue/cli-plugin-router": "~5.0.1",
"@vue/cli-service": "~4.5.15",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.11.1",
"eslint-plugin-vue": "^7.20.0",
"vue-template-compiler": "^2.6.14",
"workbox-webpack-plugin": "^6.1.5"
"workbox-webpack-plugin": "^6.5.1"
},
"eslintConfig": {
"root": true,

Binary file not shown.

View File

@ -1,9 +1,9 @@
<template>
<div :class="{ card: true, equipment: card.is_equipment, character:card.is_character, back:card.is_back, 'usable-next-turn':card.usable_next_turn, 'must-be-used':card.must_be_used}">
<div :class="{ card: true, equipment: card.is_equipment, character:card.is_character, back:card.is_back, 'usable-next-turn':card.usable_next_turn, 'must-be-used':card.must_be_used, 'gold-rush': card.expansion === 'gold_rush', 'brown':card.kind === 0, 'black':card.kind === 1,}">
<h4>{{cardName}}</h4>
<div class="emoji">{{card.icon}}</div>
<div class="emoji">{{emoji}}</div>
<div class="alt_text">{{card.alt_text}}</div>
<div class="suit">{{number}}{{suit}}</div>
<div class="suit">{{number}}<span :style="`${(card.suit !== undefined && card.suit%2 === 0)? 'color:red':''}`">{{suit}}</span></div>
<div class="expansion" v-if="card.expansion_icon">{{card.expansion_icon}}</div>
</div>
</template>
@ -21,16 +21,26 @@ export default {
if (!this.donotlocalize && this.$t(`cards.${this.card.name}.name`) !== `cards.${this.card.name}.name`) {
return this.$t(`cards.${this.card.name}.name`)
}
if (this.card.name == "you") {
return this.$t('you')
}
return this.card.name
},
emoji(){
return this.card.icon != "you" ? this.card.icon : this.$t('you')
},
suit() {
if (this.card && !isNaN(this.card.suit)) {
let x = ['♦️','♣️','♥️','♠️']
return x[this.card.suit];
} else if (this.card.suit) {
return this.card.suit;
}
return '';
},
number() {
if (isNaN(this.card.suit))
return this.card.number
if (this.card.number === 1) return 'A'
else if (this.card.number === 11) return 'J'
else if (this.card.number === 12) return 'Q'
@ -107,6 +117,17 @@ export default {
);
border: 2pt solid rgb(50 122 172);
}
.card.brown.gold-rush {
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
border: 2pt dotted #9C7340;
}
.card.black.gold-rush {
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
border: 2pt dotted #000;
}
.card.back.gold-rush {
background: repeating-linear-gradient(347deg, #ffb32f, #987e51 );
}
.card h4 {
position: absolute;
text-align: center;

View File

@ -5,12 +5,15 @@
<transition-group name="message" tag="div" id="chatbox">
<!-- <div id="chatbox"> -->
<p style="margin:1pt;" class="chat-message" v-for="(msg, i) in messages" v-bind:key="`${i}-c`" :style="`color:${msg.color}`">{{msg.text}}</p>
<p style="margin:1pt 15pt;" class="chat-message" v-for="(msg, i) in commandSuggestion" v-bind:key="`${i}-c`" :style="`color:orange`">{{msg}}</p>
<p class="end" key="end" style="color:#0000">.</p>
<!-- </div> -->
</transition-group>
<div v-if="commandSuggestion.length > 0">
<p style="margin:1pt 15pt;cursor:pointer;" class="chat-message" v-for="(msg, i) in commandSuggestion" v-bind:key="`${i}-c`" :style="`color:orange`"
@click="fillCmd(msg.cmd)">{{msg.cmd}} <i class="std-text" style="font-size:8pt;">{{msg.help}}</i></p>
</div>
<form @submit="sendChatMessage" id="msg-form">
<input v-model="text" style="flex-grow:2;"/>
<input id="my-msg" autocomplete="off" v-model="text" style="flex-grow:2;"/>
<input id="submit-message" type="submit" class="btn" :value="$t('submit')"/>
</form>
</div>
@ -23,6 +26,7 @@ import dynamite_sfx from '@/assets/sounds/dynamite.mp3'
import prison_sfx from '@/assets/sounds/prison.mp3'
import turn_sfx from '@/assets/sounds/turn.mp3'
import death_sfx from '@/assets/sounds/death.mp3'
import cash_sfx from '@/assets/sounds/cash.mp3'
export default {
name: 'Chat',
props: {
@ -32,7 +36,7 @@ export default {
messages: [],
text: '',
spectators: 0,
commands: ['/debug'],
commands: [{cmd:'/debug', help:'Toggles the debug mode'}],
}),
computed: {
commandSuggestion() {
@ -40,7 +44,7 @@ export default {
if (this.text.length < 1) {
return [];
}
return this.commands.filter(x => x.slice(0, this.text.length) == this.text);
return this.commands.filter(x => x.cmd.slice(0, this.text.length) == this.text);
},
},
sockets: {
@ -54,7 +58,7 @@ export default {
}
let params = msg.split('|')
let type = params.shift().substring(1)
if (["flipped", "respond", "play_card", "play_card_against", "play_card_for", "spilled_beer", "diligenza", "wellsfargo", "saloon", "special_calamity"].indexOf(type) !== -1){
if (["flipped", "respond", "play_card", "play_card_against", "play_card_for", "spilled_beer", "diligenza", "wellsfargo", "saloon", "special_calamity", 'won'].indexOf(type) !== -1){
params[1] = this.$t(`cards.${params[1]}.name`)
} else if (type === "choose_character"){
params.push(this.$t(`cards.${params[1]}.desc`))
@ -83,6 +87,8 @@ export default {
(new Audio(dynamite_sfx)).play();
} else if (type == 'prison_turn') {
(new Audio(prison_sfx)).play();
} else if (type == 'purchase_card') {
(new Audio(cash_sfx)).play();
} else {
(new Audio(notification_sfx)).play();
}
@ -115,6 +121,10 @@ export default {
}
e.preventDefault();
},
fillCmd(cmd) {
this.text = cmd;
document.getElementById('my-msg').focus();
}
},
}
</script>
@ -136,6 +146,9 @@ input {
height: 0pt;
margin-top: -1.5pt;
}
.std-text {
color: var(--font-color);
}
.chat {
display: flex;
flex-direction: column;

View File

@ -49,10 +49,12 @@ export default {
//console.log(card)
if (card.noDesc || card.name == null || card.name == "PewPew!")
this.desc = ""
else if (card.desc)
this.desc = (this.$i18n.locale=='it'?card.desc:card.desc_eng)
else if (card.is_character)
this.desc = card.name
else if (card.goal)
this.desc = this.$t(`cards.${card.name}.name`)
else if (card.desc)
this.desc = (this.$i18n.locale=='it'?card.desc:card.desc_eng)
else
this.desc = this.$t(`cards.${card.name}.desc`)
}
@ -62,6 +64,9 @@ export default {
if (this.realCancelText == '') {
this.realCancelText = this.$t('cancel')
}
if (this.cards.length == 1) {
this.showDesc(this.cards[0])
}
if (this.playAudio) {
(new Audio(show_sfx)).play();
}

View File

@ -2,6 +2,16 @@
<div >
<div class="deck">
<card v-if="endTurnAction && isPlaying" :donotlocalize="true" v-show="pending_action == 2" :card="endTurnCard" class="end-turn" @click.native="endTurnAction"/>
<div class="deck" style="position:relative" v-if="goldRushCards.length > 0" >
<card @pointerenter.native="()=>{setGoldRushDesc(goldRushCards[0])}" @pointerleave.native="goldRushDesc=null" :style="goldRushShopOpen?``:`position:absolute; top:0; right:0; transform: rotate(-15deg) translate(0, -50px) scale(0.6)`" v-if="goldRushCards.length > 0" :key="goldRushCards[0].name" :card="goldRushCards[0]" :class="{'shop-open':goldRushShopOpen, 'cant-play': pending_action !==2 || gold_nuggets < goldRushCards[0].number - gold_rush_discount}" @click.native="() => {buy_gold_rush_card(0)}"/>
<card @pointerenter.native="()=>{setGoldRushDesc(goldRushCards[1])}" @pointerleave.native="goldRushDesc=null" :style="goldRushShopOpen?``:`position:absolute; top:0; right:0; transform: rotate(+0deg) translate(0, -50px) scale(0.6)`" v-if="goldRushCards.length > 1" :key="goldRushCards[1].name" :card="goldRushCards[1]" :class="{'shop-open':goldRushShopOpen, 'cant-play': pending_action !==2 || gold_nuggets < goldRushCards[1].number - gold_rush_discount}" @click.native="() => {buy_gold_rush_card(1)}"/>
<card @pointerenter.native="()=>{setGoldRushDesc(goldRushCards[2])}" @pointerleave.native="goldRushDesc=null" :style="goldRushShopOpen?``:`position:absolute; top:0; right:0; transform: rotate(+15deg) translate(0, -50px) scale(0.6)`" v-if="goldRushCards.length > 2" :key="goldRushCards[2].name" :card="goldRushCards[2]" :class="{'shop-open':goldRushShopOpen, 'cant-play': pending_action !==2 || gold_nuggets < goldRushCards[2].number - gold_rush_discount}" @click.native="() => {buy_gold_rush_card(2)}"/>
<div style="position:relative">
<div class="card gold-rush back" style="position:relative; bottom:-3pt;right:-3pt;"/>
<div class="card gold-rush back" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
<card :card="goldRushCardBack" :donotlocalize="true" class="gold-rush back last-event" @click.native="goldRushShopOpen = !goldRushShopOpen"/>
</div>
</div>
<div v-if="eventCard" style="position:relative">
<div class="card fistful-of-cards" style="position:relative; bottom:-3pt;right:-3pt;"/>
<div class="card fistful-of-cards" style="position:absolute; bottom:-1.5pt;right:-1.5pt;"/>
@ -20,8 +30,17 @@
</div>
</div>
<transition name="list">
<p v-if="eventCard" class="center-stuff"><i>{{eventDesc}}</i></p>
<p v-if="eventCard" class="center-stuff"><b>{{eventDesc}}</b></p>
</transition>
<transition name="list">
<div v-if="goldRushDesc">
<p class="center-stuff">🤑 <i>{{$t(`cards.${goldRushDesc.name}.desc`)}}</i> 🤑</p>
<p class="center-stuff">🤑 <b>{{goldRushDesc.number - gold_rush_discount}} 💵</b> 🤑</p>
</div>
</transition>
<div style="margin-bottom:6pt;margin-bottom: 6pt;display: flex;flex-direction: column;">
<button class="btn" v-if="pending_action == 2 && can_gold_rush_discard" @click="$socket.emit('gold_rush_discard')">{{$t('gold_rush_discard')}}</button>
</div>
<transition name="list">
<p v-if="desc" class="center-stuff"><i>{{desc}}</i></p>
</transition>
@ -44,18 +63,31 @@ export default {
name: 'PewPew!',
icon: '💥',
},
goldRushCardBack: {
name: 'GoldRush!',
icon: '🤑️',
},
lastScrap: null,
eventCard: null,
previousScrap: null,
pending_action: false,
isPlaying: true,
desc: '',
goldRushShopOpen: true,
goldRushCards: [],
gold_nuggets: 0,
goldRushDesc: null,
can_gold_rush_discard: false,
gold_rush_discount: 0,
}),
sockets: {
self(self){
self = JSON.parse(self)
this.isPlaying = self.lives > 0 || self.is_ghost
this.pending_action = self.pending_action
this.gold_nuggets = self.gold_nuggets
this.can_gold_rush_discard = self.can_gold_rush_discard
this.gold_rush_discount = self.gold_rush_discount
},
scrap(card) {
this.lastScrap = card
@ -68,6 +100,10 @@ export default {
expansion: 'fistful-of-cards',
} : card
},
gold_rush_shop(cards) {
console.log('GOLD RUSH:'+ cards)
this.goldRushCards = JSON.parse(cards)
},
},
computed: {
endTurnCard() {
@ -102,6 +138,9 @@ export default {
this.$socket.emit('draw', pile)
}
},
buy_gold_rush_card(index) {
this.$socket.emit('buy_gold_rush_card', index)
},
event() {
if (this.pending_action !== false) {
this.$socket.emit('draw', 'event')
@ -112,6 +151,14 @@ export default {
this.desc = (this.$i18n.locale=='it'?this.lastScrap.desc:this.lastScrap.desc_eng)
else
this.desc = this.$t(`cards.${this.lastScrap.name}.desc`)
},
setGoldRushDesc(card) {
this.goldRushDesc = card
},
},
mounted() {
if (window.innerWidth < 1000) {
this.goldRushShopOpen = false;
}
},
watch: {
@ -129,6 +176,7 @@ export default {
align-items: center;
justify-content: center;
flex-direction: row-reverse;
flex-wrap: wrap-reverse;
}
.last-scrap {
position: absolute;
@ -140,6 +188,10 @@ export default {
opacity: 0.8;
transform: translateY(-10px);
}
.gold-rush:not(.back) {
animation-duration: 0.8s;
animation-name: slidein;
}
@keyframes slidein {
from {
transform: translate(-100px, 10px) scale(1.3) rotate(-10deg);

View File

@ -1,6 +1,10 @@
<template>
<div>
<h1 id="help">{{$t('help.title')}}</h1>
<a href="#thecards"><p>{{$t('help.gotocards')}}</p></a>
<a href="#highnooncards"><p>{{$t('help.gotohighnoon')}}</p></a>
<a href="#foccards"><p>{{$t('help.gotofoc')}}</p></a>
<a href="#goldrushcards"><p>{{$t('help.gotogoldrush')}}</p></a>
<h2>{{$t('help.character')}}</h2>
<p>{{$t('help.characters_special')}}</p>
<a href="#basecharacters"><p>{{$t('help.gotoallcharacters')}}</p></a>
@ -55,29 +59,31 @@
<li><p>{{$t('help.endgameshriffdeath')}}</p></li>
<li><p>{{$t('help.endgamesheriffwin')}}</p></li>
</ul>
<h2>{{$t('help.thecards')}}</h2>
<div>
<div v-for="(c, i) in cards" v-bind:key="c.name ? (c.name+c.number) : i" style="display:flex">
<h2 id="thecards">{{$t('help.thecards')}}</h2>
<div class="flexy-cards-wrapper">
<div v-for="(c, i) in cards" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
<Card :card="c" @pointerenter.native="''" @pointerleave.native="''"/>
<div style="margin-left:6pt;">
<p>{{$t(`cards.${c.name}.desc`)}}</p>
<p v-if="c.is_equipment"><b>{{$t('help.equipment')}}</b></p>
<p v-if="c.is_weapon"><b>{{$t('help.weapon')}}</b></p>
<p v-if="c.expansion"><b>{{c.expansion}}</b></p>
</div>
</div>
</div>
<h2 id="basecharacters">{{$t('help.allcharacters')}}</h2>
<div>
<div v-for="(c, i) in characters" v-bind:key="c.name ? (c.name+c.number) : i" style="display:flex">
<div class="flexy-cards-wrapper">
<div v-for="(c, i) in characters" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
<Card :card="c" @pointerenter.native="''" @pointerleave.native="''"/>
<div style="margin-left:6pt;">
<p>{{$t(`cards.${c.name}.desc`)}}</p>
<p v-if="c.expansion"><b>{{c.expansion}}</b></p>
</div>
</div>
</div>
<h2 id="highnooncards">{{$t('help.highnooncards')}}</h2>
<div>
<div v-for="(c, i) in highnooncards" v-bind:key="c.name ? (c.name+c.number) : i" style="display:flex">
<div class="flexy-cards-wrapper">
<div v-for="(c, i) in highnooncards" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
<Card :card="c" :class="'high-noon last-event'" @pointerenter.native="''" @pointerleave.native="''"/>
<div style="margin-left:6pt;">
<p>{{$t(`cards.${c.name}.desc`)}}</p>
@ -85,14 +91,23 @@
</div>
</div>
<h2 id="foccards">{{$t('help.foccards')}}</h2>
<div>
<div v-for="(c, i) in foccards" v-bind:key="c.name ? (c.name+c.number) : i" style="display:flex">
<div class="flexy-cards-wrapper">
<div v-for="(c, i) in foccards" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
<Card :card="c" :class="'fistful-of-cards last-event'" @pointerenter.native="''" @pointerleave.native="''"/>
<div style="margin-left:6pt;">
<p>{{$t(`cards.${c.name}.desc`)}}</p>
</div>
</div>
</div>
<h2 id="goldrushcards">{{$t('help.goldrushcards')}}</h2>
<div class="flexy-cards-wrapper">
<div v-for="(c, i) in goldrushcards" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
<Card :card="c" class="gold-rush" @pointerenter.native="''" @pointerleave.native="''"/>
<div style="margin-left:6pt;">
<p>{{$t(`cards.${c.name}.desc`)}}</p>
</div>
</div>
</div>
</div>
</template>
<script>
@ -111,6 +126,7 @@ export default {
characters: [],
highnooncards: [],
foccards: [],
goldrushcards: [],
}),
computed: {
endTurnCard() {
@ -140,17 +156,41 @@ export default {
...x,
}))
},
goldrushcards_info(cardsJson) {
this.goldrushcards = JSON.parse(cardsJson).map(x=>({
...x,
}))
},
},
mounted() {
this.$socket.emit('get_cards')
this.$socket.emit('get_characters')
this.$socket.emit('get_highnooncards')
this.$socket.emit('get_foccards')
this.$socket.emit('get_goldrushcards')
document.getElementById('help').scrollIntoView();
}
}
</script>
<style scoped>
.flexy-cards-wrapper {
display: flex;
flex-flow: wrap;
}
.flexy-cards {
flex: 30%;
display:flex;
}
@media only screen and (max-width:500px) {
.flexy-cards {
flex: 100%;
}
}
@media only screen and (max-width:800px) {
.flexy-cards {
flex: 50%;
}
}
@keyframes pick {
0% {
transform: translate(0,0);

View File

@ -23,7 +23,9 @@
<div v-if="!started">
<h3>{{$t("expansions")}}</h3>
<div v-for="ex in expansionsStatus" v-bind:key="ex.id">
<PrettyCheck @click.native="toggleExpansions(ex.id)" :disabled="!isRoomOwner" :checked="ex.enabled" class="p-switch p-fill" style="margin-top:5px; margin-bottom:3px;">{{ex.name}}</PrettyCheck>
<PrettyCheck @click.native="toggleExpansions(ex.id)" :disabled="!isRoomOwner" :checked="ex.enabled" class="p-switch p-fill" style="margin-top:5px; margin-bottom:3px;">{{ex.name}}
<p v-if="ex.is_beta" style="padding: 0px 10px;color: red;border-radius: 12pt;position: absolute;right: -50pt;top: -12pt;">BETA</p>
</PrettyCheck>
<br>
</div>
<h3>{{$t('mods')}}</h3>
@ -39,6 +41,9 @@
<transition-group name="list" tag="div" class="players-table">
<Card v-if="startGameCard" key="_start_game_" :donotlocalize="true" :card="startGameCard" @click.native="startGame"/>
<div v-for="p in playersTable" v-bind:key="p.card.name" style="position:relative;">
<transition-group v-if="p.gold_nuggets && p.gold_nuggets > 0" name="list" tag="div" style="position: absolute;top: -10pt; font-size:9pt;">
<span v-for="(n, i) in p.gold_nuggets" v-bind:key="i" :alt="i">💵</span>
</transition-group>
<transition-group v-if="p.max_lives && !p.is_ghost" name="list" tag="div" class="tiny-health">
<span v-for="(n, i) in p.lives" v-bind:key="i" :alt="i"></span>
<span v-for="(n, i) in (p.max_lives-p.lives)" v-bind:key="`${i}-sk`" :alt="i">💀</span>
@ -54,9 +59,12 @@
<div class="tiny-equipment">
<Card v-for="(card, i) in p.equipment" v-bind:key="card.name+card.number"
:card="card" @click.native="selectedInfo = p.equipment"
:style="`margin-top: ${i<1?10:-(Math.min((p.equipment.length+1)*12,80))}pt`"/>
:style="`margin-top: ${i<1?10:-(Math.min((p.equipment.length+p.gold_rush_equipment.length+1)*12,80))}pt`"/>
<Card v-for="(card, i) in p.gold_rush_equipment" v-bind:key="card.name+card.number"
:card="card" @click.native="selectedInfo = p.gold_rush_equipment"
:style="`margin-top: ${i+p.equipment.length<1?10:-(Math.min((p.equipment.length+p.gold_rush_equipment.length+1)*12,80))}pt`"/>
</div>
<div v-if="p.is_bot" style="position:absolute;bottom:57%;" class="center-stuff">
<div v-if="p.is_bot" style="position:absolute;bottom:57%;width:20pt;" class="center-stuff">
<span>🤖</span>
</div>
</div>
@ -70,6 +78,8 @@
<Chooser v-if="selectedInfo" :text="$t('details')" :cards="selectedInfo" :cancelText="$t('ok')" :cancel="()=>{selectedInfo = null}" :select="()=>{selectedInfo = null}"/>
<transition name="bounce">
<Chooser v-show="hasToChoose" :text="`${$t('choose_card')}${target_p?$t('choose_card_from') + target_p:''}`" :cards="chooseCards" :select="chooseCard"/>
</transition>
<transition name="bounce">
<full-screen-input v-if="!started && hasToSetUsername" :defaultValue="storedUsername" :text="$t('choose_username')" :val="username" :cancel="setUsername" :cancelText="$t('ok')"/>
</transition>
</div>
@ -114,6 +124,7 @@ export default {
password: '',
togglable_expansions: [],
expansions: [],
beta_expansions: ['gold_rush'],
hasToSetUsername: false,
is_competitive: false,
disconnect_bot: false,
@ -199,6 +210,7 @@ export default {
return {
id: x,
name: x.replace(/(^|_)([a-z])/g, function($0,$1,$2) {return ' ' + $2.toUpperCase()}),
is_beta: this.beta_expansions.indexOf(x) !== -1,
enabled: this.expansions.indexOf(x) !== -1
}
})
@ -292,6 +304,7 @@ export default {
if (Vue.config.devtools)
console.log(pl)
let arr = []
if (this.username != player_name)
for (let i=0; i<pl.ncards; i++)
arr.push({
name: 'PewPew!',

View File

@ -147,7 +147,7 @@ export default {
},
},
mounted() {
this.randomTip = `tip_${1+Math.floor(Math.random() * 7)}`
this.randomTip = `tip_${1+Math.floor(Math.random() * 8)}`
if (localStorage.getItem('username'))
this.username = localStorage.getItem('username')
else {

View File

@ -11,10 +11,16 @@
<span v-for="(n, i) in lives" v-bind:key="i" :alt="i"></span>
<span v-for="(n, i) in (max_lives-lives)" v-bind:key="`${i}-sk`" :alt="i">💀</span>
</transition-group>
<div v-if="gold_nuggets > 0" style="display: flex;align-items: center;margin-left: 12pt;margin-right: -10pt;justify-content: space-evenly;width: 25pt;">
<transition name="list">
<span :key="gold_nuggets">{{gold_nuggets}}</span>
</transition>
<span>💵</span>
</div>
<transition-group v-if="lives > 0 || is_ghost" name="list" tag="div" style="margin: 0 0 0 10pt; display:flex;">
<Card v-for="card in equipment" v-bind:key="card.name+card.number" :card="card"
<Card v-for="card in equipmentComputed" v-bind:key="card.name+card.number" :card="card"
@pointerenter.native="setDesc(card)" @pointerleave.native="desc=''"
@click.native="play_card(card, true)" :class="{'cant-play':((eventCard && eventCard.name == 'Lazo') || !card.can_be_used_now)}"/>
@click.native="play_card(card, true)" :class="{'cant-play':((eventCard && eventCard.name == 'Lazo') || (!card.can_be_used_now && !card.is_equipment))}"/>
</transition-group>
</div>
<transition name="list">
@ -22,14 +28,17 @@
</transition>
<div style="margin-bottom:6pt;margin-bottom: 6pt;display: flex;flex-direction: column;">
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Sid Ketchum' && lives < max_lives && hand.length > 1" @click="sidWantsScrapForHealth=true">{{$t('special_ability')}}</button>
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Chuck Wengam' && lives > 1" @click="chuckSpecial">{{$t('special_ability')}}</button>
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Chuck Wengam' && lives > 1" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'José Delgado' && special_use_count < 2 && hand.filter(x => x.is_equipment).length > 0" @click="joseScrap=true">{{$t('special_ability')}}</button>
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Doc Holyday' && special_use_count < 1 && hand.length > 1" @click="holydayScrap=true">{{$t('special_ability')}}</button>
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Jacky Murieta' && gold_nuggets >=2" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Josh McCloud' && gold_nuggets >=2" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
<button :class="{'btn': true, 'cant-play':(pending_action != 2)}" :disabled="pending_action != 2" v-if="!(eventCard && eventCard.name == 'Sbornia') && is_my_turn && character.name === 'Raddie Snake' && special_use_count < 2 && gold_nuggets >=1" @click="()=>{$socket.emit('special', {})}">{{$t('special_ability')}}</button>
</div>
<div v-if="lives > 0 || is_ghost" style="position:relative">
<span id="hand_text">{{$t('hand')}}</span>
<transition-group name="list" tag="div" :class="{hand:true, 'play-cards':pending_action===2}">
<Card v-for="card in handComputed" v-bind:key="card.name+card.number" :card="card"
<Card v-for="card in handComputed" v-bind:key="card.name+card.number+card.suit" :card="card"
@click.native="play_card(card, false)"
@pointerenter.native="setHint(card)" @pointerleave.native="hint=''"
:class="{'cant-play':card.cantBePlayed}"/>
@ -39,18 +48,19 @@
<p v-if="hint"><i>{{hint}}</i></p>
</transition>
<Chooser v-if="is_my_turn && pending_action == 4 && (lives > 0 || is_ghost) && !(emporioCards && emporioCards.cards && emporioCards.cards.length > 0)" :text="$t('wait')" :cards="[]"/>
<Chooser v-if="card_against" :text="$t('card_against')" :hint-text="visiblePlayers.length === 0 ? $t('no_players_in_range'):''" :cards="visiblePlayers" :select="selectAgainst" :cancel="cancelCardAgainst"/>
<Chooser v-if="card_against" :text="$t('card_against')" :hint-text="visiblePlayers.length === 0 ? $t('no_players_in_range'):''" :cards="visiblePlayers" :select="selectAgainst" :cancel="card_against.number !== 42 ? cancelCardAgainst : null"/>
<Chooser v-if="pending_action == 3" :text="respondText" :cards="respondCards" :select="respond" :playAudio="true"/>
<Chooser v-if="shouldChooseCard" :text="$t(choose_text)" :cards="available_cards" :select="choose" :playAudio="true"/>
<Chooser v-if="lives <= 0 && max_lives > 0 && !is_ghost && !spectator" :text="$t('you_died')" :cancelText="$t('spectate')" :cancel="()=>{max_lives = 0; spectator = true}"/>
<Chooser v-if="win_status !== undefined" :text="win_status?$t('you_win'):$t('you_lose')" />
<Chooser v-if="show_role" :text="$t('you_are')" :cards="[my_role]" :hintText="($i18n.locale=='it'?my_role.goal:my_role.goal_eng)" :select="() => {show_role=false}" :cancel="() => {show_role=false}" :cancelText="$t('ok')" />
<Chooser v-if="notifycard" :key="notifycard.card" :text="`${notifycard.player} ${$t('did_pick_as')}:`" :cards="[notifycard.card]" :hintText="$t(notifycard.message)" class="turn-notify-4s"/>
<Chooser v-if="cantplaycard" :key="cantplaycard" :text="`${$t('cantplaycard')}`" class="turn-notify-4s"/>
<Chooser v-if="!show_role && is_my_turn && pending_action < 2" :text="$t('play_your_turn')" :key="is_my_turn" class="turn-notify" />
<Chooser v-if="!show_role && availableCharacters.length > 0" :text="$t('choose_character')" :cards="availableCharacters" :select="setCharacter"/>
<Chooser v-if="hasToPickResponse" :playAudio="true" :text="`${$t('pick_a_card')} ${attacker?($t('to_defend_from')+' '+attacker):''}`" :key="hasToPickResponse" class="turn-notify" />
<Chooser v-if="!card_against && card_with" :text="`${$t('choose_scarp_card_to')} ${card_with.name.toUpperCase()}`" :cards="handComputed.filter(x => x !== card_with)" :select="selectWith" :cancel="()=>{card_with = null}"/>
<Chooser v-if="showScrapScreen" :text="`${$t('discard')} ${hand.length}/${lives}`" :cards="hand" :select="scrap" :cancel="cancelEndingTurn"/>
<Chooser v-if="showScrapScreen" :text="`${$t('discard')} ${hand.length}/${maxHandLength()}`" :cards="hand" :select="scrap" :cancel="cancelEndingTurn"/>
<Chooser v-if="sidWantsScrapForHealth && scrapHand.length < 2" :text="`${$t('discard')} ${2 - scrapHand.length} ${$t('to_regain_1_hp')}`"
:cards="notScrappedHand" :select="sidScrap" :cancel="() => {sidWantsScrapForHealth = false;scrapHand=[]}"/>
<Chooser v-if="joseScrap" :text="`${$t('discard')}`"
@ -83,6 +93,7 @@ export default {
character: null,
availableCharacters: [],
equipment: [],
gold_rush_equipment: [],
hand: [],
lives: 0,
max_lives: 0,
@ -118,6 +129,8 @@ export default {
spectator: false,
noStar: false,
committed_suit_manette: null,
gold_nuggets: 0,
cantplaycard: false,
}),
sockets: {
role(role) {
@ -141,11 +154,12 @@ export default {
}
this.hand = self.hand
this.equipment = self.equipment
this.gold_rush_equipment = self.gold_rush_equipment
this.lives = self.lives
this.max_lives = self.max_lives
this.has_played_bang = self.has_played_bang
this.special_use_count = self.special_use_count
this.choose_text = self.choose_text
this.choose_text = self.choose_text.split('|')[0]
this.is_my_turn = self.is_my_turn
this.committed_suit_manette = self.committed_suit_manette
if (this.is_my_turn) document.title = this.$t('your_turn')+' | PewPew!'
@ -169,6 +183,11 @@ export default {
this.shouldChooseCard = false
}
this.noStar = self.noStar
this.gold_nuggets = self.gold_nuggets
let mustplay = this.handComputed.filter(x => x.number == 42);
if (mustplay.length > 0) {
this.play_card(mustplay[0], false)
}
},
self_vis(vis) {
// console.log('received visibility update')
@ -181,6 +200,12 @@ export default {
this.notifycard = null
}.bind(this), 4000)
},
cant_play_card() {
this.cantplaycard = true
setTimeout(function(){
this.cantplaycard = false
}.bind(this), 1000)
},
event_card(card) {
this.eventCard = card
},
@ -227,7 +252,6 @@ export default {
name: this.name,
number: 0,
icon: this.$t('you'),
desc: this.$t('you'),
is_character: true,
})
}
@ -243,7 +267,7 @@ export default {
return x[this.pending_action]
},
canEndTurn() {
return (this.pending_action == 2 && this.hand.length <= (this.character.name === "Sean Mallory" && !(this.eventCard && this.eventCard.name == "Sbornia")?10:this.lives))
return (this.pending_action == 2 && this.hand.length <= this.maxHandLength())
},
respondCards() {
let cc = [{
@ -252,7 +276,9 @@ export default {
is_equipment: true,
noDesc: true,
}]
this.hand.filter(x => (x.can_be_used_now && this.expected_response.indexOf(x.name) !== -1) || this.character.name === "Elena Fuente").forEach(x=>{
let expectedBangResponse = this.expected_response.indexOf('Bang!') !== -1
let sborniaEvent = this.eventCard && this.eventCard.name == "Sbornia"
this.hand.filter(x => (x.can_be_used_now && this.expected_response.indexOf(x.name) !== -1) || (!expectedBangResponse && this.character.name === "Elena Fuente" && !sborniaEvent)).forEach(x=>{
cc.push(x)
})
this.equipment.filter(x => x.usable_next_turn && x.can_be_used_now && this.expected_response.indexOf(x.name) !== -1).forEach(x=>{
@ -260,6 +286,12 @@ export default {
})
return cc
},
equipmentComputed() {
let eq = []
this.equipment.forEach(x => eq.push(x));
this.gold_rush_equipment.forEach(x => eq.push(x));
return eq
},
handComputed() {
return this.hand.map(x => {
let cantBePlayed = false
@ -278,6 +310,9 @@ export default {
}
},
methods: {
maxHandLength() {
return (this.character.name === "Sean Mallory" && !(this.eventCard && this.eventCard.name == "Sbornia")?10:(this.gold_rush_equipment.filter(x => x.name == 'Cinturone').length>0?8:this.lives))
},
setCharacter(char) {
this.availableCharacters = []
this.$socket.emit('set_character', char.name)
@ -308,16 +343,13 @@ export default {
this.scrapHand.push(this.hand.indexOf(c))
},
holydayScrapBang(other) {
this.$socket.emit('holyday_special', {
this.$socket.emit('special', {
cards : [this.scrapHand[0], this.scrapHand[1]],
against: other.name
})
this.scrapHand = []
this.holydayScrap = false
},
chuckSpecial(){
this.$socket.emit('chuck_lose_hp_draw')
},
end_turn(){
// console.log('ending turn')
this.cancelEndingTurn()
@ -327,10 +359,11 @@ export default {
this.$socket.emit('scrap', this.hand.indexOf(c))
},
play_card(card, from_equipment) {
if (from_equipment && (!card.usable_next_turn || !card.can_be_used_now || (this.eventCard && this.eventCard.name == "Lazo"))) return;
console.log('play' + card.name)
if (from_equipment && (!card.can_be_used_now || (this.eventCard && this.eventCard.name == "Lazo"))) return;
else if (card.usable_next_turn && !card.can_be_used_now) return this.really_play_card(card, null);
let calamity_special = (card.name === 'Mancato!' && this.character.name === 'Calamity Janet')
let cant_play_bang = (this.has_played_bang && this.equipment.filter(x => x.name == 'Volcanic').length == 0)
let cant_play_bang = (this.has_played_bang && card.number !==42 && this.equipment.filter(x => x.name == 'Volcanic').length == 0)
if (this.pending_action == 2) {
this.can_target_sheriff = (card.name !== 'Prigione')
if (card.need_with && !this.card_with) {
@ -383,7 +416,7 @@ export default {
really_play_card(card, against) {
let res = this.handComputed.indexOf(card)
if (res === -1) {
res = this.equipment.indexOf(card)
res = this.equipmentComputed.indexOf(card)
if (res !== -1) res += this.hand.length
}
let card_data = {

View File

@ -7,6 +7,7 @@
"tip_5": "Found a bug? Message us on discord!",
"tip_6": "Now with a discord server!",
"tip_7": "If you want to help us translate the game in your language, ping us on discord!",
"tip_8": "If you disconnect during in an ongoing game you will be replaced by a bot (while you are gone)!",
"online_players": "Online players: ",
"choose_username": "Pick an username:",
"available_lobbies": "Available Lobbies:",
@ -46,6 +47,7 @@
"choose_manette": "Choose a suit, you will be able to play only cards with that suit on this turn.",
"hand": "HAND",
"card_against": "Who will you play your card against?",
"choose_sid_scrap": "Discard 2 cards to not die",
"choose_card_to_get": "Choose a card",
"choose_guess": "Guess the color of the suit",
"choose_ranch": "Choose the cards to replace",
@ -55,12 +57,19 @@
"choose_rimbalzo_player": "Choose the target of the bang",
"choose_rimbalzo_card": "Choose the card to discard the bang to",
"choose_nuova_identita": "Choose if you want to keep your current character, or if you want to change it and start from 2 HP",
"choose_bicchierino": "A player of your choice regains 1 hp",
"choose_bottiglia": "Choose how you will play Bottle!",
"choose_complice": "Choose how you will play Pardner!",
"choose_ricercato": "Choose who you will play Wanted against.",
"choose_birra_function": "Choose between getting 1 gold nugget by discarding beer or if you want to play the beer.",
"emporio_others": "{0} is choosing which card to get from the General Store",
"you_died": "YOU DIED",
"spectate": "SPECTATE",
"you_win": "YOU WON",
"you_lose": "YOU LOST",
"special_ability": "SPECIAL ABILITY",
"gold_rush_discard": "Discard another player's gold rush equipment (paying the price + 1)",
"gold_rush_discard_2": "Discard another player's gold rush equipment (paying the price + 1)",
"discard": "DISCARD",
"to_regain_1_hp": "TO REGAIN 1 HP",
"play_your_turn": "PLAY YOUR TURN",
@ -73,13 +82,14 @@
"submit": "Submit",
"copy": "Copy invite",
"no_players_in_range": "You can't see the other players, equip a weapon or a scope!",
"cantplaycard": "You can't play this card like that!",
"chat": {
"spectators": " | A spectator is watching the game | {n} spectators are watching the game",
"chat": "Chat",
"joined": "{0} joined the lobby",
"died": "{0} died",
"died_role": "{0} was a {1}!",
"won": "{0} won!",
"won": "{0} won! Their role was {1}",
"choose_character": "{0} has {1} as character, his special ability is: {2}!",
"starting": "The game is starting!",
"sheriff": "{0} is the sheriff!",
@ -90,7 +100,9 @@
"flipped": "{0} flipped a {1} {2}.",
"explode": "{0} blew up the dynamite.",
"beer_save": "{0} used a beer to save his life.",
"get_nugget":"{0} got a gold nugget using a Beer.",
"play_card": "{0} played {1}.",
"purchase_card": "{0} purchased {1}.",
"play_card_against": "{0} played {1} against {2}.",
"play_card_for": "{0} played {1} for {2}.",
"spilled_beer": "{0} spilled a {1}.",
@ -113,7 +125,10 @@
"prison_free": "{0} got out of prison",
"prison_turn": "{0} stayed in prison this turn",
"flip_event": "🎴 EVENT: {0} 🎴",
"choose_manette": "{0} committed to play only cards of suit {1} in this turn."
"choose_manette": "{0} committed to play only cards of suit {1} in this turn.",
"UnionPacific": "{0} played Union Pacific and draws 4 cards from the deck",
"use_special": "{0} used the special ability of their character ({1})",
"gold_rush_pay_discard": "{0} discarded {2} from {1}."
},
"foc": {
"leggedelwest": "He must play this card on this turn if possible."
@ -560,6 +575,101 @@
"Per Un Pugno Di Carte": {
"name": "A Fistful of Cards",
"desc": "On the beginning of his turn, the player is target of as many Bang as how many cards he has in his hand"
},
"Pepita": {
"name": "Gold Nugget"
},
"Bicchierino": {
"name": "Shot Glass",
"desc": "You can choose a player that regains 1 HP (even you)"
},
"Bottiglia": {
"name": "Bottle",
"desc": "Can be played as Panic!, Beer or BANG!"
},
"Complice": {
"name": "Pardner",
"desc": "Can be played as General Store, Duel or Cat Balou."
},
"Corsa All Oro_gr": {
"name": "Gold Rush",
"desc": "You end your turn, regain all your HP and start a new turn."
},
"Rum": {
"name": "Rhum",
"desc": "Flip 4 cards, for each different suit you gain 1 HP."
},
"Union Pacific": {
"name": "Union Pacific",
"desc": "Draw 4 cards from the deck."
},
"Calumet": {
"name": "Calumet",
"desc": "Cards of diamonds played by other players have no effect on you."
},
"Cinturone": {
"name": "Gun Belt",
"desc": "You can keep up to 8 cards when you end your turn."
},
"Ferro di Cavallo": {
"name": "Horseshoe",
"desc": "Every time you have to flip a card you flip twice!"
},
"Piccone": {
"name": "Pickaxe",
"desc": "You get 1 more card when you draw at the beginning of the turn."
},
"Ricercato": {
"name": "Wanted",
"desc": "Play it on another player, who kills the target gets 2 extra cards and 1 gold nugget."
},
"Setaccio": {
"name": "Gold Pan",
"desc": "Pay 1 gold nugget to draw 1 card from the deck, up to twice per turn. (Click on the equipped card to use)"
},
"Stivali": {
"name": "Boots",
"desc": "Each time you lose 1 hp, you draw 1 card from the deck."
},
"Talismano": {
"name": "Lucky Charm",
"desc": "Each time you lose 1 hp, you get 1 gold nugget."
},
"Zaino": {
"name": "Rucksack",
"desc": "Pay 2 gold nugget to heal 1 HP. (Click on the equipped card to use)"
},
"Don Bell": {
"name": "Don Bell",
"desc": "When he ends his turn, he flips a card, if it flips Hearts ❤️ or Diamonds ♦️ he plays again."
},
"Dutch Will": {
"name": "Dutch Will",
"desc": "He draws 2 cards, discards 1 and takes 1 gold nugget."
},
"Jacky Murieta": {
"name": "Jacky Murieta",
"desc": "During his turn he can pay 2 gold nuggets to shoot another BANG!"
},
"Josh McCloud": {
"name": "Josh McCloud",
"desc": "He can pay 2 gold nuggets to get the first card from the Gold Rush deck."
},
"Madam Yto": {
"name": "Madam Yto",
"desc": "She draws 1 card from the deck each time a beer is played."
},
"Pretty Luzena": {
"name": "Pretty Luzena",
"desc": "Once per turn she can have a discount of 1 on the gold rush cards shop."
},
"Raddie Snake": {
"name": "Raddie Snake",
"desc": "He can throw away 1 gold nugget to draw 1 card from the deck during his turn (2 times max per turn)."
},
"Simeon Picos": {
"name": "Simeon Picos",
"desc": "He gets 1 gold nugget every time he loses 1 hp."
}
},
"help": {
@ -604,8 +714,13 @@
"sheriff": "Sheriff",
"allcharacters": "All characters",
"gotoallcharacters": "Jump to all characters",
"gotocards": "Jump to cards",
"gotohighnoon": "Jump to High Noon",
"gotofoc": "Jump to Fistful Of Cards",
"gotogoldrush": "Jump to Gold Rush",
"highnooncards": "High Noon - Event Cards",
"foccards": "Fistful of Cards - Event Cards"
"foccards": "Fistful of Cards - Event Cards",
"goldrushcards": "Gold Rush Cards"
},
"theme": {
"sepia": "Sepia",

View File

@ -7,6 +7,7 @@
"tip_5": "Se trovi un bug comunicacelo su discord!",
"tip_6": "Adesso con un server discord!",
"tip_7": "Se vuoi aiutarci a tradurre il gioco nella tua lingua scrivicelo su discord!",
"tip_8": "Se ti disconnetti durante una partita verrai sostituito da un bot (durante la tua assenza)!",
"online_players": "Giocatori online: ",
"choose_username": "Scegli un username:",
"available_lobbies": "Stanze disponibili:",
@ -47,6 +48,7 @@
"hand": "MANO",
"card_against": "Contro chi vuoi giocare la carta",
"choose_card_to_get": "Scegli che carta pescare",
"choose_sid_scrap": "Scarta 2 carte per non morire",
"choose_guess": "Indovina il colore del seme",
"choose_ranch": "Scegli le carte da sostituire",
"choose_dalton": "Scegli che equipaggiamento scartare",
@ -55,12 +57,19 @@
"choose_rimbalzo_player": "Scegli contro chi scartare il bang",
"choose_rimbalzo_card": "Scegli contro che carta scartare il bang",
"choose_nuova_identita": "Scegli se rimanere con il tuo personaggio corrente o se cambiarlo e ripartire con 2 vite.",
"choose_bicchierino": "Scegli a chi far recuperare 1 punto vita",
"choose_bottiglia": "Scegli come giocare Bottiglia!",
"choose_complice": "Scegli come giocare Complice!",
"choose_ricercato": "Scegli il giocatore su cui vuoi giocare Ricercato",
"choose_birra_function": "Scegli tra ottenere 1 pepita scartando la birra oppure giocare la birra.",
"emporio_others": "{0} sta scegliendo che carta prendere dall'emporio",
"you_died": "SEI MORTO",
"spectate": "SPETTATORE",
"you_win": "HAI VINTO",
"you_lose": "HAI PERSO",
"special_ability": "ABILITÀ SPECIALE",
"gold_rush_discard": "Fai scartare equipaggiamento gold rush (pagando pepite + 1)",
"gold_rush_discard_2": "Fai scartare equipaggiamento gold rush (pagando pepite + 1)",
"discard": "SCARTA",
"to_regain_1_hp": "PER RECUPERARE 1 VITA",
"play_your_turn": "GIOCA IL TUO TURNO",
@ -73,13 +82,14 @@
"submit": "Invia",
"copy": "Copia invito",
"no_players_in_range": "Non vedi nessun giocatore, equipaggia un arma o un mirino!",
"cantplaycard": "Non puoi giocare questa carta in questo modo!",
"chat": {
"spectators": " | Uno spettatore sta guardando la partita | {n} spettatori stanno guardando la partita",
"chat": "Chat",
"joined": "{0} è entrato nella stanza",
"died": "{0} è morto",
"died_role": "{0} era {1}!",
"won": "{0} ha vinto!",
"won": "{0} ha vinto! Il suo ruolo era {1}",
"choose_character": "{0} ha come personaggio {1}, la sua abilità speciale è: {2}!",
"starting": "La partita sta iniziando!",
"sheriff": "{0} è lo sceriffo!",
@ -90,7 +100,9 @@
"flipped": "{0} ha estratto {1} {2}.",
"explode": "{0} ha fatto esplodere la dinamite.",
"beer_save": "{0} ha usato una birra per recuperare una vita.",
"get_nugget":"{0} ha ottenuto una pepita d'oro usando una Birra.",
"play_card": "{0} ha giocato {1}.",
"purchase_card": "{0} ha comprato {1}.",
"play_card_against": "{0} ha giocato {1} contro {2}.",
"play_card_for": "{0} ha giocato {1} per {2}.",
"spilled_beer": "{0} ha rovesciato una {1}.",
@ -113,7 +125,10 @@
"prison_free": "{0} è uscito di prigione",
"prison_turn": "{0} rimane in prigione questo turno",
"flip_event": "🎴 EVENTO: {0} 🎴",
"choose_manette": "{0} si è impegnato ad usare solo carte di seme {1} in questo turno."
"choose_manette": "{0} si è impegnato ad usare solo carte di seme {1} in questo turno.",
"UnionPacific": "{0} ha giocato Union Pacific e ha pescato 4 carte",
"use_special": "{0} ha usato l'abilità speciale del suo personaggio ({1})",
"gold_rush_pay_discard": "{0} ha fatto scartare {2} a {1}."
},
"foc": {
"leggedelwest": "Ed è obbligato a usarla nel suo turno, se possibile"
@ -560,6 +575,101 @@
"Per Un Pugno Di Carte": {
"name": "Per Un Pugno Di Carte",
"desc": "All'inizio del proprio turno, il giocatore subisce tanti bang quante carte ha in mano"
},
"Pepita": {
"name": "Pepita D'oro"
},
"Bicchierino": {
"name": "Bicchierino",
"desc": "Un giocatore a scelta recupera una vita (anche se stessi)"
},
"Bottiglia": {
"name": "Bottiglia",
"desc": "Usa questa carta come Panico!, Birra o Bang!"
},
"Complice": {
"name": "Complice",
"desc": "Usa questa carta come Emporio, Duello o Cat Balou"
},
"Corsa All Oro_gr": {
"name": "Corsa All'Oro",
"desc": "Il tuo turno termina subito. Recuperi tutti i tuoi punti vita, poi gioca un altro turno"
},
"Rum": {
"name": "Rum",
"desc": "Estrai 4 carte dal mazzo, guadagni 1 vita per ogni seme diverso"
},
"Union Pacific": {
"name": "Union Pacific",
"desc": "Pesca 4 carte dal mazzo"
},
"Calumet": {
"name": "Calumet",
"desc": "Le carte di quadri giocate dagli altri giocatori non hanno effetto su di te"
},
"Cinturone": {
"name": "Cinturone",
"desc": "Alla fine del tuo turno puoi tenere in mano fino a 8 carte"
},
"Ferro di Cavallo": {
"name": "Ferro di Cavallo",
"desc": "Ogni volta che estrai scegli la migliore tra due carte"
},
"Piccone": {
"name": "Piccone",
"desc": "Pesca una carta in più nella tua fase 1"
},
"Ricercato": {
"name": "Ricercato",
"desc": "Scegli un giocatore qualsiasi, chi elimina quel giocatore pesca 2 carte in più e pesca una pepita"
},
"Setaccio": {
"name": "Setaccio",
"desc": "Fino a due volte in un turno puoi pagare una pepita per pescare una carta dal mazzo (Clicca sulla carta per usare l'abilità)"
},
"Stivali": {
"name": "Stivali",
"desc": "Ogni volta che perdi un punto vita peschi una carta dal mazzo"
},
"Talismano": {
"name": "Talismano",
"desc": "Ogni volta che perdi un punto vita prendi una pepita"
},
"Zaino": {
"name": "Zaino",
"desc": "Paga 2 pepite per recuperare un punto vita"
},
"Don Bell": {
"name": "Don Bell",
"desc": "Alla fine del suo turno estrae, se esce Cuori ❤️ o Quadri ♦️ gioca un altro turno"
},
"Dutch Will": {
"name": "Dutch Will",
"desc": "Pesca 2 carte, ne scarta 1 e pesca una pepita."
},
"Jacky Murieta": {
"name": "Jacky Murieta",
"desc": "Durante il suo turno può pagare 2 pepite per sparare un bang, può farlo più volte per turno"
},
"Josh McCloud": {
"name": "Josh McCloud",
"desc": "Può pescare la prima carta del mazzetto pagando solo due pepite"
},
"Madam Yto": {
"name": "Madam Yto",
"desc": "Ogni volta che viene giocata una Birra pesca 1 carta dal mazzo"
},
"Pretty Luzena": {
"name": "Pretty Luzena",
"desc": "Una volta per turno può pagare una carta del mazzetto a costo diminuito di 1"
},
"Raddie Snake": {
"name": "Raddie Snake",
"desc": "Durante il suo turno, può pagare una pepita per pescare 1 carta dal mazzo (fino a due volte per turno)"
},
"Simeon Picos": {
"name": "Simeon Picos",
"desc": "Ogni volta che viene ferito, prende una pepita"
}
},
"help": {
@ -603,9 +713,14 @@
"renegade": "Rinnegato",
"vice": "Vice",
"gotoallcharacters": "Visualizza tutti i personaggi",
"gotocards": "Visualizza le carte",
"gotohighnoon": "Visualizza le carte High Noon",
"gotofoc": "Visualizza le carte Fistful Of Cards",
"gotogoldrush": "Visualizza le carte Gold Rush",
"allcharacters": "Tutti i personaggi",
"highnooncards": "Carte Evento High Noon",
"foccards": "Carte Evento Fistful of Cards"
"foccards": "Carte Evento Fistful of Cards",
"goldrushcards": "Carte Gold Rush"
},
"theme": {
"sepia": "Seppia",

File diff suppressed because it is too large Load Diff