From ebc08db84f2519958c8cee33695add347fec6dd0 Mon Sep 17 00:00:00 2001 From: RintaroAbe Date: Sat, 30 May 2026 23:40:32 +0900 Subject: [PATCH 1/5] fix a bug with opening first block --- frontend/src/components/Cell.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Cell.tsx b/frontend/src/components/Cell.tsx index 237cc45..2d6d894 100644 --- a/frontend/src/components/Cell.tsx +++ b/frontend/src/components/Cell.tsx @@ -1,7 +1,7 @@ import React from "react"; import { UserName } from "../App"; import type { Board, Cell as CellType} from "../types/types"; -import { size, numberOfMine, chainedblock} from "./Board"; +import { size, numberOfMine, chainedblock, firstblock} from "./Board"; import { posOfmine } from "../utils/board"; export let openedblock = 0; const API_BASE_URL = "https://1r2mypgiag.execute-api.ap-southeast-2.amazonaws.com/prod"; @@ -49,7 +49,7 @@ export const Cell: React.FC = ({ cell, cellSize, onClick, board, startTim } if(cell.isOpen) return; if(cell.isflagged) return; - if(cell.isMine){ + if(cell.isMine && firstblock){ const newBoard = board.map((row) => row.map((c) => ({ ...c }))); newBoard[cell.row][cell.col].openedMine = true; for(const mine of posOfmine){ From e4d81f53a51895de7cda0ab31f0cb98e6eb73370 Mon Sep 17 00:00:00 2001 From: RintaroAbe Date: Sat, 30 May 2026 23:51:20 +0900 Subject: [PATCH 2/5] refactor: move game state from module variables to React state/refs --- frontend/src/App.tsx | 5 +- frontend/src/components/Board.tsx | 87 ++++++++++++++++++------------- frontend/src/components/Cell.tsx | 41 +++------------ 3 files changed, 58 insertions(+), 75 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 79937a0..a36a17b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,8 +2,7 @@ import { useEffect, useState } from 'react'; import './App.css'; import { generateBoard } from './utils/board'; import type { Board } from './types/types'; -import { numberOfMine, size, BoardComponent, resetBoardState } from './components/Board'; -import { resetCellState } from "./components/Cell"; +import { numberOfMine, size, BoardComponent } from './components/Board'; export let UserName = ''; @@ -30,8 +29,6 @@ function App() { }; const restartGame = () => { - resetCellState(); - resetBoardState(); setBoard(generateBoard(size, size, numberOfMine)); setStart(Date.now()); setElapsedTime(0); diff --git a/frontend/src/components/Board.tsx b/frontend/src/components/Board.tsx index 05a8577..c2913fb 100644 --- a/frontend/src/components/Board.tsx +++ b/frontend/src/components/Board.tsx @@ -1,17 +1,13 @@ import React, { useRef, useState } from "react"; import type { Board } from "../types/types"; import { generateBoard } from "../utils/board"; -import {Cell} from "./Cell"; +import { Cell } from "./Cell"; +import { UserName } from "../App"; export const size = 15; export const numberOfMine = Math.floor(size * size / 10); -export let chainedblock = 0; -export let firstblock = false; -export function resetBoardState() { - chainedblock = 0; - firstblock = false; -} +const API_BASE_URL = "https://1r2mypgiag.execute-api.ap-southeast-2.amazonaws.com/prod"; interface Props { board: Board; @@ -31,36 +27,56 @@ const initialCellSize = calculateCellSize(); export const BoardComponent: React.FC = ({ board, setBoard, flaggingMode, onGameOver, onGameClear }) => { const [isGameActive, setIsGameActive] = useState(true); + const [hasClickedOnce, setHasClickedOnce] = useState(false); const startTimeRef = useRef(0); + const chainedblockRef = useRef(0); + const openedblockRef = useRef(0); - const handleGameClear = () => { - setIsGameActive(false); - onGameClear(); - }; const handleGameOver = () => { setIsGameActive(false); onGameOver(); }; + + const handleManualOpen = () => { + openedblockRef.current++; + if (chainedblockRef.current + openedblockRef.current + numberOfMine >= size * size) { + const timeTaken = Math.floor((Date.now() - startTimeRef.current) / 1000); + setIsGameActive(false); + onGameClear(); + alert("🎊 Game Clear!\n in " + timeTaken + " seconds!"); + fetch(`${API_BASE_URL}/scores`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + player_name: UserName, + time_taken: timeTaken, + blocks: (size * size), + }), + }) + .then(res => res.json()) + .then(data => console.log('Score API response:', data)) + .catch(err => console.error('API Error:', err)); + } + }; + const currentCellSize = initialCellSize; const handleClick = (r: number, c: number) => { if (!isGameActive) return; - if (!firstblock) { + if (!hasClickedOnce) { let newBoard: Board; do { newBoard = generateBoard(size, size, numberOfMine); } while (newBoard[r][c].isMine || newBoard[r][c].neighborMines !== 0); newBoard[r][c].isOpen = true; - firstblock = true; + setHasClickedOnce(true); startTimeRef.current = Date.now(); - if (newBoard[r][c].neighborMines === 0) { - chainOpen(newBoard, newBoard[r][c]); - } + chainedblockRef.current += chainOpen(newBoard, newBoard[r][c]); setBoard(newBoard); - }else{ + } else { const newBoard = board.map((row) => row.map((cell) => ({ ...cell }))); newBoard[r][c].isOpen = true; if (newBoard[r][c].neighborMines === 0 && !newBoard[r][c].isMine) { - chainOpen(newBoard, newBoard[r][c]); + chainedblockRef.current += chainOpen(newBoard, newBoard[r][c]); } setBoard(newBoard); } @@ -68,13 +84,11 @@ export const BoardComponent: React.FC = ({ board, setBoard, flaggingMode, return (
{board.map((row, r) => @@ -84,7 +98,7 @@ export const BoardComponent: React.FC = ({ board, setBoard, flaggingMode, cell={cell} cellSize={currentCellSize} board={board} - startTime={startTimeRef.current} + hasClickedOnce={hasClickedOnce} onClick={(newBoard) => { if (newBoard) { setBoard(newBoard); @@ -92,10 +106,10 @@ export const BoardComponent: React.FC = ({ board, setBoard, flaggingMode, handleClick(r, c); } }} - onGameClear={handleGameClear} onGameOver={handleGameOver} isGameActive={isGameActive} flaggingMode={flaggingMode} + onManualOpen={handleManualOpen} /> )) )} @@ -103,23 +117,22 @@ export const BoardComponent: React.FC = ({ board, setBoard, flaggingMode, ); }; - -function chainOpen(board: Board, cell: Board[0][0]): void { +function chainOpen(board: Board, cell: Board[0][0]): number { + let count = 0; const move = [-1, 0, 1]; - for(let i = 0; i < 3; i++){ - for(let j = 0; j < 3; j++){ - if(i === 1 && j === 1){ - continue; - } + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (i === 1 && j === 1) continue; const nr = cell.row + move[i], nc = cell.col + move[j]; - if(0 <= nr && nr < board.length && 0 <= nc && nc < board[0].length - && !board[nr][nc].isMine && !board[nr][nc].isOpen){ - chainedblock++; + if (0 <= nr && nr < board.length && 0 <= nc && nc < board[0].length + && !board[nr][nc].isMine && !board[nr][nc].isOpen) { board[nr][nc].isOpen = true; - if(board[nr][nc].neighborMines === 0) { - chainOpen(board, board[nr][nc]); + count++; + if (board[nr][nc].neighborMines === 0) { + count += chainOpen(board, board[nr][nc]); } } } } -} \ No newline at end of file + return count; +} diff --git a/frontend/src/components/Cell.tsx b/frontend/src/components/Cell.tsx index 2d6d894..27d6d65 100644 --- a/frontend/src/components/Cell.tsx +++ b/frontend/src/components/Cell.tsx @@ -1,28 +1,19 @@ import React from "react"; -import { UserName } from "../App"; import type { Board, Cell as CellType} from "../types/types"; -import { size, numberOfMine, chainedblock, firstblock} from "./Board"; import { posOfmine } from "../utils/board"; -export let openedblock = 0; -const API_BASE_URL = "https://1r2mypgiag.execute-api.ap-southeast-2.amazonaws.com/prod"; - type Props = { cell: CellType; cellSize: number; onClick: (newBoard?: Board) => void; board: Board; - startTime: number; - onGameClear: () => void; + hasClickedOnce: boolean; + onManualOpen: () => void; onGameOver: () => void; isGameActive: boolean; flaggingMode: boolean; }; -export function resetCellState() { - openedblock = 0; -} - const getCellText = (cell: CellType): string => { let display = ""; if(cell.isOpen){ @@ -38,7 +29,8 @@ const getCellText = (cell: CellType): string => { } return display; }; -export const Cell: React.FC = ({ cell, cellSize, onClick, board, startTime, onGameClear, onGameOver, isGameActive, flaggingMode}) => { + +export const Cell: React.FC = ({ cell, cellSize, onClick, board, hasClickedOnce, onManualOpen, onGameOver, isGameActive, flaggingMode}) => { const handleClick = () => { if(!isGameActive) return; if(flaggingMode){ @@ -49,7 +41,7 @@ export const Cell: React.FC = ({ cell, cellSize, onClick, board, startTim } if(cell.isOpen) return; if(cell.isflagged) return; - if(cell.isMine && firstblock){ + if(cell.isMine && hasClickedOnce){ const newBoard = board.map((row) => row.map((c) => ({ ...c }))); newBoard[cell.row][cell.col].openedMine = true; for(const mine of posOfmine){ @@ -62,26 +54,7 @@ export const Cell: React.FC = ({ cell, cellSize, onClick, board, startTim } onClick(); if(!cell.isOpen){ - openedblock++; - if(!(chainedblock + openedblock + numberOfMine < size * size)){ - const timeTaken = Math.floor((Date.now() - startTime) / 1000); - onGameClear(); - alert("🎊 Game Clear!\n in "+ timeTaken+" seconds!"); - fetch(`${API_BASE_URL}/scores`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - player_name: UserName, - time_taken: timeTaken, - blocks: (size * size), - }), - }) - .then(res => res.json()) - .then(data => console.log('Score API response:', data)) - .catch(err => console.error('API Error:', err)); - } + onManualOpen(); } } return ( @@ -101,4 +74,4 @@ export const Cell: React.FC = ({ cell, cellSize, onClick, board, startTim {getCellText(cell)} ); -}; \ No newline at end of file +}; From 81ce5685959b156ac2a2afc4f19899b0c2356701 Mon Sep 17 00:00:00 2001 From: RintaroAbe Date: Sat, 30 May 2026 23:55:53 +0900 Subject: [PATCH 3/5] refactor: replace module-level variables with React state/refs and fix circular import --- frontend/src/App.d.ts | 4 ---- frontend/src/App.tsx | 4 ++-- frontend/src/components/Board.d.ts | 13 ------------- frontend/src/components/Board.tsx | 12 ++++++------ frontend/src/components/Cell.d.ts | 16 ---------------- frontend/src/components/timer.d.ts | 6 ------ frontend/src/main.d.ts | 1 - 7 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 frontend/src/App.d.ts delete mode 100644 frontend/src/components/Board.d.ts delete mode 100644 frontend/src/components/Cell.d.ts delete mode 100644 frontend/src/components/timer.d.ts delete mode 100644 frontend/src/main.d.ts diff --git a/frontend/src/App.d.ts b/frontend/src/App.d.ts deleted file mode 100644 index 08238c7..0000000 --- a/frontend/src/App.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import './App.css'; -export declare let UserName: string; -declare function App(): import("react/jsx-runtime").JSX.Element; -export default App; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a36a17b..ee74ddd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,7 +3,6 @@ import './App.css'; import { generateBoard } from './utils/board'; import type { Board } from './types/types'; import { numberOfMine, size, BoardComponent } from './components/Board'; -export let UserName = ''; function App() { @@ -18,7 +17,6 @@ function App() { const startGame = () => { if (username.trim() !== '') { - UserName = username; setStart(Date.now()); setElapsedTime(0); setGameStarted(true); @@ -124,6 +122,7 @@ function App() { flaggingMode={false} onGameOver={() => setIsGameOver(true)} onGameClear={() => setIsGameOver(true)} + userName={username} />