9
9
10
10
SELECTOR_SPRITE = 9
11
11
EMPTY_SPRITE = 10
12
- DEBOUNCE_TIME = 0.1 # seconds for debouncing mouse clicks
12
+ DEBOUNCE_TIME = 0.2 # seconds for debouncing mouse clicks
13
13
14
14
class GameBoard :
15
15
"Contains the game board"
@@ -42,6 +42,10 @@ def reset(self):
42
42
for row in range (self .rows ):
43
43
if self ._game_grid [(column , row )] != EMPTY_SPRITE :
44
44
self .remove_game_piece (column , row )
45
+ # Hide the animation TileGrids
46
+ self ._selector .hidden = True
47
+ self ._swap_piece .hidden = True
48
+ self .selected_piece_group .hidden = True
45
49
46
50
def move_game_piece (self , old_x , old_y , new_x , new_y ):
47
51
if 0 <= old_x < self .columns and 0 <= old_y < self .rows :
@@ -153,18 +157,41 @@ def game_grid_copy(self):
153
157
154
158
class GameLogic :
155
159
"Contains the Logic to examine the game board and determine if a move is valid."
156
- def __init__ (self , display , game_grid , swap_piece , selected_piece_group , game_pieces ):
160
+ def __init__ (self , display , mouse , game_grid , swap_piece ,
161
+ selected_piece_group , game_pieces , hint_timeout ):
157
162
self ._display = display
163
+ self ._mouse = mouse
158
164
self .game_board = GameBoard (game_grid , swap_piece , selected_piece_group )
159
165
self ._score = 0
160
166
self ._available_moves = []
161
167
if not 3 <= game_pieces <= 8 :
162
168
raise ValueError ("game_pieces must be between 3 and 8" )
163
169
self ._game_pieces = game_pieces # Number of different game pieces
170
+ self ._hint_timeout = hint_timeout
164
171
self ._last_update_time = ticks_ms () # For hint timing
165
172
self ._last_click_time = ticks_ms () # For debouncing mouse clicks
173
+ self .pressed_btns = None
174
+ self .waiting_for_release = False
166
175
167
- def piece_clicked (self , coords ):
176
+ def update_mouse (self ):
177
+ self .pressed_btns = self ._mouse .update ()
178
+ if self .waiting_for_release and not self .pressed_btns :
179
+ # If both buttons are released, we can process the next click
180
+ self .waiting_for_release = False
181
+
182
+ def update (self ):
183
+ gb = self .game_board
184
+ if (gb .x <= self ._mouse .x <= gb .x + gb .columns * 32 and
185
+ gb .y <= self ._mouse .y <= gb .y + gb .rows * 32 and
186
+ not self .waiting_for_release ):
187
+ piece_coords = ((self ._mouse .x - gb .x ) // 32 , (self ._mouse .y - gb .y ) // 32 )
188
+ if self .pressed_btns and "left" in self .pressed_btns :
189
+ self ._piece_clicked (piece_coords )
190
+ self .waiting_for_release = True
191
+ if self .time_since_last_update > self ._hint_timeout :
192
+ self .show_hint ()
193
+
194
+ def _piece_clicked (self , coords ):
168
195
""" Handle a piece click event. """
169
196
if ticks_ms () <= self ._last_click_time :
170
197
self ._last_click_time -= 2 ** 29 # ticks_ms() wraps around after 2**29 ms
@@ -206,7 +233,7 @@ def piece_clicked(self, coords):
206
233
if ((abs (previous_x - column ) == 1 and previous_y == row ) or
207
234
(previous_x == column and abs (previous_y - row ) == 1 )):
208
235
# Swap the pieces
209
- self .swap_selected_piece (column , row )
236
+ self ._swap_selected_piece (column , row )
210
237
211
238
def show_hint (self ):
212
239
""" Show a hint by selecting a random available
@@ -216,21 +243,21 @@ def show_hint(self):
216
243
from_coords = move ['from' ]
217
244
to_coords = move ['to' ]
218
245
self .game_board .select_piece (from_coords [0 ], from_coords [1 ])
219
- self .animate_swap (to_coords [0 ], to_coords [1 ])
246
+ self ._animate_swap (to_coords [0 ], to_coords [1 ])
220
247
self .game_board .select_piece (from_coords [0 ], from_coords [1 ])
221
- self .animate_swap (to_coords [0 ], to_coords [1 ])
248
+ self ._animate_swap (to_coords [0 ], to_coords [1 ])
222
249
self ._last_update_time = ticks_ms () # Reset hint timer
223
250
224
- def swap_selected_piece (self , column , row ):
251
+ def _swap_selected_piece (self , column , row ):
225
252
""" Swap the selected piece with the piece at the specified column and row.
226
253
If the swap is not valid, revert to the previous selection. """
227
254
old_coords = self .game_board .selected_coords
228
- self .animate_swap (column , row )
229
- if not self .update ():
255
+ self ._animate_swap (column , row )
256
+ if not self ._update_board ():
230
257
self .game_board .select_piece (column , row , show_selector = False )
231
- self .animate_swap (old_coords [0 ], old_coords [1 ])
258
+ self ._animate_swap (old_coords [0 ], old_coords [1 ])
232
259
233
- def animate_swap (self , column , row ):
260
+ def _animate_swap (self , column , row ):
234
261
""" Copy the pieces to separate tilegrids, animate the swap, and update the game board. """
235
262
if 0 <= column < self .game_board .columns and 0 <= row < self .game_board .rows :
236
263
selected_coords = self .game_board .selected_coords
@@ -271,11 +298,12 @@ def animate_swap(self, column, row):
271
298
# Deselect the selected piece (which sets the value)
272
299
self .game_board .select_piece (column , row )
273
300
274
- def apply_gravity (self ):
301
+ def _apply_gravity (self ):
275
302
""" Go through each column from the bottom up and move pieces down
276
303
continue until there are no more pieces to move """
277
304
# pylint:disable=too-many-nested-blocks
278
305
while True :
306
+ self .pressed_btns = self ._mouse .update ()
279
307
moved = False
280
308
for x in range (self .game_board .columns ):
281
309
for y in range (self .game_board .rows - 1 , - 1 , - 1 ):
@@ -295,7 +323,7 @@ def apply_gravity(self):
295
323
if not moved :
296
324
break
297
325
298
- def check_for_matches (self ):
326
+ def _check_for_matches (self ):
299
327
""" Scan the game board for matches of 3 or more in a row or column """
300
328
matches = []
301
329
for x in range (self .game_board .columns ):
@@ -325,35 +353,37 @@ def check_for_matches(self):
325
353
matches .append (vertical_match )
326
354
return matches
327
355
328
- def update (self ):
356
+ def _update_board (self ):
329
357
""" Update the game logic, check for matches, and apply gravity. """
330
358
matches_found = False
331
359
multiplier = 1
332
- matches = self .check_for_matches ()
360
+ matches = self ._check_for_matches ()
333
361
while matches :
334
362
if matches :
335
363
for match in matches :
336
364
for x , y in match :
337
365
self .game_board .remove_game_piece (x , y )
338
366
self ._score += 10 * multiplier * len (matches ) * (len (match ) - 2 )
339
367
time .sleep (0.5 ) # Pause to show the match removal
340
- self .apply_gravity ()
368
+ self ._apply_gravity ()
341
369
matches_found = True
342
- matches = self .check_for_matches ()
370
+ matches = self ._check_for_matches ()
343
371
multiplier += 1
344
- self ._available_moves = self .find_all_possible_matches ()
372
+ self ._available_moves = self ._find_all_possible_matches ()
345
373
print (f"{ len (self ._available_moves )} available moves found." )
346
374
return matches_found
347
375
348
376
def reset (self ):
349
377
""" Reset the game board and score. """
378
+ print ("Reset started" )
350
379
self .game_board .reset ()
351
380
self ._score = 0
352
381
self ._last_update_time = ticks_ms ()
353
- self .apply_gravity ()
354
- self .update ()
382
+ self ._apply_gravity ()
383
+ self ._update_board ()
384
+ print ("Reset completed" )
355
385
356
- def check_match_after_move (self , row , column , direction , move_type = 'horizontal' ):
386
+ def _check_match_after_move (self , row , column , direction , move_type = 'horizontal' ):
357
387
""" Move the piece in a copy of the board to see if it creates a match."""
358
388
if move_type == 'horizontal' :
359
389
new_row , new_column = row , column + direction
@@ -371,23 +401,23 @@ def check_match_after_move(self, row, column, direction, move_type='horizontal')
371
401
new_grid [row ][column ], new_grid [new_row ][new_column ] = new_grid [new_row ][new_column ], piece
372
402
373
403
# Check for horizontal matches at the new position
374
- horizontal_match = self .check_horizontal_match (new_grid , new_row , new_column , piece )
404
+ horizontal_match = self ._check_horizontal_match (new_grid , new_row , new_column , piece )
375
405
376
406
# Check for vertical matches at the new position
377
- vertical_match = self .check_vertical_match (new_grid , new_row , new_column , piece )
407
+ vertical_match = self ._check_vertical_match (new_grid , new_row , new_column , piece )
378
408
379
409
# Also check the original position for matches after the swap
380
410
original_piece = new_grid [row ][column ]
381
- horizontal_match_orig = self .check_horizontal_match (new_grid , row , column , original_piece )
382
- vertical_match_orig = self .check_vertical_match (new_grid , row , column , original_piece )
411
+ horizontal_match_orig = self ._check_horizontal_match (new_grid , row , column , original_piece )
412
+ vertical_match_orig = self ._check_vertical_match (new_grid , row , column , original_piece )
383
413
384
414
all_matches = (horizontal_match + vertical_match +
385
415
horizontal_match_orig + vertical_match_orig )
386
416
387
417
return True , len (all_matches ) > 0
388
418
389
419
@staticmethod
390
- def check_horizontal_match (grid , row , column , piece ):
420
+ def _check_horizontal_match (grid , row , column , piece ):
391
421
"""Check for horizontal 3-in-a-row matches centered
392
422
around or including the given position."""
393
423
matches = []
@@ -406,7 +436,7 @@ def check_horizontal_match(grid, row, column, piece):
406
436
return matches
407
437
408
438
@staticmethod
409
- def check_vertical_match (grid , row , column , piece ):
439
+ def _check_vertical_match (grid , row , column , piece ):
410
440
"""Check for vertical 3-in-a-row matches centered around or including the given position."""
411
441
matches = []
412
442
rows = len (grid )
@@ -429,7 +459,7 @@ def check_for_game_over(self):
429
459
return True
430
460
return False
431
461
432
- def find_all_possible_matches (self ):
462
+ def _find_all_possible_matches (self ):
433
463
"""
434
464
Scan the entire game board to find all possible moves that would create a 3-in-a-row match.
435
465
"""
@@ -438,31 +468,35 @@ def find_all_possible_matches(self):
438
468
for row in range (self .game_board .rows ):
439
469
for column in range (self .game_board .columns ):
440
470
# Check move right
441
- can_move , creates_match = self .check_match_after_move (row , column , 1 , 'horizontal' )
471
+ can_move , creates_match = self ._check_match_after_move (
472
+ row , column , 1 , 'horizontal' )
442
473
if can_move and creates_match :
443
474
possible_moves .append ({
444
475
'from' : (column , row ),
445
476
'to' : (column + 1 , row ),
446
477
})
447
478
448
479
# Check move left
449
- can_move , creates_match = self .check_match_after_move (row , column , - 1 , 'horizontal' )
480
+ can_move , creates_match = self ._check_match_after_move (
481
+ row , column , - 1 , 'horizontal' )
450
482
if can_move and creates_match :
451
483
possible_moves .append ({
452
484
'from' : (column , row ),
453
485
'to' : (column - 1 , row ),
454
486
})
455
487
456
488
# Check move down
457
- can_move , creates_match = self .check_match_after_move (row , column , 1 , 'vertical' )
489
+ can_move , creates_match = self ._check_match_after_move (
490
+ row , column , 1 , 'vertical' )
458
491
if can_move and creates_match :
459
492
possible_moves .append ({
460
493
'from' : (column , row ),
461
494
'to' : (column , row + 1 ),
462
495
})
463
496
464
497
# Check move up
465
- can_move , creates_match = self .check_match_after_move (row , column , - 1 , 'vertical' )
498
+ can_move , creates_match = self ._check_match_after_move (
499
+ row , column , - 1 , 'vertical' )
466
500
if can_move and creates_match :
467
501
possible_moves .append ({
468
502
'from' : (column , row ),
0 commit comments