Skip to content

Commit 4a10532

Browse files
committed
Server implemented.
1 parent b71a75f commit 4a10532

File tree

6 files changed

+233
-17
lines changed

6 files changed

+233
-17
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,4 @@ ENV/
104104
.idea
105105
days/91-93-sqlalchemy/demo/persistent_rps/db/rock_paper_scissors.sqlite
106106
days/91-93-sqlalchemy/demo/persistent_rps_starter/.vscode/settings.json
107+
rock_paper_scissors.sqlite

days/97-99-online-game-api/demo_app/web/app.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
import flask
2+
from game_logic import game_decider, game_service
3+
from views import game_api, home
24

35
app = flask.Flask(__name__)
46

57

68
def main():
7-
app.run(debug=True)
9+
build_starter_data()
10+
build_views()
11+
run_web_app()
12+
813

14+
def build_views():
15+
game_api.build_views(app)
16+
home.build_views(app)
917

10-
@app.route('/')
11-
def index():
12-
return "Hello world!!!"
1318

19+
def build_starter_data():
20+
roll_names = game_decider.all_roll_names()
21+
game_service.init_rolls(roll_names)
1422

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)
23+
computer = game_service.find_player('computer')
24+
if not computer:
25+
game_service.create_player('computer')
2226

2327

24-
@app.errorhandler(404)
25-
def not_found(_):
26-
return "The page was not found."
28+
def run_web_app():
29+
app.run(debug=True)
2730

2831

2932
if __name__ == '__main__':
Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,72 @@
1-
# TODO: Game logic.
1+
from collections import defaultdict
2+
3+
from game_logic import game_service, game_decider
4+
from game_logic.game_decider import Decision
5+
from game_logic.models.player import Player
6+
from game_logic.models.roll import Roll
7+
8+
9+
class GameRound:
10+
11+
def __init__(self, game_id: str, player1: Player, player2: Player,
12+
p1_roll: Roll, p2_roll: Roll):
13+
self.p2_roll = p2_roll
14+
self.p1_roll = p1_roll
15+
self.game_id = game_id
16+
self.player1 = player1
17+
self.player2 = player2
18+
19+
self.decision_p1_to_p2 = None
20+
history = game_service.get_game_history(game_id)
21+
self.round = len(history) // 2 + 1
22+
self.player1_wins = GameRound.count_wins(self.player1, history)
23+
self.player2_wins = GameRound.count_wins(self.player2, history)
24+
self.WIN_COUNT_MIN = 3
25+
self.PLAY_COUNT_MIN = 5
26+
self.is_over = game_service.is_game_over(game_id)
27+
28+
def play(self):
29+
if self.is_over:
30+
raise Exception("Game is already over, cannot play further.")
31+
32+
d = game_decider.decide(self.p1_roll, self.p2_roll)
33+
self.decision_p1_to_p2 = d
34+
35+
self.record_roll(d, self.player1, self.p1_roll, self.player1_wins)
36+
self.record_roll(d.reversed(), self.player2, self.p2_roll, self.player2_wins)
37+
38+
print("RECORDING ROUND")
39+
print("Player 1: {}, prior wins {}, outcome: {}".format(self.p1_roll.name, self.player1_wins, d))
40+
print("Player 2: {}, prior wins {}, outcome: {}".format(self.p2_roll.name, self.player2_wins, d.reversed()))
41+
print()
42+
43+
self.is_over = game_service.is_game_over(self.game_id)
44+
45+
def record_roll(self, decision: Decision, player: Player, roll: Roll, win_count: int):
46+
final_round_candidate = self.round >= self.PLAY_COUNT_MIN and win_count + 1 >= self.WIN_COUNT_MIN
47+
wins_game = final_round_candidate and decision == Decision.win
48+
49+
game_service.record_roll(player, roll, self.game_id, wins_game, self.round)
50+
51+
@staticmethod
52+
def count_wins(player, history):
53+
grouped_moves = defaultdict(list)
54+
55+
for h in history:
56+
grouped_moves[h.roll_number].append(h)
57+
58+
win_count = 0
59+
for rnd_data in grouped_moves.values():
60+
if len(rnd_data) != 2:
61+
continue
62+
63+
player_move = [m for m in rnd_data if m.player_id == player.id][0]
64+
opponent_move = [m for m in rnd_data if m.player_id != player.id][0]
65+
66+
player_roll = game_service.find_roll_by_id(player_move.roll_id)
67+
opponent_roll = game_service.find_roll_by_id(opponent_move.roll_id)
68+
69+
if game_decider.decide(player_roll, opponent_roll) == Decision.win:
70+
win_count += 1
71+
72+
return win_count

days/97-99-online-game-api/demo_app/web/game_logic/game_service.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ def create_roll(name: str) -> 'Roll':
136136
session.commit()
137137
session.close()
138138

139-
roll = session.query(Roll).filter(Roll.id == roll.id).first()
140-
return roll
139+
return find_roll(name)
141140

142141

143142
def find_roll_by_id(roll_id):
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import random
2+
import uuid
3+
4+
import flask
5+
from game_logic import game_service
6+
from game_logic.game import GameRound
7+
8+
9+
def build_views(app):
10+
@app.route('/api/game/users/<user>', methods=['GET'])
11+
def find_user(user: str):
12+
player = game_service.find_player(user)
13+
if not player:
14+
flask.abort(404)
15+
return flask.jsonify(player.to_json())
16+
17+
@app.route('/api/game/users', methods=['PUT'])
18+
def create_user():
19+
try:
20+
if not flask.request.json \
21+
or 'user' not in flask.request.json \
22+
or not flask.request.json.get('user'):
23+
raise Exception("Invalid request: no value for user.")
24+
25+
username = flask.request.json.get('user').strip()
26+
player = game_service.create_player(username)
27+
28+
return flask.jsonify(player.to_json())
29+
30+
except Exception as x:
31+
flask.abort(flask.Response(
32+
response="Invalid request: {}".format(x),
33+
status=400
34+
))
35+
36+
@app.route('/api/game/games', methods=['POST'])
37+
def create_game():
38+
return flask.jsonify({'game_id': str(uuid.uuid4())})
39+
40+
@app.route('/api/game/rolls', methods=['GET'])
41+
def all_rolls():
42+
rolls = [r.name for r in game_service.all_rolls()]
43+
return flask.jsonify(rolls)
44+
45+
@app.route('/api/game/<game_id>/status', methods=['GET'])
46+
def game_status(game_id: str):
47+
is_over = game_service.is_game_over(game_id)
48+
history = game_service.get_game_history(game_id)
49+
50+
if not history:
51+
flask.abort(404)
52+
53+
roll_lookup = {r.id: r for r in game_service.all_rolls()}
54+
player_lookup = {p.id: p for p in game_service.all_players()}
55+
56+
player1 = game_service.find_player_by_id(history[0].player_id)
57+
player2 = game_service.find_player_by_id(history[1].player_id)
58+
59+
wins_p1 = game_service.count_round_wins(player1.id, game_id)
60+
wins_p2 = game_service.count_round_wins(player2.id, game_id)
61+
62+
data = {
63+
'is_over': is_over,
64+
'moves': [h.to_json(roll_lookup[h.roll_id], player_lookup[h.player_id]) for h in history],
65+
'player1': player1.to_json(),
66+
'player2': player2.to_json(),
67+
'winner': player1.to_json() if wins_p1 >= wins_p2 else player2.to_json()
68+
}
69+
70+
return flask.jsonify(data)
71+
72+
@app.route('/api/game/top_scores', methods=['GET'])
73+
def top_scores():
74+
players = game_service.all_players()
75+
wins = [
76+
{'player': p.to_json(), 'score': game_service.get_win_count(p)}
77+
for p in players
78+
]
79+
80+
wins.sort(key=lambda wn: -wn.get('score'))
81+
return flask.jsonify(wins[:10])
82+
83+
@app.route('/api/game/play_round', methods=['POST'])
84+
def play_round():
85+
try:
86+
db_roll, db_user, game_id = validate_round_request()
87+
computer_player = game_service.find_player('computer')
88+
computer_roll = random.choice(game_service.all_rolls())
89+
90+
game = GameRound(game_id, db_user, computer_player, db_roll, computer_roll)
91+
game.play()
92+
93+
return flask.jsonify({
94+
'roll': db_roll.to_json(),
95+
'computer_roll': computer_roll.to_json(),
96+
'player': db_user.to_json(),
97+
'opponent': computer_player.to_json(),
98+
'round_outcome': str(game.decision_p1_to_p2),
99+
'is_final_round': game.is_over,
100+
'round_number': game.round
101+
})
102+
except Exception as x:
103+
# raise x
104+
flask.abort(flask.Response(response='Invalid request: {}'.format(x), status=400))
105+
106+
def validate_round_request():
107+
if not flask.request.json:
108+
raise Exception("Invalid request: no JSON body.")
109+
game_id = flask.request.json.get('game_id')
110+
if not game_id:
111+
raise Exception("Invalid request: No game_id value")
112+
user = flask.request.json.get('user')
113+
if not user:
114+
raise Exception("Invalid request: No user value")
115+
db_user = game_service.find_player(user)
116+
if not db_user:
117+
raise Exception("Invalid request: No user with name {}".format(user))
118+
roll = flask.request.json.get('roll')
119+
if not roll:
120+
raise Exception("Invalid request: No roll value")
121+
db_roll = game_service.find_roll(roll)
122+
if not db_roll:
123+
raise Exception("Invalid request: No roll with name {}".format(roll))
124+
125+
is_over = game_service.is_game_over(game_id)
126+
if is_over:
127+
raise Exception("This game is already over.")
128+
129+
return db_roll, db_user, game_id
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import flask
2+
3+
4+
def build_views(app):
5+
@app.route('/')
6+
def index():
7+
return "Hello world!!!"
8+
9+
@app.errorhandler(404)
10+
def not_found(_):
11+
return flask.Response("The page was not found.", status=404)
12+
13+

0 commit comments

Comments
 (0)