game.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
  2. from fastapi_camelcase import CamelModel
  3. from pydantic import conint, constr
  4. from sqlalchemy.orm import Session
  5. from .. import scoring
  6. from ..schemas import GameConfig, Guess, RuleSetEnum
  7. from ..db import get_db, queries, models
  8. from ..point_gen import generate_points, restock_source, ExhaustedSourceError
  9. router = APIRouter()
  10. class LinkedGame(CamelModel):
  11. linked_game: str
  12. class JoinGame(CamelModel):
  13. player_name: constr(min_length=1)
  14. class GuessResult(CamelModel):
  15. distance: int = None
  16. score: int = 0
  17. total_score: int
  18. @router.put("")
  19. def create_game(config: GameConfig, bg: BackgroundTasks, db: Session = Depends(get_db)):
  20. try:
  21. coords = generate_points(config)
  22. except ExhaustedSourceError:
  23. # TODO might be worth logging something useful here eventually
  24. raise HTTPException(status_code=501, detail="Sufficient points could not be generated quickly enough")
  25. game_id = queries.create_game(db, config, coords)
  26. bg.add_task(restock_source, config)
  27. return { "gameId": game_id }
  28. def get_game(game_id: str, db: Session = Depends(get_db)) -> models.Game:
  29. game = queries.get_game(db, game_id)
  30. if game is None:
  31. raise HTTPException(status_code=404, detail="Game not found")
  32. return game
  33. @router.get("/{game_id}/config", response_model=GameConfig)
  34. def get_game_config(game: models.Game = Depends(get_game)):
  35. return game
  36. @router.get("/{game_id}/coords")
  37. def get_game_coords(game: models.Game = Depends(get_game)):
  38. return {
  39. str(coord.round_number): {
  40. "lat": coord.latitude,
  41. "lng": coord.longitude,
  42. "country": coord.country_code,
  43. }
  44. for coord in game.coordinates
  45. }
  46. @router.post("/{game_id}/join")
  47. def join_game(game_id: str, join: JoinGame, db: Session = Depends(get_db)):
  48. get_game(game_id, db) # confirm this game exists
  49. player_id = queries.join_game(db, game_id, join.player_name)
  50. if player_id is None:
  51. raise HTTPException(status_code=409, detail="Player name in use")
  52. return { "playerId": player_id }
  53. @router.get("/{game_id}/players")
  54. def get_players(game: models.Game = Depends(get_game)):
  55. return { "players": [
  56. {
  57. "name": p.player_name,
  58. "currentRound": queries.get_next_round_number(p),
  59. "totalScore": queries.get_total_score(p),
  60. "guesses": {
  61. str(g.round_number): {
  62. "lat": g.latitude,
  63. "lng": g.longitude,
  64. "country": g.country_code,
  65. "score": g.round_score,
  66. "timeRemaining": g.time_remaining,
  67. } for g in p.guesses
  68. }
  69. } for p in game.players
  70. ] }
  71. def get_player(game_id: str, player_id: str, db: Session = Depends(get_db)) -> models.Player:
  72. player = queries.get_player(db, player_id)
  73. if player is None:
  74. raise HTTPException(status_code=404, detail="Player not found")
  75. if player.game_id != game_id:
  76. raise HTTPException(status_code=404, detail="Player not in game")
  77. return player
  78. @router.get("/{game_id}/players/{player_id}/current")
  79. def get_current_round(db: Session = Depends(get_db), player: models.Player = Depends(get_player)):
  80. coord = queries.get_next_coordinate(db, player)
  81. if coord is None:
  82. return {
  83. "currentRound": None,
  84. "coord": None,
  85. "timer": None,
  86. }
  87. return {
  88. "currentRound": coord.round_number,
  89. "coord": {
  90. "lat": coord.latitude,
  91. "lng": coord.longitude,
  92. "country": coord.country_code,
  93. },
  94. "timer": queries.get_next_round_time(player),
  95. }
  96. @router.put("/{game_id}/linked", status_code=204)
  97. def set_linked_game(game_id: str, linked_game: LinkedGame, db: Session = Depends(get_db)):
  98. queries.link_game(db, game_id, linked_game.linked_game)
  99. @router.get("/{game_id}/linked")
  100. def get_linked_game(game: models.Game = Depends(get_game)):
  101. return { "linkedGame": game.linked_game }
  102. @router.get("/{game_id}/round/{round_number}/first")
  103. def get_first_submitter(game_id: str, round_number: conint(gt=0), db: Session = Depends(get_db)):
  104. return { "first": queries.get_first_submitter(db, game_id, round_number) }
  105. @router.post("/{game_id}/round/{round_number}/guess/{player_id}", response_model=GuessResult)
  106. def submit_guess(round_number: conint(gt=0),
  107. guess: Guess,
  108. db: Session = Depends(get_db),
  109. game: models.Game = Depends(get_game),
  110. player: models.Player = Depends(get_player)):
  111. target = queries.get_coordinate(db, player.game_id, round_number)
  112. if game.rule_set == RuleSetEnum.country_race:
  113. score = scoring.score_country_race(target.country_code, guess.country, guess.time_remaining, game.timer)
  114. distance = None
  115. else:
  116. score, distance = scoring.score((target.latitude, target.longitude), (guess.lat, guess.lng))
  117. added = queries.add_guess(db, guess, player, round_number, score)
  118. if not added:
  119. raise HTTPException(status_code=409, detail="Already submitted guess for this round")
  120. total_score = queries.get_total_score(player)
  121. return GuessResult(distance=distance, score=score, total_score=total_score)
  122. @router.post("/{game_id}/round/{round_number}/timeout/{player_id}", response_model=GuessResult, response_model_include={"total_score"})
  123. def submit_timeout(round_number: conint(gt=0), db: Session = Depends(get_db), player: models.Player = Depends(get_player)):
  124. added = queries.add_timeout(db, player, round_number)
  125. if not added:
  126. raise HTTPException(status_code=409, detail="Already submitted guess for this round")
  127. total_score = queries.get_total_score(player)
  128. return GuessResult(total_score=total_score)