(game): Generate grid & User Interactions

Generate grid with bombs & numbers, user interactions like, click on a cell, pin a flag
This commit is contained in:
2024-02-12 11:18:14 +01:00
parent d0d8093fb5
commit 28f48bacdd
10 changed files with 373 additions and 15 deletions
+134
View File
@@ -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 (
<div
className='grid'
style={{
gridTemplateRows: `repeat(${size}, var(--size))`,
}}
>
{grid.map((row, rowIndex) => (
<div
key={`row${rowIndex}`}
className='row'
style={{
gridTemplateColumns: `repeat(${size}, var(--size))`,
}}
>
{row.map((cell, cellIndex) => {
return (
<button
key={`cell${cellIndex}`}
className={`cell ${cell.hidden ? 'hidden' : cell.value !== 'empty' ? `v${cell.value}` : ''}`}
onClick={(ev) => handleLeftClick(ev, rowIndex, cellIndex)}
onContextMenu={(ev) => handleRightClick(ev, rowIndex, cellIndex)}
>
{cell.hidden && cell.flag && (
<img
src={Flag}
alt='flat'
/>
)}
{!cell.hidden &&
(cell.value === 'bomb' ? (
<img
src={Bomb}
alt='bomb'
/>
) : cell.value !== 'empty' ? (
cell.value
) : (
''
))}
</button>
);
})}
</div>
))}
</div>
);
};
export default Grid;
+76
View File
@@ -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;
}
}
}
}