Skip to content

Commit

Permalink
Merge pull request #78 from bomzheg/running_game_reader
Browse files Browse the repository at this point in the history
Running game reader
  • Loading branch information
bomzheg authored Mar 26, 2024
2 parents 77f3b45 + b1d9d48 commit 2ddb0f4
Show file tree
Hide file tree
Showing 21 changed files with 260 additions and 56 deletions.
47 changes: 46 additions & 1 deletion shvatka/api/models/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from datetime import datetime
from typing import Sequence, Generic

from shvatka.core.models import dto
from shvatka.core.games.dto import CurrentHints
from shvatka.core.models import dto, enums
from shvatka.core.models.dto import scn
from shvatka.core.models.enums import GameStatus

Expand Down Expand Up @@ -114,3 +115,47 @@ def from_core(cls, core: dto.FullGame | None = None):
start_at=core.start_at,
levels=[Level.from_core(level) for level in core.levels],
)


@dataclass(frozen=True)
class KeyTime:
text: str
type_: enums.KeyType
is_duplicate: bool
at: datetime
level_number: int
player: Player
team: Team

@classmethod
def from_core(cls, core: dto.KeyTime | None):
if core is None:
return None
return cls(
text=core.text,
type_=core.type_,
is_duplicate=core.is_duplicate,
at=core.at,
level_number=core.level_number,
player=Player.from_core(core.player),
team=Team.from_core(core.team),
)


@dataclass
class CurrentHintResponse:
hints: list[scn.TimeHint]
typed_keys: list[KeyTime]
level_number: int
started_at: datetime

@classmethod
def from_core(cls, core: CurrentHints):
if core is None:
return None
return cls(
hints=core.hints,
typed_keys=[KeyTime.from_core(kt) for kt in core.typed_keys],
level_number=core.level_number,
started_at=core.started_at,
)
13 changes: 11 additions & 2 deletions shvatka/api/routes/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from shvatka.api.models import responses
from shvatka.api.utils.error_converter import to_http_error
from shvatka.core.games.interactors import FileReader
from shvatka.core.games.interactors import GameFileReaderInteractor, GamePlayReaderInteractor
from shvatka.core.models import dto
from shvatka.core.services.game import (
get_authors_games,
Expand Down Expand Up @@ -61,7 +61,7 @@ async def get_game_card(
@inject
async def get_game_file(
user: Annotated[dto.User, FromDishka()],
file_reader: Annotated[FileReader, FromDishka()],
file_reader: Annotated[GameFileReaderInteractor, FromDishka()],
id_: Annotated[int, Path(alias="id")],
guid: Annotated[str, Path(alias="guid")],
) -> StreamingResponse:
Expand All @@ -73,11 +73,20 @@ async def get_game_file(
raise to_http_error(e, HTTP_403_FORBIDDEN) from e


@inject
async def get_running_game_hints(
user: Annotated[dto.User, FromDishka()],
interactor: Annotated[GamePlayReaderInteractor, FromDishka()],
) -> responses.CurrentHintResponse:
return responses.CurrentHintResponse.from_core(await interactor(user))


def setup() -> APIRouter:
router = APIRouter(prefix="/games")
router.add_api_route("", get_all_games, methods=["GET"])
router.add_api_route("/my", get_my_games_list, methods=["GET"])
router.add_api_route("/active", get_active_game, methods=["GET"])
router.add_api_route("/running/hints", get_running_game_hints, methods=["GET"])
router.add_api_route("/{id}", get_game_card, methods=["GET"])
router.add_api_route("/{id}/files/{guid}", get_game_file, methods=["GET"])
return router
26 changes: 26 additions & 0 deletions shvatka/core/games/adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Protocol

from shvatka.core.interfaces.dal.file_info import FileInfoGetter
from shvatka.core.interfaces.dal.game import GameByIdGetter, ActiveGameFinder
from shvatka.core.interfaces.dal.key_log import GameTeamKeyGetter
from shvatka.core.interfaces.dal.level import LevelByGameAndNumberGetter
from shvatka.core.interfaces.dal.level_times import LevelByTeamGetter
from shvatka.core.interfaces.dal.player import PlayerByUserGetter, TeamByPlayerGetter
from shvatka.core.interfaces.dal.waiver import WaiverChecker


class GameFileReader(FileInfoGetter, GameByIdGetter, PlayerByUserGetter, Protocol):
pass


class GamePlayReader(
ActiveGameFinder,
PlayerByUserGetter,
TeamByPlayerGetter,
WaiverChecker,
LevelByTeamGetter,
LevelByGameAndNumberGetter,
GameTeamKeyGetter,
Protocol,
):
pass
13 changes: 13 additions & 0 deletions shvatka/core/games/dto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from dataclasses import dataclass
from datetime import datetime

from shvatka.core.models import dto
from shvatka.core.models.dto import scn


@dataclass
class CurrentHints:
hints: list[scn.TimeHint]
typed_keys: list[dto.KeyTime]
level_number: int
started_at: datetime
40 changes: 37 additions & 3 deletions shvatka/core/games/interactors.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from datetime import datetime
from typing import BinaryIO

from shvatka.core.games.dto import CurrentHints
from shvatka.core.interfaces.clients.file_storage import FileGateway
from shvatka.core.interfaces.dal.complex import GameFileLoader
from shvatka.core.games.adapters import GameFileReader, GamePlayReader
from shvatka.core.models import dto
from shvatka.core.rules.game import check_can_read
from shvatka.core.services.scenario.files import check_file_meta_can_read
from shvatka.core.utils import exceptions
from shvatka.core.utils.datetime_utils import tz_utc


class FileReader:
def __init__(self, dao: GameFileLoader, file_gateway: FileGateway):
class GameFileReaderInteractor:
def __init__(self, dao: GameFileReader, file_gateway: FileGateway):
self.file_gateway = file_gateway
self.dao = dao

Expand All @@ -30,3 +33,34 @@ async def __call__(self, guid: str, game_id: int, user: dto.User) -> BinaryIO:
meta = await self.dao.get_by_guid(guid)
check_file_meta_can_read(player, meta, game)
return await self.file_gateway.get(meta)


class GamePlayReaderInteractor:
def __init__(self, dao: GamePlayReader):
self.dao = dao

async def __call__(self, user: dto.User) -> CurrentHints:
player = await self.dao.get_by_user(user)
team = await self.dao.get_team(player)
if not team:
raise exceptions.PlayerNotInTeam(
player=player,
user=user,
)
game = await self.dao.get_active_game()
if game is None:
raise exceptions.HaveNotActiveGame(game=game, user=user)
if not await self.dao.check_waiver(player, team, game):
raise exceptions.WaiverError(
team=team, game=game, player=player, text="игрок не заявлен на игру, но ввёл ключ"
)
level_time = await self.dao.get_current_level_time(team, game)
level = await self.dao.get_level_by_game_and_number(game, level_time.level_number)
hints = level.get_hints_for_timedelta(datetime.now(tz=tz_utc) - level_time.start_at)
keys = await self.dao.get_team_typed_keys(game, team, level_time.level_number)
return CurrentHints(
hints=hints,
typed_keys=keys,
level_number=level_time.level_number,
started_at=level_time.start_at,
)
7 changes: 1 addition & 6 deletions shvatka/core/interfaces/dal/complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
MaxGameNumberGetter,
GameNumberUpdater,
GameStatusCompleter,
GameByIdGetter,
)
from shvatka.core.interfaces.dal.key_log import TeamKeysMerger, GameKeyGetter
from shvatka.core.interfaces.dal.level_times import TeamLevelsMerger, LevelTimesGetter
from shvatka.core.interfaces.dal.organizer import OrgByPlayerGetter
from shvatka.core.interfaces.dal.player import TeamPlayersMerger, PlayerByUserGetter
from shvatka.core.interfaces.dal.player import TeamPlayersMerger
from shvatka.core.interfaces.dal.team import ForumTeamMerger, TeamRemover
from shvatka.core.interfaces.dal.waiver import WaiverMerger, GameWaiversGetter
from shvatka.core.models import dto
Expand Down Expand Up @@ -48,7 +47,3 @@ class GameCompleter(
class GamePackager(GameKeyGetter, LevelTimesGetter, GameWaiversGetter, FileInfoGetter, Protocol):
async def get_full(self, id_: int) -> dto.FullGame:
raise NotImplementedError


class GameFileLoader(FileInfoGetter, GameByIdGetter, PlayerByUserGetter, Protocol):
pass
10 changes: 3 additions & 7 deletions shvatka/core/interfaces/dal/game_play.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Iterable, Protocol

from shvatka.core.interfaces.dal.base import Committer
from shvatka.core.interfaces.dal.level_times import LevelByTeamGetter
from shvatka.core.interfaces.dal.organizer import GameOrgsGetter
from shvatka.core.interfaces.dal.waiver import WaiverChecker
from shvatka.core.models import dto, enums


Expand All @@ -16,10 +18,7 @@ async def get_poll_msg(self, team: dto.Team, game: dto.Game) -> int | None:
raise NotImplementedError


class GamePlayerDao(Committer, GameOrgsGetter, Protocol):
async def check_waiver(self, player: dto.Player, team: dto.Team, game: dto.Game) -> bool:
raise NotImplementedError

class GamePlayerDao(Committer, WaiverChecker, GameOrgsGetter, LevelByTeamGetter, Protocol):
async def is_key_duplicate(self, level: dto.Level, team: dto.Team, key: str) -> bool:
raise NotImplementedError

Expand Down Expand Up @@ -60,6 +59,3 @@ async def level_up(self, team: dto.Team, level: dto.Level, game: dto.Game) -> No

async def finish(self, game: dto.Game) -> None:
raise NotImplementedError

async def get_current_level_time(self, team: dto.Team, game: dto.Game) -> dto.LevelTime:
raise NotImplementedError
7 changes: 7 additions & 0 deletions shvatka/core/interfaces/dal/key_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ async def get_typed_keys_grouped(self, game: dto.Game) -> dict[dto.Team, list[dt
raise NotImplementedError


class GameTeamKeyGetter(Protocol):
async def get_team_typed_keys(
self, game: dto.Game, team: dto.Team, level_number: int
) -> list[dto.KeyTime]:
raise NotImplementedError


class TeamKeysMerger(Protocol):
async def replace_team_keys(self, primary: dto.Team, secondary: dto.Team):
raise NotImplementedError
Expand Down
5 changes: 5 additions & 0 deletions shvatka/core/interfaces/dal/level.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ async def unlink_all(self, game: dto.Game) -> None:
raise NotImplementedError


class LevelByGameAndNumberGetter(Protocol):
async def get_level_by_game_and_number(self, game: dto.Game, number: int) -> dto.Level:
raise NotImplementedError


class MyLevelsGetter(Protocol):
async def get_all_my(self, author: dto.Player) -> list[dto.Level]:
raise NotImplementedError
Expand Down
5 changes: 5 additions & 0 deletions shvatka/core/interfaces/dal/level_times.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ async def get_game_level_times_by_teams(
raise NotImplementedError


class LevelByTeamGetter(Protocol):
async def get_current_level_time(self, team: dto.Team, game: dto.Game) -> dto.LevelTime:
raise NotImplementedError


class TeamLevelsMerger(Protocol):
async def replace_team_levels(self, primary: dto.Team, secondary: dto.Team):
raise NotImplementedError
8 changes: 5 additions & 3 deletions shvatka/core/interfaces/dal/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ async def get_team_player(self, player: dto.Player) -> dto.TeamPlayer:
raise NotImplementedError


class PlayerTeamChecker(TeamPlayerGetter, Protocol):
async def have_team(self, player: dto.Player) -> bool:
class TeamByPlayerGetter(Protocol):
async def get_team(self, player: dto.Player) -> dto.Team | None:
raise NotImplementedError

async def get_team(self, player: dto.Player) -> dto.Team | None:

class PlayerTeamChecker(TeamPlayerGetter, TeamByPlayerGetter, Protocol):
async def have_team(self, player: dto.Player) -> bool:
raise NotImplementedError


Expand Down
5 changes: 5 additions & 0 deletions shvatka/core/interfaces/dal/waiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,8 @@ async def get_all_by_game(self, game: dto.Game) -> list[dto.Waiver]:
class WaiverMerger(Protocol):
async def replace_team_waiver(self, primary: dto.Team, secondary: dto.Team):
raise NotImplementedError


class WaiverChecker(Protocol):
async def check_waiver(self, player: dto.Player, team: dto.Team, game: dto.Game) -> bool:
raise NotImplementedError
4 changes: 4 additions & 0 deletions shvatka/core/models/dto/level.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta

from .player import Player
from .scn.level import LevelScenario, BonusKey
Expand Down Expand Up @@ -37,3 +38,6 @@ def get_guids(self) -> list[str]:
@property
def hints_count(self) -> int:
return self.scenario.hints_count

def get_hints_for_timedelta(self, delta: timedelta) -> list[TimeHint]:
return self.scenario.get_hints_for_timedelta(delta)
5 changes: 5 additions & 0 deletions shvatka/core/models/dto/scn/level.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import typing
from dataclasses import dataclass, field
from datetime import timedelta

from .time_hint import TimeHint

Expand Down Expand Up @@ -48,3 +49,7 @@ def get_guids(self) -> list[str]:
@property
def hints_count(self) -> int:
return sum(time_hint.hints_count for time_hint in self.time_hints)

def get_hints_for_timedelta(self, delta: timedelta) -> list[TimeHint]:
minutes = delta.total_seconds() // 60
return [th for th in self.time_hints if th.time <= minutes]
9 changes: 0 additions & 9 deletions shvatka/core/services/game_play.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,6 @@ async def send_hint(
await scheduler.plain_hint(level, team, next_hint_number, next_hint_time)


async def get_available_hints(
game: dto.Game, team: dto.Team, dao: GamePlayerDao
) -> list[scn.TimeHint]:
level_time = await dao.get_current_level_time(team=team, game=game)
level = await dao.get_current_level(team=team, game=game)
from_start_level_minutes = (datetime.now(tz=tz_utc) - level_time.start_at).seconds // 60
return list(filter(lambda th: th.time <= from_start_level_minutes, level.scenario.time_hints))


async def schedule_first_hint(
scheduler: Scheduler,
team: dto.Team,
Expand Down
Loading

0 comments on commit 2ddb0f4

Please sign in to comment.