diff --git a/apps/website/src/lib/components/CurrencyDisplay.svelte b/apps/website/src/lib/components/CurrencyDisplay.svelte
new file mode 100644
index 0000000..b42b2ef
--- /dev/null
+++ b/apps/website/src/lib/components/CurrencyDisplay.svelte
@@ -0,0 +1,19 @@
+
+
+
+

+
{amount}
+
diff --git a/apps/website/src/lib/components/farming/FarmDisplay.svelte b/apps/website/src/lib/components/farming/FarmDisplay.svelte
new file mode 100644
index 0000000..d5c9715
--- /dev/null
+++ b/apps/website/src/lib/components/farming/FarmDisplay.svelte
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#each plants as plant (plant.id)}
+ selectSeed(plant.id)}
+ class="cursor-pointer {selectedSeed === plant.id ? 'bg-base-200' : ''}"
+ >
+
+
+ })
+ {plant.name}
+
+ |
+ {currency.getBalance(plant.id)} |
+
+
+ |
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
+ {#each farming.plots as plot, i (i)}
+
+ {/each}
+
+
+
+
+
diff --git a/apps/website/src/lib/components/farming/FarmPlot.svelte b/apps/website/src/lib/components/farming/FarmPlot.svelte
new file mode 100644
index 0000000..46bf2db
--- /dev/null
+++ b/apps/website/src/lib/components/farming/FarmPlot.svelte
@@ -0,0 +1,32 @@
+
+
+
+ {#if plant}
+
+

+
+ {#if !farming.isReady(index)}
+
+ {/if}
+
+ {/if}
+
+
+
diff --git a/apps/website/src/lib/demo/content.ts b/apps/website/src/lib/demo/content.ts
index 8e56ed6..0e2af29 100644
--- a/apps/website/src/lib/demo/content.ts
+++ b/apps/website/src/lib/demo/content.ts
@@ -7,15 +7,154 @@ import type { PlantDetail } from '$lib/demo/model/PlantDetail';
// First we declare our fully static content
export const plants = [
- { id: '/plant/sunflower', name: 'Sunflower', growthTime: 1, moneyReward: 10 },
- { id: '/plant/cauliflower', name: 'Cauliflower', growthTime: 1.5, moneyReward: 20 },
+ {
+ id: '/plant/cabbage',
+ name: 'Cabbage',
+ growthTime: 10,
+ moneyReward: 10,
+ seedCost: 6,
+ stages: [
+ '/plants/cabbage/cabbage_00.png',
+ '/plants/cabbage/cabbage_01.png',
+ '/plants/cabbage/cabbage_02.png',
+ '/plants/cabbage/cabbage_03.png',
+ '/plants/cabbage/cabbage_04.png',
+ '/plants/cabbage/cabbage_05.png',
+ ],
+ },
+ {
+ id: '/plant/potato',
+ name: 'Potato',
+ growthTime: 20,
+ moneyReward: 20,
+ seedCost: 12,
+ stages: [
+ '/plants/potato/potato_00.png',
+ '/plants/potato/potato_01.png',
+ '/plants/potato/potato_02.png',
+ '/plants/potato/potato_03.png',
+ '/plants/potato/potato_04.png',
+ '/plants/potato/potato_05.png',
+ ],
+ },
+ {
+ id: '/plant/carrot',
+ name: 'Carrot',
+ growthTime: 30,
+ moneyReward: 40,
+ seedCost: 20,
+ stages: [
+ '/plants/carrot/carrot_00.png',
+ '/plants/carrot/carrot_01.png',
+ '/plants/carrot/carrot_02.png',
+ '/plants/carrot/carrot_03.png',
+ '/plants/carrot/carrot_04.png',
+ '/plants/carrot/carrot_05.png',
+ ],
+ },
+ {
+ id: '/plant/beetroot',
+ name: 'Beetroot',
+ growthTime: 40,
+ moneyReward: 50,
+ seedCost: 40,
+ stages: [
+ '/plants/beetroot/beetroot_00.png',
+ '/plants/beetroot/beetroot_01.png',
+ '/plants/beetroot/beetroot_02.png',
+ '/plants/beetroot/beetroot_03.png',
+ '/plants/beetroot/beetroot_04.png',
+ '/plants/beetroot/beetroot_05.png',
+ ],
+ },
+ {
+ id: '/plant/radish',
+ name: 'Radish',
+ growthTime: 50,
+ moneyReward: 20,
+ seedCost: 75,
+ stages: [
+ '/plants/radish/radish_00.png',
+ '/plants/radish/radish_01.png',
+ '/plants/radish/radish_02.png',
+ '/plants/radish/radish_03.png',
+ '/plants/radish/radish_04.png',
+ '/plants/radish/radish_05.png',
+ ],
+ },
+ {
+ id: '/plant/cauliflower',
+ name: 'Cauliflower',
+ growthTime: 30,
+ moneyReward: 20,
+ seedCost: 120,
+ stages: [
+ '/plants/cauliflower/cauliflower_00.png',
+ '/plants/cauliflower/cauliflower_01.png',
+ '/plants/cauliflower/cauliflower_02.png',
+ '/plants/cauliflower/cauliflower_03.png',
+ '/plants/cauliflower/cauliflower_04.png',
+ '/plants/cauliflower/cauliflower_05.png',
+ ],
+ },
+ {
+ id: '/plant/pumpkin',
+ name: 'Pumpkin',
+ growthTime: 30,
+ seedCost: 200,
+ moneyReward: 20,
+ stages: [
+ '/plants/pumpkin/pumpkin_00.png',
+ '/plants/pumpkin/pumpkin_01.png',
+ '/plants/pumpkin/pumpkin_02.png',
+ '/plants/pumpkin/pumpkin_03.png',
+ '/plants/pumpkin/pumpkin_04.png',
+ '/plants/pumpkin/pumpkin_05.png',
+ ],
+ },
+ {
+ id: '/plant/sunflower',
+ name: 'Sunflower',
+ growthTime: 10,
+ moneyReward: 10,
+ seedCost: 1000,
+ stages: [
+ '/plants/sunflower/sunflower_00.png',
+ '/plants/sunflower/sunflower_01.png',
+ '/plants/sunflower/sunflower_02.png',
+ '/plants/sunflower/sunflower_03.png',
+ '/plants/sunflower/sunflower_04.png',
+ '/plants/sunflower/sunflower_05.png',
+ ],
+ },
] as const satisfies PlantDetail[];
+export type PlantId = (typeof plants)[number]['id'];
+
+export const getPlant = (id: PlantId): PlantDetail => {
+ // TODO(@Isha): Make into dictionary lookup
+ const plant = plants.find((p) => p.id === id);
+ if (!plant) {
+ throw new Error(`Plant with id '${id}' not found.`);
+ }
+ return plant;
+};
export const currencies: CurrencyDetail[] = [
- { id: '/currency/money', name: 'Money', icon: '/icon/coin' },
- { id: '/currency/gems', name: 'Gems', icon: '/icon/gem-blue' },
+ { id: '/currency/money', name: 'Money', icon: '/icons/egg.png' },
+ { id: '/currency/compost', name: 'Compost', icon: '/icon/compost' },
];
+export type CurrencyId = (typeof currencies)[number]['id'];
+
+export const getCurrency = (id: CurrencyId): CurrencyDetail => {
+ // TODO(@Isha): Make into dictionary lookup
+ const currency = currencies.find((c) => c.id === id);
+ if (!currency) {
+ throw new Error(`Currency with id '${id}' not found.`);
+ }
+ return currency;
+};
+
export const statistics: StatisticDetail[] = [
{ id: '/statistic/total-money', type: 'scalar' },
{ id: '/statistic/plants-planted', type: 'map' },
diff --git a/apps/website/src/lib/demo/demo.svelte.ts b/apps/website/src/lib/demo/demo.svelte.ts
index 91d60e8..30d5cf3 100644
--- a/apps/website/src/lib/demo/demo.svelte.ts
+++ b/apps/website/src/lib/demo/demo.svelte.ts
@@ -1,20 +1,20 @@
import {
AchievementPlugin,
AlwaysTrueCondition,
- type ExtractOutput,
CouponPlugin,
createAchievementState,
createCouponState,
createCurrencyState,
createStatisticState,
CurrencyPlugin,
+ type ExtractCondition,
type ExtractInput,
+ type ExtractOutput,
LudiekEngine,
LudiekGame,
- type ExtractCondition,
StatisticPlugin,
} from '@123ishatest/ludiek';
-import { Farming } from '$lib/demo/features/Farming';
+import { Farming } from '$lib/demo/features/Farming.svelte';
import { achievements, currencies, plants, statistics } from '$lib/demo/content';
// Define plugins with reactive state
@@ -43,17 +43,19 @@ export type Condition = ExtractCondition;
export type PlantId = (typeof plants)[number]['id'];
// Create your game
-const farming = new Farming(plants);
+const farmingFeature = new Farming(plants);
export const game = new LudiekGame(engine, {
- features: [farming],
+ features: [farmingFeature],
saveKey: '@123ishatest/ludiek-demo',
tickDuration: 0.1,
saveInterval: 30,
});
engine.plugins.currency.loadContent(currencies);
+engine.plugins.currency.loadContent(plants);
engine.plugins.statistic.loadContent(statistics);
engine.plugins.achievement.loadContent(achievements);
export const { currency, statistic, achievement } = engine.plugins;
+export const { farming } = game.features;
diff --git a/apps/website/src/lib/demo/features/Farming.svelte.ts b/apps/website/src/lib/demo/features/Farming.svelte.ts
new file mode 100644
index 0000000..ceab068
--- /dev/null
+++ b/apps/website/src/lib/demo/features/Farming.svelte.ts
@@ -0,0 +1,128 @@
+import { LudiekFeature } from '@123ishatest/ludiek';
+import type { EnginePlugins } from '$lib/demo/demo.svelte';
+import type { PlantDetail } from '$lib/demo/model/PlantDetail';
+import { emptyPlot, type FarmingState, type FarmPlotState } from '$lib/demo/features/FarmingState';
+import { getPlant, type PlantId } from '$lib/demo/content';
+import { SowSeedController } from '$lib/demo/features/SowSeedController';
+import { SowAllController } from '$lib/demo/features/SowAllController';
+import { ReapAllController } from '$lib/demo/features/ReapAllController';
+
+export class Farming extends LudiekFeature {
+ public readonly name: string = 'farming';
+ public readonly config = {
+ controllers: [new SowSeedController(this), new SowAllController(this), new ReapAllController(this)],
+ };
+
+ public readonly FARM_PLOTS = 25;
+
+ protected _state: FarmingState = $state({
+ plots: [],
+ });
+
+ public readonly plants: PlantDetail[];
+
+ constructor(plants: PlantDetail[]) {
+ super();
+ this.plants = plants;
+
+ for (let i = 0; i < this.FARM_PLOTS; i++) {
+ this._state.plots.push(emptyPlot());
+ }
+ }
+
+ update(delta: number) {
+ this.growAllPlants(delta);
+ }
+
+ private growAllPlants(amount: number): void {
+ this._state.plots.forEach((plot) => {
+ if (!plot.plant) {
+ return;
+ }
+ plot.progress += amount;
+ });
+ }
+
+ public sow(index: number, id: PlantId): void {
+ if (!this.isEmpty(index)) {
+ return;
+ }
+ const plant = getPlant(id);
+ if (
+ !this._plugins.currency.payCurrency({
+ id: plant.id,
+ amount: 1,
+ })
+ ) {
+ return;
+ }
+
+ this._plugins.statistic.incrementMapStatistic('/statistic/plants-planted', id);
+ this._state.plots[index] = {
+ plant: id,
+ progress: 0,
+ };
+ }
+
+ public reap(index: number): void {
+ if (!this.isReady(index)) {
+ return;
+ }
+ const plot = this.getPlot(index);
+ const plant = getPlant(plot.plant as PlantId);
+
+ this._plugins.currency.gainCurrency({ id: '/currency/money', amount: plant.moneyReward });
+ this.clear(index);
+ }
+
+ public getGrowthStage(index: number): string {
+ if (this.isEmpty(index)) {
+ return '';
+ }
+ const progress = this.getProgress(index);
+ const plot = this.getPlot(index);
+ const plant = getPlant(plot.plant as PlantId);
+
+ const stage = Math.floor(progress * (plant.stages.length - 1));
+ return plant.stages[stage];
+ }
+
+ public getProgress(index: number): number {
+ if (this.isEmpty(index)) {
+ return 0;
+ }
+ const plot = this.getPlot(index);
+ const plant = getPlant(plot.plant as PlantId);
+ return Math.min(1, plot.progress / plant.growthTime);
+ }
+
+ public clear(index: number): void {
+ this._state.plots[index] = emptyPlot();
+ }
+
+ public getPlant(id: PlantId): PlantDetail {
+ return this.plants.find((plant) => plant.id === id) as PlantDetail;
+ }
+
+ public isEmpty(index: number): boolean {
+ return this.getPlot(index).plant == null;
+ }
+
+ public isReady(index: number): boolean {
+ const plot = this.getPlot(index);
+ if (!plot.plant) {
+ return false;
+ }
+
+ const plant = getPlant(plot.plant);
+ return plot.progress >= plant.growthTime;
+ }
+
+ public getPlot(index: number): FarmPlotState {
+ return this._state.plots[index];
+ }
+
+ public get plots(): FarmPlotState[] {
+ return this._state.plots;
+ }
+}
diff --git a/apps/website/src/lib/demo/features/Farming.ts b/apps/website/src/lib/demo/features/Farming.ts
deleted file mode 100644
index 4a4e302..0000000
--- a/apps/website/src/lib/demo/features/Farming.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { LudiekFeature } from '@123ishatest/ludiek';
-import type { EnginePlugins, PlantId } from '$lib/demo/demo.svelte';
-
-import type { PlantDetail } from '$lib/demo/model/PlantDetail';
-import { SowSeedController } from '$lib/demo/features/SowPlantController';
-
-export class Farming extends LudiekFeature {
- readonly name = 'farming';
- protected _state = {};
-
- public readonly config = {
- controllers: [new SowSeedController(this)],
- };
-
- public readonly plants: PlantDetail[];
-
- constructor(plants: PlantDetail[]) {
- super();
- this.plants = plants;
- }
-
- public sow(id: PlantId): void {
- const plant = this.getPlant(id);
- this._plugins.currency.gainCurrency({ id: '/currency/money', amount: plant.moneyReward });
- this._plugins.statistic.incrementMapStatistic('/statistic/plants-planted', plant.id);
- }
-
- public getPlant(id: PlantId): PlantDetail {
- return this.plants.find((plant) => plant.id === id) as PlantDetail;
- }
-}
diff --git a/apps/website/src/lib/demo/features/FarmingState.ts b/apps/website/src/lib/demo/features/FarmingState.ts
new file mode 100644
index 0000000..f7d3ce6
--- /dev/null
+++ b/apps/website/src/lib/demo/features/FarmingState.ts
@@ -0,0 +1,17 @@
+import type { PlantId } from '$lib/demo/content';
+
+export interface FarmPlotState {
+ plant: PlantId | null;
+ progress: number;
+}
+
+export const emptyPlot = (): FarmPlotState => {
+ return {
+ plant: null,
+ progress: 0,
+ };
+};
+
+export interface FarmingState {
+ plots: FarmPlotState[];
+}
diff --git a/apps/website/src/lib/demo/features/ReapAllController.ts b/apps/website/src/lib/demo/features/ReapAllController.ts
new file mode 100644
index 0000000..1755327
--- /dev/null
+++ b/apps/website/src/lib/demo/features/ReapAllController.ts
@@ -0,0 +1,23 @@
+import type { BaseRequestShape, LudiekController } from '@123ishatest/ludiek';
+import type { Farming } from '$lib/demo/features/Farming.svelte';
+
+export interface ReapAllRequest extends BaseRequestShape {
+ type: '/farming/reap-all';
+}
+
+export class ReapAllController implements LudiekController {
+ readonly type: string = '/farming/reap-all';
+
+ private readonly _farming: Farming;
+
+ constructor(farming: Farming) {
+ this._farming = farming;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ resolve(request: ReapAllRequest): void {
+ for (let i = 0; i < this._farming.FARM_PLOTS; i++) {
+ this._farming.reap(i);
+ }
+ }
+}
diff --git a/apps/website/src/lib/demo/features/SowAllController.ts b/apps/website/src/lib/demo/features/SowAllController.ts
new file mode 100644
index 0000000..2fe0c91
--- /dev/null
+++ b/apps/website/src/lib/demo/features/SowAllController.ts
@@ -0,0 +1,24 @@
+import type { BaseRequestShape, LudiekController } from '@123ishatest/ludiek';
+import type { PlantId } from '$lib/demo/demo.svelte';
+import type { Farming } from '$lib/demo/features/Farming.svelte';
+
+export interface SowAllRequest extends BaseRequestShape {
+ type: '/farming/sow-all';
+ plant: PlantId;
+}
+
+export class SowAllController implements LudiekController {
+ readonly type: string = '/farming/sow-all';
+
+ private readonly _farming: Farming;
+
+ constructor(farming: Farming) {
+ this._farming = farming;
+ }
+
+ resolve(request: SowAllRequest): void {
+ for (let i = 0; i < this._farming.FARM_PLOTS; i++) {
+ this._farming.sow(i, request.plant);
+ }
+ }
+}
diff --git a/apps/website/src/lib/demo/features/SowPlantController.ts b/apps/website/src/lib/demo/features/SowSeedController.ts
similarity index 79%
rename from apps/website/src/lib/demo/features/SowPlantController.ts
rename to apps/website/src/lib/demo/features/SowSeedController.ts
index 5f8e626..2569576 100644
--- a/apps/website/src/lib/demo/features/SowPlantController.ts
+++ b/apps/website/src/lib/demo/features/SowSeedController.ts
@@ -1,9 +1,10 @@
import type { BaseRequestShape, LudiekController } from '@123ishatest/ludiek';
-import type { Farming } from '$lib/demo/features/Farming';
import type { PlantId } from '$lib/demo/demo.svelte';
+import type { Farming } from '$lib/demo/features/Farming.svelte';
export interface SowSeedRequest extends BaseRequestShape {
type: '/farming/sow-seed';
+ plot: number;
plant: PlantId;
}
@@ -17,6 +18,6 @@ export class SowSeedController implements LudiekController {
}
resolve(request: SowSeedRequest): void {
- this._farming.sow(request.plant);
+ this._farming.sow(request.plot, request.plant);
}
}
diff --git a/apps/website/src/lib/demo/model/PlantDetail.ts b/apps/website/src/lib/demo/model/PlantDetail.ts
index 84f7d5c..c1e3e83 100644
--- a/apps/website/src/lib/demo/model/PlantDetail.ts
+++ b/apps/website/src/lib/demo/model/PlantDetail.ts
@@ -2,5 +2,7 @@ export interface PlantDetail {
id: string;
name: string;
growthTime: number;
+ seedCost: number;
moneyReward: number;
+ stages: string[];
}
diff --git a/apps/website/src/routes/+layout.svelte b/apps/website/src/routes/+layout.svelte
index 22b272c..97cffb2 100644
--- a/apps/website/src/routes/+layout.svelte
+++ b/apps/website/src/routes/+layout.svelte
@@ -7,7 +7,7 @@
-
diff --git a/apps/website/src/routes/demo/+page.svelte b/apps/website/src/routes/demo/+page.svelte
index 6406619..c40fef1 100644
--- a/apps/website/src/routes/demo/+page.svelte
+++ b/apps/website/src/routes/demo/+page.svelte
@@ -1,9 +1,10 @@