Browse Source

Modifying the underlying database to genericize the number of rounds

Kirk Trombley 5 years ago
parent
commit
fb8b5a3b82
3 changed files with 113 additions and 167 deletions
  1. 0 1
      README.md
  2. 85 137
      server/db.py
  3. 28 29
      server/game_api.py

+ 0 - 1
README.md

@@ -26,7 +26,6 @@ PUT /game
 GET /game/{ID}
     Returns {
         "gameId": string,
-        "creator": string,
         "timer": number,
         "coords": {
             "1": {

+ 85 - 137
server/db.py

@@ -11,169 +11,117 @@ session = db.session
 class Game(db.Model):
     game_id = db.Column(db.String, primary_key=True)
     timer = db.Column(db.Integer)
-    creator = db.Column(db.String)
-    coord_id = db.Column(db.Integer, db.ForeignKey("coord_set.coord_id"))
-    coord_set = db.relationship("CoordSet")
-    guess_sets = db.relationship("GuessSet", backref="game", lazy=True)
+    rounds = db.Column(db.Integer)
+    coordinates = db.relationship("Coordinate", lazy=True, order_by="Coordinate.round_number")
+    players = db.relationship("Player", lazy=True)
 
     @staticmethod
-    def create(timer, creator):
+    def create(timer, creator, rounds=5):
         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=creator,
-            coord_set=cs
+            rounds=rounds
         )
         db.session.add(new_game)
-        db.session.commit()
 
-        gs = GuessSet(
-            game_id=game_id, 
-            player_name=creator, 
-            coord_set=CoordSet()
-        )
-        db.session.add(gs)
-        db.session.commit()
+        for round_num in range(rounds):
+            (lat, lng) = generate_coord()
+            coord = Coordinate(
+                game_id=game_id,
+                round_number=round_num+1,
+                latitude=lat,
+                longitude=lng
+            )
+            db.session.add(coord)
 
-        return new_game
+        new_game.join(creator) # commits the session
 
+        return new_game
 
-    def __str__(self):
-        return f"Game({self.game_id}, {self.timer})"
+    def join(self, player_name):
+        p = Player(
+            game_id=self.game_id,
+            player_name=player_name
+        )
+        db.session.add(p)
+        db.session.commit()
 
     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
-            ],
+            "rounds": self.rounds,
+            "coords": [{
+                str(c.round_number): {
+                    "lat": c.latitude,
+                    "lng": c.longitude,
+                }
+            } for c in self.coordinates],
+            "players": [p.to_dict() for p in self.players],
         }
 
 
-class CoordSet(db.Model):
-    coord_id = db.Column(db.Integer, primary_key=True)
-    coord_1 = db.Column(db.String)
-    coord_2 = db.Column(db.String)
-    coord_3 = db.Column(db.String)
-    coord_4 = db.Column(db.String)
-    coord_5 = db.Column(db.String)
+class Player(db.Model):
+    player_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
+    game_id = db.Column(db.String, db.ForeignKey("game.game_id"))
+    player_name = db.Column(db.String)
+    guesses = db.relationship("Guess", lazy=True, order_by="Guess.round_number")
 
-    def to_dict(self):
-        to_return = {}
-        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(",")
-            to_return[r] = {
-                "lat": float(lat),
-                "lng": float(lng)
-            }
-        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)
-    player_name = db.Column(db.String, primary_key=True)
-    coord_id = db.Column(db.Integer, db.ForeignKey("coord_set.coord_id"))
-    coord_set = db.relationship("CoordSet")
-    score_1 = db.Column(db.Integer)
-    score_2 = db.Column(db.Integer)
-    score_3 = db.Column(db.Integer)
-    score_4 = db.Column(db.Integer)
-    score_5 = db.Column(db.Integer)
+    def get_total_score(self):
+        return sum(g.round_score for g in self.guesses)
+
+    def get_current_round(self):
+        if len(self.guesses) == 0:
+            return 1
+        next_round = self.guesses[-1].round_number + 1
+        if next_round <= Game.query.get(self.game_id).rounds:
+            return next_round
+        return None
+
+    def add_guess(self, round_num, lat, lng, score):
+        g = Guess(
+            player_id=self.player_id,
+            round_number=round_num,
+            latitude=lat,
+            longitude=lng,
+            round_score=score,
+        )
+        db.session.add(g)
+        db.session.commit()
+
+    def add_timeout(self, round_num):
+        self.add_guess(round_num, -200, -200, 0)
 
     def to_dict(self):
-        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
+        return {
+            "name": self.player_name,
+            "currentRound": self.get_current_round(),
+            "totalScore": self.get_total_score(),
+            "guesses": [{
+                str(g.round_number): {
+                    "lat": g.latitude,
+                    "lng": g.longitude,
+                    "score": g.round_score,
+                }
+            } for g in self.guesses],
+        }
 
-    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_timed_out(self, round_num):
-        self.set_guess(round_num, -200, -200, 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}")
+class Coordinate(db.Model):
+    game_id = db.Column(db.String, db.ForeignKey("game.game_id"), primary_key=True)
+    round_number = db.Column(db.Integer, primary_key=True, autoincrement=False)
+    latitude = db.Column(db.Float)
+    longitude = db.Column(db.Float)
+
+
+class Guess(db.Model):
+    player_id = db.Column(db.String, db.ForeignKey("player.player_id"), primary_key=True)
+    round_number = db.Column(db.Integer, primary_key=True, autoincrement=False)
+    latitude = db.Column(db.Float)
+    longitude = db.Column(db.Float)
+    round_score = db.Column(db.Integer)

+ 28 - 29
server/game_api.py

@@ -20,12 +20,12 @@ def require_game(game_id):
     return g
 
 
-def require_guess_set(game_id):
+def require_player(game_id):
     name = require_name()
-    gs = db.GuessSet.query.get((game_id, name))
-    if gs is None:
+    player = db.Player.query.filter(db.Player.game_id == game_id, db.Player.player_name == name).first()
+    if player is None:
         abort(404)
-    return gs
+    return player
 
 
 @game.route("", methods=["PUT"])
@@ -51,32 +51,29 @@ def join(game_id):
     name = require_name()
     g = require_game(game_id)
 
-    if db.GuessSet.query.get((g.game_id, name)) is not None:
+    if db.Player.query.filter(db.Player.game_id == game_id, db.Player.player_name == name).first() is not None:
         abort(409)
 
-    cs = db.CoordSet()
-    db.session.add(cs)
-    db.session.commit()
-
-    gs = db.GuessSet(game_id=g.game_id, player_name=name, coord_set=cs)
-    db.session.add(gs)
-    db.session.commit()
+    g.join(name)
     return "", 201
 
 
 @game.route("/<game_id>/current")
 def current_round(game_id):
     g = require_game(game_id)
-    gs = require_guess_set(game_id)
-    cur_rnd = gs.get_current_round()
+    player = require_player(game_id)
+    cur_rnd = player.get_current_round()
     if cur_rnd is None:
         coord = None
     else:
-        (lat, lng) = g.coord_set.get_coord(cur_rnd)
-        coord = {
-            "lat": float(lat),
-            "lng": float(lng),
-        }
+        lookup = db.Coordinate.query.get((game_id, cur_rnd))
+        if lookup is None:
+            coord = None
+        else:
+            coord = {
+                "lat": lookup.latitude,
+                "lng": lookup.longitude,
+            }
     return jsonify({
         "currentRound": cur_rnd,
         "coord": coord,
@@ -84,19 +81,19 @@ def current_round(game_id):
     })
 
 
-@game.route("/<game_id>/guesses/<round_num>", methods=["POST"])
+@game.route("/<game_id>/guesses/<int:round_num>", methods=["POST"])
 def make_guess(game_id, round_num):
-    gs = require_guess_set(game_id)
-    if round_num != gs.get_current_round():
+    player = require_player(game_id)
+    if round_num != player.get_current_round():
         abort(409)
 
     timed_out = request.json.get("timeout", False)
     if timed_out:
-        gs.set_timed_out(round_num)
+        player.add_timeout(round_num)
         db.session.commit()
         return jsonify({
             "score": 0,
-            "totalScore": gs.get_total_score(),
+            "totalScore": player.get_total_score(),
             "distance": None,
         }), 201
 
@@ -106,12 +103,14 @@ def make_guess(game_id, round_num):
     except ValueError:
         abort(400)
 
-    target = require_game(game_id).coord_set.get_coord(round_num)
-    guess_score, distance = lib.score(target, (lat, lng))
-    gs.set_guess(round_num, lat, lng, guess_score)
-    db.session.commit()
+    target = db.Coordinate.query.get((game_id, round_num))
+    if target is None:
+        abort(400)
+
+    guess_score, distance = lib.score((target.latitude, target.longitude), (lat, lng))
+    player.add_guess(round_num, lat, lng, guess_score)
     return jsonify({
         "score": guess_score,
-        "totalScore": gs.get_total_score(),
+        "totalScore": player.get_total_score(),
         "distance": distance,
     }), 201