Skip to content

Commit b74e9c8

Browse files
authored
Merge pull request #38 from Chessweeper/attacked-cells
Setting to show attacked cells
2 parents ccb30b4 + 2a9fbd0 commit b74e9c8

22 files changed

+790
-152
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ To build a release version of the game, run `npm run build`. The output is place
4040

4141
- Chess icons and favicon from https://github.com/oakmac/chessboardjs
4242
- Shovel icon from Vectors Market, Flaticon, https://www.flaticon.com/free-icons/shovel
43+
- Settings icon from Vectors Market, Flaticon, https://www.flaticon.com/free-icons/settings
4344
- Shogi icons from Wikimedia Commons by Hari Seldon under Creative Commons Attribution-Share Alike 3.0 Unported
4445
- Digital font from The FontStruction, https://fontstruct.com/fontstructions/show/583495, by jon889 under a Creative Commons Attribution Share Alike
4546
- Idea taken from https://www.reddit.com/r/AnarchyChess/comments/ytw69b/new_chess_2_update_just_dropped_numbers_are_based/

package-lock.json

+189-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030
},
3131
"homepage": "https://github.com/Xwilarg/Chessweeper#readme",
3232
"dependencies": {
33+
"@reduxjs/toolkit": "^1.9.1",
3334
"boardgame.io": "^0.50.2",
3435
"react": "^18.2.0",
3536
"react-dom": "^18.2.0",
37+
"react-redux": "^8.0.5",
3638
"react-router-dom": "^6.4.4"
3739
},
3840
"devDependencies": {

src/Algs.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import { Random } from "./Random";
22

3+
export function getMoves(
4+
piece: string,
5+
data: Array<string | number>,
6+
size: number,
7+
x: number,
8+
y: number
9+
) {
10+
return parseNotation(pieceMovesCheck[piece], data, size, x, y);
11+
}
12+
313
function isValid(
414
data: Array<string | number>,
515
size: number,
@@ -526,17 +536,22 @@ export function generatePuzzleBoard(
526536
}
527537
}
528538

529-
let knownCells;
539+
let cells;
530540
if (c === maxIt) {
531541
error = "Failed to generate puzzle";
532542
} else {
533-
knownCells = Array(size * size).fill(false);
543+
cells = data.map((item) => ({
544+
value: item,
545+
known: false,
546+
attackedValue: 0,
547+
}));
548+
534549
for (const i in discovered) {
535550
if (discovered[i]) {
536-
knownCells[i] = true;
551+
cells[i].known = true;
537552
}
538553
}
539554
}
540555

541-
return { cells: data, knownCells, error };
556+
return { cells, error };
542557
}

src/App.tsx

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
import { Provider } from "react-redux";
12
import { Client } from "./components/Client";
23
import { Footer } from "./components/Footer";
4+
import { store } from "./store";
35

46
export const App = (): JSX.Element => {
57
return (
6-
<div>
7-
<div className="flex">
8-
<Client />
8+
<Provider store={store}>
9+
<div>
10+
<div className="flex">
11+
<Client />
12+
</div>
13+
<hr />
14+
<Footer />
915
</div>
10-
<hr />
11-
<Footer />
12-
</div>
16+
</Provider>
1317
);
1418
};

src/Game.ts

+66-20
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Game as BgioGame } from "boardgame.io";
22
import { INVALID_MOVE } from "boardgame.io/core";
3-
import { fillPositions, generateBoard } from "./Algs";
3+
import { fillPositions, generateBoard, getMoves } from "./Algs";
44
import { Random } from "./Random";
55

6-
type Cell = boolean | number | string;
6+
export interface Cell {
7+
value: number | string;
8+
attackedValue: number;
9+
known: boolean | string;
10+
}
711
// todo: replace string with a union type for pieces?
812

913
export interface SetupData {
@@ -14,14 +18,13 @@ export interface SetupData {
1418
gamemode: "c" | "p";
1519
difficulty: number;
1620
cells?: Cell[] | null;
17-
knownCells?: Cell[] | null;
1821
}
1922

2023
export type GameState = Required<SetupData>;
2124

2225
function generateClassicBoard(G: GameState, id: number) {
2326
const random = new Random(G.seed);
24-
G.cells = fillPositions(
27+
const filledPositions = fillPositions(
2528
generateBoard(
2629
random,
2730
id,
@@ -31,7 +34,11 @@ function generateClassicBoard(G: GameState, id: number) {
3134
Array(G.size * G.size).fill(0)
3235
)
3336
);
34-
G.knownCells = Array(G.size * G.size).fill(false);
37+
G.cells = filledPositions.map((pos) => ({
38+
value: pos,
39+
known: false,
40+
attackedValue: 0,
41+
}));
3542
}
3643

3744
function isWinCondition(G: GameState, id: number) {
@@ -40,22 +47,61 @@ function isWinCondition(G: GameState, id: number) {
4047
}
4148

4249
for (let i = 0; i < G.size * G.size; i++) {
43-
if (!Number.isInteger(G.cells[i])) {
44-
if (G.cells[i] !== G.knownCells?.[i] && G.cells[i] !== id) {
50+
if (!Number.isInteger(G.cells[i].value)) {
51+
if (G.cells[i].value !== G.cells[i].known && G.cells[i].value !== id) {
4552
return false;
4653
}
47-
} else if (G.knownCells?.[i] !== true && G.knownCells?.[i] !== false) {
54+
} else if (G.cells[i].known !== true && G.cells[i].known !== false) {
4855
return false;
4956
}
5057
}
5158

5259
return true;
5360
}
5461

62+
function calcAttackedCells(G: GameState) {
63+
if (G.cells == null) return;
64+
// for some reason get a possibly null warning in some places even with null check at top of function
65+
66+
// Reset all values to zero
67+
G.cells.forEach((cell) => {
68+
cell.attackedValue = 0;
69+
});
70+
71+
// Recalculate all attacked cells
72+
G.cells.forEach((cell, id) => {
73+
if (typeof cell.known === "string") {
74+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
75+
const data = G.cells!.map((cell) => {
76+
if (typeof cell.known === "string") {
77+
return cell.known;
78+
} else if (typeof cell.value === "string") {
79+
// strip hidden pieces from data, so they do not block calculations
80+
return 0;
81+
}
82+
83+
return cell.value;
84+
});
85+
86+
const moves = getMoves(
87+
cell.known,
88+
data,
89+
G.size,
90+
id % G.size,
91+
Math.floor(id / G.size)
92+
);
93+
94+
moves.forEach((move) => {
95+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
96+
G.cells![move].attackedValue++;
97+
});
98+
}
99+
});
100+
}
101+
55102
export const Game = (setupData: SetupData): BgioGame<GameState> => ({
56103
setup: () => ({
57104
...setupData,
58-
knownCells: setupData.knownCells ?? null,
59105
cells: setupData.cells ?? null,
60106
}),
61107

@@ -67,14 +113,14 @@ export const Game = (setupData: SetupData): BgioGame<GameState> => ({
67113

68114
// cells and knownCells will be already set or set in generateClassicBoard
69115
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
70-
if (G.knownCells![id] !== false || G.gamemode === "p") {
116+
if (G.cells![id].known !== false || G.gamemode === "p") {
71117
return INVALID_MOVE;
72118
}
73119

74120
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
75-
if (Number.isInteger(G.cells![id])) {
121+
if (Number.isInteger(G.cells![id].value)) {
76122
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
77-
G.knownCells![id] = true;
123+
G.cells![id].known = true;
78124
} else {
79125
events.endGame({ isWin: false });
80126
}
@@ -87,28 +133,28 @@ export const Game = (setupData: SetupData): BgioGame<GameState> => ({
87133

88134
// cells and knownCells will be already set or set in generateClassicBoard
89135
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
90-
if (G.knownCells![id] === true) {
136+
if (G.cells![id].known === true) {
91137
return INVALID_MOVE;
92138
}
93139

94140
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
95-
G.knownCells![id] = action;
141+
G.cells![id].known = action;
142+
143+
calcAttackedCells(G);
96144

97145
if (isWinCondition(G, id)) {
98146
events.endGame({ isWin: true });
99147
}
100148
},
101149

102150
removeHint: ({ G, events }, id: number) => {
103-
if (
104-
G.knownCells?.[id] === true ||
105-
G.cells === null ||
106-
G.knownCells === null
107-
) {
151+
if (G.cells === null || G.cells[id].known === true) {
108152
return INVALID_MOVE;
109153
}
110154

111-
G.knownCells[id] = false;
155+
G.cells[id].known = false;
156+
157+
calcAttackedCells(G);
112158

113159
if (isWinCondition(G, id)) {
114160
events.endGame({ isWin: true });

src/PuzzleGenWorker.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { SetupData } from "./Game";
44
onmessage = (e) => {
55
const { seed, pieces, size, count, difficulty } = e.data as SetupData;
66

7-
const { cells, knownCells, error } = generatePuzzleBoard(
7+
const { cells, error } = generatePuzzleBoard(
88
seed,
99
pieces,
1010
size,
@@ -15,6 +15,6 @@ onmessage = (e) => {
1515
if (error) {
1616
postMessage(error);
1717
} else {
18-
postMessage({ cells, knownCells });
18+
postMessage({ cells });
1919
}
2020
};

src/assets/settings.png

18.7 KB
Loading

src/components/BoardWrapper.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Timer, TimerRefAttributes } from "./Timer";
55
import { BoardPropsWithReload } from "./Client";
66
import { BoardHeaderButton } from "./BoardHeaderButton";
77
import { BoardReport } from "./BoardReport";
8+
import { SettingsPanel } from "./SettingsPanel";
89

910
export interface BoardContextState extends BoardPropsWithReload {
1011
currAction: string;
@@ -36,7 +37,8 @@ export const BoardWrapper = (props: BoardPropsWithReload): JSX.Element => {
3637
};
3738

3839
const numPiecesPlaced =
39-
props.G.knownCells?.filter((cell) => typeof cell === "string")?.length ?? 0;
40+
props.G.cells?.filter(({ known }) => typeof known === "string")?.length ??
41+
0;
4042

4143
const numPiecesRemaining = props.G.count - numPiecesPlaced;
4244

@@ -67,6 +69,7 @@ export const BoardWrapper = (props: BoardPropsWithReload): JSX.Element => {
6769
)}
6870
</div>
6971
</div>
72+
<SettingsPanel />
7073
</div>
7174
<ActionBar />
7275
</div>

src/components/Cell.tsx

+40-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useMemo } from "react";
22
import { getPiece } from "../Pieces";
3+
import { useAppSelector } from "../store";
34
import { useBoardContext } from "./BoardWrapper";
45

56
interface CellProps {
@@ -8,8 +9,12 @@ interface CellProps {
89

910
export const Cell = ({ id }: CellProps): JSX.Element => {
1011
const { G, ctx, moves, currAction, timer } = useBoardContext();
12+
const isAttackedCellValuesEnabled = useAppSelector(
13+
(s) => s.settings.isAttackedCellValuesEnabled
14+
);
15+
1116
let className = "cell";
12-
let value: string | JSX.Element = "";
17+
let value: string | number | JSX.Element = "";
1318

1419
const isWhite = useMemo(() => {
1520
const y = Math.floor(id / G.size);
@@ -27,7 +32,7 @@ export const Cell = ({ id }: CellProps): JSX.Element => {
2732
}
2833

2934
if (currAction !== "") {
30-
if (G.knownCells?.[id] === currAction) {
35+
if (G.cells?.[id].known === currAction) {
3136
moves.removeHint(id);
3237
} else {
3338
moves.placeHint(id, currAction);
@@ -37,6 +42,33 @@ export const Cell = ({ id }: CellProps): JSX.Element => {
3742
}
3843
};
3944

45+
if (G.cells === null) {
46+
value = "";
47+
} else if (
48+
ctx.gameover?.isWin === false &&
49+
!Number.isInteger(G.cells[id].value)
50+
) {
51+
// Display pieces of gameover
52+
value = <img src={getPiece(String(G.cells[id].value))} />;
53+
className += " red";
54+
} else if (G.cells[id].known === true) {
55+
value = Number(G.cells[id].value);
56+
if (isAttackedCellValuesEnabled) {
57+
value -= G.cells[id].attackedValue;
58+
}
59+
if (value === 0 && G.cells[id].value === 0) {
60+
value = "";
61+
}
62+
if (typeof value === "number" && value < 0) {
63+
className += " red";
64+
} else {
65+
className += " open";
66+
className += isWhite ? " white" : " black";
67+
}
68+
} else if (G.cells[id].known !== false && G.cells[id].known !== true) {
69+
value = <img src={getPiece(String(G.cells[id].known))} />;
70+
}
71+
4072
// Text color
4173
const colors = [
4274
"#0001FD", // 1
@@ -49,24 +81,11 @@ export const Cell = ({ id }: CellProps): JSX.Element => {
4981
"#808080", // 8
5082
];
5183
let color = "";
52-
if (G.cells === null || G.cells[id] === 0) color = "";
53-
else if (G.cells[id] > 8) color = colors[7];
54-
else color = colors[Number(G.cells[id]) - 1];
55-
56-
if (G.cells === null || G.knownCells === null) {
57-
value = "";
58-
} else if (ctx.gameover?.isWin === false && !Number.isInteger(G.cells[id])) {
59-
// Display pieces of gameover
60-
value = <img src={getPiece(String(G.cells[id]))} />;
61-
className += " red";
62-
} else if (G.knownCells[id] === true) {
63-
if (G.cells[id] !== 0) {
64-
value = String(G.cells[id]);
65-
}
66-
className += " open";
67-
className += isWhite ? " white" : " black";
68-
} else if (G.knownCells[id] !== false && G.knownCells[id] !== true) {
69-
value = <img src={getPiece(String(G.knownCells[id]))} />;
84+
if (typeof value === "number") {
85+
if (value === 0) color = "";
86+
else if (value > 8) color = colors[7];
87+
else if (value < 0) color = "white";
88+
else color = colors[value - 1];
7089
}
7190

7291
return (
@@ -75,7 +94,7 @@ export const Cell = ({ id }: CellProps): JSX.Element => {
7594
style={{ color: color, cursor: "pointer" }}
7695
onClick={onCellClick}
7796
>
78-
{value}
97+
{typeof value === "number" ? value.toString() : value}
7998
</td>
8099
);
81100
};

0 commit comments

Comments
 (0)