Scoring System
Relevant source files
The following files were used as context for generating this wiki page:The scoring system is a fundamental feature of Farm Siege, a score-chaser browser game. Its primary purpose is to track player performance, provide immediate visual feedback for in-game actions, and persist the final score to a global highscore board. This system encompasses state management for the current score, UI components for display, and a backend API for submission and retrieval of highscores.
The entire lifecycle of a score—from earning points in-game to appearing on the highscore list—is handled by a combination of frontend state stores, UI components, and serverless API endpoints that interact with a Redis database.
Architecture Overview
The scoring system can be broken down into three main areas: State Management, UI Representation, and Highscore Persistence.
This diagram illustrates the flow from a player action triggering a score change to the score being submitted and displayed on the highscore board.
Sources: src/components/CharacterContainer.ts, src/screens/GameOverScreen.ts, api/submitScore.ts, api/highscores.ts
State Management (StatsStore)
The core of the in-game scoring system is the StatsStore. It is a global state container responsible for holding the current score and related metadata.
Sources: src/store/StatsStore.ts
Data Structure
The state is defined by the StatsStoreContent type:
| Property | Type | Description |
|---|---|---|
score | number | The player's current score. Initialized to 0. |
scoreSubmitted | boolean | A flag to prevent multiple submissions. Initialized to false. |
duration | number | The total duration of the current game session. |
Sources: src/store/StatsStore.ts:6-10
Class Diagram
This diagram shows the public interface of the StatsStore, which manages all score-related state.
Sources: src/store/StatsStore.ts:12-50, src/screens/GameOverScreen.ts:38-41
Key Methods
addScore(amount: number): This method is called from other parts of the application to increment the player's score. It clones the current state, adds the specified amount, and updates the store.
Sources: src/store/StatsStore.ts:20-26setScoreSubmitted(value: boolean): Sets thescoreSubmittedflag. This is used on theGameOverScreento disable the submission form after a successful submission.
Sources: src/store/StatsStore.ts:36-42reset(): TheGameOverScreenandStartScreencall this method (via store initialization) to reset the score andscoreSubmittedflag to their initial values when a new game starts.
Sources: src/screens/GameOverScreen.ts:38-41, src/store/StatsStore.ts:13-17
Score Accrual
Points are awarded to the player for performing specific actions during gameplay. The logic for detecting these actions and awarding points resides primarily in CharacterContainer.ts.
Sources: src/components/CharacterContainer.ts
Scoring Actions
The following actions result in points being added to the StatsStore:
| Action | Points Awarded (from values.json) | Triggering Logic |
|---|---|---|
| Harvest a fully grown plant | values.scores.plant | Using the shovel on a Plant tile where age >= values.plant.age.fullyGrown. |
| Remove a mole | values.scores.mole | Using the hammer on a Mole tile. |
| Remove a weed | values.scores.weed | Using the shovel on a Weed tile. |
| Shoot a rabbit | values.scores.rabbit | Firing the gun, resulting in a bullet from movablesStore colliding with a rabbit. |
When one of these actions occurs, CharacterContainer.ts calls statsStore.addScore() with the corresponding value and also triggers a visual effect by calling effectsStore.showScoreEffect().
Sources: src/components/CharacterContainer.ts:122-149, src/components/CharacterContainer.ts:201-205
Score Accrual Flow
This flowchart shows how a player's action is processed to award points and trigger a visual effect.
Sources: src/components/CharacterContainer.ts:80-206
UI Representation
The scoring system uses several UI components to provide feedback to the player.
Score Counter
The ScoreCounter component is a simple display element shown during gameplay.
- Functionality: It reads the
scoredirectly from theStatsStorein each frame. - Display: It renders the text
"{score} Points"inside a semi-transparent black rectangle at the top of the screen.
Sources: src/components/ScoreCounter.ts:27-35, src/Game.ts:161-165
Score Effect
When points are awarded, a floating text effect is displayed at the location of the action.
ScoreEffect.ts: A component that renders text like+10. Its props include thevalueto display.
Sources: src/components/ScoreEffect.ts:15-21EffectsStore.ts: This store manages the state for all active visual effects.- The
showScoreEffect(x, y, value)method creates aScoreEffectDataobject, which includes the position and score value, and adds it to an array in the store's state. - The
updateEffectsmethod updates the position (e.g., moving it upwards) and lifetime of each effect, removing them when they expire.
Sources: src/store/EffectsStore.ts:40-49, src/store/EffectsStore.ts:68-84
- The
Effects.ts: This component subscribes to theEffectsStoreand uses aRepeatingcomponent to render aScoreEffectfor each active score effect in the store.
Sources: src/overlays/Effects.ts:30-44
Highscore System
At the end of a game, the player's score can be submitted to a persistent, global highscore board. This involves a client-side submission process and two backend API endpoints.
Score Submission (Client-Side)
The submission process is handled by the GameOverScreen.
- After a game ends, the
GameOverScreenis displayed, showing the final score fromStatsStore. - If the score is greater than 0 and hasn't been submitted, an input field is shown for the player to enter their name.
- Upon pressing "Enter", the
submitScoremethod is called. - This method first requests a reCAPTCHA token to prevent spam.
- It then sends a
POSTrequest to the/api/submitScoreendpoint with the player's name, score, and the reCAPTCHA token. - To prevent duplicate submissions, it immediately calls
statsStore.setScoreSubmitted(true).
Sources: src/screens/GameOverScreen.ts:51-93
Score Submission API (/api/submitScore)
This is a Vercel Edge Function that receives the score submission and adds it to the database.
Sources: api/submitScore.ts
Request Body Schema
The endpoint expects a JSON body validated against the following Zod schema:
| Field | Type | Constraints | Description |
|---|---|---|---|
name | string | min(1), max(20) | The player's name. |
score | number | int(), min(0) | The player's final score. |
recaptcha | string | - | The reCAPTCHA v3 token. |
Sources: api/submitScore.ts:27-31
Submission Flow
This diagram shows the sequence of events when a user submits their score, including reCAPTCHA validation and the database write operation.
Sources: api/submitScore.ts:33-76
The API uses the ZADD command with the GT option on a Redis sorted set named highscores. This means the score is only added if it is greater than the lowest score in the set, which is an efficient way to maintain a top-N list.
Sources: api/submitScore.ts:64-68
Highscore Retrieval API (/api/highscores)
This Vercel Edge Function retrieves the top 10 highscores.
- It connects to the same Upstash Redis instance.
- It executes the
ZREVRANGE highscores 0 9 WITHSCOREScommand to get the top 10 scores in descending order. - The flat array returned by Redis (
[name1, score1, name2, score2, ...]) is parsed into a JSON array of{name, score}objects. - This JSON array is returned in the response.
Sources: api/highscores.ts:10-29
Highscore Display
The highscores are fetched and displayed on the GameOverScreen and StartScreen (though the component is on the GameOverScreen).
MiscStore.fetchHighscores(): This method in theMiscStoresends aGETrequest to/api/highscores, sorts the results, and stores them in its state.
Sources: src/store/MiscStore.ts:57-77Highscores.ts: This component reads thehighscoresarray from theMiscStore.
Sources: src/components/Highscores.ts:19-22HighscoreItem.ts: TheHighscorescomponent renders aHighscoreItemfor each entry, displaying the rank, name, and score.
Sources: src/components/HighscoreItem.ts