Adding support for flag and reveal
This commit is contained in:
108
src/App.js
108
src/App.js
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
7
src/assets/images/flag.svg
Normal file
7
src/assets/images/flag.svg
Normal 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 |
@@ -3,3 +3,5 @@ $gray-mid: #e3e3e3;
|
|||||||
$gray-dark: #333;
|
$gray-dark: #333;
|
||||||
$gray: #d1d1d1;
|
$gray: #d1d1d1;
|
||||||
$red: #ec433c;
|
$red: #ec433c;
|
||||||
|
|
||||||
|
$cell-size: 20px;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,59 +1,21 @@
|
|||||||
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"
|
||||||
@@ -74,22 +36,34 @@ const Header = () => {
|
|||||||
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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user