diff --git a/README.md b/README.md
index c102d3e..fadb970 100644
--- a/README.md
+++ b/README.md
@@ -191,3 +191,7 @@
### Test refactoring
[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/31/files)
+
+### Set flag action
+
+[Pull request](https://github.com/nickovchinnikov/minesweeper/pull/32/files)
diff --git a/src/helpers/CellsManipulator.test.ts b/src/helpers/CellsManipulator.test.ts
index 2078522..fef61bf 100644
--- a/src/helpers/CellsManipulator.test.ts
+++ b/src/helpers/CellsManipulator.test.ts
@@ -3,7 +3,6 @@ import {
incrementNeibours,
getNeigboursItems,
checkItemInField,
- openCell,
} from './CellsManipulator';
const { empty: e, hidden: h, bomb: b } = CellState;
@@ -221,113 +220,3 @@ describe('Check Increment Neibours', () => {
});
});
});
-
-describe('Open cell action', () => {
- describe('Simple cases with loose', () => {
- it('Open cell with the bomb', () => {
- expect(() =>
- openCell(
- [1, 1],
- [
- [h, h],
- [h, h],
- ],
- [
- [1, 1],
- [1, b],
- ]
- )
- ).toThrow('Game Over');
- });
- });
- describe('Open cell with number', () => {
- it('Open cell with state == 1', () => {
- const playerField = openCell(
- [1, 1],
- [
- [h, h, h],
- [h, h, h],
- [h, h, h],
- ],
- [
- [1, 1, 0],
- [9, 1, 0],
- [1, 1, 0],
- ]
- );
- expect(playerField).toStrictEqual([
- [h, h, h],
- [h, 1, h],
- [h, h, h],
- ]);
- });
- it('Open cell with state == 3', () => {
- const playerField = openCell(
- [1, 1],
- [
- [h, h, h],
- [h, h, h],
- [h, h, h],
- ],
- [
- [9, 2, 0],
- [9, 3, 0],
- [9, 2, 0],
- ]
- );
- expect(playerField).toStrictEqual([
- [h, h, h],
- [h, 3, h],
- [h, h, h],
- ]);
- });
- });
- describe('Open empty cell', () => {
- it('Open empty cell, simple 3*3 case', () => {
- const playerField = openCell(
- [1, 2],
- [
- [h, h, h],
- [h, h, h],
- [h, h, h],
- ],
- [
- [1, 1, 0],
- [9, 1, 0],
- [1, 1, 0],
- ]
- );
- expect(playerField).toStrictEqual([
- [h, 1, 0],
- [h, 1, 0],
- [h, 1, 0],
- ]);
- });
- it('Open empty cell 5*5 case', () => {
- const playerField = openCell(
- [2, 2],
- [
- [h, h, h, h, h],
- [h, h, h, h, h],
- [h, h, h, h, h],
- [h, h, h, h, h],
- [h, h, h, h, h],
- ],
- [
- [9, 9, 1, 1, 2],
- [9, 3, 1, 0, 0],
- [1, 1, 0, 1, 1],
- [1, 0, 0, 1, 9],
- [2, 1, 0, 1, 0],
- ]
- );
- expect(playerField).toStrictEqual([
- [h, h, 1, 1, 2],
- [h, 3, 1, 0, 0],
- [1, 1, 0, 1, 1],
- [1, 0, 0, 1, h],
- [2, 1, 0, 1, h],
- ]);
- });
- });
-});
diff --git a/src/helpers/CellsManipulator.ts b/src/helpers/CellsManipulator.ts
index e7fa007..3838add 100644
--- a/src/helpers/CellsManipulator.ts
+++ b/src/helpers/CellsManipulator.ts
@@ -1,4 +1,4 @@
-import { Cell, CellState, Coords, Field } from './Field';
+import { Cell, Coords, Field } from './Field';
/**
* Get neighbour cells indexes
@@ -48,46 +48,3 @@ export const incrementNeibours = (coords: Coords, field: Field): Field => {
return field;
};
-
-/**
- * Open cell in the player field using game field info
- * @param {Coords} coords
- * @param {Field} playerField
- * @param {Field} gameField
- * @returns {Field}
- */
-export const openCell = (
- coords: Coords,
- playerField: Field,
- gameField: Field
-): Field => {
- const { empty, hidden, bomb } = CellState;
-
- const [y, x] = coords;
- const gameCell = gameField[y][x];
-
- if (gameCell === bomb) {
- throw new Error('Game Over');
- }
-
- if (gameCell === empty) {
- playerField[y][x] = gameCell;
-
- const items = getNeigboursItems(coords);
-
- for (const [y, x] of Object.values(items)) {
- if (checkItemInField([y, x], gameField)) {
- const playerCell = playerField[y][x];
- const gameCell = gameField[y][x];
-
- if (playerCell === hidden && gameCell !== bomb) {
- playerField = openCell([y, x], playerField, gameField);
- }
- }
- }
- }
-
- playerField[y][x] = gameCell;
-
- return playerField;
-};
diff --git a/src/helpers/openCell.test.ts b/src/helpers/openCell.test.ts
new file mode 100644
index 0000000..a1ce5ec
--- /dev/null
+++ b/src/helpers/openCell.test.ts
@@ -0,0 +1,114 @@
+import { CellState } from './Field';
+import { openCell } from './openCell';
+
+const { empty: e, hidden: h, bomb: b } = CellState;
+
+describe('Open cell action', () => {
+ describe('Simple cases with loose', () => {
+ it('Open cell with the bomb', () => {
+ expect(() =>
+ openCell(
+ [1, 1],
+ [
+ [h, h],
+ [h, h],
+ ],
+ [
+ [1, 1],
+ [1, b],
+ ]
+ )
+ ).toThrow('Game Over');
+ });
+ });
+ describe('Open cell with number', () => {
+ it('Open cell with state == 1', () => {
+ const playerField = openCell(
+ [1, 1],
+ [
+ [h, h, h],
+ [h, h, h],
+ [h, h, h],
+ ],
+ [
+ [1, 1, 0],
+ [9, 1, 0],
+ [1, 1, 0],
+ ]
+ );
+ expect(playerField).toStrictEqual([
+ [h, h, h],
+ [h, 1, h],
+ [h, h, h],
+ ]);
+ });
+ it('Open cell with state == 3', () => {
+ const playerField = openCell(
+ [1, 1],
+ [
+ [h, h, h],
+ [h, h, h],
+ [h, h, h],
+ ],
+ [
+ [9, 2, 0],
+ [9, 3, 0],
+ [9, 2, 0],
+ ]
+ );
+ expect(playerField).toStrictEqual([
+ [h, h, h],
+ [h, 3, h],
+ [h, h, h],
+ ]);
+ });
+ });
+ describe('Open empty cell', () => {
+ it('Open empty cell, simple 3*3 case', () => {
+ const playerField = openCell(
+ [1, 2],
+ [
+ [h, h, h],
+ [h, h, h],
+ [h, h, h],
+ ],
+ [
+ [1, 1, 0],
+ [9, 1, 0],
+ [1, 1, 0],
+ ]
+ );
+ expect(playerField).toStrictEqual([
+ [h, 1, 0],
+ [h, 1, 0],
+ [h, 1, 0],
+ ]);
+ });
+ it('Open empty cell 5*5 case', () => {
+ const playerField = openCell(
+ [2, 2],
+ [
+ [h, h, h, h, h],
+ [h, h, h, h, h],
+ [h, h, h, h, h],
+ [h, h, h, h, h],
+ [h, h, h, h, h],
+ ],
+ [
+ [9, 9, 1, 1, 2],
+ [9, 3, 1, 0, 0],
+ [1, 1, 0, 1, 1],
+ [1, 0, 0, 1, 9],
+ [2, 1, 0, 1, 0],
+ ]
+ );
+ expect(playerField).toStrictEqual([
+ [h, h, 1, 1, 2],
+ [h, 3, 1, 0, 0],
+ [1, 1, 0, 1, 1],
+ [1, 0, 0, 1, h],
+ [2, 1, 0, 1, h],
+ ]);
+ });
+ });
+});
diff --git a/src/helpers/openCell.ts b/src/helpers/openCell.ts
new file mode 100644
index 0000000..edb1b41
--- /dev/null
+++ b/src/helpers/openCell.ts
@@ -0,0 +1,45 @@
+import { CellState, Coords, Field } from './Field';
+import { checkItemInField, getNeigboursItems } from './CellsManipulator';
+
+/**
+ * Open cell in the player field using game field info
+ * @param {Coords} coords
+ * @param {Field} playerField
+ * @param {Field} gameField
+ * @returns {Field}
+ */
+export const openCell = (
+ coords: Coords,
+ playerField: Field,
+ gameField: Field
+): Field => {
+ const { empty, hidden, bomb } = CellState;
+
+ const [y, x] = coords;
+ const gameCell = gameField[y][x];
+
+ if (gameCell === bomb) {
+ throw new Error('Game Over');
+ }
+
+ if (gameCell === empty) {
+ playerField[y][x] = gameCell;
+
+ const items = getNeigboursItems(coords);
+
+ for (const [y, x] of Object.values(items)) {
+ if (checkItemInField([y, x], gameField)) {
+ const playerCell = playerField[y][x];
+ const gameCell = gameField[y][x];
+
+ if (playerCell === hidden && gameCell !== bomb) {
+ playerField = openCell([y, x], playerField, gameField);
+ }
+ }
+ }
+ }
+
+ playerField[y][x] = gameCell;
+
+ return playerField;
+};
diff --git a/src/helpers/setFlag.test.ts b/src/helpers/setFlag.test.ts
new file mode 100644
index 0000000..3377c62
--- /dev/null
+++ b/src/helpers/setFlag.test.ts
@@ -0,0 +1,78 @@
+import { CellState, Field } from './Field';
+import { setFlag } from './setFlag';
+
+const { empty: e, hidden: h, bomb: b, flag: f, weakFlag: w } = CellState;
+
+describe('Set flag action', () => {
+ describe('Set flag to the cell check', () => {
+ it('Set flag to the non hidden cell should be ignored', () => {
+ const gameField: Field = [
+ [1, 1, e],
+ [b, 1, e],
+ [1, 1, e],
+ ];
+ const playerField: Field = [
+ [1, h, h],
+ [h, h, h],
+ [h, h, h],
+ ];
+
+ const newPlayerField = setFlag([0, 0], playerField, gameField);
+
+ expect(newPlayerField).toStrictEqual([
+ [1, h, h],
+ [h, h, h],
+ [h, h, h],
+ ]);
+ });
+ it('Set Flag action, simple 3*3 case', () => {
+ const gameField: Field = [
+ [1, 1, e],
+ [b, 1, e],
+ [1, 1, e],
+ ];
+
+ const playerField: Field = [
+ [h, h, h],
+ [h, h, h],
+ [h, h, h],
+ ];
+
+ const playerFieldAfterFirstClick = setFlag(
+ [0, 0],
+ playerField,
+ gameField
+ );
+
+ expect(playerFieldAfterFirstClick).toStrictEqual([
+ [f, h, h],
+ [h, h, h],
+ [h, h, h],
+ ]);
+
+ const playerFieldAfterSecondClick = setFlag(
+ [0, 0],
+ playerField,
+ gameField
+ );
+
+ expect(playerFieldAfterSecondClick).toStrictEqual([
+ [w, h, h],
+ [h, h, h],
+ [h, h, h],
+ ]);
+
+ const playerFieldAfterThirdClick = setFlag(
+ [0, 0],
+ playerField,
+ gameField
+ );
+
+ expect(playerFieldAfterThirdClick).toStrictEqual([
+ [h, h, h],
+ [h, h, h],
+ [h, h, h],
+ ]);
+ });
+ });
+});
diff --git a/src/helpers/setFlag.ts b/src/helpers/setFlag.ts
new file mode 100644
index 0000000..8384834
--- /dev/null
+++ b/src/helpers/setFlag.ts
@@ -0,0 +1,33 @@
+import { CellState, Coords, Field } from './Field';
+
+/**
+ * Set flag to the cell
+ * @param {Coords} coords
+ * @param {Field} playerField
+ * @param {Field} gameField
+ * @returns {[Field, FlagCounter]}
+ */
+export const setFlag = (
+ coords: Coords,
+ playerField: Field,
+ gameField: Field
+): Field => {
+ const [y, x] = coords;
+ const cell = playerField[y][x];
+
+ const { flag, weakFlag, hidden } = CellState;
+
+ switch (cell) {
+ case flag:
+ playerField[y][x] = weakFlag;
+ break;
+ case weakFlag:
+ playerField[y][x] = hidden;
+ break;
+ case hidden:
+ playerField[y][x] = flag;
+ break;
+ }
+
+ return playerField;
+};
diff --git a/src/modules/GameWithHooks/GameWithHooks.test.tsx b/src/modules/GameWithHooks/GameWithHooks.test.tsx
index 1e8c937..1347d69 100644
--- a/src/modules/GameWithHooks/GameWithHooks.test.tsx
+++ b/src/modules/GameWithHooks/GameWithHooks.test.tsx
@@ -7,6 +7,7 @@ import { GameWithHooks } from './GameWithHooks';
const mockOnClick = jest.fn();
const mockOnChangeLevel = jest.fn();
const mockOnReset = jest.fn();
+const mockOnContextMenu = jest.fn();
jest.mock('./useGame', () => ({
__esModule: true,
@@ -20,6 +21,7 @@ jest.mock('./useGame', () => ({
[10, 10],
],
onClick: mockOnClick,
+ onContextMenu: mockOnContextMenu,
onChangeLevel: mockOnChangeLevel,
onReset: mockOnReset,
}),
@@ -39,6 +41,11 @@ describe('GameWithHooks test cases', () => {
userEvent.click(screen.getByTestId('0,0'));
expect(mockOnClick).toHaveBeenCalled();
});
+ it('Context menu handler on a cell works fine', () => {
+ render();
+ userEvent.click(screen.getByTestId('0,0'), { button: 2 });
+ expect(mockOnContextMenu).toHaveBeenCalled();
+ });
it('Reset handler works fine', () => {
render();
userEvent.click(screen.getByRole('button'));
diff --git a/src/modules/GameWithHooks/GameWithHooks.tsx b/src/modules/GameWithHooks/GameWithHooks.tsx
index b87ae87..19490d2 100644
--- a/src/modules/GameWithHooks/GameWithHooks.tsx
+++ b/src/modules/GameWithHooks/GameWithHooks.tsx
@@ -17,6 +17,7 @@ export const GameWithHooks: FC = () => {
settings,
playerField,
onClick,
+ onContextMenu,
onChangeLevel,
onReset,
} = useGame();
@@ -42,7 +43,7 @@ export const GameWithHooks: FC = () => {
onReset={onReset}
/>
{isGameOver && }
- null}>
+
{playerField}
diff --git a/src/modules/GameWithHooks/useGame.test.ts b/src/modules/GameWithHooks/useGame.test.ts
index b545443..0a4e152 100644
--- a/src/modules/GameWithHooks/useGame.test.ts
+++ b/src/modules/GameWithHooks/useGame.test.ts
@@ -65,6 +65,17 @@ describe('useGame test cases', () => {
expect(flatWithFilter(newPlayerField, e)).toHaveLength(18);
});
+ it('Context menu handler', () => {
+ const { result } = renderHook(useGame);
+
+ const { onContextMenu } = result.current;
+
+ act(() => onContextMenu([0, 0]));
+
+ const { playerField: newPlayerField } = result.current;
+
+ expect(flatWithFilter(newPlayerField, f)).toHaveLength(1);
+ });
it('Click to the non-empty cells area', () => {
const { result } = renderHook(useGame);
diff --git a/src/modules/GameWithHooks/useGame.ts b/src/modules/GameWithHooks/useGame.ts
index 125759c..e091f92 100644
--- a/src/modules/GameWithHooks/useGame.ts
+++ b/src/modules/GameWithHooks/useGame.ts
@@ -7,7 +7,8 @@ import {
fieldGenerator,
Coords,
} from '@/helpers/Field';
-import { openCell } from '@/helpers/CellsManipulator';
+import { openCell } from '@/helpers/openCell';
+import { setFlag } from '@/helpers/setFlag';
import { LevelNames, GameSettings } from '@/modules/GameSettings';
@@ -19,6 +20,7 @@ interface ReturnType {
playerField: Field;
gameField: Field;
onClick: (coords: Coords) => void;
+ onContextMenu: (coords: Coords) => void;
onChangeLevel: (level: LevelNames) => void;
onReset: () => void;
}
@@ -49,6 +51,11 @@ export const useGame = (): ReturnType => {
}
};
+ const onContextMenu = (coords: Coords) => {
+ const newPlayerField = setFlag(coords, playerField, gameField);
+ setPlayerField([...newPlayerField]);
+ };
+
const resetHandler = ([size, bombs]: [number, number]) => {
const newGameField = fieldGenerator(size, bombs / (size * size));
const newPlayerField = generateFieldWithDefaultState(
@@ -78,6 +85,7 @@ export const useGame = (): ReturnType => {
playerField,
gameField,
onClick,
+ onContextMenu,
onChangeLevel,
onReset,
};