Download src/shared/state.ts from tostido/weed-sim: direct link, hf CLI and curl.
- Browser
- Download file 5.25 kB
-
https://huggingface.co/spaces/tostido/weed-sim/resolve/main/src/shared/state.ts
- Command line
-
hf download hf://spaces/tostido/weed-sim/src/shared/state.ts
-
curl -L -o state.ts https://huggingface.co/spaces/tostido/weed-sim/resolve/main/src/shared/state.ts
5.25 kB
| import starterProfiles from './starterProfiles.json'; | |
| import { generatePhenotype } from './phenotype'; | |
| import { newId } from './rng'; | |
| import type { Alleles, GameState, GrowthStage, SeedProfile, StarterProfile, Stabilities } from './types'; | |
| export function nowIso(): string { | |
| return new Date().toISOString(); | |
| } | |
| export function emptyGameState(): GameState { | |
| return { | |
| schema: 'weedsim.game_state/v1', | |
| seeds: [], | |
| selection: [], | |
| updatedAt: nowIso(), | |
| }; | |
| } | |
| export function canAttempt(seed: SeedProfile): boolean { | |
| return seed.attemptsUsed < seed.maxAttempts; | |
| } | |
| export function advanceGrowthStage(stage: GrowthStage): GrowthStage { | |
| if (stage === 'SEED') return 'SEEDLING'; | |
| if (stage === 'SEEDLING') return 'MATURE'; | |
| return 'MATURE'; | |
| } | |
| // Real-time growth: one in-game "day" of growTime = one wall-clock second. | |
| const GROW_MIN_SECONDS = 10; | |
| const GROW_MAX_SECONDS = 300; | |
| function growDurationMs(seed: SeedProfile): number { | |
| const seconds = Math.min(GROW_MAX_SECONDS, Math.max(GROW_MIN_SECONDS, Math.round(seed.growTime))); | |
| return seconds * 1000; | |
| } | |
| /** SEED -> SEEDLING with a wall-clock maturation deadline. No-op for other stages. */ | |
| export function beginGrowing(seed: SeedProfile, now = Date.now()): SeedProfile { | |
| if (seed.growthStage !== 'SEED') return seed; | |
| return { ...seed, growthStage: 'SEEDLING', maturesAt: new Date(now + growDurationMs(seed)).toISOString() }; | |
| } | |
| /** Flip seedlings whose timers have elapsed. Legacy seedlings without a deadline mature immediately. */ | |
| export function matureReadySeeds(state: GameState, now = Date.now()): GameState { | |
| let changed = false; | |
| const seeds = state.seeds.map((seed) => { | |
| if (seed.growthStage !== 'SEEDLING') return seed; | |
| const deadline = seed.maturesAt ? Date.parse(seed.maturesAt) : 0; | |
| if (!deadline || deadline <= now) { | |
| changed = true; | |
| return { ...seed, growthStage: 'MATURE' as GrowthStage, maturesAt: null }; | |
| } | |
| return seed; | |
| }); | |
| return changed ? { ...state, seeds, updatedAt: nowIso() } : state; | |
| } | |
| export function growSecondsLeft(seed: SeedProfile, now = Date.now()): number { | |
| if (seed.growthStage !== 'SEEDLING' || !seed.maturesAt) return 0; | |
| return Math.max(0, Math.ceil((Date.parse(seed.maturesAt) - now) / 1000)); | |
| } | |
| export function cloneAlleles(alleles: Alleles): Alleles { | |
| return { | |
| THC: [...alleles.THC], | |
| CBD: [...alleles.CBD], | |
| Yield: [...alleles.Yield], | |
| GrowTime: [...alleles.GrowTime], | |
| LeafColor: { | |
| Hue: [...alleles.LeafColor.Hue], | |
| Saturation: [...alleles.LeafColor.Saturation], | |
| Value: [...alleles.LeafColor.Value], | |
| }, | |
| BudColor: { | |
| Hue: [...alleles.BudColor.Hue], | |
| Saturation: [...alleles.BudColor.Saturation], | |
| Value: [...alleles.BudColor.Value], | |
| }, | |
| }; | |
| } | |
| export function cloneStabilities(stabilities: Stabilities): Stabilities { | |
| return { ...stabilities }; | |
| } | |
| function roundTrait(value: number): number { | |
| return Math.round(value * 10) / 10; | |
| } | |
| export function reexpressSeed(seed: SeedProfile): SeedProfile { | |
| const phenotype = generatePhenotype(seed.alleles, seed.stabilities); | |
| return { | |
| ...seed, | |
| thc: roundTrait(phenotype.thc), | |
| cbd: roundTrait(phenotype.cbd), | |
| yield: roundTrait(phenotype.yield), | |
| growTime: roundTrait(phenotype.growTime), | |
| budColor: phenotype.budColorRgb, | |
| leafColor: phenotype.leafColorRgb, | |
| budPalette: phenotype.budPaletteRgb, | |
| leafPalette: phenotype.leafPaletteRgb, | |
| budPattern: phenotype.budPattern, | |
| leafPattern: phenotype.leafPattern, | |
| }; | |
| } | |
| export function normalizeGameState(state: GameState): GameState { | |
| const validSelection = new Set(state.seeds.map((seed) => seed.seedId)); | |
| return { | |
| ...emptyGameState(), | |
| ...state, | |
| seeds: state.seeds.map(reexpressSeed), | |
| selection: state.selection.filter((seedId) => validSelection.has(seedId)), | |
| }; | |
| } | |
| function seedFromStarter(profile: StarterProfile): SeedProfile { | |
| return reexpressSeed({ | |
| seedId: newId(), | |
| strainName: profile.name, | |
| type: profile.type, | |
| alleles: cloneAlleles(profile.alleles), | |
| stabilities: cloneStabilities(profile.stabilities), | |
| thc: profile.traits_base.THC, | |
| cbd: profile.traits_base.CBD, | |
| yield: profile.traits_base.Yield, | |
| growTime: profile.traits_base.GrowTime, | |
| budColor: [0, 0, 0], | |
| leafColor: [0, 0, 0], | |
| budPalette: [], | |
| leafPalette: [], | |
| budPattern: 'solid', | |
| leafPattern: 'solid', | |
| baseImageName: profile.base_image_name, | |
| growthStage: 'SEED', | |
| lineage: [null, null], | |
| description: profile.description ?? '', | |
| quantity: 1, | |
| attemptsUsed: 0, | |
| maxAttempts: 2, | |
| isStarter: true, | |
| }); | |
| } | |
| export function createStarterSeeds(): SeedProfile[] { | |
| return (starterProfiles as StarterProfile[]).map(seedFromStarter); | |
| } | |
| export function withStarters(state: GameState): GameState { | |
| return normalizeGameState({ | |
| ...state, | |
| seeds: [...state.seeds, ...createStarterSeeds()], | |
| updatedAt: nowIso(), | |
| }); | |
| } | |
| export function findSeed(state: GameState, seedId: string): SeedProfile | undefined { | |
| return state.seeds.find((seed) => seed.seedId === seedId); | |
| } | |
| export function replaceSeeds(state: GameState, seeds: SeedProfile[]): GameState { | |
| return normalizeGameState({ ...state, seeds, updatedAt: nowIso() }); | |
| } |