Fleet Strength
-
{unknownState ? '???' : fleetHealth.toFixed(1)}% PERC
+
+ {unknownState ? '???' : fleetHealth.toFixed(1)}% PERC
+
{unknownState ? '???' : sunkShipTotal}/{unknownState ? '???' : graveyard.length}
diff --git a/bs-game-wip/src/components/HudWindow/HudWindow.tsx b/bs-game-wip/src/components/HudWindow/HudWindow.tsx
index 14fb2bd6..f79205ff 100644
--- a/bs-game-wip/src/components/HudWindow/HudWindow.tsx
+++ b/bs-game-wip/src/components/HudWindow/HudWindow.tsx
@@ -51,8 +51,8 @@ export default function HudWindow({
};
const footerAnimation = {
- initial: { opacity: 0, width: 0,},
- animate: { opacity: 1, width: '100%'},
+ initial: { opacity: 0, width: 0 },
+ animate: { opacity: 1, width: '100%' },
exit: { opacity: 0 },
transition: { delay: 1, duration: 1 * speed },
};
diff --git a/bs-game-wip/src/components/PrizePool/PrizePool.tsx b/bs-game-wip/src/components/PrizePool/PrizePool.tsx
index c6c361d0..8475ca35 100644
--- a/bs-game-wip/src/components/PrizePool/PrizePool.tsx
+++ b/bs-game-wip/src/components/PrizePool/PrizePool.tsx
@@ -14,7 +14,7 @@ export default function PrizePool() {
closedContent={
System Offline
}
>
- {unknownState ? "???.????" : prizePool} ETH
+ {unknownState ? '???.????' : prizePool} ETH
);
diff --git a/bs-game-wip/src/utils/createHexagon.ts b/bs-game-wip/src/helpers/createHexagon.ts
similarity index 100%
rename from bs-game-wip/src/utils/createHexagon.ts
rename to bs-game-wip/src/helpers/createHexagon.ts
diff --git a/bs-game-wip/src/helpers/getCellCoordsFromXY.ts b/bs-game-wip/src/helpers/getCellCoordsFromXY.ts
new file mode 100644
index 00000000..6453e9bd
--- /dev/null
+++ b/bs-game-wip/src/helpers/getCellCoordsFromXY.ts
@@ -0,0 +1,11 @@
+import { HEX_GRID_MARGIN, HEX_HEIGHT, HEX_WIDTH, VERTICAL_OFFSET } from '@/lib/constants';
+
+import getSnappedMousePosition from './getSnappedMousePosition';
+
+export default function getCellCoordsFromXY(x: number, y: number) {
+ const [sx, sy] = getSnappedMousePosition(x, y);
+ const row = (sy - HEX_GRID_MARGIN) / (HEX_HEIGHT - VERTICAL_OFFSET);
+ const col = (sx - (HEX_GRID_MARGIN + (row % 2) * (HEX_WIDTH / 2))) / HEX_WIDTH;
+
+ return [col, row];
+}
diff --git a/bs-game-wip/src/helpers/getCellXY.ts b/bs-game-wip/src/helpers/getCellXY.ts
index 6b356926..44fbddfb 100644
--- a/bs-game-wip/src/helpers/getCellXY.ts
+++ b/bs-game-wip/src/helpers/getCellXY.ts
@@ -4,7 +4,7 @@ import {
HEX_WIDTH,
HORIZONTAL_OFFSET,
VERTICAL_OFFSET,
-} from '../lib/constants';
+} from '@/lib/constants';
export default function getCellXY(col: number, row: number) {
const x =
diff --git a/bs-game-wip/src/helpers/getIndexFromCoords.ts b/bs-game-wip/src/helpers/getIndexFromCoords.ts
new file mode 100644
index 00000000..95476172
--- /dev/null
+++ b/bs-game-wip/src/helpers/getIndexFromCoords.ts
@@ -0,0 +1,5 @@
+import { COLS } from '@/lib/constants';
+
+export default function getIndexFromCoords(x: number, y: number) {
+ return y * COLS + x;
+}
diff --git a/bs-game-wip/src/utils/getSnappedMousePosition.ts b/bs-game-wip/src/helpers/getSnappedMousePosition.ts
similarity index 100%
rename from bs-game-wip/src/utils/getSnappedMousePosition.ts
rename to bs-game-wip/src/helpers/getSnappedMousePosition.ts
diff --git a/bs-game-wip/src/utils/handleMetaMaskError.ts b/bs-game-wip/src/helpers/handleMetaMaskError.ts
similarity index 100%
rename from bs-game-wip/src/utils/handleMetaMaskError.ts
rename to bs-game-wip/src/helpers/handleMetaMaskError.ts
diff --git a/bs-game-wip/src/lib/constants.ts b/bs-game-wip/src/lib/constants.ts
index 65bf6336..dd6591c9 100644
--- a/bs-game-wip/src/lib/constants.ts
+++ b/bs-game-wip/src/lib/constants.ts
@@ -1,5 +1,5 @@
+import createHexagon from '@/helpers/createHexagon';
import formatAddress from '@/helpers/formatAddress';
-import createHexagon from '@/utils/createHexagon';
export const MOVE_FEE: string = '0.0443';
export const TOTAL_SHIPS: number = 249;
diff --git a/bs-game-wip/src/stores/battleGridStore.ts b/bs-game-wip/src/stores/battleGridStore.ts
index 457b4dc5..69dc18ea 100644
--- a/bs-game-wip/src/stores/battleGridStore.ts
+++ b/bs-game-wip/src/stores/battleGridStore.ts
@@ -1,57 +1,139 @@
-import { create } from 'zustand';
+import { StateCreator, create } from 'zustand';
+import { createJSONStorage, persist } from 'zustand/middleware';
+import getCellCoordsFromXY from '@/helpers/getCellCoordsFromXY';
import getCellXY from '@/helpers/getCellXY';
-import getSnappedMousePosition from '@/utils/getSnappedMousePosition';
+import getIndexFromCoords from '@/helpers/getIndexFromCoords';
import { useContractStore } from './contractStore';
-import {createJSONStorage, persist} from "zustand/middleware";
-
-export const useBattleGridStore = create(persist((set, get) => ({
- grid: [],
- missedCells: [],
- hitCells: [],
- hoveredCell: null,
- scrollPosition: [0, 0],
- mousePosition: [0, 0],
- selectedCell: null,
-
- initGrid: (height: number, width: number) =>
- set(() => {
- const hexagons = [];
- for (let row = 0; row < height; row++) {
- for (let col = 0; col < width; col++) {
- const [x, y] = getCellXY(col, row);
- hexagons.push({ row, col, x, y, revealed: false });
+
+export type Cell = {
+ row: number;
+ col: number;
+ x: number;
+ y: number;
+};
+
+export type RevealedCellType = 'HIT' | 'MISS' | 'UNKNOWN';
+
+export type BattleGridState = {
+ grid: Cell[];
+ missedCells: Cell[];
+ hitCells: Cell[];
+ unknownCells: Cell[];
+ revealedCells: { [key: string]: RevealedCellType };
+ hoveredCell: Cell | null;
+ scrollPosition: [number, number];
+ mousePosition: [number, number];
+ selectedCell: Cell | null;
+};
+
+export type BattleGridActions = {
+ initGrid: (height: number, width: number) => void;
+ setMousePosition: (x: number, y: number) => void;
+ selectCell: () => void;
+ setRevealedCells: (cells: [col: number, row: number][], type: RevealedCellType) => void;
+ setSingleRevealedCell: (x: number, y: number, type: RevealedCellType) => void;
+ addUnknownCell: (x: number, y: number) => void;
+ clearUnknownCells: () => void;
+};
+
+export type BattleGridStore = BattleGridState & BattleGridActions;
+
+export const useBattleGridStore = create
(
+ persist(
+ (set, get) => ({
+ grid: [],
+ missedCells: [],
+ hitCells: [],
+ unknownCells: [],
+ revealedCells: {},
+ hoveredCell: null,
+ scrollPosition: [0, 0],
+ mousePosition: [0, 0],
+ selectedCell: null,
+
+ initGrid: (height: number, width: number) =>
+ set(() => {
+ const hexagons = [];
+ for (let row = 0; row < height; row++) {
+ for (let col = 0; col < width; col++) {
+ const [x, y] = getCellXY(col, row);
+ hexagons.push({ row, col, x, y });
+ }
+ }
+
+ return { grid: hexagons };
+ }),
+
+ setMousePosition: (x: number, y: number) =>
+ set((state) => {
+ const guessState = useContractStore.getState().guessState;
+
+ if (guessState !== 'IDLE') return {};
+
+ // const [sx, sy] = getSnappedMousePosition(x, y);
+ const [col, row] = getCellCoordsFromXY(x, y);
+ const hoveredCell = state.grid[getIndexFromCoords(col, row)];
+ const isRevealed = !!state.revealedCells[`${col}_${row}`];
+
+ if (!hoveredCell || isRevealed) return {};
+
+ return {
+ hoveredCell: hoveredCell || null,
+ mousePosition: [hoveredCell.x, hoveredCell.y],
+ };
+ }),
+
+ selectCell: () => {
+ const selectedCell = get().hoveredCell;
+ if (!selectedCell || useContractStore.getState().guessState !== 'IDLE') return;
+
+ set({ selectedCell });
+ const submitGuess = useContractStore.getState().submitGuess;
+ submitGuess(selectedCell.col, selectedCell.row);
+ // useContractStore.setState({lastGuessCoords: [selectedCell.x, selectedCell.y]});
+ },
+
+ setRevealedCells: (
+ cells: [col: number, row: number][],
+ type: 'HIT' | 'MISS' | 'UNKNOWN'
+ ) => {
+ const newRevealedCells = { ...get().revealedCells };
+ for (let i = 0; i < cells.length; i++) {
+ const key = `${cells[i][0]}_${cells[i][1]}`;
+ if (!newRevealedCells[key] || newRevealedCells[key] === 'UNKNOWN') {
+ newRevealedCells[key] = type;
+ }
}
- }
- return { grid: hexagons };
- }),
- setMousePosition: (x: number, y: number) =>
- set((state) => {
- const guessState = useContractStore.getState().guessState;
- if (guessState !== 'IDLE') return {};
-
- const [sx, sy] = getSnappedMousePosition(x, y);
- const hoveredCell = state.grid.find((cell) => sx === cell.x && sy === cell.y);
-
- return {
- hoveredCell: hoveredCell || null,
- mousePosition: getSnappedMousePosition(sx, sy),
- };
- }),
+ set({ revealedCells: newRevealedCells });
+ },
+
+ setSingleRevealedCell: (x: number, y: number, type: 'HIT' | 'MISS' | 'UNKNOWN') => {
+ const newRevealedCells = { ...get().revealedCells };
+ newRevealedCells[`${x}_${y}`] = type;
+ set({ revealedCells: newRevealedCells });
+ },
+
+ addUnknownCell: (x: number, y: number) =>
+ set((state) => {
+ state.setSingleRevealedCell(x, y, 'UNKNOWN');
- selectCell: () => {
- const selectedCell = get().hoveredCell;
- if (!selectedCell || useContractStore.getState().guessState !== 'IDLE') return;
-
- set({ selectedCell });
- const submitGuess = useContractStore.getState().submitGuess;
- submitGuess(selectedCell.col, selectedCell.row);
- // useContractStore.setState({lastGuessCoords: [selectedCell.x, selectedCell.y]});
- },
-}), {
- name: 'battle-grid-storage',
- storage: createJSONStorage(() => localStorage),
- partialize: (state) => ({ hitCells: state.hitCells, missedCells: state.missedCells })
-}));
+ return { unknownCells: [...state.unknownCells, { ...state.hoveredCell }] };
+ }),
+
+ clearUnknownCells: () => set({ unknownCells: [] }),
+ }),
+ {
+ name: 'battle-grid-storage',
+ storage: createJSONStorage(() => localStorage),
+ partialize: ({ hitCells, missedCells, revealedCells, unknownCells }) => ({
+ hitCells,
+ missedCells,
+ revealedCells,
+ unknownCells,
+ }),
+ }
+ ) as StateCreator
+);
diff --git a/bs-game-wip/src/stores/contractStore.ts b/bs-game-wip/src/stores/contractStore.ts
index 2e21e0f4..3e7626a7 100644
--- a/bs-game-wip/src/stores/contractStore.ts
+++ b/bs-game-wip/src/stores/contractStore.ts
@@ -1,6 +1,6 @@
import { ethers, formatUnits } from 'ethers';
-import { create } from 'zustand';
-import {createJSONStorage, persist} from 'zustand/middleware';
+import { StateCreator, create } from 'zustand';
+import { createJSONStorage, persist } from 'zustand/middleware';
import ContractAddress from '@/assets/contract/address.json';
import BattleshipGameJson from '@/assets/contract/artifacts/contracts/BattleshipGame.sol/BattleshipGame.json';
@@ -11,10 +11,10 @@ import { useWalletStore } from '@/stores/walletStore';
import { useBattleGridStore } from './battleGridStore';
-export interface IContractStore {
+export type ContractStore = {
hits: any[];
misses: any[];
- graveyard: any[];
+ graveyard: boolean[];
gameOver: boolean;
prizePool: string;
guessState: GuessState;
@@ -27,7 +27,7 @@ export interface IContractStore {
setMisses: (m: any[]) => void;
setGraveyard: (g: any[]) => void;
setLastGuessCoords: (g: any) => void;
-}
+};
export type GuessState =
| 'IDLE'
@@ -38,147 +38,159 @@ export type GuessState =
| 'HIT'
| 'MISS';
-export const useContractStore = create(persist((set, get) => ({
- hits: [],
- misses: [],
- graveyard: [],
- gameOver: false,
- prizePool: '',
- guessState: 'IDLE' as GuessState,
- lastError: '',
- lastGuessCoords: null,
-
- submitGuess: async (x: number, y: number) => {
- const signer = useWalletStore.getState().signer;
- const addNewMessage = useMessageStore.getState().addNewMessage;
-
- if (!signer) {
- throw new Error('No signer available.');
- return;
- }
+export const useContractStore = create(
+ persist(
+ (set, get) => ({
+ hits: [],
+ misses: [],
+ graveyard: [],
+ gameOver: false,
+ prizePool: '',
+ guessState: 'IDLE' as GuessState,
+ lastError: '',
+ lastGuessCoords: null,
+
+ submitGuess: async (x: number, y: number) => {
+ const signer = useWalletStore.getState().signer;
+ const addNewMessage = useMessageStore.getState().addNewMessage;
+
+ if (!signer) {
+ throw new Error('No signer available.');
+ return;
+ }
+
+ set({ guessState: 'STARTED' });
+
+ addNewMessage('Issuing Guess...');
+ const contract = new ethers.Contract(
+ ContractAddress.address,
+ BattleshipGameJson.abi,
+ signer
+ );
+ const moveFee = ethers.parseEther(MOVE_FEE);
+ try {
+ const submitTx = await contract.hit(x, y, {
+ value: moveFee,
+ });
+ console.log(submitTx);
+ set({ guessState: 'TRANSACTION_SUCCESS' });
+ const receipt = await submitTx.wait();
+ addNewMessage('Issued Guess tx: ' + receipt.hash);
+ const { allHits, allMisses, graveyard, prizePool, success, guessedCoords } =
+ receipt.logs[0].args.toObject();
+ console.log(receipt, receipt.logs[0].args.toObject());
+
+ get().setHits(allHits);
+ get().setMisses(allMisses);
+ get().setGraveyard(graveyard);
+ get().setPrizePool(prizePool);
+ set({ guessState: success ? 'HIT' : 'MISS' });
+ get().setLastGuessCoords(guessedCoords);
+ } catch (e) {
+ set({ guessState: 'ERROR' });
+ if (e.reason) {
+ addNewMessage('Failed to issue Guess - ' + e.reason + ' ...', 'ERROR');
+ set({ lastError: 'Failed to issue Guess - ' + e.reason });
+
+ if (e.reason === 'Cell already hit') {
+ useBattleGridStore.getState().addUnknownCell(x, y);
+ }
+ } else {
+ addNewMessage(
+ 'Failed to issue Guess - unexpected error occurred, check the console logs...',
+ 'ERROR'
+ );
+ set({ lastError: 'Failed to issue Guess - unexpected error occurred' });
+ throw new Error(e);
+ }
+ }
+ },
+
+ resetGuessState: () =>
+ set({
+ guessState: 'IDLE',
+ }),
+
+ setGraveyard: (latestGraveyard: any[]) => {
+ const addNewMessage = useMessageStore.getState().addNewMessage;
+
+ const graveyardHasUpdated =
+ get().graveyard.length !== latestGraveyard.length ||
+ get().graveyard.some((value, index) => value !== latestGraveyard[index]);
+
+ if (graveyardHasUpdated) {
+ set({ graveyard: latestGraveyard });
+ addNewMessage('Graveyard info updated.');
+ }
+ },
+
+ setMisses: (latestMisses: any[]) => {
+ const currentMisses = useBattleGridStore.getState().missedCells;
+ const addNewMessage = useMessageStore.getState().addNewMessage;
+ const missesHaveUpdated = latestMisses.length !== currentMisses.length;
+
+ if (missesHaveUpdated) {
+ const missedCells = latestMisses.map((entry) => {
+ const [x, y] = getCellXY(parseInt(entry[0]), parseInt(entry[1]));
+ return {
+ col: parseInt(entry[0]),
+ row: parseInt(entry[1]),
+ x,
+ y,
+ state: 'MISSED',
+ };
+ });
+
+ set({ misses: latestMisses });
+ useBattleGridStore.setState({ missedCells });
+ useBattleGridStore.getState().setRevealedCells(latestMisses, 'MISS');
+ useBattleGridStore.getState().clearUnknownCells();
+ addNewMessage('Missed. Shot failed to find target.');
+ }
+ },
+
+ //TODO: Given the similarity of the methods here might be worth combining with the above.
+ setHits: (latestHits: any[]) => {
+ const currentHits = useBattleGridStore.getState().hitCells;
+ const addNewMessage = useMessageStore.getState().addNewMessage;
+ const hitsHaveUpdated = latestHits.length !== currentHits.length;
+
+ if (hitsHaveUpdated) {
+ const hitCells = latestHits.map((entry) => {
+ const [x, y] = getCellXY(parseInt(entry[0]), parseInt(entry[1]));
+ return {
+ col: parseInt(entry[0]),
+ row: parseInt(entry[1]),
+ x,
+ y,
+ state: 'MISSED',
+ };
+ });
+
+ set({ hits: latestHits });
+ useBattleGridStore.setState({ hitCells });
+ useBattleGridStore.getState().setRevealedCells(latestHits, 'HIT');
+ addNewMessage('DIRECT HIT. Shot successfully found target.', 'SUCCESS');
+ }
+ },
+
+ setPrizePool: (prizePool: string) => {
+ const addNewMessage = useMessageStore.getState().addNewMessage;
- set({ guessState: 'STARTED' });
-
- addNewMessage('Issuing Guess...');
- const contract = new ethers.Contract(
- ContractAddress.address,
- BattleshipGameJson.abi,
- signer
- );
- const moveFee = ethers.parseEther(MOVE_FEE);
- try {
- const submitTx = await contract.hit(x, y, {
- value: moveFee,
- });
- console.log(submitTx);
- set({ guessState: 'TRANSACTION_SUCCESS' });
- const receipt = await submitTx.wait();
- addNewMessage('Issued Guess tx: ' + receipt.hash);
- const { allHits, allMisses, graveyard, prizePool, success, guessedCoords } =
- receipt.logs[0].args.toObject();
- console.log(receipt, receipt.logs[0].args.toObject());
-
- get().setHits(allHits);
- get().setMisses(allMisses);
- get().setGraveyard(graveyard);
- get().setPrizePool(prizePool);
- set({ guessState: success ? 'HIT' : 'MISS' });
- get().setLastGuessCoords(guessedCoords);
- } catch (e) {
- set({ guessState: 'ERROR' });
- if (e.reason) {
- addNewMessage('Failed to issue Guess - ' + e.reason + ' ...', 'ERROR');
- set({ lastError: 'Failed to issue Guess - ' + e.reason });
- } else {
addNewMessage(
- 'Failed to issue Guess - unexpected error occurred, check the console logs...',
- 'ERROR'
+ `[BattleshipGame Contract] Prize pool at: ${formatUnits(prizePool, 'ether')} ETH`
);
- set({ lastError: 'Failed to issue Guess - unexpected error occurred' });
- throw new Error(e);
- }
- }
- },
+ set({ prizePool: formatUnits(prizePool, 'ether') });
+ },
- resetGuessState: () =>
- set({
- guessState: 'IDLE',
+ setLastGuessCoords: (coords: any) => {
+ set({ lastGuessCoords: [parseInt(coords[0]), parseInt(coords[1])] });
+ },
}),
-
- setGraveyard: (latestGraveyard: any[]) => {
- const addNewMessage = useMessageStore.getState().addNewMessage;
-
- const graveyardHasUpdated =
- get().graveyard.length !== latestGraveyard.length ||
- get().graveyard.some((value, index) => value !== latestGraveyard[index]);
-
- if (graveyardHasUpdated) {
- set({ graveyard: latestGraveyard });
- addNewMessage('Graveyard info updated.');
- }
- },
-
- setMisses: (latestMisses: any[]) => {
- const currentMisses = useBattleGridStore.getState().missedCells;
- const addNewMessage = useMessageStore.getState().addNewMessage;
- const missesHaveUpdated = latestMisses.length !== currentMisses.length;
-
- if (missesHaveUpdated) {
- const a = latestMisses.map((entry) => {
- const [x, y] = getCellXY(parseInt(entry[0]), parseInt(entry[1]));
- return {
- col: parseInt(entry[0]),
- row: parseInt(entry[1]),
- x,
- y,
- state: 'MISSED',
- };
- });
-
- set({ misses: latestMisses });
- useBattleGridStore.setState({ missedCells: a });
- addNewMessage('Missed. Shot failed to find target.');
- }
- },
-
- //TODO: Given the similarity of the methods here might be worth combining with the above.
- setHits: (latestHits: any[]) => {
- const currentHits = useBattleGridStore.getState().hitCells;
- const addNewMessage = useMessageStore.getState().addNewMessage;
- const hitsHaveUpdated = latestHits.length !== currentHits.length;
-
- if (hitsHaveUpdated) {
- const a = latestHits.map((entry) => {
- const [x, y] = getCellXY(parseInt(entry[0]), parseInt(entry[1]));
- return {
- col: parseInt(entry[0]),
- row: parseInt(entry[1]),
- x,
- y,
- state: 'MISSED',
- };
- });
-
- set({ hits: latestHits });
- useBattleGridStore.setState({ hitCells: a });
- addNewMessage('DIRECT HIT. Shot successfully found target.', 'SUCCESS');
+ {
+ name: 'contract-storage',
+ storage: createJSONStorage(() => localStorage),
+ partialize: (state) => ({ prizePool: state.prizePool, graveyard: state.graveyard }),
}
- },
-
- setPrizePool: (prizePool: string) => {
- const addNewMessage = useMessageStore.getState().addNewMessage;
-
- addNewMessage(
- `[BattleshipGame Contract] Prize pool at: ${formatUnits(prizePool, 'ether')} ETH`
- );
- set({ prizePool: formatUnits(prizePool, 'ether') });
- },
-
- setLastGuessCoords: (coords: any) => {
- set({ lastGuessCoords: [parseInt(coords[0]), parseInt(coords[1])] });
- },
-}), {
- name: 'contract-storage',
- storage: createJSONStorage(() => localStorage),
- partialize: (state) => ({ prizePool: state.prizePool, graveyard: state.graveyard })
-}));
+ ) as StateCreator
+);
diff --git a/bs-game-wip/src/stores/messageStore.ts b/bs-game-wip/src/stores/messageStore.ts
index 21fb1b8f..fedcb010 100644
--- a/bs-game-wip/src/stores/messageStore.ts
+++ b/bs-game-wip/src/stores/messageStore.ts
@@ -25,4 +25,3 @@ export const useMessageStore = create((set) => ({
}));
},
}));
-
diff --git a/bs-game-wip/todos.md b/bs-game-wip/todos.md
index 7138fa09..8c4b3aff 100644
--- a/bs-game-wip/todos.md
+++ b/bs-game-wip/todos.md
@@ -13,7 +13,7 @@
- ~~Animate message log entries~~
- ~~animate hud windows~~
- highlight errors in message log
-- display Remaining Cells chart
+- ~~display Remaining Cells~~
- add ability to scale teh map
- percentage bar for fleet strength
- ~~finish connection window~~
@@ -22,6 +22,7 @@
- improve x,y coords display
- detect metamask disconnection
- ~~Update contract and UI as per discussion~~
-- Persist game state on page reloads
-- empty game state
+- ~~Persist game state on page reloads~~
+- ~~empty game state~~
- modify UI and contract to inform user if ship was sunk on hit/guess
+- Game rules/how to play window
\ No newline at end of file
From b361c5905d91b6e00173f97d6067ad72bfa28857 Mon Sep 17 00:00:00 2001
From: PeterB
Date: Mon, 15 Jul 2024 12:04:10 +0100
Subject: [PATCH 09/22] WIP: Vastly improve performance by significantly
reducing the number of graphic nodes draw on the canvas fo the battle grid.
---
.../BattleGrid/BattleGridCanvas.tsx | 40 ++++++++---------
.../components/BattleGrid/BattleGridCell.tsx | 45 -------------------
.../BattleGrid/BattleGridContainer.tsx | 38 +++++-----------
.../BattleGrid/BattleGridCursor.tsx | 11 +++--
.../components/BattleGrid/BattleGridHits.tsx | 14 ++----
.../BattleGrid/BattleGridMisses.tsx | 21 ++-------
.../BattleGrid/BattleGridUnknowns.tsx | 23 +++-------
bs-game-wip/src/helpers/createHexagon.ts | 29 ++++++------
bs-game-wip/src/helpers/drawGridCells.ts | 34 ++++++++++++++
bs-game-wip/src/lib/constants.ts | 4 --
bs-game-wip/todos.md | 27 ++++++-----
11 files changed, 110 insertions(+), 176 deletions(-)
delete mode 100644 bs-game-wip/src/components/BattleGrid/BattleGridCell.tsx
create mode 100644 bs-game-wip/src/helpers/drawGridCells.ts
diff --git a/bs-game-wip/src/components/BattleGrid/BattleGridCanvas.tsx b/bs-game-wip/src/components/BattleGrid/BattleGridCanvas.tsx
index c50f525b..95c8fcfb 100644
--- a/bs-game-wip/src/components/BattleGrid/BattleGridCanvas.tsx
+++ b/bs-game-wip/src/components/BattleGrid/BattleGridCanvas.tsx
@@ -1,32 +1,26 @@
import { useMemo } from 'react';
-import { Container, Stage } from '@pixi/react';
+import { Container, Graphics, Stage } from '@pixi/react';
import CellHighlight from '@/components/CellHighlight/CellHighlight';
+import drawGridCells from '@/helpers/drawGridCells';
import { HEX_GRID_MARGIN, HEX_HEIGHT, HEX_WIDTH } from '@/lib/constants';
+import { Cell } from '@/stores/battleGridStore';
-import BattleGridCell from './BattleGridCell';
import BattleGridCursor from './BattleGridCursor';
import BattleGridExplosion from './BattleGridExplosion';
import BattleGridHits from './BattleGridHits';
import BattleGridMisses from './BattleGridMisses';
import BattleGridUnknowns from './BattleGridUnknowns';
-export default function BattleGridCanvas({ grid, width, height }) {
- const gridCells = useMemo(
- () =>
- grid.map(({ row, col, x, y }) => (
-
- )),
- [grid]
- );
+type Props = {
+ grid: Cell[];
+ width: number;
+ height: number;
+};
+
+export default function BattleGridCanvas({ grid, width, height }: Props) {
+ const gridCells = useMemo(() => drawGridCells(g, grid)} />, [grid]);
return (
- {gridCells}
-
-
-
-
+
+ {gridCells}
+
+
+
+
+
diff --git a/bs-game-wip/src/components/BattleGrid/BattleGridCell.tsx b/bs-game-wip/src/components/BattleGrid/BattleGridCell.tsx
deleted file mode 100644
index 31a4d9b8..00000000
--- a/bs-game-wip/src/components/BattleGrid/BattleGridCell.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Graphics, Text } from '@pixi/react';
-
-import { hexHitArea } from '@/lib/constants';
-
-const textStyle = {
- fill: '#ffffff',
- fontSize: 14,
-};
-
-export default function BattleGridCell({ col, row, x, y, state }) {
- return (
- <>
- {
- g.position.set(x, y);
- g.clear();
-
- if (state === 'UNTOUCHED') {
- g.lineStyle(1, 0x464646, 1);
- }
- if (state === 'MISSED') {
- g.beginFill(0x5a5a5a, 1);
- g.lineStyle(1, 0x4f4f4f, 1);
- }
- if (state === 'HIT') {
- g.beginFill(0xe16f6f, 1);
- g.lineStyle(1, 0xe16f6f, 1);
- }
- if (state === 'UNKNOWN') {
- g.beginFill(0xffffff, 0.05);
- g.lineStyle(1, 0xffffff, 0.3);
- }
-
- g.drawPolygon(hexHitArea);
- g.endFill();
- }}
- >
- {state === 'UNKNOWN' && (
-
- )}
-
- >
- );
-}
diff --git a/bs-game-wip/src/components/BattleGrid/BattleGridContainer.tsx b/bs-game-wip/src/components/BattleGrid/BattleGridContainer.tsx
index af6b6bc8..e6203a38 100644
--- a/bs-game-wip/src/components/BattleGrid/BattleGridContainer.tsx
+++ b/bs-game-wip/src/components/BattleGrid/BattleGridContainer.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef } from 'react';
+import { MouseEvent, useEffect, useRef } from 'react';
import { COLS, CONTAINER_HEIGHT, ROWS } from '@/lib/constants';
import { useBattleGridStore } from '@/stores/battleGridStore';
@@ -19,37 +19,22 @@ export default function BattleGridContainer() {
const grid = useBattleGridStore((state) => state.grid);
const selectCell = useBattleGridStore((state) => state.selectCell);
- // const {handleCellSelection} = useCellSelection()
-
useEffect(() => {
initGrid(COLS, ROWS);
}, []);
- const lastScrollPosition = useRef({ top: 0, left: 0 });
- const elementRef = useRef(null);
-
- const handleScroll = () => {
- // if (elementRef.current) {
- // const newScrollLeft = elementRef.current.scrollLeft;
- // const newScrollTop = elementRef.current.scrollTop;
- // if (
- // Math.abs(newScrollTop - lastScrollPosition.current.top) > threshold ||
- // Math.abs(newScrollLeft - lastScrollPosition.current.left) > threshold
- // ) {
- // setScrollPosition(newScrollLeft, newScrollTop);
- // lastScrollPosition.current = { top: newScrollTop, left: newScrollLeft };
- // }
- // }
- };
+ const elementRef = useRef(null);
- const handleMouseMove = (event) => {
- const rect = elementRef.current.getBoundingClientRect();
- const scrollLeft = elementRef.current.scrollLeft;
- const scrollTop = elementRef.current.scrollTop;
+ const handleMouseMove = (event: MouseEvent) => {
+ if (elementRef?.current) {
+ const rect = elementRef.current.getBoundingClientRect();
+ const scrollLeft = elementRef.current.scrollLeft;
+ const scrollTop = elementRef.current.scrollTop;
- const x = event.clientX - rect.left + scrollLeft;
- const y = event.clientY - rect.top + scrollTop;
- setMousePosition(x, y);
+ const x = event.clientX - rect.left + scrollLeft;
+ const y = event.clientY - rect.top + scrollTop;
+ setMousePosition(x, y);
+ }
};
const handleMouseClick = () => {
@@ -62,7 +47,6 @@ export default function BattleGridContainer() {
style={styles.container}
onMouseMove={handleMouseMove}
onClick={handleMouseClick}
- onScroll={handleScroll}
>
diff --git a/bs-game-wip/src/components/BattleGrid/BattleGridCursor.tsx b/bs-game-wip/src/components/BattleGrid/BattleGridCursor.tsx
index 554e1496..e34505bc 100644
--- a/bs-game-wip/src/components/BattleGrid/BattleGridCursor.tsx
+++ b/bs-game-wip/src/components/BattleGrid/BattleGridCursor.tsx
@@ -1,12 +1,14 @@
import { useRef } from 'react';
-import { BloomFilter } from '@pixi/filter-bloom';
import { Container, Graphics, useTick } from '@pixi/react';
-import { hexHitArea } from '@/lib/constants';
+import createHexagon from '@/helpers/createHexagon';
+import { HEX_HEIGHT, HEX_WIDTH } from '@/lib/constants';
import { useBattleGridStore } from '@/stores/battleGridStore';
import { useContractStore } from '@/stores/contractStore';
+const hexagon = createHexagon(HEX_WIDTH, HEX_HEIGHT);
+
export default function BattleGridCursor() {
const [[x, y], hoveredCell] = useBattleGridStore((state) => [
state.mousePosition,
@@ -15,10 +17,7 @@ export default function BattleGridCursor() {
const guessState = useContractStore((state) => state.guessState);
const isLoadingState = guessState !== 'IDLE';
const shapeRef = useRef(null);
- const scaleRef = useRef(1);
- const directionRef = useRef(1);
const timeRef = useRef(0);
- const bloomFilter = new BloomFilter(3, 3, 2);
useTick((delta) => {
if (shapeRef.current) {
@@ -53,7 +52,7 @@ export default function BattleGridCursor() {
g.position.set(x, y);
g.clear();
g.lineStyle(1, guessState !== 'ERROR' ? 0xffffff : 0xdc2626, 1);
- g.drawPolygon(hexHitArea);
+ g.drawPolygon(hexagon);
g.endFill();
}}
/>
diff --git a/bs-game-wip/src/components/BattleGrid/BattleGridHits.tsx b/bs-game-wip/src/components/BattleGrid/BattleGridHits.tsx
index d93974c9..f9267272 100644
--- a/bs-game-wip/src/components/BattleGrid/BattleGridHits.tsx
+++ b/bs-game-wip/src/components/BattleGrid/BattleGridHits.tsx
@@ -1,20 +1,12 @@
import { useMemo } from 'react';
-import { Container } from '@pixi/react';
+import { Graphics } from '@pixi/react';
+import drawGridCells from '@/helpers/drawGridCells';
import { useBattleGridStore } from '@/stores/battleGridStore';
-import BattleGridCell from './BattleGridCell';
-
export default function BattleGridHits() {
const hitCells = useBattleGridStore((state) => state.hitCells);
- const revealedGridCells = useMemo(
- () =>
- hitCells.map(({ row, col, x, y }) => (
-