55import datetime
66import logging
77import random
8+ from typing import List , Optional , Set , cast
89
910from nicegui import ui
1011
1819 TILE_UNCLICKED_BG_COLOR ,
1920 TILE_UNCLICKED_TEXT_COLOR ,
2021)
22+ from src .types .ui_types import (
23+ BingoPattern ,
24+ BingoPatterns ,
25+ BoardType ,
26+ BoardViews ,
27+ ClickedTiles ,
28+ Coordinate ,
29+ TileLabelInfo ,
30+ TileButtonsDict ,
31+ )
2132from src .utils .text_processing import get_line_style_for_lines , split_phrase_into_lines
2233
2334# Global variables for game state
24- board = [] # 2D array of phrases
25- clicked_tiles = set () # Set of (row, col) tuples that are clicked
26- bingo_patterns = set () # Set of winning patterns found
27- board_iteration = 1
28- is_game_closed = False
29- today_seed = None
35+ board : BoardType = [] # 2D array of phrases
36+ clicked_tiles : ClickedTiles = set () # Set of (row, col) tuples that are clicked
37+ bingo_patterns : BingoPatterns = set () # Set of winning patterns found
38+ board_iteration : int = 1
39+ is_game_closed : bool = False
40+ today_seed : Optional [ str ] = None
3041
3142# Global variables for UI references (initialized in the UI module)
32- header_label = None
33- controls_row = None
34- seed_label = None
35- board_views = {} # Dictionary mapping view name to (container, tile_buttons) tuple
43+ header_label : Optional [ui .label ] = None
44+ controls_row : Optional [ui .row ] = None
45+ seed_label : Optional [ui .label ] = None
46+ board_views : BoardViews = (
47+ {}
48+ ) # Dictionary mapping view name to (container, tile_buttons) tuple
3649
3750
38- def generate_board (seed_val : int , phrases ) :
51+ def generate_board (seed_val : int , phrases : List [ str ]) -> BoardType :
3952 """
4053 Generate a new board using the provided seed value.
4154 Also resets the clicked_tiles (ensuring the FREE SPACE is clicked) and sets the global today_seed.
55+
56+ Args:
57+ seed_val: Integer used to seed the random generator
58+ phrases: List of phrases to use in the board
59+
60+ Returns:
61+ The generated board as a 2D array of phrases
4262 """
4363 global board , today_seed , clicked_tiles
4464
@@ -61,18 +81,22 @@ def generate_board(seed_val: int, phrases):
6181 return board
6282
6383
64- def toggle_tile (row , col ) :
84+ def toggle_tile (row : int , col : int ) -> None :
6585 """
6686 Toggle the state of a tile (clicked/unclicked).
6787 Updates the UI and checks for winner.
88+
89+ Args:
90+ row: Row index of the tile to toggle
91+ col: Column index of the tile to toggle
6892 """
6993 global clicked_tiles
7094
7195 # Don't allow toggling the free space
7296 if (row , col ) == (2 , 2 ):
7397 return
7498
75- key = (row , col )
99+ key : Coordinate = (row , col )
76100 if key in clicked_tiles :
77101 clicked_tiles .remove (key )
78102 else :
@@ -90,18 +114,22 @@ def toggle_tile(row, col):
90114 new_card_style = f"background-color: { TILE_UNCLICKED_BG_COLOR } ; color: { TILE_UNCLICKED_TEXT_COLOR } ; border: none;"
91115 new_label_color = TILE_UNCLICKED_TEXT_COLOR
92116
93- tile ["card" ].style (new_card_style )
117+ card = cast (ui .card , tile ["card" ])
118+ card .style (new_card_style )
119+
94120 lines = split_phrase_into_lines (phrase )
95121 line_count = len (lines )
96122 new_label_style = get_line_style_for_lines (line_count , new_label_color )
97123
98- for label_info in tile ["labels" ]:
99- lbl = label_info ["ref" ]
100- lbl .classes (label_info ["base_classes" ])
124+ label_list = cast (List [TileLabelInfo ], tile ["labels" ])
125+ for label_info in label_list :
126+ lbl = cast (ui .label , label_info ["ref" ])
127+ base_classes = cast (str , label_info ["base_classes" ])
128+ lbl .classes (base_classes )
101129 lbl .style (new_label_style )
102130 lbl .update ()
103131
104- tile [ " card" ] .update ()
132+ card .update ()
105133
106134 container .update ()
107135
@@ -119,12 +147,12 @@ def toggle_tile(row, col):
119147 logging .debug (f"JavaScript execution failed: { e } " )
120148
121149
122- def check_winner ():
150+ def check_winner () -> None :
123151 """
124152 Check for Bingo win condition and update the UI accordingly.
125153 """
126154 global bingo_patterns
127- new_patterns = []
155+ new_patterns : List [ BingoPattern ] = []
128156
129157 # Check rows and columns.
130158 for i in range (5 ):
@@ -158,7 +186,9 @@ def check_winner():
158186 new_patterns .append ("four_corners" )
159187
160188 # Plus shape: complete center row and center column.
161- plus_cells = {(2 , c ) for c in range (5 )} | {(r , 2 ) for r in range (5 )}
189+ plus_cells : Set [Coordinate ] = {(2 , c ) for c in range (5 )} | {
190+ (r , 2 ) for r in range (5 )
191+ }
162192 if all (cell in clicked_tiles for cell in plus_cells ):
163193 if "plus" not in bingo_patterns :
164194 new_patterns .append ("plus" )
@@ -171,7 +201,7 @@ def check_winner():
171201 new_patterns .append ("x_shape" )
172202
173203 # Outside edges (perimeter): all border cells clicked.
174- perimeter_cells = (
204+ perimeter_cells : Set [ Coordinate ] = (
175205 {(0 , c ) for c in range (5 )}
176206 | {(4 , c ) for c in range (5 )}
177207 | {(r , 0 ) for r in range (5 )}
@@ -183,15 +213,24 @@ def check_winner():
183213
184214 if new_patterns :
185215 # Separate new win patterns into standard and special ones.
186- special_set = {"blackout" , "four_corners" , "plus" , "x_shape" , "perimeter" }
187- standard_new = [p for p in new_patterns if p not in special_set ]
188- special_new = [p for p in new_patterns if p in special_set ]
216+ special_set : Set [str ] = {
217+ "blackout" ,
218+ "four_corners" ,
219+ "plus" ,
220+ "x_shape" ,
221+ "perimeter" ,
222+ }
223+ standard_new : List [BingoPattern ] = [
224+ p for p in new_patterns if p not in special_set
225+ ]
226+ special_new : List [BingoPattern ] = [p for p in new_patterns if p in special_set ]
189227
190228 # Process standard win conditions (rows, columns, diagonals).
191229 if standard_new :
192230 for pattern in standard_new :
193231 bingo_patterns .add (pattern )
194- standard_total = sum (1 for p in bingo_patterns if p not in special_set )
232+ standard_total : int = sum (1 for p in bingo_patterns if p not in special_set )
233+ message : str
195234 if standard_total == 1 :
196235 message = "BINGO!"
197236 elif standard_total == 2 :
@@ -210,11 +249,11 @@ def check_winner():
210249 for sp in special_new :
211250 bingo_patterns .add (sp )
212251 # Format the name to title-case and append "Bingo!"
213- sp_message = sp .replace ("_" , " " ).title () + " Bingo!"
252+ sp_message : str = sp .replace ("_" , " " ).title () + " Bingo!"
214253 ui .notify (sp_message , color = "blue" , duration = 5 )
215254
216255
217- def reset_board ():
256+ def reset_board () -> None :
218257 """
219258 Reset the board by clearing all clicked states, clearing winning patterns,
220259 and re-adding the FREE SPACE.
@@ -228,9 +267,12 @@ def reset_board():
228267 clicked_tiles .add ((r , c ))
229268
230269
231- def generate_new_board (phrases ) :
270+ def generate_new_board (phrases : List [ str ]) -> None :
232271 """
233272 Generate a new board with an incremented iteration seed and update all board views.
273+
274+ Args:
275+ phrases: List of phrases to use for the board
234276 """
235277 global board_iteration
236278 board_iteration += 1
@@ -246,14 +288,14 @@ def generate_new_board(phrases):
246288 container .update ()
247289
248290 # Update the seed label if available
249- if " seed_label" in globals () and seed_label :
291+ if seed_label is not None :
250292 seed_label .set_text (f"Seed: { today_seed } " )
251293 seed_label .update ()
252294
253295 reset_board ()
254296
255297
256- def close_game ():
298+ def close_game () -> None :
257299 """
258300 Close the game - show closed message instead of the board and update the header text.
259301 This function is called when the close button is clicked.
@@ -262,7 +304,7 @@ def close_game():
262304 is_game_closed = True
263305
264306 # Update header text on the current view
265- if header_label :
307+ if header_label is not None :
266308 header_label .set_text (CLOSED_HEADER_TEXT )
267309 header_label .update ()
268310
@@ -277,7 +319,7 @@ def close_game():
277319 container .update ()
278320
279321 # Modify the controls row to only show the New Board button
280- if controls_row :
322+ if controls_row is not None :
281323 controls_row .clear ()
282324 with controls_row :
283325 with ui .button ("" , icon = "autorenew" , on_click = reopen_game ).classes (
@@ -298,7 +340,7 @@ def close_game():
298340 ui .notify ("Game has been closed" , color = "red" , duration = 3 )
299341
300342
301- def reopen_game ():
343+ def reopen_game () -> None :
302344 """
303345 Reopen the game after it has been closed.
304346 This regenerates a new board and resets the UI.
@@ -309,22 +351,22 @@ def reopen_game():
309351 is_game_closed = False
310352
311353 # Update header text back to original for the current view
312- if header_label :
354+ if header_label is not None :
313355 header_label .set_text (HEADER_TEXT )
314356 header_label .update ()
315357
316358 # Generate a new board
317359 from src .utils .file_operations import read_phrases_file
318360
319- phrases = read_phrases_file ()
361+ phrases : List [ str ] = read_phrases_file ()
320362
321363 board_iteration += 1
322364 generate_board (board_iteration , phrases )
323365
324366 # Rebuild the controls row with all buttons
325367 from src .ui .controls import rebuild_controls_row
326368
327- if controls_row :
369+ if controls_row is not None :
328370 rebuild_controls_row (controls_row )
329371
330372 # Recreate and show all board views
0 commit comments