Developed with the software and tools below
Farm The Gap, a new digital puzzle game from The Plotline, gives you the keys to a virtual global farm. Modeled on real-world data, the game’s objective is to close a 50% “food gap” by simply changing what we grow and eat.
This repository contains all the source code and data for the game.
- Node v20+
- npm/pnpm
- Clone the repo and run
npm install
to install dependencies - Start a development server:
npm run dev
- Build a production version:
npm run build
- Preview the production build:
npm run preview
.
The brains of the game are defined in src/lib/stores/state.ts.
In this file are five writable
Svelte stores:
$farm
: The foods on the farm and the respective production outputs$gameState
: Food inventory, current year, remaining undos$gameSettings
: High-level global settings$userState
: User preferences and interaction states$gameHistory
: History of moves (foods added/removed and where)
The first three stores feed into a derived
Svelte store called $successMetrics
. This store monitors the overall game status (win/loss) and the warning or fail states of each individual game metric (protein supply, emissions, etc). Any changes in the dependant stores triggers a recalculation of the derived store.
Global types and class declarations can be found in src/ambient.d.ts. Some important data structures:
class Farm {
constructor(settings: GameSettings | null)
// properties
initialState: FarmState
grid: FarmGrid
items: string[]
rows: number
cols: number
// methods
getInitialState(): FarmState
getFarmMetric(fn: (item: Food) => number): FarmMetric
plantCrop(x: number, y: number, foodItem: Food): void
getTotalSum(fn: (item: Food) => number): number
getSumByFoodType(fn: (item: Food) => number): FarmGridFoodList[]
// getters
readonly yield: FarmMetric
readonly landUse: FarmMetric
readonly waterUse: FarmMetric
readonly emissions: FarmMetric
readonly eutrophy: FarmMetric
readonly protein: FarmMetric
readonly calories: FarmMetric
readonly foodChanges: Count[]
}
interface FailureMetric {
value: number
key: FailureMetricKey
label: string
suffix: string
limit: number
objective: string
warn: boolean
fail: boolean
history: number[]
farmMetricKey: FarmMetricKey
foodMetricKey: keyof Food
chartSettings: LineChartSettings
}
interface SuccessMetrics {
hectaresPerPerson: number
peopleAdequateCalories: number
calorieProductionChange: number
caloriesPerPersonPerDayValue: number
proteinPerPersonPerDay: FailureMetric
emissionsChange: FailureMetric
waterUseChange: FailureMetric
eutrophyChange: FailureMetric
hasSucceeded: boolean
hasFailed: boolean
}
I had to whittle down the hundreds of foods eaten globally into a sensible list of groups for easy gameplay. That involved making decisions about how to group and categorize foods, and finding the optimal trade-off between simplicity and accuracy. I arrived at the 13 foods and food groups in the stats table below.
Find these defined in src/lib/data/foods.json.
Food | globalLand (%) | kgYieldPerHa | caloriesPerKg | proteinPerKg | emissionsPerKg | waterUsePerKg | eutrophyPerKg | landPerKg |
---|---|---|---|---|---|---|---|---|
🐄 Beef | 53 | 50 | 2,430 | 175 | 95 | 2494 | 428.7 | 295.3 |
🐑 Lamb | 9 | 27 | 2,550 | 171 | 39.7 | 1803 | 97.1 | 369.8 |
🥛 Dairy | 6 | 2,458 | 610 | 32.7 | 3.7 | 310 | 11.5 | 4.1 |
🌾 Rice | 6 | 4,740 | 3,590 | 70.4 | 3.9 | 1,586 | 26.5 | 2.9 |
🍞 Wheat | 6 | 3,483 | 3,700 | 151 | 1.6 | 648 | 7.2 | 3.9 |
🐖 Pork | 4 | 737 | 2,280 | 178 | 9.8 | 1292 | 60.9 | 13.6 |
🥜 Nuts* | 2 | 1,600 | 5,930 | 206.7 | 1.8 | 2993 | 16.6 | 11 |
🫘 Legumes* | 3 | 1,896 | 3093 | 192 | 1.4 | 52 | 11.7 | 16.4 |
🐓 Poultry | 3 | 1,200 | 1,330 | 179 | 8.2 | 483 | 34.3 | 8.3 |
🌽 Corn | 2 | 5,847 | 3,640 | 62 | 1.7 | 216 | 4 | 2.9 |
🥦 Vegetables* | 2 | 19,862 | 663 | 16.7 | 0.5 | 48 | 1.9 | 0.4 |
🍊 Fruit* | 2 | 13,655 | 418 | 6.8 | 1.1 | 186 | 3.5 | 1 |
🥚 Eggs | 2 | 1,465 | 1,430 | 124 | 4.4 | 830 | 20 | 6.8 |
Nuts impact data is the average of Nuts and Groundnuts (commodity data); nutrition data is the average of raw peanuts, almonds, and cashews.
Legumes data is a weighted average of Beans (75%), Chickpeas (10%), Lentils (5%), Garden Peas (10%)
Vegetables impact data is the weighted average of Root Vegetables (75%), Other Vegetables (20%), and Brassicas (5%). Nutrition data is the weighted average of gold potatoes, mature carrots and raw broccoli.
Fruit impact data is weighted average of Apples (11%), Bananas (14%), Berries (1%), Citrus Fruit (18%), Tomatoes (21%), and Other Fruit (35%) (commodity data). Nutritional data is weighted average of red delicious apples, bananas, strawberries, navel oranges, roma tomatoes and bartlett pears.