From 5db71bbc5e089c6653d9b7c8e260f515cbb6e9dd Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 7 Apr 2026 21:33:04 +0700 Subject: [PATCH] fix: address code review issues (listener leak, pause, shuffle, board validation) Co-Authored-By: Claude Sonnet 4.6 --- src/game/board.ts | 16 ++++++++++++++++ src/game/emoji.ts | 6 +++++- src/phaser/scenes/GameScene.ts | 24 ++++++++++++++---------- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/game/board.ts b/src/game/board.ts index d05fc24..a59ddc6 100644 --- a/src/game/board.ts +++ b/src/game/board.ts @@ -1,6 +1,7 @@ import { Board, Difficulty, TileData, Point } from "../types"; import { DIFFICULTY_CONFIGS } from "./constants"; import { getEmojisForDifficulty } from "./emoji"; +import { hasAnyValidMove } from "./pathfinder"; let nextTileId = 0; @@ -34,6 +35,21 @@ export function createBoard(difficulty: Difficulty): Board { board.push(row); } + // Validate at least one valid move exists; reshuffle if not + while (!hasAnyValidMove(board)) { + const allTiles = board.flat().filter((t): t is TileData => t !== null); + for (let i = allTiles.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [allTiles[i], allTiles[j]] = [allTiles[j], allTiles[i]]; + } + let idx2 = 0; + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + board[r][c] = allTiles[idx2++]; + } + } + } + return board; } diff --git a/src/game/emoji.ts b/src/game/emoji.ts index f465639..007b84f 100644 --- a/src/game/emoji.ts +++ b/src/game/emoji.ts @@ -12,6 +12,10 @@ export const EMOJI_POOL: string[] = [ export function getEmojisForDifficulty(difficulty: Difficulty): string[] { const count = DIFFICULTY_CONFIGS[difficulty].pairCount; - const shuffled = [...EMOJI_POOL].sort(() => Math.random() - 0.5); + const shuffled = [...EMOJI_POOL]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } return shuffled.slice(0, count); } diff --git a/src/phaser/scenes/GameScene.ts b/src/phaser/scenes/GameScene.ts index 4c788a5..09fd878 100644 --- a/src/phaser/scenes/GameScene.ts +++ b/src/phaser/scenes/GameScene.ts @@ -19,6 +19,8 @@ export class GameScene extends Phaser.Scene { private lineGraphics!: Phaser.GameObjects.Graphics; private isProcessing = false; private timerEvent: Phaser.Time.TimerEvent | null = null; + private hintHandler!: () => void; + private shuffleHandler!: () => void; constructor() { super({ key: "GameScene" }); @@ -35,8 +37,10 @@ export class GameScene extends Phaser.Scene { this.startTimer(); // Listen for external actions (hint, shuffle) - this.stateManager.on("hint", () => this.handleHint()); - this.stateManager.on("shuffle", () => this.handleShuffle()); + this.hintHandler = () => this.handleHint(); + this.shuffleHandler = () => this.handleShuffle(); + this.stateManager.on("hint", this.hintHandler); + this.stateManager.on("shuffle", this.shuffleHandler); } private renderBoard(): void { @@ -106,6 +110,7 @@ export class GameScene extends Phaser.Scene { } private onTileClick(pos: Point): void { + if (this.stateManager.getState().status !== "playing") return; const tile = this.board[pos.row][pos.col]; if (!tile) return; @@ -302,20 +307,19 @@ export class GameScene extends Phaser.Scene { private performShuffle(): void { this.clearSelection(); - this.board = shuffleBoard(this.board); + do { + this.board = shuffleBoard(this.board); + } while (getRemainingTiles(this.board).length > 0 && !hasAnyValidMove(this.board)); this.renderBoard(); - - if ( - getRemainingTiles(this.board).length > 0 && - !hasAnyValidMove(this.board) - ) { - this.performShuffle(); - } } destroy(): void { if (this.timerEvent) { this.timerEvent.destroy(); } + if (this.stateManager) { + this.stateManager.off("hint", this.hintHandler); + this.stateManager.off("shuffle", this.shuffleHandler); + } } }