import { create } from 'zustand' import { produce } from 'immer' import { INITIAL_CASH, INITIAL_FIELD_SIZE, FIELD_UPGRADE_COSTS, INITIAL_INVENTORY, CROPS, INITIAL_GAME_SPEED, COOLDOWN_DURATION, MARKET_ITEMS, } from '../constants' import { GameState, PlotState } from '../types' const initializeField = (size: number): PlotState[][] => { return Array(size) .fill(0) .map(() => Array(size) .fill(0) .map(() => ({ moisture: 0, fertility: 1, })), ) } const SAVE_SLOT_PREFIX = 'dionysian_idle_save_' const saveGame = (slot: number, state: GameState) => { try { const saveData = JSON.stringify(state) localStorage.setItem(`${SAVE_SLOT_PREFIX}${slot}`, saveData) return true } catch (error) { console.error('Failed to save game:', error) return false } } const loadGame = (slot: number): GameState | null => { try { const saveData = localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`) if (!saveData) return null return JSON.parse(saveData) } catch (error) { console.error('Failed to load game:', error) return null } } export const hasSaveInSlot = (slot: number): boolean => { return !!localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`) } const addConsoleMessage = (state: GameState, text: string) => ( state.consoleMessages = [ { id: Math.random().toString(36).substring(7), text, timestamp: Date.now(), }, ...state.consoleMessages, ].slice(0, 50) ) export const useGameStore = create< GameState & { plant: (row: number, col: number) => void water: (row: number, col: number) => void harvest: (row: number, col: number) => void tick: () => void assignCrop: (row: number, col: number, cropId: string) => void upgradeField: () => void setGameSpeed: (speed: number) => void setActionCooldown: (cooldown: number) => void buyItem: (itemId: string) => void sellItem: (itemId: string) => void saveToSlot: (slot: number) => void loadFromSlot: (slot: number) => void } >((set, get) => ({ cash: INITIAL_CASH, inventory: INITIAL_INVENTORY, fieldSize: INITIAL_FIELD_SIZE, maxFieldSize: INITIAL_FIELD_SIZE + FIELD_UPGRADE_COSTS.length, fieldUpgradeCosts: FIELD_UPGRADE_COSTS, plots: initializeField(INITIAL_FIELD_SIZE), gameSpeed: INITIAL_GAME_SPEED, actionCooldown: 0, tickCount: 0, consoleMessages: [], assignCrop: (row, col, cropId) => { set( produce((state) => { state.plots[row][col].intended = cropId }), ) }, plant: (row, col) => { const { plots, inventory, actionCooldown } = get() if (actionCooldown > 0) { return } const plot = plots[row][col] if (plot.intended && !plot.current) { const seedId = `${plot.intended}_seed` if (inventory[seedId] && inventory[seedId] > 0) { set( produce((state) => { state.plots[row][col].current = { cropId: plot.intended!, progress: 0, mature: false, } state.inventory[seedId] = state.inventory[seedId] - 1 state.actionCooldown = COOLDOWN_DURATION }), ) } } }, water: (row, col) => { const { plots, actionCooldown } = get() if (actionCooldown > 0) { return } const plot = plots[row][col] if (plot.current && plot.moisture < 1) { set( produce((state) => { state.plots[row][col].moisture = 1 state.actionCooldown = COOLDOWN_DURATION }), ) } }, harvest: (row, col) => { const { plots, actionCooldown } = get() if (actionCooldown > 0) { return } const plot = plots[row][col] if (plot.current && plot.current.mature) { const crop = CROPS[plot.current.cropId] const yieldItem = crop.yieldType const yieldAmount = crop.yield set( produce((state) => { state.plots[row][col].current = undefined state.inventory[yieldItem] = (state.inventory[yieldItem] || 0) + yieldAmount state.plots[row][col].fertility = Math.max( 0, state.plots[row][col].fertility - crop.fertilityDepletion, ) state.actionCooldown = COOLDOWN_DURATION }), ) } }, tick: () => { set( produce((state) => { // Update plots state.plots.forEach((row: PlotState[], rowIndex: number) => { row.forEach((plot: PlotState, colIndex: number) => { // Regenerate fertility every 100 ticks if (state.tickCount % 100 === 0 && plot.fertility < 1) { state.plots[rowIndex][colIndex].fertility = Math.min( 1, plot.fertility + 0.1, ) } if (!plot.current || plot.current.mature) { return } const crop = CROPS[plot.current.cropId] const waterNeeded = crop.waterPerTick // Check if water is running low (less than 25% of what's needed) if (plot.moisture < waterNeeded * 0.25 && plot.moisture > 0) { addConsoleMessage( state, `Plot (${rowIndex + 1},${colIndex + 1}) ${crop.name} is running low on water!`, ) } // Only grow if fertility is above 0.2 if (plot.moisture >= waterNeeded && plot.fertility >= 0.2) { let growthRate = 1 // Half growth rate if fertility is between 0.2 and 0.5 if (plot.fertility < 0.5) { growthRate = 0.5 } const newProgress = plot.current.progress + growthRate const mature = newProgress >= crop.growthTicks state.plots[rowIndex][colIndex].moisture = plot.moisture - waterNeeded state.plots[rowIndex][colIndex].current.progress = newProgress // If the plot just became mature, add a message if (mature && !plot.current.mature) { addConsoleMessage( state, `Plot (${rowIndex + 1},${colIndex + 1}) ${crop.name} is ready to harvest!`, ) } state.plots[rowIndex][colIndex].current.mature = mature } }) }) state.tickCount = state.tickCount + 1 }), ) }, upgradeField: () => { set( produce((state) => { if (state.fieldSize >= state.maxFieldSize) { return } const upgradeIndex = state.fieldSize - INITIAL_FIELD_SIZE const cost = state.fieldUpgradeCosts[upgradeIndex] if (state.cash < cost) { return } const newSize = state.fieldSize + 1 state.cash = state.cash - cost state.fieldSize = newSize state.plots = initializeField(newSize) }), ) }, setGameSpeed: (speed) => { set( produce((state) => { state.gameSpeed = speed }), ) }, setActionCooldown: (cooldown) => { set( produce((state) => { state.actionCooldown = cooldown }), ) }, buyItem: (itemId) => { const { cash } = get() const item = MARKET_ITEMS[itemId] if (!item || item.buyPrice === null || item.buyPrice === undefined) { return } if (cash < item.buyPrice) { return } set( produce((state) => { state.cash -= item.buyPrice! state.inventory[itemId] = (state.inventory[itemId] || 0) + 1 }), ) }, sellItem: (itemId) => { const { inventory } = get() const item = MARKET_ITEMS[itemId] if ( !item || item.sellPrice === null || !inventory[itemId] || inventory[itemId] <= 0 ) { return } set( produce((state) => { state.cash += item.sellPrice state.inventory[itemId] -= 1 }), ) }, saveToSlot: (slot: number) => { const state = get() const { plant, water, harvest, tick, assignCrop, upgradeField, setGameSpeed, setActionCooldown, buyItem, sellItem, saveToSlot, loadFromSlot, ...gameState } = state saveGame(slot, gameState) }, loadFromSlot: (slot: number) => { const savedState = loadGame(slot) if (savedState) { set(savedState) } }, }))