Now that we can move forward through infinite rows of obstacles let’s add a score indicator to keep track of how many rows we’ve crossed.
Now that we can move forward through infinite rows of obstacles let’s add a score indicator to keep track of how many rows we’ve crossed.
Let’s add our last store called game store. This is another zustand store that keeps track of the score and other top-level game states.
For now, we will only keep track of the score. The store has a score property and an updateScore method that will update the score if the new score is higher than the current score.
import { create } from "zustand";
interface StoreState { score: number; updateScore: (rowIndex: number) => void;}
const useStore = create<StoreState>((set) => ({ score: 0, updateScore: (rowIndex: number) => { set((state) => ({ score: Math.max(rowIndex, state.score) })); },}));
export default useStore;import { create } from "zustand";
const useStore = create((set) => ({ score: 0, updateScore: (rowIndex) => { set((state) => ({ score: Math.max(rowIndex, state.score) })); },}));
export default useStore;Now that we have a store that tracks the score, let’s display it on the screen. This simple React component reads the score from the store and displays it.
import useStore from "../stores/game";import "./Score.css";
export function Score() { const score = useStore((state) => state.score);
return <div id="score">{score}</div>;}import useStore from "../stores/game";import "./Score.css";
export function Score() { const score = useStore((state) => state.score);
return <div id="score">{score}</div>;}It also comes with a CSS file to style the score indicator. We are positioning the score in the top-left corner of the screen with absolute position. This element will use the Press Start 2P font we set in the game.css style.
#score { position: absolute; top: 20px; left: 20px;
font-size: 2em; color: white;}Finally, we need to add the Score component to the Game component. Since the score indicator is not part of the 3D scene, we add it to the container div element.
import { Scene } from "./components/Scene";import { Player } from "./components/Player";import { Map } from "./components/Map";import { Score } from "./components/Score";import { Controls } from "./components/Controls";import "./index.css";
export default function Game() { return ( <div className="game"> <Scene> <Player /> <Map /> </Scene> <Score /> <Controls /> </div> );}import { Scene } from "./components/Scene";import { Player } from "./components/Player";import { Map } from "./components/Map";import { Score } from "./components/Score";import { Controls } from "./components/Controls";import "./index.css";
export default function Game() { return ( <div className="game"> <Scene> <Player /> <Map /> </Scene> <Score /> <Controls /> </div> );}Finally, once the player completes a step, we call the updateScore method from the game store. We can do this as the last statement of the stepCompleted function of the player store.
The updateScore method is smart enough to only update the score if the current row index is higher than the current score, so we can call it after every movement.
import type { MoveDirection } from "../types";import { endsUpInValidPosition } from "../utilities/endsUpInValidPosition";import useMapStore from "./map";import useGameStore from "./game";
20 collapsed lines
export const state: { currentRow: number; currentTile: number; movesQueue: MoveDirection[];} = { currentRow: 0, currentTile: 0, movesQueue: [],};
export function queueMove(direction: MoveDirection) { const isValidMove = endsUpInValidPosition( { rowIndex: state.currentRow, tileIndex: state.currentTile }, [...state.movesQueue, direction] );
if (!isValidMove) return;
state.movesQueue.push(direction);}
export function stepCompleted() { const direction = state.movesQueue.shift();
if (direction === "forward") state.currentRow += 1; if (direction === "backward") state.currentRow -= 1; if (direction === "left") state.currentTile -= 1; if (direction === "right") state.currentTile += 1;
// Add a batch of new rows if the player is running out of them; rows are infinite if (state.currentRow === useMapStore.getState().rows.length - 10) { useMapStore.getState().addRows(); }
useGameStore.getState().updateScore(state.currentRow);}import { endsUpInValidPosition } from "../utilities/endsUpInValidPosition";import useMapStore from "./map";import useGameStore from "./game";
16 collapsed lines
export const state = { currentRow: 0, currentTile: 0, movesQueue: [],};
export function queueMove(direction) { const isValidMove = endsUpInValidPosition( { rowIndex: state.currentRow, tileIndex: state.currentTile }, [...state.movesQueue, direction] );
if (!isValidMove) return;
state.movesQueue.push(direction);}
export function stepCompleted() { const direction = state.movesQueue.shift();
if (direction === "forward") state.currentRow += 1; if (direction === "backward") state.currentRow -= 1; if (direction === "left") state.currentTile -= 1; if (direction === "right") state.currentTile += 1;
// Add a batch of new rows if the player is running out of them; rows are infinite if (state.currentRow === useMapStore.getState().rows.length - 10) { useMapStore.getState().addRows(); }
useGameStore.getState().updateScore(state.currentRow);}