game.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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
  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. }
  43. for coord in game.coordinates
  44. }
  45. @router.post("/{game_id}/join")
  46. def join_game(game_id: str, join: JoinGame, db: Session = Depends(get_db)):
  47. get_game(game_id, db) # confirm this game exists
  48. player_id = queries.join_game(db, game_id, join.player_name)
  49. if player_id is None:
  50. raise HTTPException(status_code=409, detail="Player name in use")
  51. return { "playerId": player_id }
  52. @router.get("/{game_id}/players")
  53. def get_players(game: models.Game = Depends(get_game)):
  54. return { "players": [
  55. {
  56. "name": p.player_name,
  57. "currentRound": queries.get_next_round_number(p),
  58. "totalScore": queries.get_total_score(p),
  59. "guesses": {
  60. str(g.round_number): {
  61. "lat": g.latitude,
  62. "lng": g.longitude,
  63. "score": g.round_score,
  64. "timeRemaining": g.time_remaining,
  65. } for g in p.guesses
  66. }
  67. } for p in game.players
  68. ] }
  69. def get_player(game_id: str, player_id: str, db: Session = Depends(get_db)) -> models.Player:
  70. player = queries.get_player(db, player_id)
  71. if player is None:
  72. raise HTTPException(status_code=404, detail="Player not found")
  73. if player.game_id != game_id:
  74. raise HTTPException(status_code=404, detail="Player not in game")
  75. return player
  76. @router.get("/{game_id}/players/{player_id}/current")
  77. def get_current_round(db: Session = Depends(get_db), player: models.Player = Depends(get_player)):
  78. coord = queries.get_next_coordinate(db, player)
  79. if coord is None:
  80. return {
  81. "currentRound": None,
  82. "coord": None,
  83. "timer": None,
  84. }
  85. return {
  86. "currentRound": coord.round_number,
  87. "coord": {
  88. "lat": coord.latitude,
  89. "lng": coord.longitude,
  90. },
  91. "timer": queries.get_next_round_time(player),
  92. }
  93. @router.put("/{game_id}/linked", status_code=204)
  94. def set_linked_game(game_id: str, linked_game: LinkedGame, db: Session = Depends(get_db)):
  95. queries.link_game(db, game_id, linked_game.linked_game)
  96. @router.get("/{game_id}/linked")
  97. def get_linked_game(game: models.Game = Depends(get_game)):
  98. return { "linkedGame": game.linked_game }
  99. @router.get("/{game_id}/round/{round_number}/first")
  100. def get_first_submitter(game_id: str, round_number: conint(gt=0), db: Session = Depends(get_db)):
  101. return { "first": queries.get_first_submitter(db, game_id, round_number) }
  102. @router.post("/{game_id}/round/{round_number}/guess/{player_id}", response_model=GuessResult)
  103. def submit_guess(round_number: conint(gt=0), guess: Guess, db: Session = Depends(get_db), player: models.Player = Depends(get_player)):
  104. target = queries.get_coordinate(db, player.game_id, round_number)
  105. score, distance = scoring.score((target.latitude, target.longitude), (guess.lat, guess.lng))
  106. added = queries.add_guess(db, guess, player, round_number, score)
  107. if not added:
  108. raise HTTPException(status_code=409, detail="Already submitted guess for this round")
  109. total_score = queries.get_total_score(player)
  110. return GuessResult(distance=distance, score=score, total_score=total_score)
  111. @router.post("/{game_id}/round/{round_number}/timeout/{player_id}", response_model=GuessResult, response_model_include={"total_score"})
  112. def submit_timeout(round_number: conint(gt=0), db: Session = Depends(get_db), player: models.Player = Depends(get_player)):
  113. added = queries.add_timeout(db, player, round_number)
  114. if not added:
  115. raise HTTPException(status_code=409, detail="Already submitted guess for this round")
  116. total_score = queries.get_total_score(player)
  117. return GuessResult(total_score=total_score)