From 6d72c8c0f967c56da3368a5b402fc10014185324 Mon Sep 17 00:00:00 2001 From: Ryan Lanny Jenkins Date: Mon, 19 May 2025 23:35:45 -0500 Subject: [PATCH] Add our first upgrade to the temple. --- src/App.tsx | 8 ++- src/components/Temple.tsx | 121 ++++++++++++++++++++++++++++++++++++++ src/constants/index.ts | 14 ++++- src/store/useGameStore.ts | 51 +++++++++++++++- src/types/index.ts | 13 ++++ 5 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 src/components/Temple.tsx diff --git a/src/App.tsx b/src/App.tsx index b8ddbe1..7510d1f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import { useGameTick } from './hooks/useGameTick' import Field from './components/Field' import Warehouse from './components/Warehouse' import Market from './components/Market' +import Temple from './components/Temple' import { ActionCooldown } from './components/ActionCooldown' import { useSaveSystem } from './store/useSaveSystem' import { Console } from './components/Console' @@ -38,9 +39,7 @@ function App() { Fields Warehouse Market - - Temple - + Temple @@ -51,6 +50,9 @@ function App() { + + + diff --git a/src/components/Temple.tsx b/src/components/Temple.tsx new file mode 100644 index 0000000..b666165 --- /dev/null +++ b/src/components/Temple.tsx @@ -0,0 +1,121 @@ +import React from 'react' +import { useGameStore } from '../store/useGameStore' +import { UPGRADES } from '../constants' +import { styled } from '@linaria/react' +import type { UpgradeCost } from '../types' + +const TempleContainer = styled.div` + padding: 1rem; +` + +const Title = styled.h2` + font-size: 1.5rem; + font-weight: bold; + margin-bottom: 1rem; + text-align: center; +` + +const UpgradesGrid = styled.div` + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1rem; + margin-top: 1rem; +` + +const UpgradeCard = styled.div<{ purchased?: boolean }>` + background-color: ${props => props.purchased ? '#e5e7eb' : '#f3f4f6'}; + border: 1px solid #d1d5db; + border-radius: 0.5rem; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +` + +const UpgradeName = styled.h3` + font-size: 1.25rem; + font-weight: 500; + margin: 0; +` + +const UpgradeDescription = styled.p` + color: #4b5563; + margin: 0; +` + +const UpgradeCost = styled.div` + display: flex; + flex-direction: column; + gap: 0.25rem; + margin-top: 0.5rem; +` + +const CostItem = styled.div` + display: flex; + align-items: center; + gap: 0.5rem; +` + +const PurchaseButton = styled.button<{ disabled?: boolean }>` + padding: 0.5rem 1rem; + border-radius: 0.25rem; + background-color: ${props => props.disabled ? '#9ca3af' : '#3b82f6'}; + color: white; + font-weight: bold; + cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'}; + border: none; + margin-top: auto; +` + +const TempleComponent: React.FC = () => { + const { inventory, purchasedUpgrades, purchaseUpgrade } = useGameStore() + + const handlePurchase = (upgradeId: string) => { + purchaseUpgrade(upgradeId) + } + + const canAffordUpgrade = (costs: UpgradeCost[]) => { + return costs.every(cost => + inventory[cost.itemId] !== undefined && + inventory[cost.itemId] >= cost.amount + ) + } + + return ( + + Temple + + {Object.values(UPGRADES).map((upgrade) => { + const isPurchased = purchasedUpgrades.includes(upgrade.id) + const canAfford = canAffordUpgrade(upgrade.cost) + + return ( + + {upgrade.name} + {upgrade.description} + + Cost: + {upgrade.cost.map((cost, index) => ( + + {cost.amount} {cost.itemId} + + (You have: {inventory[cost.itemId] || 0}) + + + ))} + + handlePurchase(upgrade.id)} + disabled={isPurchased || !canAfford} + > + {isPurchased ? 'Purchased' : 'Purchase'} + + + ) + })} + + + ) +} + +export default TempleComponent \ No newline at end of file diff --git a/src/constants/index.ts b/src/constants/index.ts index 387c7e6..c5345ed 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,4 +1,4 @@ -import { CropDefinitions } from '../types' +import { CropDefinitions, Upgrade } from '../types' export const INITIAL_CASH = 50 export const INITIAL_FIELD_SIZE = 3 @@ -38,6 +38,18 @@ export const INITIAL_INVENTORY = { celery_seed: 8, } +export const UPGRADES: Record = { + aqueous_vigor_1: { + id: 'aqueous_vigor_1', + name: 'Aqueous Vigor I', + description: 'Reduces watering cooldown by 25%', + cost: [{ + itemId: 'celery', + amount: 10 + }] + } +} + export interface MarketItem { id: string name: string diff --git a/src/store/useGameStore.ts b/src/store/useGameStore.ts index b558c2e..264335c 100644 --- a/src/store/useGameStore.ts +++ b/src/store/useGameStore.ts @@ -9,6 +9,7 @@ import { INITIAL_GAME_SPEED, COOLDOWN_DURATION, MARKET_ITEMS, + UPGRADES, } from '../constants' import { GameState, PlotState } from '../types' @@ -79,6 +80,7 @@ export const useGameStore = create< sellItem: (itemId: string) => void saveToSlot: (slot: number) => void loadFromSlot: (slot: number) => void + purchaseUpgrade: (upgradeId: string) => void } >((set, get) => ({ cash: INITIAL_CASH, @@ -91,6 +93,7 @@ export const useGameStore = create< actionCooldown: 0, tickCount: 0, consoleMessages: [], + purchasedUpgrades: [], assignCrop: (row, col, cropId) => { set( @@ -129,7 +132,7 @@ export const useGameStore = create< }, water: (row, col) => { - const { plots, actionCooldown } = get() + const { plots, actionCooldown, purchasedUpgrades } = get() if (actionCooldown > 0) { return @@ -137,10 +140,21 @@ export const useGameStore = create< const plot = plots[row][col] if (plot.current && plot.moisture < 1) { + // Calculate cooldown reduction from upgrades + let cooldownReduction = 0 + if (purchasedUpgrades.includes('aqueous_vigor_1')) { + cooldownReduction += 0.25 // 25% reduction + } + + const finalCooldown = Math.max( + 0, + COOLDOWN_DURATION * (1 - cooldownReduction) + ) + set( produce((state) => { state.plots[row][col].moisture = 1 - state.actionCooldown = COOLDOWN_DURATION + state.actionCooldown = finalCooldown }), ) } @@ -331,6 +345,7 @@ export const useGameStore = create< sellItem, saveToSlot, loadFromSlot, + purchaseUpgrade, ...gameState } = state saveGame(slot, gameState) @@ -342,4 +357,36 @@ export const useGameStore = create< set(savedState) } }, + + purchaseUpgrade: (upgradeId) => { + const { inventory, purchasedUpgrades } = get() + const upgrade = UPGRADES[upgradeId] + + if (!upgrade || purchasedUpgrades.includes(upgradeId)) { + return + } + + // Check if player can afford all costs + const canAfford = upgrade.cost.every( + cost => inventory[cost.itemId] !== undefined && inventory[cost.itemId] >= cost.amount + ) + + if (!canAfford) { + return + } + + set( + produce((state) => { + // Deduct all costs + upgrade.cost.forEach(cost => { + state.inventory[cost.itemId] -= cost.amount + }) + state.purchasedUpgrades.push(upgradeId) + addConsoleMessage( + state, + `Purchased upgrade: ${upgrade.name}` + ) + }), + ) + }, })) diff --git a/src/types/index.ts b/src/types/index.ts index 5f0d942..bfb1a05 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -36,6 +36,18 @@ export interface ConsoleMessage { timestamp: number } +export interface UpgradeCost { + itemId: string + amount: number +} + +export interface Upgrade { + id: string + name: string + description: string + cost: UpgradeCost[] +} + export interface GameState { cash: number inventory: Record @@ -46,6 +58,7 @@ export interface GameState { gameSpeed: number actionCooldown: number consoleMessages: ConsoleMessage[] + purchasedUpgrades: string[] } export interface MarketTransaction {