/
game_manager.py
345 lines (284 loc) · 13.9 KB
/
game_manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import numpy as np
from sqlalchemy.exc import IntegrityError
import errors
import model
from app import db
from callbacks import socket_actions as actions
import logging
def db_commit(function):
def func(*args, **kwargs):
try:
result = function(*args, **kwargs)
db.session.commit()
return result
except IntegrityError as err:
# logging.error(str(err))
db.session.rollback()
raise errors.ForbiddenAction()
return func
class GameManager:
# Getters
def configure(self, rules):
self.min_players = min(rules['team'].keys())
self.max_players = max(rules['team'].keys())
self.rules = rules
model.Game.missions_to_win = rules['missions_to_win']
def request_game(self, id):
game = db.session.query(model.Game).filter(model.Game.id == id).first()
if game is None:
raise errors.GameNotFound()
return game
def request_mission(self, id):
return db.session.query(model.Mission).filter(model.Mission.id == id).first()
def request_games(self):
return db.session.query(model.Game).filter(model.Game.stage == model.GameStage.pending).all()
def request_player(self, id):
player = db.session.query(model.Player).filter(model.Player.id == id).first()
if player is None:
raise errors.UknownPlayer()
return player
def request_player_with_sid(self, sid, game_id):
player = db.session.query(model.Player).filter(db.and_(model.Player.sid == sid,
model.Player.game_id == game_id)).first()
if player is None:
raise errors.UknownPlayer()
return player
@db_commit
def activate_player(self, player):
player.active = True
@db_commit
def deactivate_player(self, player):
player.active = False
if not self.is_game_active(player.game_id):
host_id = player.game.host_id
self.delete_game(player.game_id)
return actions.GameDeleted(host_id, self.request_games())
else:
return actions.GameUpdated(player.game.to_dict(), player.game.host_id)
@db_commit
def update_player_sid(self, old_sid, sid):
player = db.session.query(model.Player).filter(model.Player.sid == old_sid).first()
if player is not None:
player.sid = sid
# Setters
@db_commit
def create_game(self, host_name, sid):
game = model.Game()
player = model.Player(name=host_name, sid=sid, game=game)
player.games.append(game)
game.host = player
db.session.add(game)
db.session.add(player)
return game
@db_commit
def new_game(self, game_, **kwargs):
if game_.host.sid != kwargs['sid']:
raise errors.ForbiddenAction()
game = model.Game()
db.session.add(game)
for p in game_.players:
p.game = game
p.games.append(game)
game.players = game_.players
game.host_id = game_.host.id
return game
@db_commit
def join_game(self, game, name, sid):
game_stage = db.session.query(model.Game.stage).filter(db.and_(model.Game.id == game.id)).first()
if game_stage is None:
raise errors.GameNotFound()
elif game_stage[0] != model.GameStage.pending:
raise errors.ForbiddenAction()
elif len(db.session.query(model.Player.id).filter(model.Player.game_id == game.id).all()) == self.max_players:
raise errors.GameFull()
player = model.Player(name=name, sid=sid, game_id=game.id)
player.games.append(game)
db.session.add(player)
return player
@db_commit
def leave_game(self, game_id, sid, player_id):
player = db.session.query(model.Player).filter(db.and_(model.Player.sid == sid,
model.Player.game_id == game_id)).first()
if player is None:
raise errors.UknownPlayer()
# Player wants to leave
if player.id == player_id:
db.session.query(model.Player).filter(db.and_(model.Player.sid == sid,
model.Player.game_id == game_id)).delete()
db.session.query(model.Game).filter(model.Game.host_id == player.id).delete()
# Player wants to kick
elif db.session.query(model.Game).filter(db.and_(model.Game.host_id == player.id,
model.Game.id == game_id)).first() is not None:
db.session.query(model.Player).filter(db.and_(model.Player.id == player_id,
model.Player.game_id == game_id)).delete()
else:
raise errors.ForbiddenAction()
game = db.session.query(model.Game).filter(model.Game.id == game_id).first()
if game is not None and game.stage != model.GameStage.pending:
self.reset_game(game)
def is_host(self, game_id, sid):
player_id = db.session.query(model.Player.id).filter(db.and_(model.Player.sid == sid,
model.Player.game_id == game_id)).first()
if player_id is None:
raise errors.UknownPlayer()
elif db.session.query(model.Game).filter(db.and_(model.Game.host_id == player_id[0],
model.Game.id == game_id)).first() is None:
return False
return True
def try_delete_game(self, game_id, sid):
if self.is_host(game_id, sid):
self.delete_game(game_id)
else:
raise errors.ForbiddenAction()
@db_commit
def delete_game(self, game_id):
db.session.query(model.Game).filter(model.Game.id == game_id).delete()
def is_game_active(self, game_id):
return len(db.session.query(model.Player.id).filter(db.and_(model.Player.game_id == game_id,
model.Player.active == True)).all()) > 0
@db_commit
def update_game(self, game, **kwargs):
if game.stage == model.GameStage.pending:
return self._handle_pending(game, **kwargs)
if game.stage == model.GameStage.starting:
return self._handle_starting(game, **kwargs)
elif game.stage == model.GameStage.start_mission:
return self._handle_start_mission(game, **kwargs)
elif game.stage == model.GameStage.executing_mission:
return self._handle_executing_mission(game, **kwargs)
elif game.stage == model.GameStage.finished:
raise errors.GameFinished()
@db_commit
def update_mission(self, mission, **kwargs):
if mission.stage == model.RoundStage.proposal_request:
return self._handle_proposal_request(mission, **kwargs)
elif mission.stage == model.RoundStage.troop_proposal:
return self._handle_troop_proposal(mission, **kwargs)
elif mission.stage == model.RoundStage.troop_voting:
return self._handle_troop_voting(mission, **kwargs)
elif mission.stage == model.RoundStage.troop_voting_results:
return self._handle_troop_voting_results(mission, **kwargs)
elif mission.stage == model.RoundStage.mission_voting:
return self._handle_mission_voting(mission, **kwargs)
elif mission.stage == model.RoundStage.mission_voting_result:
return self._handle_mission_voting_result(mission, **kwargs)
elif mission.stage == model.RoundStage.mission_results:
return actions.MissionUpdated(mission.game_id, mission.to_dict())
@db_commit
def reset_game(self, game):
game.stage = model.GameStage.pending
db.session.query(model.Mission).filter(model.Mission.game_id == game.id).delete()
return actions.GameUpdated(game.to_dict(), game.host_id)
def _handle_pending(self, game, **kwargs):
if game.host.sid != kwargs['sid']:
raise errors.ForbiddenAction()
elif len(game.players) < self.min_players:
raise errors.InsufficientPlayersNumber()
game.next()
return self.update_game(game, **kwargs)
def _handle_starting(self, game, **kwargs):
game.setup(self.rules['team'][len(game.players)]['spies'])
game.next()
return self.update_game(game, **kwargs)
def _handle_start_mission(self, game, **kwargs):
_ = self._create_mission(game.id, len(game.players), len(game.missions))
game.next()
return self.update_game(game, **kwargs)
def _handle_executing_mission(self, game, **kwargs):
action = self.update_mission(game.current_mission(), **kwargs)
if game.current_mission().stage == model.RoundStage.mission_results:
if game._complete_game():
game.next()
return actions.GameUpdated(game.to_dict(), game.host_id)
else:
game.stage = model.GameStage.start_mission
return self.update_game(game, **kwargs)
return actions.GameUpdated(game.to_dict(), game.host_id)
@db_commit
def _create_mission(self, game_id, num_players, index):
mission = model.Mission(game_id=game_id, index=index,
num_of_fails=self.rules['team'][num_players]['fails_num'][index])
db.session.add(mission)
return mission
def _handle_proposal_request(self, mission, **kwargs):
mission.game.next_leader()
mission.next()
return actions.MissionUpdated(mission.game, mission.to_dict())
def _handle_troop_proposal(self, mission, **kwargs):
target_players = self.rules['team'][len(mission.game.players)]['mission_team'][len(mission.game.missions) - 1]
if 'players_ids' not in kwargs:
raise errors.InvalidPlayersNumber(0, target_players)
elif kwargs['sid'] != mission.game.current_leader().sid:
raise errors.NotLeader()
members_ids = kwargs['players_ids']
if len(members_ids) != target_players:
raise errors.InvalidPlayersNumber(len(members_ids), target_players)
players_ids = [p.id for p in mission.game.players]
_ = self._create_proposal(mission.id, mission.game.current_leader().id, members_ids, players_ids)
mission.next()
return actions.MissionUpdated(mission.game_id, mission.to_dict())
@db_commit
def _create_proposal(self, mission_id, proposer_id, members_ids, players_ids):
players = db.session.query(model.Player).filter(model.Player.id.in_(members_ids)).all()
voting = model.Voting()
voting.votes = [model.Vote(voter_id=player) for player in players_ids]
proposal = model.TroopProposal(members=players,
proposer_id=proposer_id,
mission_id=mission_id,
voting=voting)
db.session.add(proposal)
db.session.add(voting)
return proposal
def _handle_troop_voting(self, mission, **kwargs):
player_id = db.session.query(model.Player.id) \
.filter(db.and_(model.Player.sid == kwargs['sid'],
model.Player.game_id == mission.game_id,
model.Player.id.in_([v.voter_id for v
in mission.current_voting().votes if v.result is None]))).first()
if 'result' not in kwargs:
return actions.MissionUpdated(mission.game_id, mission.to_dict())
elif player_id is None:
raise errors.CantVote()
mission.current_voting().vote(player_id[0], kwargs['result'])
if mission.current_voting().is_complete():
mission.next()
return self.update_mission(mission, **kwargs)
@db_commit
def _handle_troop_voting_results(self, mission, **kwargs):
voting = mission.troop_proposals[-1].voting
voting.result = sum([v.result for v in voting.votes]) > len(voting.votes) // 2
if voting.result:
mission.troop_members = mission.troop_proposals[-1].members
mission.voting = model.Voting()
mission.voting.votes = [model.Vote(voter=player) for player in mission.troop_members]
db.session.add(mission.voting)
mission.next()
return actions.MissionUpdated(mission.game_id, mission.to_dict())
else:
if len(mission.troop_proposals) >= self.rules['proposals_to_lose']:
mission._stage = model.RoundStage.mission_results
mission.resistance_won = False
return actions.GameUpdated(mission.game.to_dict(), mission.game.host_id)
mission._stage = model.RoundStage.proposal_request
return self.update_mission(mission, **kwargs)
def _handle_mission_voting(self, mission, **kwargs):
voter_ids = [v.voter_id for v in mission.current_voting().votes if v.result is None]
player_id = db.session.query(model.Player.id) \
.filter(db.and_(model.Player.sid == kwargs['sid'],
model.Player.game_id == mission.game_id,
model.Player.id.in_(voter_ids))).first()
if 'result' not in kwargs:
return actions.MissionUpdated(mission.game_id, mission.to_dict())
elif player_id is None:
raise errors.CantVote()
mission.current_voting().vote(player_id[0], kwargs['result'])
if mission.current_voting().is_complete():
mission.next()
return self.update_mission(mission, **kwargs)
def _handle_mission_voting_result(self, mission, **kwargs):
voting = mission.voting
voting.result = np.bitwise_not([vote.result for vote in voting.votes]).sum() < mission.num_of_fails
mission.resistance_won = voting.result
mission.next()
return self.update_mission(mission, **kwargs)
shared = GameManager()