Skip to content

Commit 8a1e29b

Browse files
committed
Refactor code into constants and menus modules
1 parent 30d5125 commit 8a1e29b

3 files changed

Lines changed: 144 additions & 126 deletions

File tree

game/constants.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Global constants are stored here."""
2+
3+
from typing import Final
4+
5+
from tcod.event import KeySym
6+
7+
DIRECTION_KEYS: Final = {
8+
# Arrow keys
9+
KeySym.LEFT: (-1, 0),
10+
KeySym.RIGHT: (1, 0),
11+
KeySym.UP: (0, -1),
12+
KeySym.DOWN: (0, 1),
13+
# Arrow key diagonals
14+
KeySym.HOME: (-1, -1),
15+
KeySym.END: (-1, 1),
16+
KeySym.PAGEUP: (1, -1),
17+
KeySym.PAGEDOWN: (1, 1),
18+
# Keypad
19+
KeySym.KP_4: (-1, 0),
20+
KeySym.KP_6: (1, 0),
21+
KeySym.KP_8: (0, -1),
22+
KeySym.KP_2: (0, 1),
23+
KeySym.KP_7: (-1, -1),
24+
KeySym.KP_1: (-1, 1),
25+
KeySym.KP_9: (1, -1),
26+
KeySym.KP_3: (1, 1),
27+
# VI keys
28+
KeySym.h: (-1, 0),
29+
KeySym.l: (1, 0),
30+
KeySym.k: (0, -1),
31+
KeySym.j: (0, 1),
32+
KeySym.y: (-1, -1),
33+
KeySym.b: (-1, 1),
34+
KeySym.u: (1, -1),
35+
KeySym.n: (1, 1),
36+
}

game/menus.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""Menu UI classes."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Callable
6+
from typing import Protocol
7+
8+
import attrs
9+
import tcod.console
10+
import tcod.event
11+
from tcod.event import KeySym
12+
13+
import game.state_tools
14+
from game.constants import DIRECTION_KEYS
15+
from game.state import Pop, State, StateResult
16+
17+
18+
class MenuItem(Protocol):
19+
"""Menu item protocol."""
20+
21+
__slots__ = ()
22+
23+
def on_event(self, event: tcod.event.Event) -> StateResult:
24+
"""Handle events passed to the menu item."""
25+
26+
def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None:
27+
"""Draw is item at the given position."""
28+
29+
30+
@attrs.define()
31+
class SelectItem(MenuItem):
32+
"""Clickable menu item."""
33+
34+
label: str
35+
callback: Callable[[], StateResult]
36+
37+
def on_event(self, event: tcod.event.Event) -> StateResult:
38+
"""Handle events selecting this item."""
39+
match event:
40+
case tcod.event.KeyDown(sym=sym) if sym in {KeySym.RETURN, KeySym.RETURN2, KeySym.KP_ENTER}:
41+
return self.callback()
42+
case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.LEFT):
43+
return self.callback()
44+
case _:
45+
return None
46+
47+
def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None:
48+
"""Render this items label."""
49+
console.print(x, y, self.label, fg=(255, 255, 255), bg=(64, 64, 64) if highlight else (0, 0, 0))
50+
51+
52+
@attrs.define()
53+
class ListMenu(State):
54+
"""Simple list menu state."""
55+
56+
items: tuple[MenuItem, ...]
57+
selected: int | None = 0
58+
x: int = 0
59+
y: int = 0
60+
61+
def on_event(self, event: tcod.event.Event) -> StateResult:
62+
"""Handle events for menus."""
63+
match event:
64+
case tcod.event.Quit():
65+
raise SystemExit()
66+
case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:
67+
dx, dy = DIRECTION_KEYS[sym]
68+
if dx != 0 or dy == 0:
69+
return self.activate_selected(event)
70+
if self.selected is not None:
71+
self.selected += dy
72+
self.selected %= len(self.items)
73+
else:
74+
self.selected = 0 if dy == 1 else len(self.items) - 1
75+
return None
76+
case tcod.event.MouseMotion(position=(_, y)):
77+
y -= self.y
78+
self.selected = y if 0 <= y < len(self.items) else None
79+
return None
80+
case tcod.event.KeyDown(sym=KeySym.ESCAPE):
81+
return self.on_cancel()
82+
case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.RIGHT):
83+
return self.on_cancel()
84+
case _:
85+
return self.activate_selected(event)
86+
87+
def activate_selected(self, event: tcod.event.Event) -> StateResult:
88+
"""Call the selected menu items callback."""
89+
if self.selected is not None:
90+
return self.items[self.selected].on_event(event)
91+
return None
92+
93+
def on_cancel(self) -> StateResult:
94+
"""Handle escape or right click being pressed on menus."""
95+
return Pop()
96+
97+
def on_draw(self, console: tcod.console.Console) -> None:
98+
"""Render the menu."""
99+
game.state_tools.draw_previous_state(self, console)
100+
for i, item in enumerate(self.items):
101+
item.on_draw(console, x=self.x, y=self.y + i, highlight=i == self.selected)

game/states.py

Lines changed: 7 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,20 @@
22

33
from __future__ import annotations
44

5-
from collections.abc import Callable
6-
from typing import Final, Protocol
7-
85
import attrs
96
import tcod.console
107
import tcod.constants
118
import tcod.event
129
from tcod.event import KeySym
1310

1411
import g
15-
import game.state_tools
12+
import game.menus
1613
import game.world_tools
1714
from game.components import Gold, Graphic, Position
18-
from game.state import Pop, Push, Reset, State, StateResult
15+
from game.constants import DIRECTION_KEYS
16+
from game.state import Push, Reset, State, StateResult
1917
from game.tags import IsItem, IsPlayer
2018

21-
DIRECTION_KEYS: Final = {
22-
# Arrow keys
23-
KeySym.LEFT: (-1, 0),
24-
KeySym.RIGHT: (1, 0),
25-
KeySym.UP: (0, -1),
26-
KeySym.DOWN: (0, 1),
27-
# Arrow key diagonals
28-
KeySym.HOME: (-1, -1),
29-
KeySym.END: (-1, 1),
30-
KeySym.PAGEUP: (1, -1),
31-
KeySym.PAGEDOWN: (1, 1),
32-
# Keypad
33-
KeySym.KP_4: (-1, 0),
34-
KeySym.KP_6: (1, 0),
35-
KeySym.KP_8: (0, -1),
36-
KeySym.KP_2: (0, 1),
37-
KeySym.KP_7: (-1, -1),
38-
KeySym.KP_1: (-1, 1),
39-
KeySym.KP_9: (1, -1),
40-
KeySym.KP_3: (1, 1),
41-
# VI keys
42-
KeySym.h: (-1, 0),
43-
KeySym.l: (1, 0),
44-
KeySym.k: (0, -1),
45-
KeySym.j: (0, 1),
46-
KeySym.y: (-1, -1),
47-
KeySym.b: (-1, 1),
48-
KeySym.u: (1, -1),
49-
KeySym.n: (1, 1),
50-
}
51-
5219

5320
@attrs.define(eq=False)
5421
class InGame(State):
@@ -87,105 +54,19 @@ def on_draw(self, console: tcod.console.Console) -> None:
8754
console.print(x=0, y=console.height - 1, string=text, fg=(255, 255, 255), bg=(0, 0, 0))
8855

8956

90-
class MenuItem(Protocol):
91-
"""Menu item protocol."""
92-
93-
__slots__ = ()
94-
95-
def on_event(self, event: tcod.event.Event) -> StateResult:
96-
"""Handle events passed to the menu item."""
97-
98-
def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None:
99-
"""Draw is item at the given position."""
100-
101-
102-
@attrs.define()
103-
class SelectItem(MenuItem):
104-
"""Clickable menu item."""
105-
106-
label: str
107-
callback: Callable[[], StateResult]
108-
109-
def on_event(self, event: tcod.event.Event) -> StateResult:
110-
"""Handle events selecting this item."""
111-
match event:
112-
case tcod.event.KeyDown(sym=sym) if sym in {KeySym.RETURN, KeySym.RETURN2, KeySym.KP_ENTER}:
113-
return self.callback()
114-
case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.LEFT):
115-
return self.callback()
116-
case _:
117-
return None
118-
119-
def on_draw(self, console: tcod.console.Console, x: int, y: int, highlight: bool) -> None:
120-
"""Render this items label."""
121-
console.print(x, y, self.label, fg=(255, 255, 255), bg=(64, 64, 64) if highlight else (0, 0, 0))
122-
123-
124-
@attrs.define(eq=False)
125-
class ListMenu(State):
126-
"""Simple list menu state."""
127-
128-
items: tuple[MenuItem, ...]
129-
selected: int | None = 0
130-
x: int = 0
131-
y: int = 0
132-
133-
def on_event(self, event: tcod.event.Event) -> StateResult:
134-
"""Handle events for menus."""
135-
match event:
136-
case tcod.event.Quit():
137-
raise SystemExit()
138-
case tcod.event.KeyDown(sym=sym) if sym in DIRECTION_KEYS:
139-
dx, dy = DIRECTION_KEYS[sym]
140-
if dx != 0 or dy == 0:
141-
return self.activate_selected(event)
142-
if self.selected is not None:
143-
self.selected += dy
144-
self.selected %= len(self.items)
145-
else:
146-
self.selected = 0 if dy == 1 else len(self.items) - 1
147-
return None
148-
case tcod.event.MouseMotion(position=(_, y)):
149-
y -= self.y
150-
self.selected = y if 0 <= y < len(self.items) else None
151-
return None
152-
case tcod.event.KeyDown(sym=KeySym.ESCAPE):
153-
return self.on_cancel()
154-
case tcod.event.MouseButtonUp(button=tcod.event.MouseButton.RIGHT):
155-
return self.on_cancel()
156-
case _:
157-
return self.activate_selected(event)
158-
159-
def activate_selected(self, event: tcod.event.Event) -> StateResult:
160-
"""Call the selected menu items callback."""
161-
if self.selected is not None:
162-
return self.items[self.selected].on_event(event)
163-
return None
164-
165-
def on_cancel(self) -> StateResult:
166-
"""Handle escape or right click being pressed on menus."""
167-
return Pop()
168-
169-
def on_draw(self, console: tcod.console.Console) -> None:
170-
"""Render the menu."""
171-
game.state_tools.draw_previous_state(self, console)
172-
for i, item in enumerate(self.items):
173-
item.on_draw(console, x=self.x, y=self.y + i, highlight=i == self.selected)
174-
175-
176-
class MainMenu(ListMenu):
57+
class MainMenu(game.menus.ListMenu):
17758
"""Main/escape menu."""
17859

17960
__slots__ = ()
18061

18162
def __init__(self) -> None:
18263
"""Initialize the main menu."""
18364
items = [
184-
SelectItem("New game", self.new_game),
185-
SelectItem("Quit", self.quit),
65+
game.menus.SelectItem("New game", self.new_game),
66+
game.menus.SelectItem("Quit", self.quit),
18667
]
18768
if hasattr(g, "world"):
188-
items.insert(0, SelectItem("Continue", self.continue_))
69+
items.insert(0, game.menus.SelectItem("Continue", self.continue_))
18970

19071
super().__init__(
19172
items=tuple(items),

0 commit comments

Comments
 (0)