From 28f48bacdd8b41a4b5d3a4f12f4f74721119863c Mon Sep 17 00:00:00 2001 From: UnEpicier Date: Mon, 12 Feb 2024 11:18:14 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20(game):=20Generate=20grid=20&=20Use?= =?UTF-8?q?r=20Interactions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate grid with bombs & numbers, user interactions like, click on a cell, pin a flag --- src/App.scss | 6 +- src/App.tsx | 16 +--- src/assets/bomb.png | Bin 0 -> 3863 bytes src/assets/flag.png | Bin 0 -> 7151 bytes src/components/Grid/Grid.tsx | 134 ++++++++++++++++++++++++++++++ src/components/Grid/styles.scss | 76 +++++++++++++++++ src/index.scss | 7 ++ src/types/game.d.ts | 3 + src/utils/game.ts | 143 ++++++++++++++++++++++++++++++++ src/utils/generics.ts | 3 + 10 files changed, 373 insertions(+), 15 deletions(-) create mode 100644 src/assets/bomb.png create mode 100644 src/assets/flag.png create mode 100644 src/components/Grid/Grid.tsx create mode 100644 src/components/Grid/styles.scss create mode 100644 src/types/game.d.ts create mode 100644 src/utils/game.ts create mode 100644 src/utils/generics.ts diff --git a/src/App.scss b/src/App.scss index c735afc..eb442da 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,3 +1,7 @@ .App { - text-align: center; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; } diff --git a/src/App.tsx b/src/App.tsx index 169f89a..4f0cd48 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,22 +1,10 @@ -import React from 'react'; import './App.scss'; +import Grid from './components/Grid/Grid'; const App = () => { return (
-
-

- Edit src/App.tsx and save to reload. -

- - Learn React - -
+
); }; diff --git a/src/assets/bomb.png b/src/assets/bomb.png new file mode 100644 index 0000000000000000000000000000000000000000..88d67e947b63ae82fba807cc1f93d564de29fb9d GIT binary patch literal 3863 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 literal 0 HcmV?d00001 diff --git a/src/assets/flag.png b/src/assets/flag.png new file mode 100644 index 0000000000000000000000000000000000000000..f751de76ce1417f255661ac9c7ce192a3d861d32 GIT binary patch literal 7151 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 literal 0 HcmV?d00001 diff --git a/src/components/Grid/Grid.tsx b/src/components/Grid/Grid.tsx new file mode 100644 index 0000000..c91dcce --- /dev/null +++ b/src/components/Grid/Grid.tsx @@ -0,0 +1,134 @@ +// --------------------------------------------------------------------------------------------------------------------- +//! Imports +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------------ React -------------------------------------------------------- +import { MouseEvent, useCallback, useEffect, useState } from 'react'; +// --------------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------------ Utils -------------------------------------------------------- +import { Difficulty } from '../../types/game'; +import { discoverAroundCell, genBombs, genGrid, genNumbers } from '../../utils/game'; +// --------------------------------------------------------------------------------------------------------------------- + +// ----------------------------------------------------- Assets -------------------------------------------------------- +import Bomb from '../../assets/bomb.png'; +import Flag from '../../assets/flag.png'; +import './styles.scss'; +// --------------------------------------------------------------------------------------------------------------------- + +const Grid = ({ size = 12, difficulty = 'beginner' }) => { + const [grid, setGrid] = useState(genGrid(size)); + const [gameStatus, setGameStatus] = useState('idle'); + + const [, updateGrid] = useState({}); + const forceUpdate = useCallback(() => updateGrid({}), []); + + // ----------------------------------------------------- Actions ------------------------------------------------------- + const handleLeftClick = (ev: MouseEvent, rowIndex: number, colIndex: number) => { + ev.preventDefault(); + + if (grid[rowIndex][colIndex].hidden && !grid[rowIndex][colIndex].flag) { + setGrid((prev) => { + if (gameStatus === 'idle') { + do { + prev = genNumbers(genBombs(prev, difficulty as Difficulty)); + } while (prev[rowIndex][colIndex].value !== 'empty'); + setGameStatus('started'); + } + + return discoverAroundCell(prev, rowIndex, colIndex); + }); + forceUpdate(); + } + }; + + const handleRightClick = (ev: MouseEvent, rowIndex: number, colIndex: number) => { + ev.preventDefault(); + + if (grid[rowIndex][colIndex].hidden) { + setGrid((prev) => { + return prev.map((row, idx) => { + if (idx === rowIndex) { + return row.map((col, idy) => { + if (idy === colIndex) { + return { + ...col, + flag: !col.flag, + }; + } + return col; + }); + } + return row; + }); + }); + } + }; + + // --- Development + useEffect(() => { + if (process.env.NODE_ENV === 'development') { + document.addEventListener('keyup', (ev) => { + if (ev.key === 'v') { + setGrid(() => { + let newGrid = genGrid(size); + + return genNumbers(genBombs(newGrid, difficulty as Difficulty)); + }); + } + }); + } + }, [size, difficulty]); + // --------------------------------------------------------------------------------------------------------------------- + + return ( +
+ {grid.map((row, rowIndex) => ( +
+ {row.map((cell, cellIndex) => { + return ( + + ); + })} +
+ ))} +
+ ); +}; + +export default Grid; diff --git a/src/components/Grid/styles.scss b/src/components/Grid/styles.scss new file mode 100644 index 0000000..33273ab --- /dev/null +++ b/src/components/Grid/styles.scss @@ -0,0 +1,76 @@ +.grid { + --size: 30px; + display: grid; + gap: 2px; + padding: 2px; + border-radius: 5px; + background-color: #ffff; + overflow: hidden; + + & > .row { + display: grid; + gap: 2px; + height: var(--size); + + & > .cell { + display: flex; + justify-content: center; + align-items: center; + width: var(--size); + border: none; + background-color: #f6f6f6; + font-weight: 700; + outline: none; + + &.hidden { + background-color: #dedede; + cursor: pointer; + } + + &.v1 { + color: blue; + } + + &.v2 { + color: green; + } + + &.v3 { + color: orange; + } + + &.v4 { + color: red; + } + + &.v5 { + color: purple; + } + + & > img { + width: 70%; + height: 70%; + object-fit: cover; + } + } + + &:first-child > .cell { + &:first-child { + border-top-left-radius: 5px; + } + + &:last-child { + border-top-right-radius: 5px; + } + } + &:last-child > .cell { + &:first-child { + border-bottom-left-radius: 5px; + } + + &:last-child { + border-bottom-right-radius: 5px; + } + } + } +} diff --git a/src/index.scss b/src/index.scss index 1270a2d..1b0969f 100644 --- a/src/index.scss +++ b/src/index.scss @@ -5,5 +5,12 @@ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + user-select: none; } +#root { + width: 100svw; + height: 100svh; + background: #598b8e; + overflow: hidden; +} diff --git a/src/types/game.d.ts b/src/types/game.d.ts new file mode 100644 index 0000000..2ac084a --- /dev/null +++ b/src/types/game.d.ts @@ -0,0 +1,3 @@ +export type Difficulty = 'beginner' | 'intermediate' | 'expert'; +export type CellValue = 'empty' | 'bomb' | number; +export type GridType = { hidden: boolean; value: CellValue; flag: boolean }[][]; diff --git a/src/utils/game.ts b/src/utils/game.ts new file mode 100644 index 0000000..e8c0d92 --- /dev/null +++ b/src/utils/game.ts @@ -0,0 +1,143 @@ +import { Difficulty, CellValue, GridType } from '../types/game'; +import { getRandomArbitrary } from './generics'; + +export const genGrid = (size: number): GridType => { + let grid: GridType = Array.apply(null, Array(size)).map(() => { + return Array.apply(null, Array(size)).map(() => { + return { + hidden: true, + value: 'empty', + flag: false, + }; + }); + }); + + return grid; +}; + +export const genBombs = (grid: GridType, difficulty: Difficulty) => { + const surface = grid.length * grid.length; + + let bombs = 0; + if (difficulty === 'beginner') { + bombs = surface * 0.05; + } else if (difficulty === 'intermediate') { + bombs = surface * 0.1; + } else if (difficulty === 'expert') { + bombs = surface * 0.15; + } + + bombs = Math.floor(bombs); + + for (let i = 0; i <= bombs; i++) { + let needRegenerate = false; + + do { + const row = getRandomArbitrary(0, grid.length); + const col = getRandomArbitrary(0, grid.length); + let bombsArounds = 0; + + for (let testRowIndex = -1; testRowIndex <= 1; testRowIndex++) { + const testedRow = row + testRowIndex; + + if (testedRow < 0) continue; + if (testedRow >= grid.length) continue; + + for (let testColIndex = -1; testColIndex <= 1; testColIndex++) { + const testedCol = col + testColIndex; + + if (testedCol < 0) continue; + if (testedCol >= grid.length) continue; + if (testedRow === row && testedCol === col) continue; + + if (grid[testedRow][testedCol].value === 'bomb') { + bombsArounds++; + + if (bombsArounds > 3) { + needRegenerate = true; + break; + } + } + } + + if (needRegenerate) break; + } + + if (bombsArounds <= 3) { + grid[row][col].value = 'bomb'; + } + } while (needRegenerate); + } + + return grid; +}; + +export const genNumbers = (grid: GridType): GridType => { + for (let row = 0; row < grid.length; row++) { + for (let col = 0; col < grid[row].length; col++) { + let bombsArounds = 0; + + checkAroundCell(grid, row, col, (testedCell: { hidden: boolean; value: CellValue }, testedRow: number, testedCol: number) => { + if (testedCell.value === 'bomb') { + bombsArounds++; + } + }); + + if (bombsArounds > 0) { + grid[row][col].value = bombsArounds; + } + } + } + + return grid; +}; + +export const checkAroundCell = (grid: GridType, row: number, col: number, callback: Function): void => { + for (let testRowIndex = -1; testRowIndex <= 1; testRowIndex++) { + const testedRow = row + testRowIndex; + + if (testedRow < 0) continue; + if (testedRow >= grid.length) continue; + + for (let testColIndex = -1; testColIndex <= 1; testColIndex++) { + const testedCol = col + testColIndex; + + if (testedCol < 0) continue; + if (testedCol >= grid.length) continue; + if (testedRow === row && testedCol === col) continue; + + callback(grid[testedRow][testedCol], testedRow, testedCol); + } + } +}; + +export const discoverAroundCell = (grid: GridType, rowIndex: number, colIndex: number): GridType => { + grid[rowIndex][colIndex].hidden = false; + + checkAroundCell(grid, rowIndex, colIndex, (cell: { hidden: boolean; value: CellValue; flag: boolean }, testedRowIndex: number, testedColIndex: number) => { + if (!cell.flag && cell.hidden) { + if (cell.value === 'empty') { + grid = discoverAroundCell(grid, testedRowIndex, testedColIndex); + } else if (typeof cell.value === 'number') { + grid[testedRowIndex][testedColIndex].hidden = false; + } + } + }); + + return grid; +}; + +export const revealAllGrid = (grid: GridType, excludeGoodFlags: boolean = true) => { + return grid.map((row) => { + return row.map((col) => { + if (excludeGoodFlags && col.value === 'bomb' && col.flag) { + return col; + } + return { + ...col, + hidden: false, + flag: false, + }; + }); + }); +}; diff --git a/src/utils/generics.ts b/src/utils/generics.ts new file mode 100644 index 0000000..36c5950 --- /dev/null +++ b/src/utils/generics.ts @@ -0,0 +1,3 @@ +export const getRandomArbitrary = (min: number, max: number): number => { + return Math.floor(Math.random() * (max - min) + min); +};