Getting Started
Relevant source files
The following files were used as context for generating this wiki page:
- package.json
- index.html
- README.md
- tsconfig.json
- src/Game.ts
- lib/Canvas.ts
- src/screens/StartScreen.ts
- src/components/CharacterContainer.ts
- api/submitScore.ts
- api/highscores.ts
- src/store/MiscStore.ts
- src/screens/GameOverScreen.ts
- src/screens/HelpScreen.ts
- src/components/instructions/Instructions10.ts
- src/components/ScoreCounter.ts
Farm Siege is a browser-based score-chaser game where players must grow and defend their crops from various threats. The project is built entirely from scratch in TypeScript, utilizing a custom canvas-based game engine. The game logic resides in the /src directory, while the engine code is in /lib. Highscores are managed through a serverless API and stored in an Upstash Redis instance.
This guide provides a technical overview of the project's structure, build process, core engine mechanics, and backend services. It is intended for developers looking to understand, contribute to, or learn from the Farm Siege codebase.
Sources: README.md, index.html
Project Setup and Build Process
The project uses Vite for its development server and build process, with TypeScript for type safety.
Sources: package.json, tsconfig.json
Scripts
The package.json file defines the primary scripts for development, building, and code quality.
| Script | Description |
|---|---|
dev | Starts the Vite development server. |
build | Creates a production build using Vite. |
lint | Lints all TypeScript files using ESLint. |
format | Formats the codebase using Prettier. |
Sources: package.json:10-15
Dependencies
The project relies on several key libraries for its functionality.
| Dependency | Usage |
|---|---|
vite | The build tool and development server. |
typescript | The primary programming language. |
zod | Used in the API for schema declaration and validation. |
better-timer | A dependency for timing, likely used within the game loop. |
js-cookie | Used to manage client-side cookies, for settings like helpShown. |
eslint, prettier | Used for code linting and formatting. |
Sources: package.json:16-43, src/screens/StartScreen.ts:43-45, api/submitScore.ts:1,27
Build and Development Flow
The development and build process is orchestrated by Vite, which compiles the TypeScript source files into browser-compatible JavaScript.
This diagram illustrates the main development workflows defined in package.json.
Sources: package.json:11-14
Application Architecture
The game is a single-page application rendered on an HTML <canvas> element. A custom engine handles rendering, the game loop, and state management.
Entry Point and Initialization
The application's entry point is index.html, which loads the main TypeScript module /src/main.ts. This script initializes the core Canvas class, which in turn mounts the root Game component.
The initialization sequence is as follows:
- The browser loads
index.html. - The script
/src/main.tsis executed. - A new
Canvasinstance is created, which takes the rootGamecomponent. - The
Gamecomponent'sonInitmethod is called. This sets up game stores, audio, and loads external scripts like Google reCAPTCHA. - A
SplashScreenis displayed. Once it finishes, a callback hides it, allowing theStartScreento become interactive.
Sources: index.html:29, lib/Canvas.ts:25-31, src/Game.ts:42-70, src/overlays/SplashScreen.ts:18-22
This sequence diagram shows the initialization flow from loading the page to the game being ready.
Game Loop and State Management
The game loop is managed by the Canvas class, which uses window.requestAnimationFrame for efficient rendering.
Canvas.render(): On each frame, it clears the canvas, calculates the time difference since the last frame (timeDifference), and calls the root component'srendermethod.Component.render(): This method recursively callsonTickfor itself and its children, then renders them.Game.onTick(): This is the main logic update hook. It updates timers, checks for game-over conditions (e.g., no friendly plants left), and updates the game speed multiplier in various stores.
Sources: lib/Canvas.ts:91-112, src/Game.ts:72-105
State is managed centrally through a collection of Store objects, each responsible for a specific domain of the application's state.
| Store | Responsibility |
|---|---|
CharacterStore | Player position, direction, and held items. |
GridStore | The state of the game grid, including plants & weeds. |
StatsStore | Player score, game duration, and game speed. |
ScreensStore | The currently active screen (e.g., Start, Game). |
MovablesStore | State of moving entities like rabbits and bullets. |
EffectsStore | Manages visual effects like smoke and score pop-ups. |
MiscStore | Highscores, instruction pages, and other misc state. |
SettingsStore | Game settings like volume. |
CosmeticsStore | Player cosmetic choices. |
Sources: src/Game.ts:162-173
Screen Management
The game uses a state machine pattern, managed by ScreensStore, to control which screen is visible. The Game component conditionally renders screen components based on the active state.
This diagram shows the possible transitions between different game screens.
Sources: src/Game.ts:107-141, src/screens/StartScreen.ts:28-100, src/screens/GameOverScreen.ts:31-38, 110-113
Player Input
Player input from keyboards and gamepads is handled by the InputMap class within CharacterContainer. It maps physical inputs to abstract game actions.
| Action | Keyboard Keys | Gamepad Buttons | Description |
|---|---|---|---|
up | W, ArrowUp | LeftStickUp | Move character up. |
down | S, ArrowDown | LeftStickDown | Move character down. |
left | A, ArrowLeft | LeftStickLeft | Move character left. |
right | D, ArrowRight | LeftStickRight | Move character right. |
use | Space | ButtonB | Harvest plant, remove weed/molehill. |
place | V | ButtonA | Place a plant from hand. |
fire | C | ButtonX | Fire the shotgun to shoot rabbits. |
Sources: src/components/CharacterContainer.ts:24-46
Highscore API
The game features a highscore board powered by serverless functions and an Upstash Redis database.
Sources: api/highscores.ts, api/submitScore.ts
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/highscores | Fetches the top 10 highscores. |
POST | /api/submitScore | Submits a new score. Requires reCAPTCHA validation. |
Sources: api/highscores.ts:10, api/submitScore.ts:26
Score Submission Flow
When a game ends, the player can submit their score. This process involves client-side reCAPTCHA validation to prevent spam.
This diagram details the steps involved in submitting a score, from user action to database entry.
Sources: src/screens/GameOverScreen.ts:60-93, api/submitScore.ts:13-59
Fetching Highscores
The highscores are fetched from the /api/highscores endpoint, which queries the Redis database for the top 10 scores in descending order.
// api/highscores.ts
const handler = async () => {
const response = await fetch(`${process.env.UPSTASH_REDIS_REST_URL}/zrevrange/highscores/0/9/WITHSCORES`, {
headers: {
Authorization: `Bearer ${process.env.UPSTASH_REDIS_REST_TOKEN}`
}
});
// ... processing logic
};
Sources: api/highscores.ts:10-15