Skip to content

Commit 10d2312

Browse files
committed
feat: add matchmaking waiting cancellation (#45)
1 parent 7c4e1d7 commit 10d2312

File tree

8 files changed

+173
-15
lines changed

8 files changed

+173
-15
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from dataclasses import dataclass
2+
3+
from ttt.application.common.ports.map import Map
4+
from ttt.application.common.ports.transaction import Transaction
5+
from ttt.application.user.common.ports.user_views import CommonUserViews
6+
from ttt.application.user.common.ports.users import Users
7+
from ttt.application.user.game.ports.user_log import GameUserLog
8+
from ttt.application.user.game.ports.user_views import GameUserViews
9+
from ttt.entities.core.user.user import UserIsNotWaitingForMatchmakingError
10+
from ttt.entities.tools.tracking import Tracking
11+
12+
13+
@dataclass(frozen=True, unsafe_hash=False)
14+
class DontWaitForMatchmaking:
15+
map_: Map
16+
transaction: Transaction
17+
users: Users
18+
user_views: CommonUserViews
19+
views: GameUserViews
20+
log: GameUserLog
21+
22+
async def __call__(self, user_id: int) -> None:
23+
async with self.transaction:
24+
user = await self.users.user_with_id(user_id)
25+
26+
if user is None:
27+
await self.user_views.user_is_not_registered_view(user_id)
28+
return
29+
30+
try:
31+
tracking = Tracking()
32+
user.dont_wait_for_matchmaking(tracking)
33+
except UserIsNotWaitingForMatchmakingError:
34+
await self.log.user_is_not_waiting_for_matchmaking_to_dont_wait(
35+
user,
36+
)
37+
await (
38+
self.views
39+
.user_is_not_waiting_for_matchmaking_to_dont_wait_view(user)
40+
)
41+
else:
42+
await self.log.user_is_not_waiting_for_matchmaking(user)
43+
await self.map_(tracking)
44+
45+
await self.views.user_is_not_waiting_for_matchmaking_view(user)

src/ttt/application/user/game/ports/user_log.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ async def user_is_in_game_to_wait_for_matchmaking(
3333

3434
@abstractmethod
3535
async def games_were_matched(self, games: list[Game], /) -> None: ...
36+
37+
@abstractmethod
38+
async def user_is_not_waiting_for_matchmaking_to_dont_wait(
39+
self, user: User, /,
40+
) -> None: ...

src/ttt/application/user/game/ports/user_views.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,16 @@ async def user_is_in_game_to_wait_for_matchmaking_view(
2626

2727
@abstractmethod
2828
async def matched_games_view(self, games: list[Game], /) -> None: ...
29+
30+
@abstractmethod
31+
async def user_is_not_waiting_for_matchmaking_to_dont_wait_view(
32+
self, user: User, /,
33+
) -> None: ...
34+
35+
@abstractmethod
36+
async def user_is_not_waiting_for_matchmaking_view(
37+
self, user: User, /,
38+
) -> None: ...
39+
40+
@abstractmethod
41+
async def matchmaking_view(self, user_id: int, /) -> None: ...
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from dataclasses import dataclass
2+
3+
from ttt.application.common.ports.transaction import Transaction
4+
from ttt.application.user.game.ports.user_views import GameUserViews
5+
6+
7+
@dataclass(frozen=True, unsafe_hash=False)
8+
class ViewMatchmaking:
9+
transaction: Transaction
10+
views: GameUserViews
11+
12+
async def __call__(self, user_id: int) -> None:
13+
async with self.transaction:
14+
await self.views.matchmaking_view(user_id)

src/ttt/infrastructure/adapters/user_log.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,11 @@ async def _game_was_matched(self, game: Game) -> None:
394394
"game_was_matched",
395395
game_id=game.id.hex,
396396
)
397+
398+
async def user_is_not_waiting_for_matchmaking_to_dont_wait(
399+
self, user: User, /,
400+
) -> None:
401+
await self._logger.ainfo(
402+
"user_is_not_waiting_for_matchmaking_to_dont_wait",
403+
user_id=user.id,
404+
)

src/ttt/main/tg_bot/di.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@
105105
EmojiSelectionUserViews,
106106
)
107107
from ttt.application.user.emoji_selection.select_emoji import SelectEmoji
108+
from ttt.application.user.game.dont_wait_for_matchmaking import (
109+
DontWaitForMatchmaking,
110+
)
108111
from ttt.application.user.game.matchmake import Matchmake
109112
from ttt.application.user.game.ports.user_views import GameUserViews
113+
from ttt.application.user.game.view_matchmaking import ViewMatchmaking
110114
from ttt.application.user.game.wait_for_matchmaking import WaitForMatchmaking
111115
from ttt.application.user.register_user import RegisterUser
112116
from ttt.application.user.relinquish_admin_right import RelinquishAdminRight
@@ -378,6 +382,10 @@ class ApplicationProvider(Provider):
378382
provide_wait_for_matchmaking = provide(
379383
WaitForMatchmaking, scope=Scope.REQUEST,
380384
)
385+
provide_dont_wait_for_matchmaking = provide(
386+
DontWaitForMatchmaking, scope=Scope.REQUEST,
387+
)
388+
provide_view_matchmaking = provide(ViewMatchmaking, scope=Scope.REQUEST)
381389

382390
provide_start_stars_purchase = provide(
383391
StartStarsPurchase,

src/ttt/presentation/adapters/user_views.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
from ttt.presentation.aiogram_dialog.main_dialog.emojis_window import (
6262
EmojiMenuView,
6363
)
64+
from ttt.presentation.aiogram_dialog.main_dialog.game_start_window import (
65+
GameStartView,
66+
)
6467
from ttt.presentation.aiogram_dialog.main_dialog.game_window import (
6568
ActiveGameView,
6669
)
@@ -630,30 +633,21 @@ async def _account_view(
630633
class AiogramGameUserViews(GameUserViews):
631634
_dialog_manager_for_user: DialogManagerForUser
632635
_result_buffer: ResultBuffer
636+
_session: AsyncSession
633637

634638
async def user_is_waiting_for_matchmaking_view(
635639
self,
636640
user: User,
637641
/,
638642
) -> None:
639-
dialog_manager = self._dialog_manager_for_user(user.id)
640-
await dialog_manager.start(
641-
MainDialogState.game_mode_to_start_game,
642-
{"hint": "⚔️ Подбор начат"},
643-
StartMode.RESET_STACK,
644-
)
643+
...
645644

646645
async def user_is_already_waiting_for_matchmaking_view(
647646
self,
648647
user: User,
649648
/,
650649
) -> None:
651-
dialog_manager = self._dialog_manager_for_user(user.id)
652-
await dialog_manager.start(
653-
MainDialogState.game_mode_to_start_game,
654-
{"hint": "⚔️ Подбор начат"},
655-
StartMode.RESET_STACK,
656-
)
650+
...
657651

658652
async def user_is_in_game_to_wait_for_matchmaking_view(
659653
self, user: User, /,
@@ -683,3 +677,27 @@ async def _started_game_view_for_user(
683677
ActiveGameView.of(game, user_id).window_data(),
684678
StartMode.RESET_STACK,
685679
)
680+
681+
async def user_is_not_waiting_for_matchmaking_to_dont_wait_view(
682+
self, user: User, /,
683+
) -> None:
684+
...
685+
686+
async def user_is_not_waiting_for_matchmaking_view(
687+
self, user: User, /,
688+
) -> None:
689+
...
690+
691+
async def matchmaking_view(self, user_id: int, /) -> None:
692+
stmt = (
693+
select(TableUser.has_matchmaking_waiting)
694+
.where(TableUser.id == user_id)
695+
)
696+
has_matchmaking_waiting = await self._session.scalar(stmt)
697+
698+
if has_matchmaking_waiting is None:
699+
self._result_buffer.result = None
700+
else:
701+
self._result_buffer.result = GameStartView(
702+
is_user_waiting_for_matchmaking=has_matchmaking_waiting,
703+
)

src/ttt/presentation/aiogram_dialog/main_dialog/game_start_window.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
from dataclasses import dataclass
2+
from typing import Any
3+
14
from aiogram.types import CallbackQuery
5+
from aiogram.types.user import User
26
from aiogram_dialog import DialogManager, Window
37
from aiogram_dialog.widgets.kbd import (
48
Button,
@@ -10,16 +14,27 @@
1014
from dishka.integrations.aiogram_dialog import inject
1115
from magic_filter import F
1216

17+
from ttt.application.user.game.dont_wait_for_matchmaking import (
18+
DontWaitForMatchmaking,
19+
)
20+
from ttt.application.user.game.view_matchmaking import ViewMatchmaking
1321
from ttt.application.user.game.wait_for_matchmaking import WaitForMatchmaking
22+
from ttt.presentation.aiogram_dialog.common.data import EncodableToWindowData
1423
from ttt.presentation.aiogram_dialog.common.wigets.hint import hint
1524
from ttt.presentation.aiogram_dialog.common.wigets.one_time_key import (
1625
OneTimekey,
1726
)
1827
from ttt.presentation.aiogram_dialog.main_dialog.common import MainDialogState
28+
from ttt.presentation.result_buffer import ResultBuffer
29+
30+
31+
@dataclass(frozen=True)
32+
class GameStartView(EncodableToWindowData):
33+
is_user_waiting_for_matchmaking: bool
1934

2035

2136
@inject
22-
async def on_matchmaking_clicked(
37+
async def on_wait_for_matchmaking_clicked(
2338
callback: CallbackQuery,
2439
_: Button,
2540
__: DialogManager,
@@ -28,14 +43,45 @@ async def on_matchmaking_clicked(
2843
await wait_for_matchmaking(callback.from_user.id)
2944

3045

46+
@inject
47+
async def on_dont_wait_for_matchmaking_clicked(
48+
callback: CallbackQuery,
49+
_: Button,
50+
__: DialogManager,
51+
dont_wait_for_matchmaking: FromDishka[DontWaitForMatchmaking],
52+
) -> None:
53+
await dont_wait_for_matchmaking(callback.from_user.id)
54+
55+
56+
@inject
57+
async def getter(
58+
*,
59+
event_from_user: User,
60+
view_matchmaking: FromDishka[ViewMatchmaking],
61+
result_buffer: FromDishka[ResultBuffer],
62+
**_: Any, # noqa: ANN401
63+
) -> dict[str, Any]:
64+
await view_matchmaking(event_from_user.id)
65+
view = result_buffer(GameStartView)
66+
67+
return view.window_data()
68+
69+
3170
game_start_window = Window(
3271
Const("⚔️ Выберите режим", when=~F["start_data"]["hint"]),
3372
hint(key="hint"),
3473
Row(
3574
Button(
3675
Const("🗡 Подбор игр"),
37-
id="matchmaking",
38-
on_click=on_matchmaking_clicked,
76+
id="wait_for_matchmaking",
77+
on_click=on_wait_for_matchmaking_clicked,
78+
when=~F["main"]["is_user_waiting_for_matchmaking"],
79+
),
80+
Button(
81+
Const("🗡❌ Отменить"),
82+
id="dont_wait_for_matchmaking",
83+
on_click=on_dont_wait_for_matchmaking_clicked,
84+
when=F["main"]["is_user_waiting_for_matchmaking"],
3985
),
4086
SwitchTo(
4187
Const("🤖 Играть с ИИ"),
@@ -54,4 +100,5 @@ async def on_matchmaking_clicked(
54100

55101
OneTimekey("hint"),
56102
state=MainDialogState.game_mode_to_start_game,
103+
getter=getter,
57104
)

0 commit comments

Comments
 (0)