Bladeren bron

Still needs some clean up, but the game API is functional now

Kirk Trombley 5 jaren geleden
bovenliggende
commit
69d9d5821e
3 gewijzigde bestanden met toevoegingen van 146 en 26 verwijderingen
  1. 7 2
      README.md
  2. 70 6
      server/db.py
  3. 69 18
      server/game_api.py

+ 7 - 2
README.md

@@ -29,7 +29,7 @@ PUT /game
 GET /game/{ID}
     Returns {
         "game_id": string,
-        "cretor": string,
+        "creator": string,
         "timer": number,
         "coords": {
             "1": {
@@ -52,6 +52,9 @@ GET /game/{ID}
             }, ...
         ]
     }
+POST /game/{ID}/join
+    Requires session cookie
+    Returns 201 vs 401
 GET /game/{ID}/guesses
     Requires session cookie
     Returns {
@@ -70,7 +73,9 @@ POST /game/{ID}/guesses/{round}
         "lat": number,
         "lng": number
     }
-    Returns 400 vs 201
+    Returns 400 vs 201 and {
+        "score": number
+    }
 ```
 
 ## Front End

+ 70 - 6
server/db.py

@@ -24,7 +24,7 @@ class CoordSet(db.Model):
 
     def to_dict(self):
         to_return = {}
-        for r, c in zip(("1", "2", "3", "4", "5"), (self.coord_1, self.coord_2, self.coord_3, self.coord_4, self.coord_5)):
+        for r, c in zip("12345", (self.coord_1, self.coord_2, self.coord_3, self.coord_4, self.coord_5)):
             if c is None:
                 continue
             lat, lng = c.split(",")
@@ -34,6 +34,40 @@ class CoordSet(db.Model):
             }
         return to_return
 
+    def get_coord(self, round_num):
+        # TODO this logic is a little gross
+        if round_num == "1":
+            c = self.coord_1
+        elif round_num == "2":
+            c = self.coord_2
+        elif round_num == "3":
+            c = self.coord_3
+        elif round_num == "4":
+            c = self.coord_4
+        elif round_num == "5":
+            c = self.coord_5
+        else:
+            raise ValueError(f"Invalid round number {round_num}")
+        
+        if c is not None:
+            return tuple(float(x) for x in c.split(","))
+        # returns None if the selected coord was None
+
+    def set_coord(self, round_num, lat, lng):
+        # TODO this logic is a little gross
+        if round_num == "1":
+            self.coord_1 = f"{lat},{lng}"
+        elif round_num == "2":
+            self.coord_2 = f"{lat},{lng}"
+        elif round_num == "3":
+            self.coord_3 = f"{lat},{lng}"
+        elif round_num == "4":
+            self.coord_4 = f"{lat},{lng}"
+        elif round_num == "5":
+            self.coord_5 = f"{lat},{lng}"
+        else:
+            raise ValueError(f"Invalid round number {round_num}")
+
 
 class GuessSet(db.Model):
     game_id = db.Column(db.String, db.ForeignKey("game.game_id"), primary_key=True)
@@ -47,9 +81,39 @@ class GuessSet(db.Model):
     score_5 = db.Column(db.Integer)
 
     def to_dict(self):
-        c = self.coords.to_dict()
-        for r, s in zip(("1", "2", "3", "4", "5"), (self.score_1, self.score_2, self.score_3, self.score_4, self.score_5)):
-            if r not in c:
-                continue
-            c[r]["score"] = s
+        c = self.coord_set.to_dict()
+        for r, s in zip("12345", (self.score_1, self.score_2, self.score_3, self.score_4, self.score_5)):
+            if r in c:
+                c[r]["score"] = s
+            else:
+                c[r] = None
         return c
+
+    def get_current_round(self):
+        for r, s in zip("12345", (self.score_1, self.score_2, self.score_3, self.score_4, self.score_5)):
+            if s is None:
+                return r
+        # returns None if all rounds completed
+
+    def get_total_score(self):
+        return ((self.score_1 or 0) + 
+            (self.score_2 or 0) + 
+            (self.score_3 or 0) + 
+            (self.score_4 or 0) + 
+            (self.score_5 or 0))
+
+    def set_guess(self, round_num, lat, lng, score):
+        self.coord_set.set_coord(round_num, lat, lng)
+        # TODO this logic is a little gross
+        if round_num == "1":
+            self.score_1 = score
+        elif round_num == "2":
+            self.score_2 = score
+        elif round_num == "3":
+            self.score_3 = score
+        elif round_num == "4":
+            self.score_4 = score
+        elif round_num == "5":
+            self.score_5 = score
+        else:
+            raise ValueError(f"Invalid round number {round_num}")

+ 69 - 18
server/game_api.py

@@ -4,7 +4,7 @@ import uuid
 from flask import Blueprint, session, abort, request, current_app, jsonify
 
 from db import db, Game, CoordSet, GuessSet
-from lib import generate_coord
+from lib import generate_coord, score
 
 game = Blueprint("game", __name__)
 
@@ -23,8 +23,15 @@ def require_game(game_id):
     return g
 
 
+def require_guess_set(game_id):
+    name = require_name()
+    gs = GuessSet.query.get((game_id, name))
+    if gs is None:
+        abort(404)
+    return gs
+
+
 def new_coord_string():
-    lat, lng = generate_coord(current_app.config["GOOGLE_API_KEY"])
     return f"{lat},{lng}"
 
 
@@ -40,13 +47,14 @@ def create_game():
         # basically impossible collision, but let's be safe
         game_id = str(uuid.uuid4())
 
-    cs = CoordSet(
-        coord_1=new_coord_string(),
-        coord_2=new_coord_string(),
-        coord_3=new_coord_string(),
-        coord_4=new_coord_string(),
-        coord_5=new_coord_string()
-    )
+    key = current_app.config["GOOGLE_API_KEY"]
+    cs = CoordSet()
+    for round_num in "12345":
+        coord = generate_coord(key)
+        while coord is None:
+            # very unlikely, but it is possible for generate_coord to fail
+            coord = generate_coord(key)
+        cs.set_coord(round_num, *coord)
     db.session.add(cs)
     db.session.commit()
 
@@ -59,6 +67,10 @@ def create_game():
     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
     })
@@ -73,19 +85,58 @@ def game_settings(game_id):
         "creator": g.creator,
         "timer": g.timer,
         "coords": g.coord_set.to_dict(),
-        "guesses": [], # TODO
+        "players": [
+            {
+                "name": gs.player_name,
+                "currentRound": gs.get_current_round(),
+                "totalScore": gs.get_total_score(),
+                "guesses": gs.to_dict(),
+            } for gs in g.guess_sets
+        ],
     })
 
 
-@game.route("/<game_id>/guesses")
-def guesses(game_id):
+@game.route("/<game_id>/join", methods=["POST"])
+def join(game_id):
     name = require_name()
     g = require_game(game_id)
-    return "Unimplemented", 500 # TODO
 
+    if GuessSet.query.get((g.game_id, name)) is not None:
+        abort(400)
 
-@game.route("/<game_id>/guesses/<round>", methods=["POST"])
-def make_guess(game_id, round):
-    name = require_name()
-    g = require_game(game_id)
-    return "Unimplemented", 500 # TODO
+    cs = CoordSet()
+    db.session.add(cs)
+    db.session.commit()
+
+    gs = GuessSet(game_id=g.game_id, player_name=name, coord_set=cs)
+    db.session.add(gs)
+    db.session.commit()
+    return "", 201
+
+
+@game.route("/<game_id>/guesses")
+def guesses(game_id):
+    gs = require_guess_set(game_id)
+    return jsonify({
+        "currentRound": gs.get_current_round(),
+        "guesses": gs.to_dict(),
+    })
+
+
+@game.route("/<game_id>/guesses/<round_num>", methods=["POST"])
+def make_guess(game_id, round_num):
+    gs = require_guess_set(game_id)
+    if round_num != gs.get_current_round():
+        abort(400)
+
+    lat = request.json.get("lat", None)
+    lng = request.json.get("lng", None)
+    if not isinstance(lat, float) or not isinstance(lng, float):
+        abort(400)
+    
+    
+    target = require_game(game_id).coord_set.get_coord(round_num)
+    guess_score = score(target, (lat, lng))
+    gs.set_guess(round_num, lat, lng, guess_score)
+    db.session.commit()
+    return jsonify({"score": guess_score}), 201