13 Commits

Author SHA1 Message Date
Jakub Senderák 2281e030f6 ui enhancements 2021-04-18 17:40:38 +02:00
Jakub Senderák 889f787f29 shuffler added as explicit dependency 2021-04-18 17:40:38 +02:00
Jakub Senderák 618e632ce4 sort_card_list moved to utils and refactored 2021-04-18 17:40:38 +02:00
Jakub Senderák 834e03c172 main module name change 2021-04-18 17:40:38 +02:00
Jakub Senderák 40b18cbf41 previous stash added to display 2021-04-18 17:40:38 +02:00
Jakub Senderák 71c9e33b85 Bridzik.get_previous_stash added 2021-04-18 17:40:38 +02:00
Jakub Senderák 6ebff98db7 guesses display order fix 2021-04-18 17:40:38 +02:00
Jakub Senderák f4fd27a5c1 added points summary 2021-04-18 17:40:38 +02:00
Jakub Senderák aa7b3c1a02 requirements 2021-04-18 17:40:38 +02:00
Jakub Senderák d12f5c093c ui tweaks, admin form 2021-04-18 17:40:37 +02:00
Jakub Senderák d657dedf5f first working prototype 2021-04-18 17:40:37 +02:00
Jakub Senderák a43a7c4881 added config - form csrf tag 2021-04-18 17:40:37 +02:00
Jakub Senderák e851da47ce Flask api init 2021-04-18 17:40:37 +02:00
13 changed files with 476 additions and 6 deletions
+23
View File
@@ -0,0 +1,23 @@
from flask import Flask
import os
import logging
from logging.handlers import RotatingFileHandler
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
if not app.debug:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/bridzik_api.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Bridzik_API startup')
from api import routes
+22
View File
@@ -0,0 +1,22 @@
from flask_wtf import FlaskForm
from wtforms import SubmitField, IntegerField, RadioField, StringField
from wtforms.validators import DataRequired, NumberRange
class GuessForm(FlaskForm):
guess = IntegerField('Tip', validators=[DataRequired(message='Zadaj tip'), NumberRange(min=0, max=8)])
submit = SubmitField('Zadaj tip')
def __init__(self, max_guess: int = 8, *args, **kwargs):
super(GuessForm, self).__init__(*args, **kwargs)
self.max_guess = max_guess
class PlayForm(FlaskForm):
card = RadioField('Vyber kartu', validators=[DataRequired(message='Musíš vybrať kartu')])
submit = SubmitField('Zahraj')
class AdminForm(FlaskForm):
player0 = StringField('0', validators=[DataRequired()])
player1 = StringField('1', validators=[DataRequired()])
player2 = StringField('2', validators=[DataRequired()])
player3 = StringField('3', validators=[DataRequired()])
submit = SubmitField()
+87
View File
@@ -0,0 +1,87 @@
from api import app
from bridzik import Bridzik, Card, Card_colors, Card_values, BridzikException
import json
from flask import render_template, url_for, flash, redirect
from api.forms import GuessForm, PlayForm, AdminForm
from api.utils import get_points_sums, sort_card_list
b = Bridzik()
players = [
'Jakub',
'Timo',
'Katka',
'Ondrej'
]
@app.route('/bridzik_api/get_status/<id>')
def get_status(id: int):
return json.dumps(b.get_status(int(id)), cls=Card.JSONEncoder)
@app.route('/bridzik/<player>/status')
def status(player):
player = int(player)
game_status = b.get_status(player)
action = None
form = None
player_cards = sort_card_list(b.series[-1].get_last_round().player_cards[player])
game_status['player_cards'] = [str(c) for c in player_cards]
points_sums = get_points_sums(game_status['standings'])
if b.is_completed() or b.series[-1].get_last_round().get_active_player() != player:
pass
elif not b.series[-1].get_last_round().is_guessing_completed():
form = GuessForm(max_guess= 8 - b.series[-1].get_last_round().round_number)
action = 'guess'
else:
form = PlayForm()
form.card.choices = [(str(c), str(c)) for c in player_cards]
action = 'play'
return render_template(
'status.html', status=game_status, player=player, action=action,
form=form, players=players, points_sums=points_sums
)
@app.route('/bridzik/<player>/guess', methods=['POST'])
def guess(player):
player = int(player)
form = GuessForm()
try:
b.add_player_guess(player, int(form.guess.data))
except BridzikException:
flash('Nie je možné zadať tip.')
return redirect(url_for('status', player=player))
@app.route('/bridzik/<player>/play_card', methods=['POST'])
def play_card(player):
player = int(player)
player_cards = b.series[-1].get_last_round().player_cards[player]
form = PlayForm()
form.card.choices = [(str(c), str(c)) for c in player_cards]
color, value = form.card.data.split('_')
try:
card = Card(Card_colors[color], Card_values[value])
except KeyError:
flash('Chyba. Opakuj pokus znovu.')
try:
b.play_card(player, card)
except BridzikException:
flash('Nie je možné zahrať kartu.')
return redirect(url_for('status', player=player))
@app.route('/bridzik/admin', methods=['GET', 'POST'])
def admin():
form = AdminForm()
if form.validate_on_submit():
players[0] = form.player0.data
players[1] = form.player1.data
players[2] = form.player2.data
players[3] = form.player3.data
return redirect(url_for('admin'))
else:
form.player0.data = players[0]
form.player1.data = players[1]
form.player2.data = players[2]
form.player3.data = players[3]
return render_template('admin.html', form=form)
+51
View File
@@ -0,0 +1,51 @@
#header {
display: flex;
}
#header>div {
padding: 10px;
border: 2px solid grey;
}
.state_row_header {
white-space: nowrap;
text-align: right;
}
.points {
text-align: center;
}
#wrapper {
display: grid;
grid-template-columns: 1fr 250px;
grid-template-rows: 1fr;
grid-gap: 10px;
}
.card_display {
width: 82px;
height: 150px;
}
#points_summary_row {
border-top: 4px double black;
}
table {
border-collapse: collapse;
}
.active_player_highlight {
background: grey;
color: white;
}
.player_name_highlight {
background: grey;
color: white;
}
.standings_round_final_row {
border-bottom: 1px solid black;
}
+11
View File
@@ -0,0 +1,11 @@
<form action="{{ url_for('guess', player=player) }}" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.guess.label }}:
{{ form.guess(size=15, autocomplete="off") }}
{% for error in form.guess.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{{ form.submit() }}
</p>
</form>
+39
View File
@@ -0,0 +1,39 @@
<html>
<head>
<title>Bridžik admin</title>
</head>
<body>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.player0.label }}:
{{ form.player0(size=15) }}
{% for error in form.player0.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.player1.label }}:
{{ form.player1(size=15) }}
{% for error in form.player1.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.player2.label }}:
{{ form.player2(size=15) }}
{% for error in form.player2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.player3.label }}:
{{ form.player3(size=15) }}
{% for error in form.player3.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
</body>
</html>
+176
View File
@@ -0,0 +1,176 @@
<html>
<head>
<title>Bridžik</title>
<link href="/static/style.css" rel="stylesheet">
{% if not action%}
<meta http-equiv="refresh" content="2">
{% endif %}
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
<hr>
{% endif %}
{% endwith %}
{% if 'active_player' not in status %}
<h1>Hra sa skončila.</h1>
{% else %}
<div id="wrapper">
<div id="table">
<div id="header">
<div {% if player == status['active_player'] %}class="active_player_highlight"{% endif %}>
<p id="active_player" >Na ťahu je: {{ players[status['active_player']] }}</p>
</div>
<div id="active_round_guesses">
<p></p>
<table style="width: auto;">
<thead>
<th></th>
<th class="state_column_header">{{ players[0] }}</th>
<th class="state_column_header">{{ players[1] }}</th>
<th class="state_column_header">{{ players[2] }}</th>
<th class="state_column_header">{{ players[3] }}</th>
</thead>
<tbody>
<tr>
<th class="state_row_header">Tipy v tomto kole:</th>
{% for player in range(4) %}
<td class="points">{{ status['active_round_guesses'][player] }}</td>
{% endfor %}
</tr>
{% if status['active_round_stashes'] %}
<tr>
<th class="state_row_header">Kôpky v tomto kole:</th>
{% for player in status['active_round_stashes'] %}
<td class="points">{{ player }}</td>
{% endfor %}
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<hr>
{% if action == 'guess' %}
<h1>Zadaj tip:</h1>
{% include "_guess_form.html" %}
{% endif %}
{% if status['active_stash'] %}
<h1>Aktuálna kôpka:</h1>
<table style="width: auto;">
{% for player in range(status['active_stash']['first_player'], status['active_stash']['first_player'] + 4) %}
<th>{{ players[player % 4] }}</th>
{% endfor %}
<tr>
{% for player in range(status['active_stash']['first_player'], status['active_stash']['first_player'] + 4) %}
<td class="card_display">
{% if status['active_stash']['cards'][player % 4] %}
<img src="/static/cards/{{ status['active_stash']['cards'][player % 4] }}.png" width="80" height="auto">
{% endif %}
</td>
{% endfor %}
</tr>
</table>
{% endif %}
<hr>
{% if action != 'play' %}
<h1>Moje karty:</h1>
<table id="player_hand">
{% for card in status['player_cards'] %}
<td>
<img src="/static/cards/{{ card }}.png" width="80" height="auto">
</td>
{% endfor %}
</table>
{% else %}
<h1>{{ form.card.label }}:</h1>
<form action="{{ url_for('play_card', player=player) }}" method="post">
{{ form.hidden_tag() }}
<p>
<table style="width: auto">
{% for card in status['player_cards'] %}
<td>
<img src="/static/cards/{{ card }}.png" width="80" height="auto">
</td>
{% endfor %}
<tr>
{% for card in status['player_cards'] %}
<td>
<input id="card-{{ loop.index }}" name="card" type="radio" value="{{ card }}">
</td>
{% endfor %}
</tr>
</table>
{% for error in form.card.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
{{ form.submit() }}
</p>
</form>
{% endif %}
{% endif %}
{% if status['previous_stash'] %}
<h1>Predchádzajúca kôpka:</h1>
<table style="width: auto;">
{% for player in range(status['previous_stash']['first_player'], status['previous_stash']['first_player'] + 4) %}
<th>{{ players[player % 4] }}</th>
{% endfor %}
<tr>
{% for player in range(status['previous_stash']['first_player'], status['previous_stash']['first_player'] + 4) %}
<td class="card_display">
{% if status['previous_stash']['cards'][player % 4] %}
<img src="/static/cards/{{ status['previous_stash']['cards'][player % 4] }}.png" width="80" height="auto">
{% endif %}
</td>
{% endfor %}
</tr>
</table>
{% endif %}
</div>
<div id="standings">
<h1>Výsledky</h1>
<table style="width: auto;">
<tr>
<th>{{ players[0] }}</th>
<th>{{ players[1] }}</th>
<th>{{ players[2] }}</th>
<th>{{ players[3] }}</th>
</tr>
{% for series in status['standings'] %}
{% if series %}
{% for round in series %}
{% if round %}
<tr {% if loop.index == 7 %}class="standings_round_final_row"{% endif %} >
{% for player in round %}
<td class="points">{{ player }}</td>
{% endfor %}
</tr>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<tr id="points_summary_row">
{% for player in points_sums %}
<td class="points">{{ player }}</td>
{% endfor %}
</tr>
</table>
</div>
</div>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
from bridzik import Card_colors
def get_points_sums(standings: []):
sums = [0] * 4
for series in standings:
for round in series:
for player, points in enumerate(round):
sums[player] += points
return sums
def sort_card_list(input_card_set: []) -> []:
color_paritions = [
[c for c in input_card_set if c.color == Card_colors['HEARTS']],
[c for c in input_card_set if c.color == Card_colors['LEAVES']],
[c for c in input_card_set if c.color == Card_colors['ACORNS']],
[c for c in input_card_set if c.color == Card_colors['BELLS']]
]
output_list = []
for color_list in color_paritions:
color_list.sort(key=lambda a : a.value)
output_list.extend(color_list)
return output_list
+22 -5
View File
@@ -83,15 +83,16 @@ cards = [Card(color, value) for value in Card_values for color in Card_colors]
class Bridzik():
def __init__(self):
self.series = [Series(0, 0)]
def __init__(self, shuffler = shuffle):
self.shuffler = shuffler
self.series = [Series(0, 0, shuffler=self.shuffler)]
def play_card(self, player: int, card: Card):
if self.is_completed():
raise BridzikException('Hra je ukoncena.')
self.series[-1].play_card(player, card)
if self.series[-1].is_completed() and not self.is_completed():
self.series.append(Series(len(self.series)))
self.series.append(Series(len(self.series), shuffler=self.shuffler))
def add_player_guess(self, player: int, guess):
if self.is_completed():
@@ -111,17 +112,33 @@ class Bridzik():
'first_player': last_series.get_last_round().get_last_stash().first_player,
'cards': last_series.get_last_round().get_last_stash().get_cards()
}
if self.get_previous_stash():
status['previous_stash'] = {
'first_player': self.get_previous_stash().first_player,
'cards': self.get_previous_stash().get_cards()
}
status['standings'] = [s.get_standings() for s in self.series]
return status
def is_completed(self):
return len(self.series) == 4 and self.series[-1].is_completed()
def get_previous_stash(self):
if len(self.series[-1].get_last_round().stashes) > 1:
return self.series[-1].get_last_round().stashes[-2]
elif len(self.series[-1].rounds) > 1:
return self.series[-1].rounds[-2].get_last_stash()
elif len(self.series) > 1:
return self.series[-2].get_last_round().get_last_stash()
return None
class Series():
def __init__(self, series_number: int, first_player: int = None):
def __init__(self, series_number: int, first_player: int = None, shuffler = shuffle):
self.series_number = series_number
self.first_player = first_player if first_player else series_number
self.rounds = []
self.shuffler = shuffler
self.start_new_round()
def add_player_guess(self, player: int, guess: int):
@@ -164,7 +181,7 @@ class Series():
if round_number != 0 and not self.get_last_round().is_completed():
raise BridzikException('Predchadzajuce kolo nie je ukoncene')
self.rounds.append(
Round(round_number, (self.first_player + round_number) % 4)
Round(round_number, (self.first_player + round_number) % 4, shuffler=self.shuffler)
)
+2
View File
@@ -0,0 +1,2 @@
class Config(object):
SECRET_KEY = 'nbusr123'
+18
View File
@@ -0,0 +1,18 @@
astroid==2.3.3
click==7.1.1
colorama==0.4.3
Flask==1.1.1
Flask-WTF==0.14.3
isort==4.3.21
itsdangerous==1.1.0
Jinja2==2.11.1
lazy-object-proxy==1.4.3
MarkupSafe==1.1.1
mccabe==0.6.1
pylint==2.4.4
python-dotenv==0.12.0
six==1.14.0
typed-ast==1.4.1
Werkzeug==1.0.0
wrapt==1.11.2
WTForms==2.2.1
+1
View File
@@ -0,0 +1 @@
from api import app
+1 -1
View File
@@ -1,5 +1,5 @@
import unittest
from game import Stash, cards, Card_colors, Card_values,\
from bridzik import Stash, cards, Card_colors, Card_values,\
RuleException, BridzikException, Card, Round
class StashCase(unittest.TestCase):