game_api.py 6.0 KB

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