Working prototype
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
temp/
|
||||||
|
.vscode/
|
||||||
|
__pycache__/
|
||||||
|
venv/
|
62
game.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
from faker import Faker
|
||||||
|
from flask import Flask, render_template, redirect, url_for
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
from models import Game
|
||||||
|
|
||||||
|
FAKER = Faker()
|
||||||
|
|
||||||
|
CURRENT_GAMES = {}
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def root():
|
||||||
|
return "I am root"
|
||||||
|
|
||||||
|
@app.route("/new_game")
|
||||||
|
def new_game_invalid():
|
||||||
|
return "Try /new_game/(number of players)"
|
||||||
|
|
||||||
|
@app.route("/new_game/<int:num_players>")
|
||||||
|
def new_game(num_players):
|
||||||
|
game_id = FAKER.bs().replace(" ", "-").lower()
|
||||||
|
players = [FAKER.color_name().lower() for _ in range(int(num_players))]
|
||||||
|
CURRENT_GAMES[game_id] = Game(game_id, players)
|
||||||
|
return game_id
|
||||||
|
|
||||||
|
@app.route("/games/<gameid>")
|
||||||
|
def game_detail(gameid):
|
||||||
|
return render_template("game.html", game=CURRENT_GAMES[gameid])
|
||||||
|
|
||||||
|
@app.route("/games/<gameid>/<playerid>")
|
||||||
|
def playerview(gameid, playerid):
|
||||||
|
return render_template("player.html", game=CURRENT_GAMES[gameid], playerid=playerid)
|
||||||
|
|
||||||
|
@app.route("/games/<gameid>/<playerid>/pickup")
|
||||||
|
def pickup(gameid, playerid):
|
||||||
|
g = CURRENT_GAMES[gameid]
|
||||||
|
g.pickup(playerid)
|
||||||
|
return redirect(url_for('playerview', gameid=gameid, playerid=playerid))
|
||||||
|
|
||||||
|
@app.route("/games/<gameid>/<playerid>/undo")
|
||||||
|
def undo(gameid, playerid):
|
||||||
|
g = CURRENT_GAMES[gameid]
|
||||||
|
g.undo(playerid)
|
||||||
|
return redirect(url_for('playerview', gameid=gameid, playerid=playerid))
|
||||||
|
|
||||||
|
@app.route("/games/<gameid>/<playerid>/play/<card>")
|
||||||
|
def play(gameid, playerid, card):
|
||||||
|
g = CURRENT_GAMES[gameid]
|
||||||
|
g.play(playerid, card)
|
||||||
|
return redirect(url_for('playerview', gameid=gameid, playerid=playerid))
|
||||||
|
|
||||||
|
@app.route("/games/<gameid>/<playerid>/give/<victim>")
|
||||||
|
def give(gameid, playerid, victim):
|
||||||
|
g = CURRENT_GAMES[gameid]
|
||||||
|
g.pickup(victim)
|
||||||
|
return redirect(url_for('playerview', gameid=gameid, playerid=playerid))
|
||||||
|
|
||||||
|
@app.route("/games/<gameid>/<playerid>/shuffle_deck")
|
||||||
|
def shuffle_deck(gameid, playerid):
|
||||||
|
g = CURRENT_GAMES[gameid]
|
||||||
|
g.shuffle_deck()
|
||||||
|
return redirect(url_for('playerview', gameid=gameid, playerid=playerid))
|
48
models.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import random
|
||||||
|
|
||||||
|
ALL_CARDS = [ number + suit for number in list("A23456789JQK") + ["10"] for suit in "SCHD"]
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self, game_id, players):
|
||||||
|
self.game_id = game_id
|
||||||
|
self.players = players
|
||||||
|
self.cards = {
|
||||||
|
"pickup_pile": ALL_CARDS.copy(),
|
||||||
|
"played_cards": []
|
||||||
|
}
|
||||||
|
random.shuffle(self.cards["pickup_pile"])
|
||||||
|
for player in players:
|
||||||
|
self.cards[player] = []
|
||||||
|
|
||||||
|
def play(self, player, card):
|
||||||
|
self.cards[player].remove(card)
|
||||||
|
self.cards["played_cards"].append(card)
|
||||||
|
|
||||||
|
def undo(self, player):
|
||||||
|
card = self.cards["played_cards"].pop(-1)
|
||||||
|
self.cards[player].append(card)
|
||||||
|
|
||||||
|
def pickup(self, player):
|
||||||
|
card = self.cards["pickup_pile"].pop(-1)
|
||||||
|
self.cards[player].append(card)
|
||||||
|
|
||||||
|
def shuffle_deck(self):
|
||||||
|
cards = self.cards["played_cards"].copy()
|
||||||
|
self.cards["played_cards"] = []
|
||||||
|
random.shuffle(cards)
|
||||||
|
self.cards["pickup_pile"] = cards
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_card(self):
|
||||||
|
if self.cards["played_cards"] == []:
|
||||||
|
return "XX"
|
||||||
|
return self.cards["played_cards"][-1]
|
||||||
|
|
||||||
|
def cards_for(self, player):
|
||||||
|
return self.cards[player]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Game {self.game_id} " + str(self.cards)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Game {self.game_id} " + str(self.cards)
|
BIN
static/images/cards/10C.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/images/cards/10D.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
static/images/cards/10H.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cards/10S.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
static/images/cards/2C.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
static/images/cards/2D.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
static/images/cards/2H.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
static/images/cards/2S.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/cards/3C.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
static/images/cards/3D.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
static/images/cards/3H.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
static/images/cards/3S.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
static/images/cards/4C.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
static/images/cards/4D.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
static/images/cards/4H.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
static/images/cards/4S.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
static/images/cards/5C.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
static/images/cards/5D.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
static/images/cards/5H.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
static/images/cards/5S.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
static/images/cards/6C.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
static/images/cards/6D.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
static/images/cards/6H.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
static/images/cards/6S.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
static/images/cards/7C.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
static/images/cards/7D.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
static/images/cards/7H.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
static/images/cards/7S.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
static/images/cards/8C.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
static/images/cards/8D.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cards/8H.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cards/8S.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cards/9C.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
static/images/cards/9D.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
static/images/cards/9H.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cards/9S.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/cards/AC.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/cards/AD.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
static/images/cards/AH.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/cards/AS.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
static/images/cards/JC.png
Normal file
After Width: | Height: | Size: 174 KiB |
BIN
static/images/cards/JD.png
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
static/images/cards/JH.png
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
static/images/cards/JS.png
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
static/images/cards/KC.png
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
static/images/cards/KD.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
static/images/cards/KH.png
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
static/images/cards/KS.png
Normal file
After Width: | Height: | Size: 176 KiB |
BIN
static/images/cards/QC.png
Normal file
After Width: | Height: | Size: 179 KiB |
BIN
static/images/cards/QD.png
Normal file
After Width: | Height: | Size: 183 KiB |
BIN
static/images/cards/QH.png
Normal file
After Width: | Height: | Size: 190 KiB |
BIN
static/images/cards/QS.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
static/images/cards/XX.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
static/images/cards/back.png
Normal file
After Width: | Height: | Size: 24 KiB |
14
templates/game.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% from "partials.html" import render_card, deck %}
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>{{game.game_id | e}}</h1>
|
||||||
|
<h2>Players:</h2>
|
||||||
|
<ul>
|
||||||
|
{% for playerid in game.players %}
|
||||||
|
<li>{{ playerid }}: {{ game.cards[playerid] | length}} cards</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<h2>Deck:</h2>
|
||||||
|
{{ deck(game, "") }}
|
||||||
|
</body>
|
||||||
|
</html>
|
26
templates/partials.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% macro render_card(card, game, playerid, play=False, undo=False, pickup=False, shuffle_deck=False) -%}
|
||||||
|
{% if play %}
|
||||||
|
<a href={{ url_for("play", card=card, gameid=game.game_id, playerid=playerid) }}>{{_render_card(card)}} </a>
|
||||||
|
{% elif undo %}
|
||||||
|
<a href={{ url_for("undo", gameid=game.game_id, playerid=playerid) }}>{{_render_card(card)}} </a>
|
||||||
|
{% elif pickup %}
|
||||||
|
<a href={{ url_for("pickup", gameid=game.game_id, playerid=playerid) }}>{{_render_card(card)}} </a>
|
||||||
|
{% elif shuffle_deck %}
|
||||||
|
<a href={{ url_for("shuffle_deck", gameid=game.game_id, playerid=playerid) }}>{{_render_card(card)}} </a>
|
||||||
|
{% else %}
|
||||||
|
{{_render_card(card)}}
|
||||||
|
{% endif %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro _render_card(card) -%}
|
||||||
|
<img src={{ url_for('static', filename='images/cards/' + card + '.png') }} alt={{ card }} width=100/>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro deck(game, playerid="") -%}
|
||||||
|
{% if game.cards["pickup_pile"] == [] %}
|
||||||
|
{{ render_card("XX", game, playerid, shuffle_deck=(playerid != "")) }}
|
||||||
|
{% else %}
|
||||||
|
{{ render_card("back", game, playerid, pickup=(playerid != "")) }}
|
||||||
|
{% endif %}
|
||||||
|
{{ render_card(game.last_card, game, playerid, undo=(playerid != "" and game.cards["played_cards"] != [])) }}
|
||||||
|
{%- endmacro %}
|
18
templates/player.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% from "partials.html" import render_card, deck %}
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>{{playerid | e}}</h1>
|
||||||
|
<h2> Cards: </h2>
|
||||||
|
{% for card in game.cards_for(playerid) %}
|
||||||
|
{{ render_card(card, game, playerid, play=True) }}
|
||||||
|
{% endfor %}
|
||||||
|
<h2>Deck:</h2>
|
||||||
|
{{ deck(game, playerid) }}
|
||||||
|
<h2>Other players:</h2>
|
||||||
|
{% for other_playerid in game.players %}
|
||||||
|
{% if other_playerid != playerid %}
|
||||||
|
<li>{{ other_playerid }}: {{ game.cards[other_playerid] | length}} cards <a href={{ url_for("give", gameid=game.game_id, playerid=playerid, victim=other_playerid)}}>Give card</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|