Skip to content

Commit 1b3dba7

Browse files
committed
feat: use cell numbers for moves
1 parent d030aec commit 1b3dba7

File tree

8 files changed

+136
-65
lines changed

8 files changed

+136
-65
lines changed

src/ttt/application/game/make_move_in_game.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
NotCurrentPlayerError,
1616
)
1717
from ttt.entities.core.player.location import PlayerLocation
18-
from ttt.entities.math.vector import Vector
1918
from ttt.entities.tools.assertion import not_none
2019
from ttt.entities.tools.tracking import Tracking
2120

@@ -33,7 +32,7 @@ class MakeMoveInGame:
3332
async def __call__(
3433
self,
3534
location: PlayerLocation,
36-
cell_position: Vector,
35+
cell_number_int: int,
3736
) -> None:
3837
async with self.transaction:
3938
game = await self.games.game_with_game_location(location.player_id)
@@ -55,7 +54,7 @@ async def __call__(
5554
tracking = Tracking()
5655
game.make_move(
5756
location.player_id,
58-
cell_position,
57+
cell_number_int,
5958
game_result_id,
6059
random,
6160
tracking,

src/ttt/entities/core/game/cell.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass
22
from uuid import UUID
33

4+
from ttt.entities.core.game.cell_number import CellNumber
45
from ttt.entities.math.vector import Vector
56
from ttt.entities.tools.assertion import assert_
67
from ttt.entities.tools.tracking import Tracking
@@ -16,6 +17,9 @@ class Cell:
1617
board_position: Vector
1718
filler_id: int | None
1819

20+
def number(self) -> CellNumber:
21+
return CellNumber.of_board_position(self.board_position)
22+
1923
def is_filled(self) -> bool:
2024
return self.filler_id is not None
2125

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from dataclasses import dataclass
2+
from typing import ClassVar, cast
3+
4+
from ttt.entities.math.vector import Vector
5+
from ttt.entities.tools.assertion import assert_, not_none
6+
7+
8+
class InvalidCellNumberError(Exception): ...
9+
10+
11+
@dataclass(frozen=True)
12+
class CellNumber:
13+
"""
14+
:raises ttt.entities.core.game.cell_number.InvalidCellNumberError:
15+
"""
16+
17+
_int: int
18+
19+
_board_position_by_int: ClassVar = {
20+
1: (0, 0),
21+
2: (1, 0),
22+
3: (2, 0),
23+
4: (0, 1),
24+
5: (1, 1),
25+
6: (2, 1),
26+
7: (0, 2),
27+
8: (1, 2),
28+
9: (2, 2),
29+
}
30+
_int_by_board_position: ClassVar = dict(zip(
31+
_board_position_by_int.values(),
32+
_board_position_by_int.keys(),
33+
strict=True,
34+
))
35+
36+
def __post_init__(self) -> None:
37+
assert_(1 <= self._int <= 9, else_=InvalidCellNumberError) # noqa: PLR2004
38+
39+
def __int__(self) -> int:
40+
return self._int
41+
42+
@classmethod
43+
def of_board_position(cls, board_position: Vector) -> "CellNumber":
44+
int_ = CellNumber._int_by_board_position.get(board_position)
45+
int_ = cast(int, not_none(int_, InvalidCellNumberError))
46+
47+
return CellNumber(int_)
48+
49+
def board_position(self) -> "Vector":
50+
return cast(Vector, CellNumber._board_position_by_int[int(self)])

src/ttt/entities/core/game/game.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22
from dataclasses import dataclass
33
from enum import Enum, auto
44
from itertools import chain
5-
from typing import cast
65
from uuid import UUID
76

87
from ttt.entities.core.game.board import (
98
Board,
109
create_empty_board,
1110
is_board_standard,
1211
)
13-
from ttt.entities.core.game.cell import AlreadyFilledCellError, Cell
12+
from ttt.entities.core.game.cell import Cell
13+
from ttt.entities.core.game.cell_number import (
14+
CellNumber,
15+
InvalidCellNumberError,
16+
)
1417
from ttt.entities.core.player.player import (
1518
Player,
1619
PlayerAlreadyInGameError,
@@ -20,7 +23,7 @@
2023
from ttt.entities.math.random import Random
2124
from ttt.entities.math.vector import Vector
2225
from ttt.entities.text.emoji import Emoji
23-
from ttt.entities.tools.assertion import assert_, not_none
26+
from ttt.entities.tools.assertion import assert_, none, not_none
2427
from ttt.entities.tools.tracking import Tracking
2528

2629

@@ -78,9 +81,6 @@ def number_of_unfilled_cells(board: Matrix[Cell]) -> int:
7881
return sum(int(not cell.is_filled()) for cell in chain.from_iterable(board))
7982

8083

81-
type FilledCellPosition = None
82-
83-
8484
@dataclass
8585
class Game:
8686
"""
@@ -127,9 +127,7 @@ def cancel(
127127
:raises ttt.entities.core.game.game.NotPlayerError:
128128
"""
129129

130-
self.result = cast(
131-
GameResult, not_none(self.result, AlreadyCompletedGameError),
132-
)
130+
none(self.result, else_=AlreadyCompletedGameError)
133131
canceler = not_none(self._player(player_id), else_=NotPlayerError)
134132

135133
self.player1.leave_game(tracking)
@@ -145,7 +143,7 @@ def cancel(
145143
def make_move(
146144
self,
147145
player_id: int,
148-
cell_position: Vector | FilledCellPosition,
146+
cell_number_int: int,
149147
game_result_id: UUID,
150148
random: Random,
151149
tracking: Tracking,
@@ -167,8 +165,12 @@ def make_move(
167165
)
168166
assert_(current_player.id == player_id, else_=NotCurrentPlayerError())
169167

170-
if cell_position is None:
171-
raise AlreadyFilledCellError
168+
try:
169+
cell_number = CellNumber(cell_number_int)
170+
except InvalidCellNumberError as error:
171+
raise NoCellError from error
172+
else:
173+
cell_position = cell_number.board_position()
172174

173175
self._fill_cell(cell_position, player_id, tracking)
174176

src/ttt/entities/tools/assertion.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Literal, NoReturn, overload
1+
from typing import Any, Literal, NoReturn, overload
22

33

44
def not_none[ValueT](
@@ -11,6 +11,14 @@ def not_none[ValueT](
1111
raise else_
1212

1313

14+
def none(
15+
value: Any, # noqa: ANN401
16+
else_: Exception | type[Exception] = ValueError,
17+
) -> None:
18+
if value is not None:
19+
raise else_
20+
21+
1422
@overload
1523
def assert_(
1624
assertion: Literal[False],

src/ttt/presentation/aiogram/game/keyboards.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def game_keyboard(game: Game) -> ReplyKeyboardMarkup:
1212
return ReplyKeyboardMarkup(
1313
keyboard=kb,
1414
resize_keyboard=True,
15-
input_field_placeholder="Введите позицию закрашиваемой ячейки",
15+
input_field_placeholder="Введите номер закрашиваемой ячейки",
1616
)
1717

1818

@@ -26,8 +26,6 @@ def _game_keyboard_button(
2626
case game.player2.id:
2727
return KeyboardButton(text=game.player2_emoji.str_)
2828
case None:
29-
return KeyboardButton(text=(
30-
f"{cell.board_position[0] + 1} {cell.board_position[1] + 1}"
31-
))
29+
return KeyboardButton(text=f"{int(cell.number())}")
3230
case _:
3331
raise ValueError((cell.filler_id, game.player1.id, game.player2.id))

src/ttt/presentation/aiogram/game/routes/make_move_in_game.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
make_move_in_game_router = Router(name=__name__)
1414

1515

16-
@make_move_in_game_router.message(F.text.regexp(r"^-?\d+\s-?\d+$"))
16+
@make_move_in_game_router.message(F.text.regexp(r"^\d+$"))
1717
@inject
1818
async def _(
1919
message: Message,
@@ -23,10 +23,8 @@ async def _(
2323
await anons_are_rohibited_message(message)
2424
return
2525

26-
x, y = map(int, not_none(message.text).split()[:2])
27-
x -= 1
28-
y -= 1
26+
cell_number_int = int(not_none(message.text))
2927

3028
await make_move_in_game(
31-
PlayerLocation(message.from_user.id, message.chat.id), (x, y),
29+
PlayerLocation(message.from_user.id, message.chat.id), cell_number_int,
3230
)

0 commit comments

Comments
 (0)