game_api.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from flask import Blueprint, abort, request, jsonify
  2. import db
  3. import lib
  4. from sources import sources, restock_all
  5. restock_all()
  6. game = Blueprint("game", __name__)
  7. def require_game(game_id):
  8. g = db.Game.query.get(game_id)
  9. if g is None:
  10. abort(404)
  11. return g
  12. def require_player(game_id):
  13. auth = request.headers.get("Authorization", type=str)
  14. if auth is None:
  15. abort(401)
  16. try:
  17. pid = int(auth.split(maxsplit=1)[-1])
  18. except ValueError:
  19. abort(401)
  20. player = db.Player.query.get(pid)
  21. if player is None:
  22. abort(404)
  23. return player
  24. @game.route("", methods=["PUT"])
  25. def create_game():
  26. js = request.get_json()
  27. if js is None:
  28. abort(400)
  29. timer = js.get("timer", None)
  30. if not isinstance(timer, int) or timer <= 0:
  31. abort(400)
  32. rounds = js.get("rounds", None)
  33. if not isinstance(rounds, int) or rounds <= 0:
  34. abort(400)
  35. only_america = js.get("onlyAmerica", False)
  36. if not isinstance(only_america, bool):
  37. abort(400)
  38. gen_method = js.get("generationMethod", "MAPCRUNCH")
  39. if not isinstance(gen_method, str):
  40. abort(400)
  41. rule_set = js.get("ruleSet", "NORMAL")
  42. if not isinstance(rule_set, str):
  43. abort(400)
  44. src = sources.get((gen_method, only_america), None)
  45. if src is None:
  46. abort(400)
  47. coords = src.get_points(n=rounds)
  48. new_game = db.Game.create(timer, rounds, only_america, gen_method, rule_set, coords)
  49. return jsonify({"gameId": new_game.game_id})
  50. @game.route("/<game_id>/config")
  51. def game_config(game_id):
  52. g = require_game(game_id)
  53. return jsonify({
  54. "timer": g.timer,
  55. "rounds": g.rounds,
  56. "onlyAmerica": g.only_america,
  57. "generationMethod": g.gen_method,
  58. "ruleSet": g.rule_set,
  59. })
  60. @game.route("/<game_id>/coords")
  61. def coords(game_id):
  62. g = require_game(game_id)
  63. return jsonify({
  64. str(c.round_number): {
  65. "lat": c.latitude,
  66. "lng": c.longitude,
  67. } for c in g.coordinates
  68. })
  69. @game.route("/<game_id>/players")
  70. def players(game_id):
  71. g = require_game(game_id)
  72. return jsonify({
  73. "players": [{
  74. "name": p.player_name,
  75. "currentRound": p.get_current_round(),
  76. "totalScore": p.get_total_score(),
  77. "guesses": {
  78. str(g.round_number): {
  79. "lat": g.latitude,
  80. "lng": g.longitude,
  81. "score": g.round_score,
  82. "timeRemaining": g.time_remaining,
  83. } for g in p.guesses
  84. },
  85. } for p in g.players]
  86. })
  87. @game.route("/<game_id>/linked", methods=["GET", "POST"])
  88. def link_game(game_id):
  89. g = require_game(game_id)
  90. if request.method == "GET":
  91. return jsonify({"linkedGame": g.linked_game})
  92. js = request.get_json()
  93. if js is None:
  94. abort(400)
  95. link_id = js.get("linkedGame", None)
  96. if link_id is None or db.Game.query.get(link_id) is None:
  97. abort(401)
  98. g.link(link_id)
  99. return "", 201
  100. @game.route("/<game_id>/round/<int:round_num>/first")
  101. def first_submitter(game_id, round_num):
  102. fs = db.FirstSubmission.query.get((game_id, round_num))
  103. if fs is None:
  104. abort(404)
  105. return jsonify({
  106. "first": fs.player_name
  107. })
  108. @game.route("/<game_id>/join", methods=["POST"])
  109. def join(game_id):
  110. js = request.get_json()
  111. if js is None:
  112. abort(400)
  113. name = js.get("playerName", None)
  114. if name is None:
  115. abort(400)
  116. g = require_game(game_id)
  117. if db.Player.query.filter(db.Player.game_id == game_id, db.Player.player_name == name).first() is not None:
  118. abort(409)
  119. p = g.join(name)
  120. return jsonify({
  121. "playerId": str(p.player_id)
  122. }), 201
  123. @game.route("/<game_id>/current")
  124. def current_round(game_id):
  125. g = require_game(game_id)
  126. player = require_player(game_id)
  127. cur_rnd = player.get_current_round()
  128. if cur_rnd is None:
  129. coord = None
  130. else:
  131. lookup = db.Coordinate.query.get((game_id, cur_rnd))
  132. cur_rnd = str(cur_rnd)
  133. if lookup is None:
  134. coord = None
  135. else:
  136. coord = {
  137. "lat": lookup.latitude,
  138. "lng": lookup.longitude,
  139. }
  140. if g.rule_set == "TIMEBANK":
  141. timer = player.get_last_round_time_remaining()
  142. if timer is None:
  143. timer = g.timer * g.rounds
  144. else:
  145. timer = g.timer
  146. return jsonify({
  147. "currentRound": cur_rnd,
  148. "coord": coord,
  149. "timer": int(timer),
  150. })
  151. @game.route("/<game_id>/guesses/<int:round_num>", methods=["POST"])
  152. def make_guess(game_id, round_num):
  153. g = require_game(game_id)
  154. player = require_player(game_id)
  155. if round_num != player.get_current_round():
  156. abort(409)
  157. js = request.get_json()
  158. if js is None:
  159. abort(400)
  160. timed_out = js.get("timeout", False)
  161. if timed_out:
  162. player.add_timeout(round_num)
  163. if g.rule_set == "TIMEBANK":
  164. player.timeout_remaining_rounds()
  165. db.session.commit()
  166. return jsonify({
  167. "score": 0,
  168. "totalScore": player.get_total_score(),
  169. "distance": None,
  170. }), 201
  171. try:
  172. lat = float(js.get("lat", None))
  173. lng = float(js.get("lng", None))
  174. remaining = int(js.get("timeRemaining", None))
  175. except ValueError:
  176. abort(400)
  177. target = db.Coordinate.query.get((game_id, round_num))
  178. if target is None:
  179. abort(400)
  180. guess_score, distance = lib.score((target.latitude, target.longitude), (lat, lng))
  181. player.add_guess(round_num, lat, lng, guess_score, remaining)
  182. if remaining <= 0 and g.rule_set == "TIMEBANK":
  183. player.timeout_remaining_rounds()
  184. return jsonify({
  185. "score": guess_score,
  186. "totalScore": player.get_total_score(),
  187. "distance": distance,
  188. }), 201