Move to a tool bar style interaction for the fields view.
This commit is contained in:
parent
b38cc9762e
commit
00db07e637
|
|
@ -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<PlotInfoModalProps> = ({ plot, onClose }) => {
|
||||
const formatPercentage = (value: number) => `${Math.round(value * 100)}%`
|
||||
|
||||
return (
|
||||
<div style={modalOverlayStyle} onClick={onClose}>
|
||||
<div style={modalContentStyle} onClick={(e) => e.stopPropagation()}>
|
||||
<button style={modalCloseButtonStyle} onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
<h3 style={{ marginTop: 0 }}>Plot Information</h3>
|
||||
<div style={plotInfoStyle}>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Current Crop:</span>
|
||||
<span>
|
||||
{plot.current
|
||||
? `${CROPS[plot.current.cropId].name}${
|
||||
plot.current.mature ? ' (Mature)' : ''
|
||||
}`
|
||||
: 'None'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Intended Crop:</span>
|
||||
<span>
|
||||
{plot.intended ? CROPS[plot.intended].name : 'None'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Water Level:</span>
|
||||
<span>{formatPercentage(plot.moisture)}</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Fertility Level:</span>
|
||||
<span>{formatPercentage(plot.fertility)}</span>
|
||||
</div>
|
||||
{plot.current && !plot.current.mature && (
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Growth Progress:</span>
|
||||
<span>
|
||||
{formatPercentage(
|
||||
plot.current.progress / CROPS[plot.current.cropId].growthTicks,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FieldComponent: React.FC = () => {
|
||||
const {
|
||||
plots,
|
||||
|
|
@ -157,10 +264,34 @@ const FieldComponent: React.FC = () => {
|
|||
} = useGameStore()
|
||||
|
||||
const [selectedCrop, setSelectedCrop] = useState<string | null>(null)
|
||||
const [selectedTool, setSelectedTool] = useState<FieldTool>('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 (
|
||||
<div style={fieldContainerStyle}>
|
||||
<h2 style={titleStyle}>Fields</h2>
|
||||
|
|
@ -201,42 +340,33 @@ const FieldComponent: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div style={cropSelectionContainerStyle}>
|
||||
<p style={cropSelectionLabelStyle}>Select a crop to plant:</p>
|
||||
{Object.values(CROPS).map((crop) => (
|
||||
<div style={toolbarStyle}>
|
||||
{tools.map((tool) => (
|
||||
<button
|
||||
key={crop.id}
|
||||
style={getCropButtonStyle(selectedCrop === crop.id)}
|
||||
onClick={() => setSelectedCrop(crop.id)}
|
||||
key={tool.id}
|
||||
style={getToolButtonStyle(selectedTool === tool.id)}
|
||||
onClick={() => setSelectedTool(tool.id)}
|
||||
>
|
||||
{crop.name}
|
||||
<span>{tool.icon}</span>
|
||||
<span>{tool.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={actionsContainerStyle}>
|
||||
<button
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={plant}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Plant Crop
|
||||
</button>
|
||||
<button
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={water}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Water Crop
|
||||
</button>
|
||||
<button
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={harvest}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Harvest Crop
|
||||
</button>
|
||||
</div>
|
||||
{selectedTool === 'mark' && (
|
||||
<div style={cropSelectionContainerStyle}>
|
||||
<p style={cropSelectionLabelStyle}>Select a crop to mark:</p>
|
||||
{Object.values(CROPS).map((crop) => (
|
||||
<button
|
||||
key={crop.id}
|
||||
style={getCropButtonStyle(selectedCrop === crop.id)}
|
||||
onClick={() => setSelectedCrop(crop.id)}
|
||||
>
|
||||
{crop.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={getFieldGridStyle(fieldSize)}>
|
||||
{plots.slice(0, fieldSize).map((row, rowIndex) =>
|
||||
|
|
@ -276,6 +406,13 @@ const FieldComponent: React.FC = () => {
|
|||
}),
|
||||
)}
|
||||
</div>
|
||||
|
||||
{inspectedPlot && (
|
||||
<PlotInfoModal
|
||||
plot={inspectedPlot.plot}
|
||||
onClose={() => setInspectedPlot(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export interface InventoryItem {
|
|||
count: number
|
||||
}
|
||||
|
||||
export type FieldTool = 'mark' | 'plant' | 'water' | 'harvest' | 'inspect'
|
||||
|
||||
export interface GameState {
|
||||
cash: number
|
||||
inventory: Record<string, number>
|
||||
|
|
|
|||
Loading…
Reference in a new issue