Adding support for flag and reveal

This commit is contained in:
2020-11-07 15:17:24 -03:00
parent ec8ccb8203
commit c724c2c0cf
8 changed files with 230 additions and 88 deletions

View File

@@ -1,12 +1,114 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import { Header } from 'components'; import { Board, Header } from 'components';
function App() { function App() {
const [rows, setRows] = useState(10);
const [cols, setCols] = useState(10);
const [mines, setMines] = useState(5);
const [flags, setFlags] = useState(5);
const [mounted, setMounted] = useState(false);
const [dummyBoard, setDummyBoard] = useState([]);
const [updateDummyBoard, setUpdateDummyBoard] = useState(true);
const handleSetRows = value => {
// TODO can't be less than 3
setRows(value);
setUpdateDummyBoard(true);
};
const handleSetCols = value => {
// TODO can't be less than 3
setCols(value);
setUpdateDummyBoard(true);
};
const generateDummyBoard = () => {
const board = [];
for (let row = 0; row < rows; row++) {
board.push(
Array(cols)
.join(0)
.split(0)
);
}
setDummyBoard(board);
};
const handleSetMines = event => {
// TODO can't be less than 1
setMines(parseInt(event.target.value));
setFlags(parseInt(event.target.value));
generateDummyBoard();
};
useEffect(() => {
function bootstrap() {
setMounted(true);
generateDummyBoard();
}
if (!mounted) {
bootstrap();
}
if (updateDummyBoard) {
setUpdateDummyBoard(false);
generateDummyBoard();
}
});
const handleCellClick = (row, col) => {
const cell = document.getElementById(`cell_${row}_${col}`);
if (cell.classList.contains('flagged')) {
return;
}
if (!cell.classList.contains('revealed')) {
cell.classList.add('revealed');
}
};
const handleCellContextMenu = (event, row, col) => {
event.preventDefault();
const cell = document.getElementById(`cell_${row}_${col}`);
if (cell.classList.contains('revealed')) {
return;
}
if (cell.classList.contains('flagged')) {
cell.classList.remove('flagged');
} else {
cell.classList.add('flagged');
}
};
if (!mounted) {
return null;
}
return ( return (
<div className="app"> <div className="app">
<div className="canvas"> <div className="canvas">
<Header /> <Header
cols={cols}
flags={flags}
handleSetMines={handleSetMines}
mines={mines}
rows={rows}
setCols={handleSetCols}
setMines={setMines}
setRows={handleSetRows}
/>
<Board
cols={cols}
dummyBoard={dummyBoard}
handleCellClick={handleCellClick}
handleCellContextMenu={handleCellContextMenu}
rows={rows}
/>
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16.439" height="15.031" viewBox="0 0 16.439 15.031">
<line id="Line_6" data-name="Line 6" y2="13" transform="translate(5.259 1.531)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1"/>
<g id="Polygon_2" data-name="Polygon 2" transform="matrix(-0.657, 0.293, 0.752, 0.308, 11.211, -0.035)" fill="red">
<path d="M 15.6063289642334 6.5 L 1.393670678138733 6.5 L 8.5 0.6477288007736206 L 15.6063289642334 6.5 Z" stroke="none"/>
<path d="M 8.5 1.295455932617188 L 2.787339210510254 6 L 14.21266078948975 6 L 8.5 1.295455932617188 M 8.5 0 L 17 7 L 0 7 L 8.5 0 Z" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View File

@@ -3,3 +3,5 @@ $gray-mid: #e3e3e3;
$gray-dark: #333; $gray-dark: #333;
$gray: #d1d1d1; $gray: #d1d1d1;
$red: #ec433c; $red: #ec433c;
$cell-size: 20px;

View File

@@ -4,18 +4,19 @@
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px; font-size: 14px;
background-color: $gray-light;
background-color: $gray-mid;
} }
.canvas { .canvas {
max-width: 1000px; max-width: 1000px;
width: 100%; width: 100%;
min-width: 500px; min-width: 500px;
background-color: $gray-mid;
margin: 0 auto; margin: 0 auto;
border-radius: 3px; border-radius: 3px;
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-direction: column;
} }
.box { .box {

View File

@@ -1,8 +1,37 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import './styles.scss'; import './styles.scss';
const Board = () => { const Board = ({ dummyBoard, handleCellClick, handleCellContextMenu }) => {
return <div className="board">Board here</div>; return (
<div className="board">
{dummyBoard.map((cols, rowId) => (
<div className="board-row" key={`row_${rowId}`}>
{cols.map((col, colId) => (
<div
className="board-row-cell box"
id={`cell_${rowId}_${colId}`}
onClick={() => handleCellClick(rowId, colId)}
onContextMenu={event =>
handleCellContextMenu(event, rowId, colId)
}
key={`cell_${rowId}_${colId}`}>
{' '}
</div>
))}
</div>
))}
</div>
);
};
Board.propTypes = {
cols: PropTypes.number.isRequired,
dummyBoard: PropTypes.array.isRequired,
handleCellClick: PropTypes.func.isRequired,
rows: PropTypes.number.isRequired,
handleCellContextMenu: PropTypes.func.isRequired
}; };
export default Board; export default Board;

View File

@@ -1 +1,27 @@
@import '../../assets/scss/variables'; @import '../../assets/scss/variables';
.board {
display: flex;
flex-direction: column;
&-row {
display: flex;
justify-content: center;
&-cell.flagged {
background-image: url(../../assets/images/flag.svg);
background-repeat: no-repeat;
background-position: center center;
}
&-cell.revealed {
margin: 1px;
border: 2px solid $gray-dark;
}
&-cell {
width: $cell-size;
height: $cell-size;
line-height: $cell-size;
}
}
}

View File

@@ -1,95 +1,69 @@
import React, { useState } from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import './styles.scss'; import './styles.scss';
const difficulty = { const Header = ({
custom: { cols,
rows: 10, flags,
cols: 10, handleSetMines,
mines: 5, mines,
flags: 5 rows,
}, setCols,
beginner: { setRows
rows: 20, }) => {
cols: 20,
mines: 5,
flags: 5
},
intermediate: {
rows: 50,
cols: 50,
mines: 15,
flags: 15
},
expert: {
rows: 100,
cols: 100,
mines: 50,
flags: 50
}
};
const Header = () => {
const [selectedDifficulty, setSelectedDifficulty] = useState('beginner');
const [rows, setRows] = useState(difficulty.beginner.rows);
const [cols, setCols] = useState(difficulty.beginner.cols);
const [mines, setMines] = useState(difficulty.beginner.mines);
const [flags, setFlags] = useState(5);
const [resetContent] = useState('🙂');
const handleSetDifficulty = event => {
setSelectedDifficulty(event.target.value);
setRows(difficulty[event.target.value].rows);
setCols(difficulty[event.target.value].cols);
setMines(difficulty[event.target.value].mines);
setFlags(difficulty[event.target.value].flags);
};
const handleSetMines = event => {
setMines(parseInt(event.target.value));
setFlags(parseInt(event.target.value));
};
return ( return (
<div className="header"> <div className="header">
<div className="flags led-panel box">{flags}</div> <div className="flags led-panel box">{flags}</div>
<div className="settings"> <div className="settings">
{selectedDifficulty === 'custom' && ( <div className="custom">
<div className="custom"> <input
<input className="box"
className="box" onChange={event => setRows(parseInt(event.target.value))}
onChange={event => setRows(parseInt(event.target.value))} type="number"
type="number" value={rows}
value={rows} />
/> <input
<input className="box"
className="box" onChange={event => setCols(parseInt(event.target.value))}
onChange={event => setCols(parseInt(event.target.value))} type="number"
type="number" value={cols}
value={cols} />
/> <input
<input className="box"
className="box" onChange={event => handleSetMines(event)}
onChange={event => handleSetMines(event)} type="number"
type="number" value={mines}
value={mines} />
/> </div>
</div>
)}
<select
className="box"
onChange={event => handleSetDifficulty(event)}
value={selectedDifficulty}>
<option value="custom">Custom</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="expert">Expert</option>
</select>
</div> </div>
<div className="reset box">{resetContent}</div> <div
className="reset box"
onMouseDown={event => {
event.target.innerHTML = '😮';
console.log('onmousedown');
}}
onMouseUp={event => {
event.target.innerHTML = '🙂';
console.log('onMouseUp');
}}>
🙂
</div>
<div className="timer led-panel box">000</div> <div className="timer led-panel box">000</div>
</div> </div>
); );
}; };
Header.propTypes = {
cols: PropTypes.number,
flags: PropTypes.number,
handleSetMines: PropTypes.func.isRequired,
mines: PropTypes.number,
rows: PropTypes.number,
setCols: PropTypes.func.isRequired,
setMines: PropTypes.func.isRequired,
setRows: PropTypes.func.isRequired
};
export default Header; export default Header;

View File

@@ -6,6 +6,7 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 5px; padding: 5px;
margin: 0 auto;
.led-panel { .led-panel {
font-family: 'Space Mono', monospace; font-family: 'Space Mono', monospace;