initial commit

This commit is contained in:
2018-06-17 20:28:44 +02:00
commit 7300faff00
14 changed files with 11091 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
# Created by https://www.gitignore.io/api/python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule.*
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.idea/
# End of https://www.gitignore.io/api/python
+100
View File
@@ -0,0 +1,100 @@
from flask import request, url_for
from flask_api import FlaskAPI, status, exceptions
from typing import Union, Optional
from random import choices
from game import CamelGame
app = FlaskAPI(__name__)
games = {}
allowed_id_chars = [chr(c) for c in range(0x41, 0x5B)] + [chr(c) for c in range(0x61, 0x7B)]
allowed_id_chars.remove('l')
allowed_id_chars.remove('I')
def _generate_id():
while True:
new_id = ''.join(choices(allowed_id_chars, k=6))
if new_id not in games:
return new_id
def _get_game_obj(game_id: str) -> Optional[CamelGame]:
return games.get(game_id, None)
@app.route('/game/<game_id>', methods=['GET'])
def get_game(game_id: str):
game = _get_game_obj(game_id)
if game is None:
return '', status.HTTP_404_NOT_FOUND
return {
'status': game.state,
'amount_left': game.side_length,
'winning_ace': game.get_winner(),
'left_cards': game.get_open_side_cards(),
'aces': game.ace_position,
'drinks': game.get_drinks(),
'bets': game.bets,
}
@app.route('/game/<game_id>/start', methods=['GET'])
def start_game(game_id: str):
game = _get_game_obj(game_id)
if game is None:
return '', status.HTTP_404_NOT_FOUND
return {
'started': game.start_game()
}
@app.route('/game/<game_id>/bet', methods=['POST'])
def bet_on_game(game_id: str):
game = _get_game_obj(game_id)
if game is None:
return '', status.HTTP_404_NOT_FOUND
user = request.data['USER']
suit = request.data['SUIT']
amount = int(request.data['AMOUNT'])
if game.bet(user, suit, amount):
return {'ok': True}
return {'ok': False}
@app.route('/game/<game_id>/round', methods=['GET'])
def do_round(game_id: str):
game = _get_game_obj(game_id)
if game is None:
return '', status.HTTP_404_NOT_FOUND
card = game.do_round()
if card is None:
return "game probably already done", status.HTTP_400_BAD_REQUEST
return {
'card': card
}
@app.route('/game/', methods=['GET'])
def start_new_game():
game = CamelGame()
game_id = _generate_id()
games[game_id] = game
game.init_game()
return {
'initialized': True,
'url': url_for('get_game', game_id=game_id)
}
if __name__ == '__main__':
app.run()
Executable
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
from game import CamelGame
game = CamelGame()
if __name__ != '__main__':
print("Not a module!")
exit(1)
def print_game():
print(f'status: {game.state}')
print(', '.join(game.get_open_side_cards()))
for ace, pos in game.ace_position.items():
print(" " * 4 * pos + ace)
def print_drinks():
drinks = game.get_drinks()
if drinks is None:
print("Not done yet!")
return
for user, amount in drinks.items():
print(f"{user:16}{amount}")
while True:
inp = input("command: ")
if inp == 'r':
game.do_round()
print_game()
elif inp == 'i':
game.init_game()
elif inp.startswith('b '):
_, u, s, a = inp.split()
a = int(a)
game.bet(u, s, a)
elif inp == 's':
game.start_game()
elif inp == 'd':
print_drinks()
else:
print("Unknown " + inp)
+131
View File
@@ -0,0 +1,131 @@
from random import shuffle
from typing import List, Dict, Optional
from collections import deque
nums = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
suits = ['H', 'S', 'D', 'C']
def get_deck(add_jokers: bool=False) -> deque:
if add_jokers:
jokers = ['J', 'J']
else:
jokers = []
return deque([n + s for n in nums for s in suits] + jokers)
def create_new_random_card_deck(add_jokers: bool=False) -> deque:
deck = get_deck(add_jokers)
shuffle(deck)
return deck
class CamelGame:
deck: deque
ace_position: dict
side_list: list
side_length = 8
side_open = 0
state: str
bets: List[Dict]
def __init__(self):
self.deck = create_new_random_card_deck(False)
for ace in ['AH', 'AS', 'AD', 'AC']:
self.deck.remove(ace) # remove the aces
self.ace_position = {
'AH': 0,
'AS': 0,
'AD': 0,
'AC': 0,
}
self.side_list = []
self.state = None
self.bets = []
def init_game(self):
for _ in range(self.side_length):
self.side_list.append(self.deck.pop())
self.state = 'initialized'
def do_round(self) -> Optional[str]:
if self.state != 'active':
print("Game is not stated yet or already done")
return None
card = self.popcard()
if any(v == self.side_length for v in self.ace_position.values()):
self.state = 'done'
return card
def bet(self, user: str, suit: str, amount: int) -> bool:
if self.state != 'initialized':
return False
if any(b['user'] == user for b in self.bets):
return False
if suit not in suits:
return False
self.bets.append({'user': user, 'suit': suit, 'amount': amount})
return True
def start_game(self):
if self.state != 'initialized':
return False
self.state = 'active'
return True
def get_drinks(self) -> Optional[Dict]:
if self.state != 'done':
return None
result = {}
winner = self.get_winner()[-1]
drinks = sum(map(lambda b: b['amount'], filter(lambda b: b['suit'] == winner, self.bets)))
for bet in self.bets:
if bet['suit'] == winner:
mdrinks = 0
else:
mdrinks = drinks + bet['amount']
result[bet['user']] = mdrinks
return result
def get_winner(self) -> Optional[str]:
for card, pos in self.ace_position.items():
if pos == self.side_length:
return card
return None
def get_open_side_cards(self) -> list:
open_cards = self.side_list[:self.side_open]
return open_cards + ['XX'] * (self.side_length - self.side_open)
def popcard(self) -> str:
next_card = self.deck.pop()
move_card = 'A' + next_card[-1]
self.ace_position[move_card] += 1
self.check_and_open_side()
return move_card
def check_and_open_side(self):
if not all(v > self.side_open for v in self.ace_position.values()):
return
opened_card = self.side_list[self.side_open]
moved_ace = 'A' + opened_card[-1]
self.ace_position[moved_ace] = self.side_open
self.side_open += 1
+2
View File
@@ -0,0 +1,2 @@
Flask
Flask-API
+48
View File
@@ -0,0 +1,48 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
+26
View File
@@ -0,0 +1,26 @@
html, body {
width: 100%;
height: 100%; }
h1 {
font-size: xx-large;
font-weight: bolder; }
#topcards > .card {
display: inline-block; }
.card-ace {
position: relative;
-webkit-transition: left 0.25s;
-moz-transition: left 0.25s;
-ms-transition: left 0.25s;
-o-transition: left 0.25s;
transition: left 0.25s;
display: inline; }
.card {
height: 100px; }
.card svg {
height: 100px; }
/*# sourceMappingURL=style.css.map */
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAAA,UAAU;EACR,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;;AAEd,EAAE;EACA,SAAS,EAAE,QAAQ;EACnB,WAAW,EAAE,MAAM;;AAErB,iBAAiB;EACf,OAAO,EAAE,YAAY;;AAEvB,SAAS;EACP,QAAQ,EAAE,QAAQ;EAClB,kBAAkB,EAAE,UAAU;EAC9B,eAAe,EAAE,UAAU;EAC3B,cAAc,EAAE,UAAU;EAC1B,aAAa,EAAE,UAAU;EACzB,UAAU,EAAE,UAAU;EACtB,OAAO,EAAE,MAAM;;AAEjB,KAAK;EACH,MAAM,EAAE,KAAK;EACb,SAAG;IACD,MAAM,EAAE,KAAK",
"sources": ["style.sass"],
"names": [],
"file": "style.css"
}
+24
View File
@@ -0,0 +1,24 @@
html, body
width: 100%
height: 100%
h1
font-size: xx-large
font-weight: bolder
#topcards > .card
display: inline-block
.card-ace
position: relative
-webkit-transition: left 0.25s
-moz-transition: left 0.25s
-ms-transition: left 0.25s
-o-transition: left 0.25s
transition: left 0.25s
display: inline
.card
height: 100px
svg
height: 100px
+236
View File
@@ -0,0 +1,236 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Card Game</title>
<script src="js/jquery-3.3.1.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/style.css">
<script>
function cardToPlayCard(card) {
let pre, post;
if (card === 'XX') {
return "svg/svg-cards.svg#back";
}
switch (card[0]) {
case 'A':
pre = '1';
break;
case 'K':
pre = 'king';
break;
case 'Q':
pre = 'queen';
break;
case 'J':
pre = 'jack';
break;
default:
pre = card.substr(0, card.length - 1);
}
switch (card.substr(card.length - 1)) {
case 'H':
post = 'heart';
break;
case 'S':
post = 'spade';
break;
case 'D':
post = 'diamond';
break;
case 'C':
post = 'club';
break;
}
return `svg/svg-cards.svg#${pre}_${post}`
}
$(function () {
let btnNewGame = $("#newgame");
let btnStartGame = $("#startgame");
let btnDoRound = $("#doround");
let txtBetName = $("#bet-name");
let selBetSuit = $("#bet-suit");
let numBetAmount = $("#bet-amount");
let btnBet = $("#bet");
let aces = {
'AS': $("#card-ace-spades"),
'AH': $("#card-ace-hearts"),
'AD': $("#card-ace-diamonds"),
'AC': $("#card-ace-clubs"),
};
let templateCard = $("#template-card").html();
let topcards = $("#topcards");
let gameBase = null;
function buildTopCards(cards) {
let html;
topcards.empty();
cards.forEach(function (card, ind, array) {
console.log(card, ind, array);
html = templateCard;
html = html.replace("{href}", cardToPlayCard(card));
topcards.append($(html));
});
}
function updateGame() {
$.ajax({
'url': gameBase
}).done(function (data) {
console.log(data);
buildTopCards(data['left_cards']);
$.each(data['aces'], function (key, value) {
aces[key].css({'left': value * 150});
});
// todo check if game is done
}).fail(function (data) {
alert(data);
console.log(data);
})
}
btnNewGame.click(function () {
$.ajax({
'url': '/game/'
}).done(function (data) {
gameBase = data['url'];
updateGame();
}).fail(function (data) {
alert(data);
console.log(data);
});
});
btnStartGame.click(function () {
$.ajax({
'url': gameBase + '/start'
}).done(function (data) {
updateGame();
}).fail(function (data) {
alert(data);
console.log(data);
});
});
btnDoRound.click(function () {
$.ajax({
'url': gameBase + '/round'
}).done(function (data) {
console.log(data); // todo display card
updateGame();
}).fail(function (data) {
alert(data);
console.log(data);
});
});
btnBet.click(function () {
let user, suit, amount;
user = txtBetName.val();
suit = selBetSuit.val();
amount = numBetAmount.val();
$.ajax({
'url': gameBase + '/bet',
'method': 'POST',
'data': {
'USER': user,
'SUIT': suit,
'AMOUNT': amount,
}
}).done(function (data) {
console.log(data);
}).fail(function (data) {
alert(data);
});
txtBetName.val("");
numBetAmount.val("")
});
});
</script>
</head>
<body>
<nav id="header">
<h1>Kamelen Race!</h1>
</nav>
<div id="game">
<div id="control">
<div>
<button id="newgame">New Game</button>
<button id="startgame">Start Game</button>
<button id="doround">Do Round</button>
</div>
<div>
<h1>Bet</h1>
<input type="text" id="bet-name" placeholder="Name">
<label for="bet-suit">Suit</label>
<select id="bet-suit">
<option value="S">Spades</option>
<option value="H">Hearts</option>
<option value="D">Diamonds</option>
<option value="C">Clubs</option>
</select>
<input type="number" placeholder="Amount" id="bet-amount">
<button id="bet">Bet</button>
</div>
</div>
<div id="topcards">
</div>
<div id="track">
<div class="tracklane">
<div id="card-ace-spades" class="card card-ace">
<svg width="150" viewBox="0 0 262 180">
<use href="svg/svg-cards.svg#1_spade" transform="rotate(90) translate(0,-260)"></use>
</svg>
</div>
</div>
<div class="tracklane">
<div id="card-ace-hearts" class="card card-ace">
<svg width="150" viewBox="0 0 262 180">
<use href="svg/svg-cards.svg#1_heart" transform="rotate(90) translate(0,-260)"></use>
</svg>
</div>
</div>
<div class="tracklane">
<div id="card-ace-diamonds" class="card card-ace">
<svg width="150" viewBox="0 0 262 180">
<use href="svg/svg-cards.svg#1_diamond" transform="rotate(90) translate(0,-260)"></use>
</svg>
</div>
</div>
<div class="tracklane">
<div id="card-ace-clubs" class="card card-ace">
<svg width="150" viewBox="0 0 262 180">
<use href="svg/svg-cards.svg#1_club" transform="rotate(90) translate(0,-260)"></use>
</svg>
</div>
</div>
</div>
</div>
<script language="html/template" id="template-card">
<div class="card">
<svg width="150" viewBox="0 0 262 180">
<use href="{href}" transform="rotate(90) translate(0,-260)"></use>
</svg>
</div>
</script>
</body>
</html>
+10364
View File
File diff suppressed because it is too large Load Diff
+2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 941 KiB