Skip to content

Commit 9f09e77

Browse files
author
Roman Babaev
committed
Bitwise cells
1 parent 2076a42 commit 9f09e77

File tree

4 files changed

+97
-45
lines changed

4 files changed

+97
-45
lines changed

src/Arena/index.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { memo } from "react";
1+
import React from "react";
22
import b_ from "b_";
3-
import { ArenaCell } from '../ArenaCell';
3+
import { PureArenaCell } from '../ArenaCell';
44
import "./styles.css";
55

66
const b = b_.lock("Arena");
@@ -51,7 +51,7 @@ export class Arena extends React.Component {
5151
width,
5252
height
5353
} = this.state;
54-
54+
5555
return (
5656
<div
5757
className={b()}
@@ -70,7 +70,7 @@ export class Arena extends React.Component {
7070
>
7171
{cells.map((cell, i) => {
7272
return (
73-
<ArenaCell
73+
<PureArenaCell
7474
cell={cell}
7575
cellSize={cellSize}
7676
i={i}
@@ -87,5 +87,3 @@ export class Arena extends React.Component {
8787
);
8888
}
8989
}
90-
91-
export const PureArena = memo(Arena);

src/ArenaCell/index.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import React, { useState } from "react";
1+
import React, { memo, useState } from "react";
22
import b_ from "b_";
3+
import { getCellNeighborMines, CELL_FLAGGED, CELL_OPENED, CELL_MINED } from '../core/helpers';
34
import "./styles.css";
45

56
const b = b_.lock("ArenaCell");
@@ -14,28 +15,35 @@ export const ArenaCell = ({
1415
}) => {
1516
const [pressed, setPressed] = useState(false);
1617

17-
const opened = cell.opened || (gameState === "lost" && ((cell.mined && !cell.flagged) || (!cell.mined && cell.flagged)));
18+
const opened =
19+
(cell & CELL_OPENED)
20+
|| (
21+
gameState === "lost" && (
22+
((cell & CELL_MINED) && !(cell & CELL_FLAGGED))
23+
|| (!(cell & CELL_MINED) && (cell & CELL_FLAGGED))
24+
)
25+
);
1826

1927
const content = opened
20-
? cell.mined
28+
? (cell & CELL_MINED)
2129
? '🦠'
22-
: cell.flagged && gameState === "lost"
30+
: (cell & CELL_FLAGGED) && gameState === "lost"
2331
? "❌"
24-
: cell.neighborMines
25-
: cell.flagged || gameState === "won"
32+
: getCellNeighborMines(cell) || ''
33+
: (cell & CELL_FLAGGED) || gameState === "won"
2634
? "🚩"
2735
: null;
2836

2937
return (
3038
<div
3139
className={b({
32-
opened: opened,
40+
opened: !!opened,
3341
closed: !opened,
3442
pressed: pressed && !opened
3543
})}
3644
style={{ width: cellSize, height: cellSize }}
3745
key={i}
38-
data-neighbor-mines={cell.neighborMines}
46+
data-neighbor-mines={getCellNeighborMines(cell)}
3947
onClick={() => onCellOpen(i)}
4048
onMouseDown={e => !pressed && (e.buttons & 1) && setPressed(true)}
4149
onMouseUp={() => pressed && setPressed(false)}
@@ -51,3 +59,5 @@ export const ArenaCell = ({
5159
</div>
5260
);
5361
};
62+
63+
export const PureArenaCell = memo(ArenaCell);

src/core/helpers.js

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
import { times } from "ramda";
22

3+
/*
4+
mines opened
5+
near | mined
6+
|.....| | | flagged
7+
0 0 0 0 0 0 0 0
8+
*/
9+
10+
// const toIntCell = ({
11+
// flagged = false,
12+
// mined = false,
13+
// opened = false,
14+
// neighborMines = 0
15+
// }) => {
16+
// if (neighborMines < 0 || neighborMines > 8)
17+
// throw new Error('neighborMines is out of range');
18+
19+
// return (neighborMines << 3) + (+opened << 2) + (+mined << 1) + +flagged;
20+
// }
21+
22+
export const CELL_FLAGGED = 1;
23+
export const CELL_MINED = 2;
24+
export const CELL_OPENED = 4;
25+
26+
export const isCellMined = (cell = 0) => !!(cell & CELL_MINED);
27+
export const isCellOpened = (cell = 0) => !!(cell & CELL_OPENED);
28+
export const isCellFlagged = (cell = 0) => !!(cell & CELL_FLAGGED);
29+
export const getCellNeighborMines = (cell = 0) => cell >> 3;
30+
// export const openCell = (cell = 0) => cell | CELL_OPENED;
31+
// export const flagCell = (cell = 0) => cell | CELL_FLAGGED;
32+
// export const unflagCell = (cell = 0) => cell & (~CELL_FLAGGED);
33+
export const toggleCellFlag = (cell = 0) => cell ^ CELL_FLAGGED;
34+
35+
336
export function getFreeRandomCell([width, height], flatBusyCells) {
437
const sortedBusyCells = flatBusyCells
538
.slice(0)
@@ -25,7 +58,7 @@ export function getFreeRandomCellXY(arena, busyCells) {
2558
export const getMines = (arena, minesCount, safeCell) => {
2659
const mines = [safeCell];
2760
times(() => mines.push(getFreeRandomCell(arena, mines)), minesCount);
28-
return mines.slice(1);
61+
return mines.slice(1).sort((a, b) => (a > b ? 1 : -1));
2962
};
3063

3164
const enlargeMapValue = (map, cell) => (map[cell] = (map[cell] || 0) + 1);
@@ -80,15 +113,12 @@ export function genCells(arena, minesCount, clicked) {
80113
const mines = getMines(arena, minesCount, clicked);
81114
const heatMap = calcHeatMap(arena, mines);
82115

83-
return times(
84-
i => ({
85-
mined: mines.includes(i),
86-
neighborMines: heatMap[i],
87-
flagged: false,
88-
opened: false
89-
}),
116+
const cells = times(
117+
i => (heatMap[i] << 3) + (+mines.includes(i) << 1),
90118
arena[0] * arena[1]
91119
);
120+
121+
return [cells, mines];
92122
}
93123

94124
export function arraysCompare(a, b) {

src/core/index.js

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { arraysCompare, genCells, getNeighborCells } from './helpers';
1+
import {
2+
arraysCompare,
3+
genCells,
4+
getNeighborCells,
5+
CELL_FLAGGED,
6+
CELL_MINED,
7+
CELL_OPENED,
8+
getCellNeighborMines,
9+
toggleCellFlag
10+
} from './helpers';
211

312
export class Minesweeper {
413
constructor(arena, minesCount, redrawFn) {
@@ -29,14 +38,14 @@ export class Minesweeper {
2938

3039
const cell = this.cells[i];
3140

32-
if (this.flaggingMode && !cell.opened) {
41+
if (this.flaggingMode && !(cell & CELL_OPENED)) {
3342
this.flagCell(i);
3443
return;
3544
}
3645

37-
if (cell.flagged) return;
46+
if (cell & CELL_FLAGGED) return;
3847

39-
if (cell.opened) this._openNeighborCells(i);
48+
if (cell & CELL_OPENED) this._openNeighborCells(i);
4049
else this._openCell(i);
4150
this._render();
4251
};
@@ -58,17 +67,18 @@ export class Minesweeper {
5867
const cell = this.cells[i];
5968

6069
if (this.openedCells === 0) {
61-
this.cells = genCells(this.arena, this.minesCountTotal, i);
62-
this.minedCells = this.cells.filter(({ mined }) => mined);
70+
const [cells, minedCells] = genCells(this.arena, this.minesCountTotal, i);
71+
this.cells = cells;
72+
this.minedCells = minedCells;
6373
this.startTimer();
6474
}
65-
if (cell.mined) {
75+
if (cell & CELL_MINED) {
6676
this._explode(i);
67-
} else if (!cell.opened) {
77+
} else if (!(cell & CELL_OPENED)) {
6878
let cellsToOpen = [i];
6979

7080
for (let x = 0; x < cellsToOpen.length; x++) {
71-
if (!this.cells[cellsToOpen[x]].neighborMines) {
81+
if (!getCellNeighborMines(this.cells[cellsToOpen[x]])) {
7282
const neighborCells = getNeighborCells(
7383
this.arena,
7484
cellsToOpen[x]
@@ -79,13 +89,16 @@ export class Minesweeper {
7989
}
8090

8191
cellsToOpen.forEach(i => {
82-
this.cells[i].opened = true;
92+
this.cells[i] = (this.cells[i] | CELL_OPENED)
8393
this.openedCells++;
8494
});
8595

86-
const openedCells = this.cells.filter(({ opened }) => !opened);
96+
const restCells = this.cells
97+
.map((cell, i) => (cell & CELL_OPENED) ? null : i)
98+
.filter(cell => cell !== null);
8799

88-
if (arraysCompare(this.minedCells, openedCells)) {
100+
if (arraysCompare(this.minedCells, restCells)) {
101+
89102
this.gameState = 'won';
90103
this.flaggingMode = false;
91104
this.stopTimer();
@@ -95,16 +108,16 @@ export class Minesweeper {
95108

96109
_openNeighborCells = i => {
97110
const cell = this.cells[i];
98-
if (!cell.mined) {
111+
if (!(cell & CELL_MINED)) {
99112
const neighborCells = getNeighborCells(this.arena, i);
100113
const flaggedNeighborsCount = neighborCells
101114
.map(i => this.cells[i])
102-
.filter(({ flagged }) => !!flagged).length;
115+
.filter(cell => !!(cell & CELL_FLAGGED)).length;
103116

104-
if (flaggedNeighborsCount === cell.neighborMines) {
117+
if (flaggedNeighborsCount === getCellNeighborMines(cell)) {
105118
neighborCells
106119
.map(i => [i, this.cells[i]])
107-
.filter(([i, cell]) => !cell.flagged)
120+
.filter(([i, cell]) => !(cell & CELL_FLAGGED))
108121
.forEach(([i]) => this._openCell(i));
109122
}
110123
}
@@ -114,9 +127,9 @@ export class Minesweeper {
114127
if (this.gameState !== 'playing') return;
115128

116129
const cell = this.cells[i];
117-
if (!cell.opened) {
118-
cell.flagged = !cell.flagged;
119-
cell.flagged ? this.flaggedCells++ : this.flaggedCells--;
130+
if (!(cell & CELL_OPENED)) {
131+
this.cells[i] = toggleCellFlag(cell);
132+
(this.cells[i] & CELL_FLAGGED) ? this.flaggedCells++ : this.flaggedCells--;
120133
}
121134
this._render();
122135
};
@@ -126,11 +139,11 @@ export class Minesweeper {
126139
}
127140

128141
_countFlaggedCells() {
129-
return this.cells.filter(({ flagged }) => flagged === true).length;
142+
return this.cells.filter(cell => !!(cell & CELL_FLAGGED)).length;
130143
}
131144

132145
_countOpenedCells() {
133-
return this.cells.filter(({ opened }) => opened === true).length;
146+
return this.cells.filter(cell => !!(cell & CELL_OPENED)).length;
134147
}
135148

136149
getStats() {
@@ -147,12 +160,13 @@ export class Minesweeper {
147160
}
148161

149162
reset = () => {
163+
const [cells, minedCells] = genCells(this.arena, 0, 0);
150164
this.stopTimer();
151165
this.openedCells = 0;
152166
this.flaggedCells = 0;
153167
this.gameState = 'playing';
154-
this.cells = genCells(this.arena, 0, 0);
155-
this.minedCells = [];
168+
this.cells = cells;
169+
this.minedCells = minedCells;
156170
this.startTime = null;
157171
this.endTime = null;
158172
this.flaggingMode = false;

0 commit comments

Comments
 (0)