DionysianIdle/src/store/useGameStore.ts
Ryan Lanny Jenkins d75e9f3cbb Set up prettier.
2025-05-18 10:17:42 -05:00

297 lines
6.7 KiB
TypeScript

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'
import { saveGame, loadGame } from '../utils/saveSystem'
const initializeField = (size: number): PlotState[][] => {
return Array(size)
.fill(0)
.map(() =>
Array(size)
.fill(0)
.map(() => ({
moisture: 0,
})),
)
}
export const useGameStore = create<
GameState & {
plant: () => void
water: () => void
harvest: () => 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,
assignCrop: (row, col, cropId) => {
set(
produce((state) => {
state.plots[row][col].intended = cropId
}),
)
},
plant: () => {
const { plots, inventory, actionCooldown } = get()
if (actionCooldown > 0) {
return
}
for (let row = 0; row < plots.length; row++) {
for (let col = 0; col < plots[row].length; col++) {
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
}),
)
return
}
}
}
}
},
water: () => {
const { plots, actionCooldown } = get()
if (actionCooldown > 0) {
return
}
let driestRow = -1
let driestCol = -1
let lowestMoisture = 1
for (let row = 0; row < plots.length; row++) {
for (let col = 0; col < plots[row].length; col++) {
if (
plots[row][col].current &&
plots[row][col].moisture < lowestMoisture
) {
lowestMoisture = plots[row][col].moisture
driestRow = row
driestCol = col
}
}
}
if (driestRow >= 0 && driestCol >= 0) {
set(
produce((state) => {
state.plots[driestRow][driestCol].moisture = 1
state.actionCooldown = COOLDOWN_DURATION
}),
)
}
},
harvest: () => {
const { plots, actionCooldown } = get()
if (actionCooldown > 0) {
return
}
for (let row = 0; row < plots.length; row++) {
for (let col = 0; col < plots[row].length; col++) {
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.actionCooldown = COOLDOWN_DURATION
}),
)
return
}
}
}
},
tick: () => {
set(
produce((state) => {
// Update plots
state.plots.forEach((row: PlotState[], rowIndex: number) => {
row.forEach((plot: PlotState, colIndex: number) => {
if (!plot.current || plot.current.mature) {
return
}
const crop = CROPS[plot.current.cropId]
const waterNeeded = crop.waterPerTick
if (plot.moisture >= waterNeeded) {
const newProgress = plot.current.progress + 1
const mature = newProgress >= crop.growthTicks
state.plots[rowIndex][colIndex].moisture =
plot.moisture - waterNeeded
state.plots[rowIndex][colIndex].current.progress = newProgress
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) {
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)
}
},
}))