From 00db07e6376f28278e9c01b0e4e7d56adbed659f Mon Sep 17 00:00:00 2001 From: Ryan Lanny Jenkins Date: Sun, 18 May 2025 13:10:35 -0500 Subject: [PATCH] Move to a tool bar style interaction for the fields view. --- src/components/Field.tsx | 251 +++++++++++++++++++++++++++++--------- src/store/useGameStore.ts | 110 +++++++---------- src/types/index.ts | 2 + 3 files changed, 237 insertions(+), 126 deletions(-) diff --git a/src/components/Field.tsx b/src/components/Field.tsx index c53751a..dd3335c 100644 --- a/src/components/Field.tsx +++ b/src/components/Field.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react' import { useGameStore } from '../store/useGameStore' import { CROPS } from '../constants' +import { FieldTool, PlotState } from '../types' const fieldContainerStyle: React.CSSProperties = { display: 'flex', @@ -16,6 +17,24 @@ const titleStyle: React.CSSProperties = { textAlign: 'center', } +const toolbarStyle: React.CSSProperties = { + display: 'flex', + gap: '0.5rem', + marginBottom: '1rem', + justifyContent: 'center', +} + +const getToolButtonStyle = (active: boolean): React.CSSProperties => ({ + padding: '0.5rem 1rem', + borderRadius: '0.25rem', + border: '1px solid #ccc', + backgroundColor: active ? '#4ade80' : '#f3f4f6', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: '0.5rem', +}) + const cropSelectionContainerStyle: React.CSSProperties = { display: 'flex', flexWrap: 'wrap', @@ -39,13 +58,6 @@ const getCropButtonStyle = (active: boolean): React.CSSProperties => ({ cursor: 'pointer', }) -const actionsContainerStyle: React.CSSProperties = { - display: 'flex', - gap: '0.5rem', - marginBottom: '1rem', - justifyContent: 'center', -} - const speedSelectorContainerStyle: React.CSSProperties = { display: 'flex', flexDirection: 'column', @@ -75,15 +87,6 @@ const getSpeedButtonStyle = (active: boolean): React.CSSProperties => ({ cursor: 'pointer', }) -const getActionButtonStyle = (disabled: boolean): React.CSSProperties => ({ - padding: '0.5rem 1rem', - borderRadius: '0.25rem', - fontWeight: 'bold', - color: 'white', - backgroundColor: disabled ? '#9ca3af' : '#22c55e', - cursor: disabled ? 'not-allowed' : 'pointer', -}) - const getFieldGridStyle = (size: number): React.CSSProperties => ({ display: 'grid', gridTemplateColumns: `repeat(${size}, 1fr)`, @@ -119,13 +122,13 @@ const getMoistureIndicatorStyle = (level: number): React.CSSProperties => ({ const getMaturityProgressContainerStyle = (): React.CSSProperties => ({ position: 'absolute', - bottom: '10px', // Moved up from the bottom to separate from moisture indicator + bottom: '10px', left: 0, width: '100%', - height: '10px', // Increased height for better visibility - backgroundColor: 'rgba(0, 0, 0, 0.3)', // Darker background for better contrast - zIndex: 5, // Increased z-index to ensure it's above other elements - border: '1px solid #000', // Added border for better visibility + height: '10px', + backgroundColor: 'rgba(0, 0, 0, 0.3)', + zIndex: 5, + border: '1px solid #000', }) const getMaturityProgressBarStyle = ( @@ -136,13 +139,117 @@ const getMaturityProgressBarStyle = ( bottom: 0, left: 0, width: `${Math.min((progress / total) * 100, 100)}%`, - height: '10px', // Increased height to match container - backgroundColor: '#ff5722', // Brighter orange color for better visibility + height: '10px', + backgroundColor: '#ff5722', transition: 'width 0.3s', - zIndex: 6, // Increased z-index to ensure it's above the container - boxShadow: '0 0 3px rgba(0, 0, 0, 0.5)', // Added shadow for better visibility + zIndex: 6, + boxShadow: '0 0 3px rgba(0, 0, 0, 0.5)', }) +const modalOverlayStyle: React.CSSProperties = { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + zIndex: 1000, +} + +const modalContentStyle: React.CSSProperties = { + backgroundColor: 'white', + padding: '2rem', + borderRadius: '0.5rem', + maxWidth: '400px', + width: '90%', + position: 'relative', +} + +const modalCloseButtonStyle: React.CSSProperties = { + position: 'absolute', + top: '0.5rem', + right: '0.5rem', + padding: '0.5rem', + border: 'none', + background: 'none', + cursor: 'pointer', + fontSize: '1.5rem', +} + +const plotInfoStyle: React.CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: '1rem', +} + +const plotInfoItemStyle: React.CSSProperties = { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '0.5rem', + backgroundColor: '#f3f4f6', + borderRadius: '0.25rem', +} + +interface PlotInfoModalProps { + plot: PlotState + onClose: () => void +} + +const PlotInfoModal: React.FC = ({ plot, onClose }) => { + const formatPercentage = (value: number) => `${Math.round(value * 100)}%` + + return ( +
+
e.stopPropagation()}> + +

Plot Information

+
+
+ Current Crop: + + {plot.current + ? `${CROPS[plot.current.cropId].name}${ + plot.current.mature ? ' (Mature)' : '' + }` + : 'None'} + +
+
+ Intended Crop: + + {plot.intended ? CROPS[plot.intended].name : 'None'} + +
+
+ Water Level: + {formatPercentage(plot.moisture)} +
+
+ Fertility Level: + {formatPercentage(plot.fertility)} +
+ {plot.current && !plot.current.mature && ( +
+ Growth Progress: + + {formatPercentage( + plot.current.progress / CROPS[plot.current.cropId].growthTicks, + )} + +
+ )} +
+
+
+ ) +} + const FieldComponent: React.FC = () => { const { plots, @@ -157,10 +264,34 @@ const FieldComponent: React.FC = () => { } = useGameStore() const [selectedCrop, setSelectedCrop] = useState(null) + const [selectedTool, setSelectedTool] = useState('mark') + const [inspectedPlot, setInspectedPlot] = useState<{ + plot: PlotState + row: number + col: number + } | null>(null) const handlePlotClick = (row: number, col: number) => { - if (selectedCrop) { - assignCrop(row, col, selectedCrop) + if (actionCooldown > 0) return + + switch (selectedTool) { + case 'mark': + if (selectedCrop) { + assignCrop(row, col, selectedCrop) + } + break + case 'plant': + plant(row, col) + break + case 'water': + water(row, col) + break + case 'harvest': + harvest(row, col) + break + case 'inspect': + setInspectedPlot({ plot: plots[row][col], row, col }) + break } } @@ -182,6 +313,14 @@ const FieldComponent: React.FC = () => { const availableSpeeds = [1, 2, 4, 8, 16, 32, 64] + const tools: { id: FieldTool; label: string; icon: string }[] = [ + { id: 'mark', label: 'Mark', icon: '🎯' }, + { id: 'plant', label: 'Plant', icon: '🌱' }, + { id: 'water', label: 'Water', icon: '💧' }, + { id: 'harvest', label: 'Harvest', icon: '✂️' }, + { id: 'inspect', label: 'Inspect', icon: '🔍' }, + ] + return (

Fields

@@ -201,42 +340,33 @@ const FieldComponent: React.FC = () => {
-
-

Select a crop to plant:

- {Object.values(CROPS).map((crop) => ( +
+ {tools.map((tool) => ( ))}
-
- - - -
+ {selectedTool === 'mark' && ( +
+

Select a crop to mark:

+ {Object.values(CROPS).map((crop) => ( + + ))} +
+ )}
{plots.slice(0, fieldSize).map((row, rowIndex) => @@ -276,6 +406,13 @@ const FieldComponent: React.FC = () => { }), )}
+ + {inspectedPlot && ( + setInspectedPlot(null)} + /> + )}
) } diff --git a/src/store/useGameStore.ts b/src/store/useGameStore.ts index 5eef734..d6fb702 100644 --- a/src/store/useGameStore.ts +++ b/src/store/useGameStore.ts @@ -10,7 +10,7 @@ import { COOLDOWN_DURATION, MARKET_ITEMS, } from '../constants' -import { GameState, PlotState } from '../types' +import { GameState, PlotState, FieldTool } from '../types' const initializeField = (size: number): PlotState[][] => { return Array(size) @@ -55,9 +55,9 @@ export const hasSaveInSlot = (slot: number): boolean => { export const useGameStore = create< GameState & { - plant: () => void - water: () => void - harvest: () => void + 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 @@ -87,105 +87,77 @@ export const useGameStore = create< ) }, - plant: () => { + plant: (row, col) => { 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] + const plot = plots[row][col] + if (plot.intended && !plot.current) { + const seedId = `${plot.intended}_seed` - 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, + } - 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 - } - } + state.inventory[seedId] = state.inventory[seedId] - 1 + state.actionCooldown = COOLDOWN_DURATION + }), + ) } } }, - water: () => { + water: (row, col) => { 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) { + const plot = plots[row][col] + if (plot.current && plot.moisture < 1) { set( produce((state) => { - state.plots[driestRow][driestCol].moisture = 1 + state.plots[row][col].moisture = 1 state.actionCooldown = COOLDOWN_DURATION }), ) } }, - harvest: () => { + harvest: (row, col) => { 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] + 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 - 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 - }), + 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, ) - return - } - } + state.actionCooldown = COOLDOWN_DURATION + }), + ) } }, diff --git a/src/types/index.ts b/src/types/index.ts index e1fd1b1..7fb72a7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -28,6 +28,8 @@ export interface InventoryItem { count: number } +export type FieldTool = 'mark' | 'plant' | 'water' | 'harvest' | 'inspect' + export interface GameState { cash: number inventory: Record