Skip to content

Commit b71a75f

Browse files
committed
Save point: Just added existing game logic and data from persistent rock paper scissors demo.
1 parent 1005575 commit b71a75f

File tree

13 files changed

+405
-0
lines changed

13 files changed

+405
-0
lines changed

days/97-99-online-game-api/demo_app/client/requirements.txt

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import flask
2+
3+
app = flask.Flask(__name__)
4+
5+
6+
def main():
7+
app.run(debug=True)
8+
9+
10+
@app.route('/')
11+
def index():
12+
return "Hello world!!!"
13+
14+
15+
@app.route('/api/test', methods=['GET'])
16+
def api_test():
17+
data = {
18+
'name': 'Michael',
19+
'day': 97
20+
}
21+
return flask.jsonify(data)
22+
23+
24+
@app.errorhandler(404)
25+
def not_found(_):
26+
return "The page was not found."
27+
28+
29+
if __name__ == '__main__':
30+
main()

days/97-99-online-game-api/demo_app/web/data/battle-table.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Attacker,Rock,Gun,Lightning,Devil,Dragon,Water,Air,Paper,Sponge,Wolf,Tree,Human,Snake,Scissors,FireRock,draw,lose,lose,lose,lose,lose,lose,lose,win,win,win,win,win,win,winGun,win,draw,lose,lose,lose,lose,lose,lose,lose,win,win,win,win,win,winLightning,win,win,draw,lose,lose,lose,lose,lose,lose,lose,win,win,win,win,winDevil,win,win,win,draw,lose,lose,lose,lose,lose,lose,lose,win,win,win,winDragon,win,win,win,win,draw,lose,lose,lose,lose,lose,lose,lose,win,win,winWater,win,win,win,win,win,draw,lose,lose,lose,lose,lose,lose,lose,win,winAir,win,win,win,win,win,win,draw,lose,lose,lose,lose,lose,lose,lose,winPaper,win,win,win,win,win,win,win,draw,lose,lose,lose,lose,lose,lose,loseSponge,lose,win,win,win,win,win,win,win,draw,lose,lose,lose,lose,lose,loseWolf,lose,lose,win,win,win,win,win,win,win,draw,lose,lose,lose,lose,loseTree,lose,lose,lose,win,win,win,win,win,win,win,draw,win,win,win,winHuman,lose,lose,lose,lose,win,win,win,win,win,win,win,draw,win,win,winSnake,lose,lose,lose,lose,lose,win,win,win,win,win,win,win,draw,win,winScissors,lose,lose,lose,lose,lose,lose,win,win,win,win,win,win,win,draw,loseFire,lose,lose,lose,lose,lose,lose,lose,win,win,win,win,win,win,win,draw
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import os
2+
3+
4+
def get_db_path(base_file):
5+
base_folder = os.path.dirname(__file__)
6+
return os.path.join(base_folder, base_file)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# TODO: Game logic.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import csv
2+
from enum import Enum
3+
4+
from game_logic.models.roll import Roll
5+
from data import db_folder
6+
7+
__winner_lookup = {}
8+
9+
10+
class Decision(Enum):
11+
tie = 1
12+
win = 2
13+
lose = 3
14+
15+
def reversed(self):
16+
if self == Decision.win:
17+
return Decision.lose
18+
if self == Decision.lose:
19+
return Decision.win
20+
21+
return Decision.tie
22+
23+
def __str__(self):
24+
if self == Decision.win:
25+
return 'win'
26+
if self == Decision.lose:
27+
return 'lose'
28+
if self == Decision.tie:
29+
return 'tie'
30+
31+
return "UNKNOWN DECISION: {}".format(self)
32+
33+
34+
def decide(roll1: Roll, roll2: Roll) -> Decision:
35+
__build_decisions()
36+
37+
if roll1.name == roll2.name:
38+
return Decision.tie
39+
40+
roll1_wins = roll2.name in __winner_lookup[roll1.name]
41+
42+
if roll1_wins:
43+
return Decision.win
44+
else:
45+
return Decision.lose
46+
47+
48+
def __build_decisions():
49+
if __winner_lookup:
50+
return
51+
52+
file = db_folder.get_db_path('battle-table.csv')
53+
54+
with open(file) as fin:
55+
reader = csv.DictReader(fin)
56+
for row in reader:
57+
__build_roll(row)
58+
59+
60+
def __build_roll(row: dict):
61+
row = dict(row)
62+
name = row['Attacker']
63+
64+
del row['Attacker']
65+
del row[name]
66+
67+
__winner_lookup[name] = set()
68+
for k in row.keys():
69+
can_defeat = row[k].strip().lower() == 'win'
70+
if can_defeat:
71+
__winner_lookup[name].add(k)
72+
73+
74+
def all_roll_names():
75+
__build_decisions()
76+
return [k for k, v in __winner_lookup.items()]
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from collections import defaultdict
2+
from typing import List, Optional
3+
4+
# noinspection PyPackageRequirements
5+
from game_logic import session_factory, game_decider
6+
# noinspection PyPackageRequirements
7+
from game_logic.game_decider import Decision
8+
from game_logic.models.move import Move
9+
# noinspection PyPackageRequirements
10+
from game_logic.models.player import Player
11+
# noinspection PyPackageRequirements
12+
from game_logic.models.roll import Roll
13+
14+
15+
def get_game_history(game_id: str) -> List[Move]:
16+
session = session_factory.create_session()
17+
18+
query = session.query(Move) \
19+
.filter(Move.game_id == game_id) \
20+
.order_by(Move.roll_number) \
21+
.all()
22+
23+
moves = list(query)
24+
25+
session.close()
26+
27+
return moves
28+
29+
30+
def is_game_over(game_id: str) -> bool:
31+
history = get_game_history(game_id)
32+
return any([h.is_winning_play for h in history])
33+
34+
35+
def get_win_count(player: Player) -> int:
36+
session = session_factory.create_session()
37+
38+
wins = session.query(Move) \
39+
.filter(Move.player_id == player.id). \
40+
filter(Move.is_winning_play) \
41+
.count()
42+
43+
session.close()
44+
45+
return wins
46+
47+
48+
def find_player(name: str) -> Player:
49+
session = session_factory.create_session()
50+
51+
player = session.query(Player).filter(Player.name == name).first()
52+
session.close()
53+
54+
return player
55+
56+
57+
def create_player(name: str) -> Player:
58+
session = session_factory.create_session()
59+
60+
player = session.query(Player).filter(Player.name == name).first()
61+
if player:
62+
raise Exception("Player already exists")
63+
64+
player = Player()
65+
player.name = name
66+
session.add(player)
67+
session.commit()
68+
session.close()
69+
70+
player = session.query(Player).filter(Player.name == name).first()
71+
return player
72+
73+
74+
def all_players() -> List[Player]:
75+
session = session_factory.create_session()
76+
77+
players = list(session.query(Player).all())
78+
session.close()
79+
return players
80+
81+
82+
def record_roll(player, roll: 'Roll', game_id: str, is_winning_play: bool, roll_num: int):
83+
session = session_factory.create_session()
84+
85+
move = Move()
86+
move.player_id = player.id
87+
move.roll_id = roll.id
88+
move.game_id = game_id
89+
move.is_winning_play = is_winning_play
90+
move.roll_number = roll_num
91+
session.add(move)
92+
93+
session.commit()
94+
session.close()
95+
96+
97+
def all_rolls() -> List[Roll]:
98+
session = session_factory.create_session()
99+
100+
query = session.query(Roll).order_by(Roll.name).all()
101+
rolls = list(query)
102+
103+
session.close()
104+
105+
return rolls
106+
107+
108+
def init_rolls(rolls: List[str]):
109+
session = session_factory.create_session()
110+
roll_count = session.query(Roll).count()
111+
session.close()
112+
113+
if roll_count:
114+
return
115+
116+
for roll_name in rolls:
117+
create_roll(roll_name)
118+
119+
120+
def find_roll(name: str) -> Optional['Roll']:
121+
session = session_factory.create_session()
122+
123+
roll = session.query(Roll).filter(Roll.name == name).first()
124+
125+
session.close()
126+
return roll
127+
128+
129+
def create_roll(name: str) -> 'Roll':
130+
session = session_factory.create_session()
131+
132+
roll = Roll()
133+
roll.name = name
134+
session.add(roll)
135+
136+
session.commit()
137+
session.close()
138+
139+
roll = session.query(Roll).filter(Roll.id == roll.id).first()
140+
return roll
141+
142+
143+
def find_roll_by_id(roll_id):
144+
session = session_factory.create_session()
145+
roll = session.query(Roll).filter(Roll.id == roll_id).first()
146+
session.close()
147+
148+
return roll
149+
150+
151+
def find_player_by_id(player_id: int) -> Player:
152+
session = session_factory.create_session()
153+
player = session.query(Player).filter(Player.id == player_id).first()
154+
session.close()
155+
156+
return player
157+
158+
159+
def count_round_wins(player_id: int, game_id: str) -> int:
160+
history = get_game_history(game_id)
161+
wins = 0
162+
grouped_moves = defaultdict(list)
163+
164+
for h in history:
165+
grouped_moves[h.roll_number].append(h)
166+
167+
for rnd_num, moves in grouped_moves.items():
168+
player_move = [m for m in moves if m.player_id == player_id][0]
169+
opponent_move = [m for m in moves if m.player_id != player_id][0]
170+
171+
player_roll = find_roll_by_id(player_move.roll_id)
172+
opponent_roll = find_roll_by_id(opponent_move.roll_id)
173+
174+
outcome = game_decider.decide(player_roll, opponent_roll)
175+
if outcome == Decision.win:
176+
wins += 1
177+
178+
return wins
179+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from sqlalchemy.ext.declarative import declarative_base
2+
3+
ModelBase = declarative_base()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import datetime
2+
import sqlalchemy
3+
4+
# noinspection PyPackageRequirements
5+
from game_logic.models.model_base import ModelBase
6+
7+
8+
class Move(ModelBase):
9+
__tablename__ = 'moves'
10+
11+
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True)
12+
created = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.datetime.now, index=True)
13+
roll_id = sqlalchemy.Column(sqlalchemy.Integer, index=True)
14+
game_id = sqlalchemy.Column(sqlalchemy.String, index=True)
15+
roll_number = sqlalchemy.Column(sqlalchemy.Integer, index=True)
16+
player_id = sqlalchemy.Column(sqlalchemy.Integer, index=True)
17+
is_winning_play = sqlalchemy.Column(sqlalchemy.Boolean, index=True, default=False)
18+
19+
def to_json(self, roll: 'Roll', player: 'Player'):
20+
if self.roll_id != roll.id:
21+
raise Exception("Mismatched roll values")
22+
if self.player_id != player.id:
23+
raise Exception("Mismatched player values")
24+
25+
return {
26+
'id': self.id,
27+
'created': self.created.isoformat(),
28+
'roll_id': self.roll_id,
29+
'roll': roll.name,
30+
'player_id': self.player_id,
31+
'player': player.name,
32+
'roll_number': self.roll_number,
33+
'is_winning_play': self.is_winning_play,
34+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import datetime
2+
3+
import sqlalchemy
4+
5+
# noinspection PyPackageRequirements
6+
from game_logic.models.model_base import ModelBase
7+
8+
9+
class Player(ModelBase):
10+
__tablename__ = 'players'
11+
12+
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True, autoincrement=True)
13+
created = sqlalchemy.Column(sqlalchemy.DateTime, default=datetime.datetime.now)
14+
name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
15+
16+
def to_json(self):
17+
return {
18+
'id': self.id,
19+
'created': self.created.isoformat(),
20+
'name': self.name,
21+
}

0 commit comments

Comments
 (0)