Skip to content

Commit

Permalink
WIP - update food data table; enforce 1 of each item; etc
Browse files Browse the repository at this point in the history
  • Loading branch information
brodysmith1 committed Dec 12, 2023
1 parent b763d7a commit f82cb68
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 39 deletions.
4 changes: 2 additions & 2 deletions src/lib/components/BlockGameState.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
<div class="flex align-center">
{#if suffix === "%"}
<span class="label sign">{value >= 0 ? "+" : "-"}</span>
<span>{(100 * Math.abs(value)).toFixed(0)}</span>
<Number value={100 * Math.abs(value)} />
<span class="label suffix text-secondary-2">{suffix}</span>
{:else}
<span>{value}</span>
<Number {value} />
<span class="label suffix text-secondary-2">{suffix}</span>
{/if}
</div>
Expand Down
22 changes: 20 additions & 2 deletions src/lib/components/Farm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
}}
>
<div class="food-item-avatar bg-{food.colorId}" />
</button>
Expand Down
121 changes: 96 additions & 25 deletions src/lib/components/FoodStatsGrid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
</script>

<div class="food-items-grid block">
<h3 class="block-title flex align-center">
Food output per hectare
Food output data
<sup class="label" data-tooltip-title="Sources" data-tooltip="Our World in Data; USDA.">ⓘ</sup>
</h3>
<div class="measure-buttons label-caps flex align-center">
{#each measures as { key, label }}
<Button
active={currentMeasure.key === key}
onClick={() => {
const sortedIndex = data.findIndex((o) => o[currentMeasure.key].sort === sortFunction)

currentMeasure = measures.find((m) => m.key === key) ?? measures[0]

sortFunction = data[sortedIndex][currentMeasure.key].sort
}}
classList="label-caps"
>
{label}
</Button>
{/each}
</div>
<div class="food-items-grid-body">
<div class="food-card table-head">
{#each columns as { label, sort }, i}
{#each data as column, i}
<div class="th">
<Button
classList="bare bold"
color="secondary"
active={sortFunction === sort}
active={sortFunction === column[currentMeasure.key].sort}
onClick={() => {
if (sortFunction === sort) foods = foods.reverse()
else sortFunction = sort
if (sortFunction === column[currentMeasure.key].sort) foods = foods.reverse()
else sortFunction = column[currentMeasure.key].sort
}}
>
{label}
{column.label}
</Button>
</div>
{/each}
Expand All @@ -80,14 +150,11 @@
>
<div class="food-item-avatar flex-center bg-{f.colorId}" />
<strong class="name">{f.name}</strong>
<div class="td">{largeNumber(nutritionPerHectare(f, f.calorieRatio))}</div>
<div class="td">
{largeNumber(
nutritionPerHectare(f, f.proteinRatio, $gameState.coefficients.proteinMultiplier)
)}
</div>
<div class="td">{largeNumber(impactPerHectare(f, f.ghgPerKg))}</div>
<div class="td">{largeNumber(impactPerHectare(f, f.waterPerKg))}</div>
{#each data.slice(1) as column}
<div class="td">
{column[currentMeasure.key].value(f)}
</div>
{/each}
</div>
{/each}
</div>
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/lib/data/toasts/guide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <b class='food-item-avatar bg-animal-1'>3</b> of each item to use. You must keep at least <b class='food-item-avatar bg-animal-1'>1</b> of each item on the farm at all times.",
button: "Next",
target: "#food-menu-wrapper",
next: 2,
Expand Down
12 changes: 8 additions & 4 deletions src/lib/stores/models/Farm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/lib/stores/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 3 additions & 3 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
</div>
<FoodInformationCard />
<Tooltip />
<WelcomeScreen />
<EndScreen />
<!-- <WelcomeScreen /> -->
<!-- <EndScreen /> -->
<AboutScreen />
<Toast />
<!-- <Toast /> -->
</main>

<style lang="sass">
Expand Down
2 changes: 1 addition & 1 deletion src/styles/components/food-item.sass
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.food-item-avatar
width: 1em
height: 1em
display: flex
display: inline flex
flex-shrink: 0
border-radius: 100%
align-items: center
Expand Down

0 comments on commit f82cb68

Please sign in to comment.