diff --git a/src/lib/components/BlockGameState.svelte b/src/lib/components/BlockGameState.svelte index 3acf3e6..9575bf8 100644 --- a/src/lib/components/BlockGameState.svelte +++ b/src/lib/components/BlockGameState.svelte @@ -25,10 +25,10 @@
{#if suffix === "%"} {value >= 0 ? "+" : "-"} - {(100 * Math.abs(value)).toFixed(0)} + {suffix} {:else} - {value} + {suffix} {/if}
diff --git a/src/lib/components/Farm.svelte b/src/lib/components/Farm.svelte index 5080d24..caf05fe 100644 --- a/src/lib/components/Farm.svelte +++ b/src/lib/components/Farm.svelte @@ -34,10 +34,28 @@ class="land-cell" class:tall={x == 4} data-tooltip={food.name} + class:is-only={$farm.getCropCount(food.id) === 1} class:unswappable={$userState.itemSelectedForSwap && !isSwappable(food)} class:swappable={$userState.itemSelectedForSwap && isSwappable(food)} - on:click={(e) => - $userState.itemSelectedForSwap && isSwappable(food) && swapFoodItem(e, x, y)} + on:click={(e) => { + const isOnly = $farm.getCropCount(food.id) === 1 + if (isOnly) { + // show toast popup here + console.log( + `You must keep at least one ${food.name.replace( + /s$/, + "" + )} square on the board at all times.` + ) + } + + return ( + $userState.itemSelectedForSwap && + !isOnly && + isSwappable(food) && + swapFoodItem(e, x, y) + ) + }} >
diff --git a/src/lib/components/FoodStatsGrid.svelte b/src/lib/components/FoodStatsGrid.svelte index 71ad262..a432a50 100644 --- a/src/lib/components/FoodStatsGrid.svelte +++ b/src/lib/components/FoodStatsGrid.svelte @@ -9,6 +9,16 @@ import Button from "$lib/components/Button.svelte" import { largeNumber } from "$lib/utils/written" + type Key = "hectare" | "kilogram" + type Measure = { key: Key; label: string } + type KeyMetricGetter = { value: (f: Food) => string | number; sort: (a: Food, b: Food) => number } + type SortableData = { label: string; hectare: KeyMetricGetter; kilogram: KeyMetricGetter } + + const measures: Measure[] = [ + { key: "hectare", label: "per hectare land" }, + { key: "kilogram", label: "per kilogram food" } + ] + const impactPerHectare = (food: Food, xPerKg: number) => xPerKg * food.yieldPerHa * $gameState.coefficients.yieldMultiplier @@ -19,54 +29,114 @@ $gameState.coefficients.yieldMultiplier * $gameState.coefficients.lossRatio - const columns = [ - { label: "Name", sort: (a: Food, b: Food) => a.name.localeCompare(b.name) }, + const data: SortableData[] = [ + { + label: "Name", + hectare: { + value: (f: Food) => f.name, + sort: (a: Food, b: Food) => a.name.localeCompare(b.name) + }, + kilogram: { + value: (f: Food) => f.name, + sort: (a: Food, b: Food) => a.name.localeCompare(b.name) + } + }, { label: "Calories", - sort: (a: Food, b: Food) => - nutritionPerHectare(b, b.calorieRatio) - nutritionPerHectare(a, a.calorieRatio) + hectare: { + value: (f: Food) => largeNumber(nutritionPerHectare(f, f.calorieRatio)), + sort: (a: Food, b: Food) => + nutritionPerHectare(b, b.calorieRatio) - nutritionPerHectare(a, a.calorieRatio) + }, + kilogram: { + value: (f: Food) => f.calorieRatio.toFixed(0), + sort: (a: Food, b: Food) => b.calorieRatio - a.calorieRatio + } }, { label: "Protein", - sort: (a: Food, b: Food) => - nutritionPerHectare(b, b.proteinRatio, $gameState.coefficients.proteinMultiplier) - - nutritionPerHectare(a, a.proteinRatio, $gameState.coefficients.proteinMultiplier) + hectare: { + value: (f: Food) => + largeNumber( + nutritionPerHectare(f, f.proteinRatio, $gameState.coefficients.proteinMultiplier) + ), + sort: (a: Food, b: Food) => + nutritionPerHectare(b, b.proteinRatio, $gameState.coefficients.proteinMultiplier) - + nutritionPerHectare(a, a.proteinRatio, $gameState.coefficients.proteinMultiplier) + }, + kilogram: { + value: (f: Food) => f.proteinRatio.toFixed(0), + sort: (a: Food, b: Food) => b.proteinRatio - a.proteinRatio + } }, { label: "Emissions", - sort: (a: Food, b: Food) => impactPerHectare(b, b.ghgPerKg) - impactPerHectare(a, a.ghgPerKg) + hectare: { + value: (f: Food) => largeNumber(impactPerHectare(f, f.ghgPerKg)), + sort: (a: Food, b: Food) => + impactPerHectare(b, b.ghgPerKg) - impactPerHectare(a, a.ghgPerKg) + }, + kilogram: { + value: (f: Food) => f.ghgPerKg.toFixed(1), + sort: (a: Food, b: Food) => b.ghgPerKg - a.ghgPerKg + } }, { label: "Water", - sort: (a: Food, b: Food) => - impactPerHectare(b, b.waterPerKg) - impactPerHectare(a, a.waterPerKg) + hectare: { + value: (f: Food) => largeNumber(impactPerHectare(f, f.waterPerKg)), + sort: (a: Food, b: Food) => + impactPerHectare(b, b.waterPerKg) - impactPerHectare(a, a.waterPerKg) + }, + kilogram: { + value: (f: Food) => f.waterPerKg.toFixed(0), + sort: (a: Food, b: Food) => b.waterPerKg - a.waterPerKg + } } ] - let sortFunction = columns[1].sort + let currentMeasure: Measure = measures[0] + let sortFunction = data[1][currentMeasure.key].sort $: foods = foodItems.sort(sortFunction)

- Food output per hectare + Food output data

+
+ {#each measures as { key, label }} + + {/each} +
- {#each columns as { label, sort }, i} + {#each data as column, i}
{/each} @@ -80,14 +150,11 @@ >
{f.name} -
{largeNumber(nutritionPerHectare(f, f.calorieRatio))}
-
- {largeNumber( - nutritionPerHectare(f, f.proteinRatio, $gameState.coefficients.proteinMultiplier) - )} -
-
{largeNumber(impactPerHectare(f, f.ghgPerKg))}
-
{largeNumber(impactPerHectare(f, f.waterPerKg))}
+ {#each data.slice(1) as column} +
+ {column[currentMeasure.key].value(f)} +
+ {/each}
{/each}
@@ -98,6 +165,10 @@ sup margin-left: 0.5em +.measure-buttons + gap: 0.25rem + margin: 0.25rem 0 0 + .food-items-grid gap: 0.25rem diff --git a/src/lib/data/toasts/guide.ts b/src/lib/data/toasts/guide.ts index 5d45d0d..f15387f 100644 --- a/src/lib/data/toasts/guide.ts +++ b/src/lib/data/toasts/guide.ts @@ -26,7 +26,7 @@ export const guide = [ img: "guide.png", title: "Your food inventory", message: - "These are the foods available to you. You will close the food gap by replacing the squares on your global farm with these food items.", + "These are the foods available to you. Close the food gap by swapping these with the foods on your global farm. To maintain nutritional and market balance, you only have 3 of each item to use. You must keep at least 1 of each item on the farm at all times.", button: "Next", target: "#food-menu-wrapper", next: 2, diff --git a/src/lib/stores/models/Farm.ts b/src/lib/stores/models/Farm.ts index 6f1abc3..50e929e 100644 --- a/src/lib/stores/models/Farm.ts +++ b/src/lib/stores/models/Farm.ts @@ -109,16 +109,20 @@ export class Farm { ) } - getFarmMetric(fn: (item: Food) => number, unit: Unit): FarmMetric { - const total = this.getTotalSum(fn) - const byFood = this.getSumByFoodType(fn, unit) - return { total, byFood } + getCropCount(id: FoodId) { + return this.grid.flat().filter((o) => o?.id === id).length } plantCrop(x: number, y: number, foodItem: Food) { this.grid[y][x] = JSON.parse(JSON.stringify(foodItem)) } + getFarmMetric(fn: (item: Food) => number, unit: Unit): FarmMetric { + const total = this.getTotalSum(fn) + const byFood = this.getSumByFoodType(fn, unit) + return { total, byFood } + } + getTotalSum(fn: (item: Food) => number): number { return +this.grid .flat() diff --git a/src/lib/stores/state.ts b/src/lib/stores/state.ts index 9ac6141..b8625c8 100644 --- a/src/lib/stores/state.ts +++ b/src/lib/stores/state.ts @@ -64,7 +64,7 @@ export const successMetrics = derived( value: proteinPerPersonPerDay, label: "Protein per capita", suffix: "g", - objective: `Keep above ${$gameState.nutritionalRequirements.protein}`, + objective: `Keep above ${$gameState.nutritionalRequirements.protein}g`, warn: proteinPerPersonPerDay < $gameState.nutritionalRequirements.protein + 5 }, emissionsChange: { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3489962..68fd6ee 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -60,10 +60,10 @@
- - + + - +