297 lines
6.7 KiB
TypeScript
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)
|
|
}
|
|
},
|
|
}))
|