Browse Source

Refactoring the back-end a little

Kirk Trombley 5 years ago
parent
commit
1a18c722f4
4 changed files with 68 additions and 62 deletions
  1. 3 5
      README.md
  2. 45 0
      server/db.py
  3. 7 47
      server/game_api.py
  4. 13 10
      server/lib.py

+ 3 - 5
README.md

@@ -74,15 +74,14 @@ POST /game/{ID}/guesses/{round}
     }
     Returns 400 vs 201 and {
         "score": number,
-        "totalScore": number
+        "totalScore": number,
+        "distance": number || null,
     }
 ```
 
 ## Next Steps
 
 - Refactor round tracking in database logic
-- Reduce complexity of `game_api` in back-end
-- Reduce complexity of `GamePanel` in front-end
 - Improve MapCrunch logic to distribute countries
 - Genericize number of rounds
 - Genericize round timer in UI
@@ -96,5 +95,4 @@ POST /game/{ID}/guesses/{round}
 - Round summary should use dynamic map
 - Styling and layout improvements
 - Optimize docker file or set up compose structure
-- Move controls in street view
-- Make reset button into proper google api control
+- Override google controls in streetview, make custom divs

+ 45 - 0
server/db.py

@@ -14,6 +14,51 @@ class Game(db.Model):
     def __str__(self):
         return f"Game({self.game_id}, {self.timer})"
 
+    def to_dict(self):
+        return {
+            "gameId": self.game_id,
+            "timer": self.timer,
+            "creator": self.creator,
+            "coords": self.coord_set.to_dict(),
+            "players": [
+                {
+                    "name": gs.player_name,
+                    "currentRound": gs.get_current_round(),
+                    "totalScore": gs.get_total_score(),
+                    "guesses": gs.to_dict(),
+                } for gs in self.guess_sets
+            ],
+        }
+
+
+def create_game():
+    game_id = str(uuid.uuid4())
+    while Game.query.get(game_id) is not None:
+        # basically impossible collision, but let's be safe
+        game_id = str(uuid.uuid4())
+
+    cs = CoordSet()
+    for round_num in "12345":
+        coord = generate_coord()
+        cs.set_coord(round_num, *coord)
+    db.session.add(cs)
+    db.session.commit()
+
+    new_game = Game(
+        game_id=game_id,
+        timer=timer,
+        creator=name,
+        coord_set=cs
+    )
+    db.session.add(new_game)
+    db.session.commit()
+
+    gs = GuessSet(game_id=game_id, player_name=name, coord_set=CoordSet())
+    db.session.add(gs)
+    db.session.commit()
+
+    return new_game
+
 
 class CoordSet(db.Model):
     coord_id = db.Column(db.Integer, primary_key=True)

+ 7 - 47
server/game_api.py

@@ -3,7 +3,7 @@ import uuid
 
 from flask import Blueprint, abort, request, jsonify
 
-from db import db, Game, CoordSet, GuessSet
+from db import db, Game, CoordSet, GuessSet, create_game
 from lib import generate_coord, score
 
 game = Blueprint("game", __name__)
@@ -38,57 +38,15 @@ def create_game():
     if not isinstance(timer, int) or timer <= 0:
         abort(400)
 
-    game_id = str(uuid.uuid4())
-    while Game.query.get(game_id) is not None:
-        # basically impossible collision, but let's be safe
-        game_id = str(uuid.uuid4())
+    new_game = create_game()
 
-    cs = CoordSet()
-    for round_num in "12345":
-        coord = generate_coord()
-        while coord is None:
-            # very unlikely, but it is possible for generate_coord to fail
-            coord = generate_coord()
-        cs.set_coord(round_num, *coord)
-    db.session.add(cs)
-    db.session.commit()
-
-    new_game = Game(
-        game_id=game_id,
-        timer=timer,
-        creator=name,
-        coord_set=cs
-    )
-    db.session.add(new_game)
-    db.session.commit()
-
-    gs = GuessSet(game_id=game_id, player_name=name, coord_set=CoordSet())
-    db.session.add(gs)
-    db.session.commit()
-
-    return jsonify({
-        "gameId": game_id
-    })
+    return jsonify({"gameId": new_game.game_id})
 
 
 @game.route("/<game_id>")
 def game_settings(game_id):
     g = require_game(game_id)
-
-    return jsonify({
-        "gameId": g.game_id,
-        "creator": g.creator,
-        "timer": g.timer,
-        "coords": g.coord_set.to_dict(),
-        "players": sorted([
-            {
-                "name": gs.player_name,
-                "currentRound": gs.get_current_round(),
-                "totalScore": gs.get_total_score(),
-                "guesses": gs.to_dict(),
-            } for gs in g.guess_sets
-        ], key=lambda x: x["totalScore"] or 0, reverse=True),
-    })
+    return jsonify(g.to_dict())
 
 
 @game.route("/<game_id>/join", methods=["POST"])
@@ -131,6 +89,7 @@ def make_guess(game_id, round_num):
         return jsonify({
             "score": 0,
             "totalScore": gs.get_total_score(),
+            "distance": None,
         }), 201
 
     lat = request.json.get("lat", None)
@@ -139,10 +98,11 @@ def make_guess(game_id, round_num):
         abort(400)
 
     target = require_game(game_id).coord_set.get_coord(round_num)
-    guess_score = score(target, (lat, lng))
+    guess_score, distance = score(target, (lat, lng))
     gs.set_guess(round_num, lat, lng, guess_score)
     db.session.commit()
     return jsonify({
         "score": guess_score,
         "totalScore": gs.get_total_score(),
+        "distance": distance,
     }), 201

+ 13 - 10
server/lib.py

@@ -13,21 +13,22 @@ mapcrunch_url = "http://www.mapcrunch.com/_r/"
 def generate_coord():
     """
     Returns (latitude, longitude) of usable coord (where google has data).
-    Returns None if, after trying several random points, no usable coords could be found.
+    This function will run until a suitable coordinate is found.
 
     This function calls the streetview metadata endpoint - there is no quota consumed
     """
     points_res = requests.get(mapcrunch_url).text
     points_js = json.loads(points_res.strip("while(1); "))
 
-    for lat, lng in points_js["points"]:
-        params = {
-            "key": google_api_key,
-            "location": f"{lat},{lng}",
-        }
-        js = requests.get(metadata_url, params=params).json()
-        if js["status"] != "ZERO_RESULTS":
-            return (lat, lng)
+    while True:
+        for lat, lng in points_js["points"]:
+            params = {
+                "key": google_api_key,
+                "location": f"{lat},{lng}",
+            }
+            js = requests.get(metadata_url, params=params).json()
+            if js["status"] != "ZERO_RESULTS":
+                return (lat, lng)
 
 
 mean_earth_radius_km = (6378 + 6357) / 2
@@ -44,6 +45,8 @@ def score(target, guess):
     Takes in two (latitude, longitude) pairs and produces an int score.
     Score is in the (inclusive) range [0, 5000]
     Higher scores are closer.
+
+    Returns (score, distance in km)
     """
     dist_km = haversine.haversine(target, guess)
     if dist_km <= min_dist_km:
@@ -53,4 +56,4 @@ def score(target, guess):
         return 0
 
     # TODO probably still needs tweaking
-    return int(perfect_score * (1 - (dist_km / max_dist_km) ** 2))
+    return int(perfect_score * (1 - (dist_km / max_dist_km) ** 2)), dist_km