menu.pyΒΆ
Part of Tic Tac Toe.
1"""TicTacToe: Main menu with score tracking.
2
3Provides a 2D UI menu that wraps the game, tracking wins/draws across
4multiple rounds. Demonstrates VBoxContainer, Label, Button, and signal-driven
5navigation between menu and gameplay.
6"""
7
8from simvx.core import Anchor, Button, Label, Node, Signal, VBoxContainer
9from simvx.core.math.types import Vec2
10
11
12class ScoreBoard:
13 """Tracks wins/draws for the current session."""
14
15 __slots__ = ("x_wins", "o_wins", "draws")
16
17 def __init__(self):
18 self.x_wins = 0
19 self.o_wins = 0
20 self.draws = 0
21
22 @property
23 def total(self) -> int:
24 return self.x_wins + self.o_wins + self.draws
25
26 def record(self, winner: str | None):
27 if winner == "X":
28 self.x_wins += 1
29 elif winner == "O":
30 self.o_wins += 1
31 else:
32 self.draws += 1
33
34 def summary(self) -> str:
35 return f"X: {self.x_wins} | O: {self.o_wins} | Draws: {self.draws}"
36
37
38# ============================================================================
39# Colours
40# ============================================================================
41
42_ACCENT = (0.3, 0.7, 1.0, 1.0)
43_GOLD = (1.0, 0.85, 0.3, 1.0)
44_BTN_BG = (0.15, 0.25, 0.4, 1.0)
45_BTN_HOVER = (0.2, 0.35, 0.55, 1.0)
46_BTN_PRESSED = (0.1, 0.18, 0.3, 1.0)
47_BTN_BORDER = (0.35, 0.55, 0.8, 1.0)
48_SUBTLE = (0.5, 0.55, 0.65, 1.0)
49
50
51class MainMenu(Node):
52 """Full-screen main menu with Play button, scores, and quit.
53
54 The layout is anchor-driven, so it stays centred at any window size.
55
56 Signals:
57 play_pressed: emitted when the user clicks Play.
58 quit_pressed: emitted when the user clicks Quit.
59 """
60
61 def __init__(self, scores: ScoreBoard, **kw):
62 super().__init__(**kw)
63 self.name = "MainMenu"
64 self.play_pressed = Signal()
65 self.quit_pressed = Signal()
66 self._scores = scores
67
68 def on_ready(self):
69 w = 360
70 h = 430
71
72 # Horizontally centred, vertically centred on the viewport so the menu
73 # scales with window resize instead of clipping at a fixed 400x550.
74 root = VBoxContainer(name="MenuLayout", anchor=Anchor.CENTER, size=(w, h))
75 root.separation = 14
76
77 # Title
78 title = Label("TIC TAC TOE", name="Title")
79 title.font_size = 32.0
80 title.text_colour = _GOLD
81 title.alignment = "centre"
82 title.size = Vec2(w, 44)
83 root.add_child(title)
84
85 # Subtitle
86 sub = Label("Two Player", name="Subtitle")
87 sub.font_size = 16.0
88 sub.text_colour = _ACCENT
89 sub.alignment = "centre"
90 sub.size = Vec2(w, 24)
91 root.add_child(sub)
92
93 # Spacer
94 spacer = Label("", name="Spacer1")
95 spacer.size = Vec2(w, 20)
96 root.add_child(spacer)
97
98 # Score display
99 score_label = Label(self._scores.summary(), name="Scores")
100 score_label.font_size = 16.0
101 score_label.text_colour = (0.85, 0.9, 1.0, 1.0)
102 score_label.alignment = "centre"
103 score_label.size = Vec2(w, 24)
104 root.add_child(score_label)
105
106 games_label = Label(f"Games played: {self._scores.total}", name="GamesPlayed")
107 games_label.font_size = 12.0
108 games_label.text_colour = _SUBTLE
109 games_label.alignment = "centre"
110 games_label.size = Vec2(w, 18)
111 root.add_child(games_label)
112
113 # Spacer
114 spacer2 = Label("", name="Spacer2")
115 spacer2.size = Vec2(w, 30)
116 root.add_child(spacer2)
117
118 # Play button
119 play_btn = Button("Play", name="PlayBtn")
120 play_btn.size = Vec2(w, 50)
121 play_btn.font_size = 22.0
122 play_btn.text_colour = (1.0, 1.0, 1.0, 1.0)
123 play_btn.bg_colour = _BTN_BG
124 play_btn.hover_colour = _BTN_HOVER
125 play_btn.pressed_colour = _BTN_PRESSED
126 play_btn.border_colour = _BTN_BORDER
127 play_btn.pressed.connect(self.play_pressed.emit)
128 root.add_child(play_btn)
129
130 # Spacer
131 spacer3 = Label("", name="Spacer3")
132 spacer3.size = Vec2(w, 8)
133 root.add_child(spacer3)
134
135 # Quit button
136 quit_btn = Button("Quit", name="QuitBtn")
137 quit_btn.size = Vec2(w, 40)
138 quit_btn.font_size = 16.0
139 quit_btn.text_colour = (0.7, 0.7, 0.75, 1.0)
140 quit_btn.bg_colour = (0.12, 0.12, 0.16, 1.0)
141 quit_btn.hover_colour = (0.2, 0.12, 0.14, 1.0)
142 quit_btn.pressed_colour = (0.1, 0.08, 0.1, 1.0)
143 quit_btn.border_colour = (0.3, 0.3, 0.35, 1.0)
144 quit_btn.pressed.connect(self.quit_pressed.emit)
145 root.add_child(quit_btn)
146
147 # Footer
148 footer = Label("Built with SimVX Engine", name="Footer")
149 footer.font_size = 10.0
150 footer.text_colour = (0.35, 0.38, 0.45, 1.0)
151 footer.alignment = "centre"
152 footer.size = Vec2(w, 16)
153 root.add_child(footer)
154
155 self.add_child(root)