In this technical blog, we'll explore the architecture and implementation of Botanical Battle, a strategic AI-driven garden simulation built with React 19, TypeScript, and the Canvas API.
The Architecture: A Deterministic Game Engine
The core of Botanical Battle is a deterministic game engine that processes actions and returns the next game state. This approach ensures that the simulation is predictable and easy to debug.
The Game State Machine
The game state is defined by the GameState interface, which includes the grid, water levels, scores, stats, and current turn information.
// From /src/types.ts
export interface GameState {
grid: Cell[][];
water: { A: number; B: number };
scores: { A: number; B: number };
stats: GameStats;
turn: number;
maxTurns: number;
currentAgent: 'A' | 'B';
logs: string[];
isGameOver: boolean;
agentConfigs: { A: AgentConfig; B: AgentConfig };
}
The processAction function in engine.ts acts as the state transition function. It takes the current state and an action (Plant, Water, Sabotage, Save) and returns the new state.
AI Decision-Making: Heuristic-Based Logic
The AI agents, Elder Bloom and Nightshade, use heuristic-based logic to make decisions. Their behavior is driven by their "personality" strings, which weight different actions.
Elder Bloom: Defensive Growth
Elder Bloom focuses on high-scoring trees and long-term growth. It also employs "defensive planting" by placing low-cost flowers around its trees to block the opponent from planting weeds.
// Defensive planting logic in /src/ai.ts
if (prefersTrees && water >= COSTS.plant + COSTS.water) {
const myTrees = findCells(agentId, ['tree'], ['seed', 'sprout', 'mature', 'bloom']);
for (const [tx, ty] of myTrees) {
const neighbors = findEmptyNeighbors(tx, ty);
if (neighbors.length > 0) {
return { agentId, type: 'plant', target: neighbors[0], plantType: 'flower' };
}
}
}
Nightshade: Aggressive Sabotage
Nightshade is the opposite. It prioritizes sabotaging high-value opponent plants (Trees and Vines) by destroying them and replacing them with weeds.
High-Performance Rendering with Canvas
To ensure smooth performance, especially with a 10x10 grid and various animations, we used the HTML5 Canvas API for the GameGrid component.
The "Drought" Visual Effect
One of the most interesting visual features is the "Drought" effect. When both agents' water levels drop below 10 units, the grid subtly dims and a fog effect appears.
// From /src/components/GameGrid.tsx
if (isDrought) {
// Subtle dimming
ctx.fillStyle = 'rgba(0, 0, 0, 0.15)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Fog effect using a radial gradient
const gradient = ctx.createRadialGradient(
canvas.width / 2, canvas.height / 2, 50,
canvas.width / 2, canvas.height / 2, canvas.width * 0.8
);
gradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
gradient.addColorStop(1, 'rgba(100, 100, 100, 0.2)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
State Management and Persistence
We used React's useState and useEffect hooks for local state management. For persistence, we implemented a Leaderboard component that stores battle results in LocalStorage.
Saving Results
// From /src/App.tsx
const saveResult = (finalState: GameState) => {
const winner = finalState.scores.A > finalState.scores.B ? 'A' : 'B';
const newResult: BattleResult = {
id: Math.random().toString(36).substr(2, 9),
date: new Date().toISOString(),
scoreA: finalState.scores.A,
scoreB: finalState.scores.B,
nameA: agentConfigs.A.name,
nameB: agentConfigs.B.name,
winner,
stats: finalState.stats
};
setResults([newResult, ...results]);
};
Future Roadmap
- Multiplayer Support: Enabling human vs. AI or human vs. human battles.
- Dynamic Weather: Rain, heatwaves, and storms that affect water levels and plant growth.
- Advanced AI: Integrating LLMs (like Gemini) to drive more complex and unpredictable agent behavior.
Botanical Battle demonstrates how simple game mechanics can lead to complex emergent behavior when combined with distinct AI personalities. By leveraging React and the Canvas API, we've created a performant and visually engaging simulation that's easy to extend and experiment with.
Top comments (0)