Set up prettier.
This commit is contained in:
parent
39309aa9e7
commit
d75e9f3cbb
5
.prettierrc
Normal file
5
.prettierrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
|
||||
17
package-lock.json
generated
17
package-lock.json
generated
|
|
@ -24,6 +24,7 @@
|
|||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@wyw-in-js/vite": "^0.6.0",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.0.1"
|
||||
}
|
||||
|
|
@ -1628,6 +1629,22 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"type-check": "tsc --noEmit",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"lint": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@linaria/core": "^6.3.0",
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@wyw-in-js/vite": "^0.6.0",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.0.1"
|
||||
}
|
||||
|
|
|
|||
68
src/App.tsx
68
src/App.tsx
|
|
@ -1,47 +1,53 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/CustomTabs';
|
||||
import { useGameTick } from './hooks/useGameTick';
|
||||
import Field from './components/Field';
|
||||
import Warehouse from './components/Warehouse';
|
||||
import Market from './components/Market';
|
||||
import { ActionCooldown } from './components/ActionCooldown';
|
||||
import { useGameStore } from './store/useGameStore';
|
||||
import { hasSaveInSlot } from './utils/saveSystem';
|
||||
import React, { useEffect } from 'react'
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from './components/CustomTabs'
|
||||
import { useGameTick } from './hooks/useGameTick'
|
||||
import Field from './components/Field'
|
||||
import Warehouse from './components/Warehouse'
|
||||
import Market from './components/Market'
|
||||
import { ActionCooldown } from './components/ActionCooldown'
|
||||
import { useGameStore } from './store/useGameStore'
|
||||
import { hasSaveInSlot } from './utils/saveSystem'
|
||||
|
||||
const appContainerStyle: React.CSSProperties = {
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '2rem'
|
||||
};
|
||||
padding: '2rem',
|
||||
}
|
||||
|
||||
const tabsListStyles: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)'
|
||||
};
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
}
|
||||
|
||||
function App() {
|
||||
useGameTick();
|
||||
const { saveToSlot, loadFromSlot } = useGameStore();
|
||||
useGameTick()
|
||||
const { saveToSlot, loadFromSlot } = useGameStore()
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const slot = {
|
||||
'1': 1,
|
||||
'2': 2,
|
||||
'3': 3,
|
||||
'!': 1,
|
||||
'@': 2,
|
||||
'#': 3
|
||||
}[e.key] ?? null
|
||||
|
||||
const slot =
|
||||
{
|
||||
'1': 1,
|
||||
'2': 2,
|
||||
'3': 3,
|
||||
'!': 1,
|
||||
'@': 2,
|
||||
'#': 3,
|
||||
}[e.key] ?? null
|
||||
|
||||
if (slot !== null) {
|
||||
if (e.shiftKey) {
|
||||
saveToSlot(slot);
|
||||
console.log(`Game saved to slot ${slot}`);
|
||||
saveToSlot(slot)
|
||||
console.log(`Game saved to slot ${slot}`)
|
||||
} else if (hasSaveInSlot(slot)) {
|
||||
loadFromSlot(slot);
|
||||
console.log(`Game loaded from slot ${slot}`);
|
||||
loadFromSlot(slot)
|
||||
console.log(`Game loaded from slot ${slot}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +65,9 @@ function App() {
|
|||
<TabsTrigger value="fields">Fields</TabsTrigger>
|
||||
<TabsTrigger value="warehouse">Warehouse</TabsTrigger>
|
||||
<TabsTrigger value="market">Market</TabsTrigger>
|
||||
<TabsTrigger value="temple" disabled>Temple</TabsTrigger>
|
||||
<TabsTrigger value="temple" disabled>
|
||||
Temple
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="fields">
|
||||
<Field />
|
||||
|
|
@ -73,7 +81,7 @@ function App() {
|
|||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useEffect, useState } from 'react'
|
||||
import { styled } from '@linaria/react'
|
||||
|
||||
import { useGameStore } from '../store/useGameStore';
|
||||
import { useGameStore } from '../store/useGameStore'
|
||||
|
||||
const CooldownContainer = styled.div`
|
||||
position: relative;
|
||||
|
|
@ -9,51 +9,51 @@ const CooldownContainer = styled.div`
|
|||
width: 100%;
|
||||
background-color: #e5e7eb;
|
||||
overflow: hidden;
|
||||
`;
|
||||
`
|
||||
|
||||
const ProgressBar = styled.div<{ progress: string }>`
|
||||
height: 100%;
|
||||
background-color: #3b82f6;
|
||||
width: ${props => props.progress};
|
||||
`;
|
||||
width: ${(props) => props.progress};
|
||||
`
|
||||
|
||||
export const ActionCooldown = () => {
|
||||
const { actionCooldown, setActionCooldown } = useGameStore();
|
||||
const [progress, setProgress] = useState(0);
|
||||
const { actionCooldown, setActionCooldown } = useGameStore()
|
||||
const [progress, setProgress] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (actionCooldown <= 0) {
|
||||
setProgress(0);
|
||||
return;
|
||||
setProgress(0)
|
||||
return
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const duration = actionCooldown;
|
||||
|
||||
const startTime = Date.now()
|
||||
const duration = actionCooldown
|
||||
|
||||
const updateProgress = () => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const newProgress = Math.min(100, (elapsed / duration) * 100);
|
||||
|
||||
const elapsed = Date.now() - startTime
|
||||
const newProgress = Math.min(100, (elapsed / duration) * 100)
|
||||
|
||||
if (newProgress >= 100) {
|
||||
setProgress(0);
|
||||
setActionCooldown(0);
|
||||
return;
|
||||
setProgress(0)
|
||||
setActionCooldown(0)
|
||||
return
|
||||
}
|
||||
|
||||
setProgress(newProgress);
|
||||
requestAnimationFrame(updateProgress);
|
||||
};
|
||||
setProgress(newProgress)
|
||||
requestAnimationFrame(updateProgress)
|
||||
}
|
||||
|
||||
const animationFrame = requestAnimationFrame(updateProgress);
|
||||
const animationFrame = requestAnimationFrame(updateProgress)
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
};
|
||||
}, [actionCooldown, setActionCooldown]);
|
||||
cancelAnimationFrame(animationFrame)
|
||||
}
|
||||
}, [actionCooldown, setActionCooldown])
|
||||
|
||||
return (
|
||||
<CooldownContainer>
|
||||
<ProgressBar progress={`${progress}%`} />
|
||||
</CooldownContainer>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export interface TabProps {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
value: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export interface TabsProps {
|
||||
defaultValue: string;
|
||||
children: React.ReactNode;
|
||||
defaultValue: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export interface TabsListProps {
|
||||
children: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
children: React.ReactNode
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export interface TabsContentProps {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
value: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export interface TabsTriggerProps {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
value: string
|
||||
children: React.ReactNode
|
||||
disabled?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const tabsContainerStyle: React.CSSProperties = {
|
||||
width: '100%'
|
||||
};
|
||||
width: '100%',
|
||||
}
|
||||
|
||||
const tabsListContainerStyle: React.CSSProperties = {
|
||||
display: 'inline-flex',
|
||||
|
|
@ -39,10 +39,13 @@ const tabsListContainerStyle: React.CSSProperties = {
|
|||
borderRadius: '0.5rem',
|
||||
backgroundColor: '#f4f4f5',
|
||||
padding: '0.25rem',
|
||||
color: '#71717a'
|
||||
};
|
||||
color: '#71717a',
|
||||
}
|
||||
|
||||
const getTabButtonStyle = (active: boolean, disabled: boolean): React.CSSProperties => ({
|
||||
const getTabButtonStyle = (
|
||||
active: boolean,
|
||||
disabled: boolean,
|
||||
): React.CSSProperties => ({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
|
@ -57,68 +60,71 @@ const getTabButtonStyle = (active: boolean, disabled: boolean): React.CSSPropert
|
|||
boxShadow: active ? '0 1px 3px rgba(0, 0, 0, 0.1)' : 'none',
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
border: 'none'
|
||||
});
|
||||
border: 'none',
|
||||
})
|
||||
|
||||
const tabContentStyle: React.CSSProperties = {
|
||||
marginTop: '0.5rem',
|
||||
outline: 'none'
|
||||
};
|
||||
outline: 'none',
|
||||
}
|
||||
|
||||
const TabsContext = React.createContext<{
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
value: string
|
||||
setValue: (value: string) => void
|
||||
}>({
|
||||
value: '',
|
||||
setValue: () => {},
|
||||
});
|
||||
})
|
||||
|
||||
export const Tabs: React.FC<TabsProps> = ({ defaultValue, children }) => {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
const [value, setValue] = useState(defaultValue)
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={{ value, setValue }}>
|
||||
<div style={tabsContainerStyle}>{children}</div>
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export const TabsList: React.FC<TabsListProps> = ({ children, style }) => {
|
||||
return <div style={{...tabsListContainerStyle, ...style}}>{children}</div>;
|
||||
};
|
||||
return <div style={{ ...tabsListContainerStyle, ...style }}>{children}</div>
|
||||
}
|
||||
|
||||
export const TabsTrigger: React.FC<TabsTriggerProps> = ({
|
||||
value,
|
||||
children,
|
||||
export const TabsTrigger: React.FC<TabsTriggerProps> = ({
|
||||
value,
|
||||
children,
|
||||
disabled = false,
|
||||
onClick
|
||||
onClick,
|
||||
}) => {
|
||||
const { value: selectedValue, setValue } = React.useContext(TabsContext);
|
||||
const active = selectedValue === value;
|
||||
const { value: selectedValue, setValue } = React.useContext(TabsContext)
|
||||
const active = selectedValue === value
|
||||
|
||||
const handleClick = () => {
|
||||
if (!disabled) {
|
||||
setValue(value);
|
||||
if (onClick) onClick();
|
||||
setValue(value)
|
||||
if (onClick) onClick()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
<button
|
||||
style={getTabButtonStyle(active, disabled)}
|
||||
disabled={disabled}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
type="button"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export const TabsContent: React.FC<TabsContentProps> = ({ value, children }) => {
|
||||
const { value: selectedValue } = React.useContext(TabsContext);
|
||||
|
||||
if (selectedValue !== value) return null;
|
||||
|
||||
return <div style={tabContentStyle}>{children}</div>;
|
||||
};
|
||||
export const TabsContent: React.FC<TabsContentProps> = ({
|
||||
value,
|
||||
children,
|
||||
}) => {
|
||||
const { value: selectedValue } = React.useContext(TabsContext)
|
||||
|
||||
if (selectedValue !== value) return null
|
||||
|
||||
return <div style={tabContentStyle}>{children}</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useGameStore } from '../store/useGameStore';
|
||||
import { CROPS } from '../constants';
|
||||
import React, { useState } from 'react'
|
||||
import { useGameStore } from '../store/useGameStore'
|
||||
import { CROPS } from '../constants'
|
||||
|
||||
const fieldContainerStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem',
|
||||
padding: '1rem'
|
||||
};
|
||||
padding: '1rem',
|
||||
}
|
||||
|
||||
const titleStyle: React.CSSProperties = {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '1rem',
|
||||
textAlign: 'center'
|
||||
};
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const cropSelectionContainerStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '0.5rem',
|
||||
marginBottom: '1rem',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
justifyContent: 'center',
|
||||
}
|
||||
|
||||
const cropSelectionLabelStyle: React.CSSProperties = {
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
fontWeight: 500,
|
||||
marginBottom: '0.5rem'
|
||||
};
|
||||
marginBottom: '0.5rem',
|
||||
}
|
||||
|
||||
const getCropButtonStyle = (active: boolean): React.CSSProperties => ({
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '0.25rem',
|
||||
border: '1px solid #ccc',
|
||||
backgroundColor: active ? '#4ade80' : '#f3f4f6',
|
||||
cursor: 'pointer'
|
||||
});
|
||||
cursor: 'pointer',
|
||||
})
|
||||
|
||||
const actionsContainerStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
gap: '0.5rem',
|
||||
marginBottom: '1rem',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
justifyContent: 'center',
|
||||
}
|
||||
|
||||
const speedSelectorContainerStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
|
|
@ -54,26 +54,26 @@ const speedSelectorContainerStyle: React.CSSProperties = {
|
|||
padding: '0.5rem',
|
||||
backgroundColor: '#f9fafb',
|
||||
borderRadius: '0.5rem',
|
||||
border: '1px solid #e5e7eb'
|
||||
};
|
||||
border: '1px solid #e5e7eb',
|
||||
}
|
||||
|
||||
const speedSelectorLabelStyle: React.CSSProperties = {
|
||||
fontWeight: 500,
|
||||
marginBottom: '0.5rem'
|
||||
};
|
||||
marginBottom: '0.5rem',
|
||||
}
|
||||
|
||||
const speedButtonsContainerStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
gap: '0.5rem'
|
||||
};
|
||||
gap: '0.5rem',
|
||||
}
|
||||
|
||||
const getSpeedButtonStyle = (active: boolean): React.CSSProperties => ({
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '0.25rem',
|
||||
border: '1px solid #ccc',
|
||||
backgroundColor: active ? '#4ade80' : '#f3f4f6',
|
||||
cursor: 'pointer'
|
||||
});
|
||||
cursor: 'pointer',
|
||||
})
|
||||
|
||||
const getActionButtonStyle = (disabled: boolean): React.CSSProperties => ({
|
||||
padding: '0.5rem 1rem',
|
||||
|
|
@ -81,8 +81,8 @@ const getActionButtonStyle = (disabled: boolean): React.CSSProperties => ({
|
|||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
backgroundColor: disabled ? '#9ca3af' : '#22c55e',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer'
|
||||
});
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
})
|
||||
|
||||
const getFieldGridStyle = (size: number): React.CSSProperties => ({
|
||||
display: 'grid',
|
||||
|
|
@ -91,8 +91,8 @@ const getFieldGridStyle = (size: number): React.CSSProperties => ({
|
|||
gap: '0.5rem',
|
||||
width: '100%',
|
||||
maxWidth: '600px',
|
||||
margin: '0 auto'
|
||||
});
|
||||
margin: '0 auto',
|
||||
})
|
||||
|
||||
const getPlotStyle = (bgColor: string): React.CSSProperties => ({
|
||||
border: '1px solid #78350f',
|
||||
|
|
@ -103,8 +103,8 @@ const getPlotStyle = (bgColor: string): React.CSSProperties => ({
|
|||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: bgColor
|
||||
});
|
||||
backgroundColor: bgColor,
|
||||
})
|
||||
|
||||
const getMoistureIndicatorStyle = (level: number): React.CSSProperties => ({
|
||||
position: 'absolute',
|
||||
|
|
@ -114,69 +114,72 @@ const getMoistureIndicatorStyle = (level: number): React.CSSProperties => ({
|
|||
height: `${Math.min(level * 100, 100)}%`,
|
||||
backgroundColor: 'rgba(147, 197, 253, 0.3)',
|
||||
transition: 'height 0.3s',
|
||||
zIndex: 1
|
||||
});
|
||||
zIndex: 1,
|
||||
})
|
||||
|
||||
const getMaturityProgressContainerStyle = (): React.CSSProperties => ({
|
||||
position: 'absolute',
|
||||
bottom: '10px', // Moved up from the bottom to separate from moisture indicator
|
||||
bottom: '10px', // Moved up from the bottom to separate from moisture indicator
|
||||
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', // 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
|
||||
})
|
||||
|
||||
const getMaturityProgressBarStyle = (progress: number, total: number): React.CSSProperties => ({
|
||||
const getMaturityProgressBarStyle = (
|
||||
progress: number,
|
||||
total: number,
|
||||
): React.CSSProperties => ({
|
||||
position: 'absolute',
|
||||
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', // Increased height to match container
|
||||
backgroundColor: '#ff5722', // Brighter orange color for better visibility
|
||||
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, // 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
|
||||
})
|
||||
|
||||
const FieldComponent: React.FC = () => {
|
||||
const {
|
||||
plots,
|
||||
fieldSize,
|
||||
plant,
|
||||
water,
|
||||
harvest,
|
||||
const {
|
||||
plots,
|
||||
fieldSize,
|
||||
plant,
|
||||
water,
|
||||
harvest,
|
||||
assignCrop,
|
||||
gameSpeed,
|
||||
setGameSpeed,
|
||||
actionCooldown
|
||||
} = useGameStore();
|
||||
actionCooldown,
|
||||
} = useGameStore()
|
||||
|
||||
const [selectedCrop, setSelectedCrop] = useState<string | null>(null);
|
||||
const [selectedCrop, setSelectedCrop] = useState<string | null>(null)
|
||||
|
||||
const handlePlotClick = (row: number, col: number) => {
|
||||
if (selectedCrop) {
|
||||
assignCrop(row, col, selectedCrop);
|
||||
assignCrop(row, col, selectedCrop)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getBgColor = (hasCrop: boolean, isMature: boolean) => {
|
||||
if (isMature) return "#22c55e";
|
||||
if (hasCrop) return "#86efac";
|
||||
return "#92400e";
|
||||
};
|
||||
if (isMature) return '#22c55e'
|
||||
if (hasCrop) return '#86efac'
|
||||
return '#92400e'
|
||||
}
|
||||
|
||||
const availableSpeeds = [1, 2, 4, 8, 16, 32, 64]
|
||||
|
||||
return (
|
||||
<div style={fieldContainerStyle}>
|
||||
<h2 style={titleStyle}>Fields</h2>
|
||||
|
||||
|
||||
<div style={speedSelectorContainerStyle}>
|
||||
<p style={speedSelectorLabelStyle}>Game Speed:</p>
|
||||
<div style={speedButtonsContainerStyle}>
|
||||
{availableSpeeds.map(speed => (
|
||||
{availableSpeeds.map((speed) => (
|
||||
<button
|
||||
style={getSpeedButtonStyle(gameSpeed === speed)}
|
||||
onClick={() => setGameSpeed(speed)}
|
||||
|
|
@ -187,10 +190,10 @@ const FieldComponent: React.FC = () => {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={cropSelectionContainerStyle}>
|
||||
<p style={cropSelectionLabelStyle}>Select a crop to plant:</p>
|
||||
{Object.values(CROPS).map(crop => (
|
||||
{Object.values(CROPS).map((crop) => (
|
||||
<button
|
||||
key={crop.id}
|
||||
style={getCropButtonStyle(selectedCrop === crop.id)}
|
||||
|
|
@ -200,59 +203,71 @@ const FieldComponent: React.FC = () => {
|
|||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
<div style={actionsContainerStyle}>
|
||||
<button
|
||||
<button
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={plant}
|
||||
onClick={plant}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Plant Crop
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={water}
|
||||
onClick={water}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Water Crop
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={harvest}
|
||||
onClick={harvest}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Harvest Crop
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={getFieldGridStyle(fieldSize)}>
|
||||
{plots.slice(0, fieldSize).map((row, rowIndex) =>
|
||||
{plots.slice(0, fieldSize).map((row, rowIndex) =>
|
||||
row.slice(0, fieldSize).map((plot, colIndex) => {
|
||||
const hasCrop = !!plot.current;
|
||||
const isMature = !!(plot.current?.mature);
|
||||
const bgColor = getBgColor(hasCrop, isMature);
|
||||
|
||||
const hasCrop = !!plot.current
|
||||
const isMature = !!plot.current?.mature
|
||||
const bgColor = getBgColor(hasCrop, isMature)
|
||||
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
key={`${rowIndex}-${colIndex}`}
|
||||
style={getPlotStyle(bgColor)}
|
||||
onClick={() => handlePlotClick(rowIndex, colIndex)}
|
||||
>
|
||||
{plot.intended && !plot.current && <div>🌱 {CROPS[plot.intended]?.name}</div>}
|
||||
{plot.current && <div>{plot.current.mature ? '🌿' : '🌱'} {CROPS[plot.current.cropId]?.name}</div>}
|
||||
{plot.intended && !plot.current && (
|
||||
<div>🌱 {CROPS[plot.intended]?.name}</div>
|
||||
)}
|
||||
{plot.current && (
|
||||
<div>
|
||||
{plot.current.mature ? '🌿' : '🌱'}{' '}
|
||||
{CROPS[plot.current.cropId]?.name}
|
||||
</div>
|
||||
)}
|
||||
<div style={getMoistureIndicatorStyle(plot.moisture)} />
|
||||
{plot.current && !plot.current.mature && (
|
||||
<div style={getMaturityProgressContainerStyle()}>
|
||||
<div style={getMaturityProgressBarStyle(plot.current.progress, CROPS[plot.current.cropId].growthTicks)} />
|
||||
<div
|
||||
style={getMaturityProgressBarStyle(
|
||||
plot.current.progress,
|
||||
CROPS[plot.current.cropId].growthTicks,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default FieldComponent;
|
||||
export default FieldComponent
|
||||
|
|
|
|||
|
|
@ -1,53 +1,59 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
interface FloatingMessageProps {
|
||||
message: string;
|
||||
startPosition: { x: number, y: number };
|
||||
onComplete: () => void;
|
||||
message: string
|
||||
startPosition: { x: number; y: number }
|
||||
onComplete: () => void
|
||||
}
|
||||
|
||||
const FloatingMessage: React.FC<FloatingMessageProps> = ({ message, startPosition, onComplete }) => {
|
||||
const [position, setPosition] = useState({ y: startPosition.y });
|
||||
const [opacity, setOpacity] = useState(1);
|
||||
|
||||
const FloatingMessage: React.FC<FloatingMessageProps> = ({
|
||||
message,
|
||||
startPosition,
|
||||
onComplete,
|
||||
}) => {
|
||||
const [position, setPosition] = useState({ y: startPosition.y })
|
||||
const [opacity, setOpacity] = useState(1)
|
||||
|
||||
useEffect(() => {
|
||||
const startTime = Date.now();
|
||||
const duration = 1000; // 1 second animation
|
||||
|
||||
const startTime = Date.now()
|
||||
const duration = 1000 // 1 second animation
|
||||
|
||||
const animate = () => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
setPosition({ y: startPosition.y - progress * 50 });
|
||||
setOpacity(1 - progress);
|
||||
|
||||
const elapsed = Date.now() - startTime
|
||||
const progress = Math.min(elapsed / duration, 1)
|
||||
|
||||
setPosition({ y: startPosition.y - progress * 50 })
|
||||
setOpacity(1 - progress)
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
requestAnimationFrame(animate)
|
||||
} else {
|
||||
onComplete();
|
||||
onComplete()
|
||||
}
|
||||
};
|
||||
|
||||
const animationFrame = requestAnimationFrame(animate);
|
||||
|
||||
}
|
||||
|
||||
const animationFrame = requestAnimationFrame(animate)
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
};
|
||||
}, [startPosition, onComplete]);
|
||||
|
||||
cancelAnimationFrame(animationFrame)
|
||||
}
|
||||
}, [startPosition, onComplete])
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
left: startPosition.x,
|
||||
top: position.y,
|
||||
opacity,
|
||||
fontWeight: 'bold',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 100
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: startPosition.x,
|
||||
top: position.y,
|
||||
opacity,
|
||||
fontWeight: 'bold',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default FloatingMessage;
|
||||
export default FloatingMessage
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useGameStore } from '../store/useGameStore';
|
||||
import { MARKET_ITEMS } from '../constants';
|
||||
import FloatingMessage from './FloatingMessage';
|
||||
import { styled } from '@linaria/react';
|
||||
import React, { useState } from 'react'
|
||||
import { useGameStore } from '../store/useGameStore'
|
||||
import { MARKET_ITEMS } from '../constants'
|
||||
import FloatingMessage from './FloatingMessage'
|
||||
import { styled } from '@linaria/react'
|
||||
|
||||
const MarketContainer = styled.div`
|
||||
padding: 1rem;
|
||||
`;
|
||||
`
|
||||
|
||||
const Title = styled.h2`
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
`;
|
||||
`
|
||||
|
||||
const CashDisplay = styled.div`
|
||||
text-align: center;
|
||||
|
|
@ -21,124 +21,135 @@ const CashDisplay = styled.div`
|
|||
padding: 1rem;
|
||||
background-color: #dcfce7;
|
||||
border-radius: 0.375rem;
|
||||
`;
|
||||
`
|
||||
|
||||
const CashAmount = styled.h3`
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
`;
|
||||
`
|
||||
|
||||
const SectionTitle = styled.h3`
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-top: 1.5rem;
|
||||
`;
|
||||
`
|
||||
|
||||
const MarketTable = styled.table`
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 0.5rem;
|
||||
`;
|
||||
`
|
||||
|
||||
const MarketItemRow = styled.tr`
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 0.5rem;
|
||||
`;
|
||||
`
|
||||
|
||||
const MarketItemCell = styled.td`
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
`;
|
||||
`
|
||||
|
||||
const TableHeader = styled.th`
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
`;
|
||||
`
|
||||
|
||||
const ItemIcon = styled.span`
|
||||
font-size: 1.5rem;
|
||||
margin-right: 0.5rem;
|
||||
`;
|
||||
`
|
||||
|
||||
const ActionButton = styled.button<{ disabled?: boolean }>`
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: ${props => props.disabled ? '#9ca3af' : '#3b82f6'};
|
||||
background-color: ${(props) => (props.disabled ? '#9ca3af' : '#3b82f6')};
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
|
||||
cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
|
||||
border: none;
|
||||
`;
|
||||
`
|
||||
|
||||
interface FloatingMessageData {
|
||||
id: string;
|
||||
message: string;
|
||||
position: { x: number, y: number };
|
||||
id: string
|
||||
message: string
|
||||
position: { x: number; y: number }
|
||||
}
|
||||
|
||||
const MarketComponent: React.FC = () => {
|
||||
const { inventory, cash, buyItem, sellItem } = useGameStore();
|
||||
const [floatingMessages, setFloatingMessages] = useState<FloatingMessageData[]>([]);
|
||||
const { inventory, cash, buyItem, sellItem } = useGameStore()
|
||||
const [floatingMessages, setFloatingMessages] = useState<
|
||||
FloatingMessageData[]
|
||||
>([])
|
||||
|
||||
const handleBuy = (itemId: string, e: React.MouseEvent) => {
|
||||
const item = MARKET_ITEMS[itemId];
|
||||
if (!item || item.buyPrice === null || cash < item.buyPrice) return;
|
||||
|
||||
buyItem(itemId);
|
||||
|
||||
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
||||
const position = { x: rect.left + rect.width / 2, y: rect.top };
|
||||
|
||||
const item = MARKET_ITEMS[itemId]
|
||||
if (!item || item.buyPrice === null || cash < item.buyPrice) return
|
||||
|
||||
buyItem(itemId)
|
||||
|
||||
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
||||
const position = { x: rect.left + rect.width / 2, y: rect.top }
|
||||
|
||||
setFloatingMessages([
|
||||
...floatingMessages,
|
||||
{
|
||||
id: `buy-${itemId}-${Date.now()}`,
|
||||
message: `+1 ${item.emoji} ${item.name}`,
|
||||
position
|
||||
}
|
||||
]);
|
||||
};
|
||||
position,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const handleSell = (itemId: string, e: React.MouseEvent) => {
|
||||
const item = MARKET_ITEMS[itemId];
|
||||
if (!item || item.sellPrice === null || !inventory[itemId] || inventory[itemId] <= 0) return;
|
||||
|
||||
sellItem(itemId);
|
||||
|
||||
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
||||
const position = { x: rect.left + rect.width / 2, y: rect.top };
|
||||
|
||||
const item = MARKET_ITEMS[itemId]
|
||||
if (
|
||||
!item ||
|
||||
item.sellPrice === null ||
|
||||
!inventory[itemId] ||
|
||||
inventory[itemId] <= 0
|
||||
)
|
||||
return
|
||||
|
||||
sellItem(itemId)
|
||||
|
||||
const rect = (e.target as HTMLElement).getBoundingClientRect()
|
||||
const position = { x: rect.left + rect.width / 2, y: rect.top }
|
||||
|
||||
setFloatingMessages([
|
||||
...floatingMessages,
|
||||
{
|
||||
id: `sell-${itemId}-${Date.now()}`,
|
||||
message: `-1 ${item.emoji} ${item.name}`,
|
||||
position
|
||||
}
|
||||
]);
|
||||
};
|
||||
position,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const removeFloatingMessage = (messageId: string) => {
|
||||
setFloatingMessages(floatingMessages.filter(msg => msg.id !== messageId));
|
||||
};
|
||||
setFloatingMessages(floatingMessages.filter((msg) => msg.id !== messageId))
|
||||
}
|
||||
|
||||
const sellableItems = Object.entries(MARKET_ITEMS)
|
||||
.filter(([_, item]) => item.sellPrice !== null && inventory[item.id] && inventory[item.id] > 0)
|
||||
.map(([_, item]) => item);
|
||||
.filter(
|
||||
([_, item]) =>
|
||||
item.sellPrice !== null && inventory[item.id] && inventory[item.id] > 0,
|
||||
)
|
||||
.map(([_, item]) => item)
|
||||
|
||||
const buyableItems = Object.entries(MARKET_ITEMS)
|
||||
.filter(([_, item]) => item.buyPrice !== null)
|
||||
.map(([_, item]) => item);
|
||||
.map(([_, item]) => item)
|
||||
|
||||
return (
|
||||
<MarketContainer>
|
||||
<Title>Market</Title>
|
||||
|
||||
|
||||
<CashDisplay>
|
||||
<CashAmount>Cash: ${cash.toFixed(2)}</CashAmount>
|
||||
</CashDisplay>
|
||||
|
||||
|
||||
{sellableItems.length > 0 && (
|
||||
<>
|
||||
<SectionTitle>Sell Items</SectionTitle>
|
||||
|
|
@ -152,7 +163,7 @@ const MarketComponent: React.FC = () => {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sellableItems.map(item => (
|
||||
{sellableItems.map((item) => (
|
||||
<MarketItemRow key={`sell-${item.id}`}>
|
||||
<MarketItemCell>
|
||||
<ItemIcon>{item.emoji}</ItemIcon>
|
||||
|
|
@ -161,7 +172,7 @@ const MarketComponent: React.FC = () => {
|
|||
<MarketItemCell>${item.sellPrice}</MarketItemCell>
|
||||
<MarketItemCell>{inventory[item.id] || 0}</MarketItemCell>
|
||||
<MarketItemCell>
|
||||
<ActionButton
|
||||
<ActionButton
|
||||
onClick={(e) => handleSell(item.id, e)}
|
||||
disabled={!inventory[item.id] || inventory[item.id] <= 0}
|
||||
>
|
||||
|
|
@ -174,7 +185,7 @@ const MarketComponent: React.FC = () => {
|
|||
</MarketTable>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<SectionTitle>Buy Items</SectionTitle>
|
||||
<MarketTable>
|
||||
<thead>
|
||||
|
|
@ -185,7 +196,7 @@ const MarketComponent: React.FC = () => {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{buyableItems.map(item => (
|
||||
{buyableItems.map((item) => (
|
||||
<MarketItemRow key={`buy-${item.id}`}>
|
||||
<MarketItemCell>
|
||||
<ItemIcon>{item.emoji}</ItemIcon>
|
||||
|
|
@ -193,7 +204,7 @@ const MarketComponent: React.FC = () => {
|
|||
</MarketItemCell>
|
||||
<MarketItemCell>${item.buyPrice}</MarketItemCell>
|
||||
<MarketItemCell>
|
||||
<ActionButton
|
||||
<ActionButton
|
||||
onClick={(e) => handleBuy(item.id, e)}
|
||||
disabled={item.buyPrice ? cash < item.buyPrice : true}
|
||||
>
|
||||
|
|
@ -204,8 +215,8 @@ const MarketComponent: React.FC = () => {
|
|||
))}
|
||||
</tbody>
|
||||
</MarketTable>
|
||||
|
||||
{floatingMessages.map(msg => (
|
||||
|
||||
{floatingMessages.map((msg) => (
|
||||
<FloatingMessage
|
||||
key={msg.id}
|
||||
message={msg.message}
|
||||
|
|
@ -214,7 +225,7 @@ const MarketComponent: React.FC = () => {
|
|||
/>
|
||||
))}
|
||||
</MarketContainer>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default MarketComponent;
|
||||
export default MarketComponent
|
||||
|
|
|
|||
|
|
@ -1,47 +1,47 @@
|
|||
import React from 'react';
|
||||
import { useGameStore } from '../store/useGameStore';
|
||||
import { CROPS } from '../constants';
|
||||
import React from 'react'
|
||||
import { useGameStore } from '../store/useGameStore'
|
||||
import { CROPS } from '../constants'
|
||||
|
||||
const warehouseContainerStyle: React.CSSProperties = {
|
||||
padding: '1rem'
|
||||
};
|
||||
padding: '1rem',
|
||||
}
|
||||
|
||||
const titleStyle: React.CSSProperties = {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '1rem',
|
||||
textAlign: 'center'
|
||||
};
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const cashDisplayStyle: React.CSSProperties = {
|
||||
textAlign: 'center',
|
||||
marginBottom: '1.5rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: '#dcfce7',
|
||||
borderRadius: '0.375rem'
|
||||
};
|
||||
borderRadius: '0.375rem',
|
||||
}
|
||||
|
||||
const cashAmountStyle: React.CSSProperties = {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 500
|
||||
};
|
||||
fontWeight: 500,
|
||||
}
|
||||
|
||||
const inventoryTitleStyle: React.CSSProperties = {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 500,
|
||||
marginBottom: '0.5rem'
|
||||
};
|
||||
marginBottom: '0.5rem',
|
||||
}
|
||||
|
||||
const getInventoryGridStyle = (): React.CSSProperties => {
|
||||
const style: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||
gap: '1rem',
|
||||
marginTop: '1rem'
|
||||
};
|
||||
marginTop: '1rem',
|
||||
}
|
||||
|
||||
return style;
|
||||
};
|
||||
return style
|
||||
}
|
||||
|
||||
const inventoryItemStyle: React.CSSProperties = {
|
||||
backgroundColor: '#f3f4f6',
|
||||
|
|
@ -51,67 +51,68 @@ const inventoryItemStyle: React.CSSProperties = {
|
|||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center'
|
||||
};
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const itemIconStyle: React.CSSProperties = {
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: '0.5rem'
|
||||
};
|
||||
marginBottom: '0.5rem',
|
||||
}
|
||||
|
||||
const itemNameStyle: React.CSSProperties = {
|
||||
fontWeight: 500,
|
||||
marginBottom: '0.25rem'
|
||||
};
|
||||
marginBottom: '0.25rem',
|
||||
}
|
||||
|
||||
const formatItemName = (id: string) => {
|
||||
if (id.endsWith('_seed')) {
|
||||
const cropId = id.replace('_seed', '');
|
||||
return `${CROPS[cropId]?.name || cropId} Seeds`;
|
||||
const cropId = id.replace('_seed', '')
|
||||
return `${CROPS[cropId]?.name || cropId} Seeds`
|
||||
}
|
||||
|
||||
return CROPS[id]?.name || id;
|
||||
};
|
||||
|
||||
return CROPS[id]?.name || id
|
||||
}
|
||||
|
||||
const getItemIcon = (id: string) => {
|
||||
if (id.endsWith('_seed')) {
|
||||
return '🌰';
|
||||
return '🌰'
|
||||
}
|
||||
|
||||
|
||||
const cropIcons: Record<string, string> = {
|
||||
celery: '🥬',
|
||||
corn: '🌽',
|
||||
};
|
||||
|
||||
return cropIcons[id] || '📦';
|
||||
};
|
||||
}
|
||||
|
||||
return cropIcons[id] || '📦'
|
||||
}
|
||||
|
||||
const WarehouseComponent: React.FC = () => {
|
||||
const { inventory, cash } = useGameStore();
|
||||
const { inventory, cash } = useGameStore()
|
||||
|
||||
return (
|
||||
<div style={warehouseContainerStyle}>
|
||||
<h2 style={titleStyle}>Warehouse</h2>
|
||||
|
||||
|
||||
<div style={cashDisplayStyle}>
|
||||
<h3 style={cashAmountStyle}>Cash: ${cash.toFixed(2)}</h3>
|
||||
</div>
|
||||
|
||||
|
||||
<h3 style={inventoryTitleStyle}>Inventory:</h3>
|
||||
|
||||
|
||||
<div style={getInventoryGridStyle()}>
|
||||
{Object.entries(inventory).map(([id, count]) => (
|
||||
count > 0 && (
|
||||
<div key={id} style={inventoryItemStyle}>
|
||||
<div style={itemIconStyle}>{getItemIcon(id)}</div>
|
||||
<h4 style={itemNameStyle}>{formatItemName(id)}</h4>
|
||||
<p>Quantity: {count}</p>
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{Object.entries(inventory).map(
|
||||
([id, count]) =>
|
||||
count > 0 && (
|
||||
<div key={id} style={inventoryItemStyle}>
|
||||
<div style={itemIconStyle}>{getItemIcon(id)}</div>
|
||||
<h4 style={itemNameStyle}>{formatItemName(id)}</h4>
|
||||
<p>Quantity: {count}</p>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default WarehouseComponent;
|
||||
export default WarehouseComponent
|
||||
|
|
|
|||
|
|
@ -1,50 +1,70 @@
|
|||
import { CropDefinitions } from '../types';
|
||||
import { CropDefinitions } from '../types'
|
||||
|
||||
export const INITIAL_CASH = 50;
|
||||
export const INITIAL_FIELD_SIZE = 4;
|
||||
export const COOLDOWN_DURATION = 2000; // 2 seconds in milliseconds
|
||||
export const TICK_INTERVAL = 12000; // 12 seconds in milliseconds
|
||||
export const INITIAL_CASH = 50
|
||||
export const INITIAL_FIELD_SIZE = 4
|
||||
export const COOLDOWN_DURATION = 2000 // 2 seconds in milliseconds
|
||||
export const TICK_INTERVAL = 12000 // 12 seconds in milliseconds
|
||||
export const GAME_SPEEDS = {
|
||||
NORMAL: 1, FAST: 5, SUPER_FAST: 10
|
||||
};
|
||||
export const INITIAL_GAME_SPEED = GAME_SPEEDS.NORMAL;
|
||||
NORMAL: 1,
|
||||
FAST: 5,
|
||||
SUPER_FAST: 10,
|
||||
}
|
||||
export const INITIAL_GAME_SPEED = GAME_SPEEDS.NORMAL
|
||||
|
||||
export const FIELD_UPGRADE_COSTS = [100, 1000, 10000, 100000];
|
||||
export const FIELD_UPGRADE_COSTS = [100, 1000, 10000, 100000]
|
||||
|
||||
export const CROPS: CropDefinitions = {
|
||||
celery: {
|
||||
id: 'celery',
|
||||
name: 'Celery',
|
||||
growthTicks: 36,
|
||||
waterPerTick: 1/20,
|
||||
waterPerTick: 1 / 20,
|
||||
yield: 1,
|
||||
yieldType: 'celery'
|
||||
yieldType: 'celery',
|
||||
},
|
||||
corn: {
|
||||
id: 'corn',
|
||||
name: 'Corn',
|
||||
growthTicks: 120,
|
||||
waterPerTick: 1/40,
|
||||
waterPerTick: 1 / 40,
|
||||
yield: 5,
|
||||
yieldType: 'corn'
|
||||
}
|
||||
};
|
||||
yieldType: 'corn',
|
||||
},
|
||||
}
|
||||
|
||||
export const INITIAL_INVENTORY = {
|
||||
'celery_seed': 8
|
||||
};
|
||||
celery_seed: 8,
|
||||
}
|
||||
|
||||
export interface MarketItem {
|
||||
id: string;
|
||||
name: string;
|
||||
emoji: string;
|
||||
buyPrice: number | null; // null means not buyable
|
||||
sellPrice: number | null; // null means not sellable
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
buyPrice: number | null // null means not buyable
|
||||
sellPrice: number | null // null means not sellable
|
||||
}
|
||||
|
||||
export const MARKET_ITEMS: Record<string, MarketItem> = {
|
||||
'celery_seed': { id: 'celery_seed', name: 'Celery Seeds', emoji: '🌰', buyPrice: 1, sellPrice: null },
|
||||
'corn_seed': { id: 'corn_seed', name: 'Corn Seeds', emoji: '🌰', buyPrice: 5, sellPrice: null },
|
||||
'celery': { id: 'celery', name: 'Celery', emoji: '🥬', buyPrice: null, sellPrice: 5 },
|
||||
'corn': { id: 'corn', name: 'Corn', emoji: '🌽', buyPrice: null, sellPrice: 8 },
|
||||
};
|
||||
celery_seed: {
|
||||
id: 'celery_seed',
|
||||
name: 'Celery Seeds',
|
||||
emoji: '🌰',
|
||||
buyPrice: 1,
|
||||
sellPrice: null,
|
||||
},
|
||||
corn_seed: {
|
||||
id: 'corn_seed',
|
||||
name: 'Corn Seeds',
|
||||
emoji: '🌰',
|
||||
buyPrice: 5,
|
||||
sellPrice: null,
|
||||
},
|
||||
celery: {
|
||||
id: 'celery',
|
||||
name: 'Celery',
|
||||
emoji: '🥬',
|
||||
buyPrice: null,
|
||||
sellPrice: 5,
|
||||
},
|
||||
corn: { id: 'corn', name: 'Corn', emoji: '🌽', buyPrice: null, sellPrice: 8 },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ import { useGameStore } from '../store/useGameStore'
|
|||
import { TICK_INTERVAL } from '../constants'
|
||||
|
||||
export const useGameTick = () => {
|
||||
const tick = useGameStore(state => state.tick)
|
||||
const gameSpeed = useGameStore(state => state.gameSpeed)
|
||||
const tick = useGameStore((state) => state.tick)
|
||||
const gameSpeed = useGameStore((state) => state.gameSpeed)
|
||||
const intervalRef = useRef<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (intervalRef.current !== null) {
|
||||
clearInterval(intervalRef.current)
|
||||
}
|
||||
|
||||
const adjustedInterval = TICK_INTERVAL / gameSpeed;
|
||||
|
||||
const adjustedInterval = TICK_INTERVAL / gameSpeed
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
tick()
|
||||
}, adjustedInterval)
|
||||
|
|
@ -23,4 +23,4 @@ export const useGameTick = () => {
|
|||
}
|
||||
}
|
||||
}, [tick, gameSpeed])
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ git /* Base styles */
|
|||
|
||||
/* Global styles */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
|
@ -33,4 +35,3 @@ body {
|
|||
* {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +1,46 @@
|
|||
import { create } from 'zustand';
|
||||
import { produce } from 'immer';
|
||||
import {
|
||||
INITIAL_CASH,
|
||||
INITIAL_FIELD_SIZE,
|
||||
FIELD_UPGRADE_COSTS,
|
||||
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';
|
||||
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
|
||||
}))
|
||||
);
|
||||
};
|
||||
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) => ({
|
||||
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,
|
||||
|
|
@ -46,37 +52,41 @@ export const useGameStore = create<GameState & {
|
|||
tickCount: 0,
|
||||
|
||||
assignCrop: (row, col, cropId) => {
|
||||
set(produce(state => {
|
||||
state.plots[row][col].intended = cropId;
|
||||
}));
|
||||
set(
|
||||
produce((state) => {
|
||||
state.plots[row][col].intended = cropId
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
plant: () => {
|
||||
const { plots, inventory, actionCooldown } = get();
|
||||
|
||||
const { plots, inventory, actionCooldown } = get()
|
||||
|
||||
if (actionCooldown > 0) {
|
||||
return;
|
||||
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`;
|
||||
|
||||
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;
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,163 +94,203 @@ export const useGameStore = create<GameState & {
|
|||
},
|
||||
|
||||
water: () => {
|
||||
const { plots, actionCooldown } = get();
|
||||
|
||||
const { plots, actionCooldown } = get()
|
||||
|
||||
if (actionCooldown > 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let driestRow = -1;
|
||||
let driestCol = -1;
|
||||
let lowestMoisture = 1;
|
||||
|
||||
|
||||
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 (
|
||||
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;
|
||||
}));
|
||||
set(
|
||||
produce((state) => {
|
||||
state.plots[driestRow][driestCol].moisture = 1
|
||||
state.actionCooldown = COOLDOWN_DURATION
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
harvest: () => {
|
||||
const { plots, actionCooldown } = get();
|
||||
|
||||
const { plots, actionCooldown } = get()
|
||||
|
||||
if (actionCooldown > 0) {
|
||||
return;
|
||||
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;
|
||||
|
||||
set(produce(state => {
|
||||
state.plots[row][col].current = undefined;
|
||||
state.inventory[yieldItem] = (state.inventory[yieldItem] || 0) + yieldAmount;
|
||||
state.actionCooldown = COOLDOWN_DURATION;
|
||||
}));
|
||||
return;
|
||||
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;
|
||||
}
|
||||
});
|
||||
});
|
||||
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
|
||||
}
|
||||
|
||||
state.tickCount = state.tickCount + 1;
|
||||
}));
|
||||
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);
|
||||
}));
|
||||
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;
|
||||
}));
|
||||
set(
|
||||
produce((state) => {
|
||||
state.gameSpeed = speed
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
setActionCooldown: (cooldown) => {
|
||||
set(produce(state => {
|
||||
state.actionCooldown = cooldown;
|
||||
}));
|
||||
set(
|
||||
produce((state) => {
|
||||
state.actionCooldown = cooldown
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
buyItem: (itemId) => {
|
||||
const { cash } = get();
|
||||
const item = MARKET_ITEMS[itemId];
|
||||
const { cash } = get()
|
||||
const item = MARKET_ITEMS[itemId]
|
||||
|
||||
if (!item || item.buyPrice === null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (cash < item.buyPrice) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
set(produce(state => {
|
||||
state.cash -= item.buyPrice;
|
||||
state.inventory[itemId] = (state.inventory[itemId] || 0) + 1;
|
||||
}));
|
||||
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];
|
||||
const { inventory } = get()
|
||||
const item = MARKET_ITEMS[itemId]
|
||||
|
||||
if (!item || item.sellPrice === null || !inventory[itemId] || inventory[itemId] <= 0) {
|
||||
return;
|
||||
if (
|
||||
!item ||
|
||||
item.sellPrice === null ||
|
||||
!inventory[itemId] ||
|
||||
inventory[itemId] <= 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
set(produce(state => {
|
||||
state.cash += item.sellPrice;
|
||||
state.inventory[itemId] -= 1;
|
||||
}));
|
||||
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);
|
||||
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);
|
||||
const savedState = loadGame(slot)
|
||||
if (savedState) {
|
||||
set(savedState);
|
||||
set(savedState)
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
export interface Crop {
|
||||
id: string;
|
||||
name: string;
|
||||
growthTicks: number;
|
||||
waterPerTick: number;
|
||||
yield: number;
|
||||
yieldType: string;
|
||||
id: string
|
||||
name: string
|
||||
growthTicks: number
|
||||
waterPerTick: number
|
||||
yield: number
|
||||
yieldType: string
|
||||
}
|
||||
|
||||
export interface CropDefinitions {
|
||||
[key: string]: Crop;
|
||||
[key: string]: Crop
|
||||
}
|
||||
|
||||
export interface PlotState {
|
||||
intended?: string;
|
||||
intended?: string
|
||||
current?: {
|
||||
cropId: string;
|
||||
progress: number;
|
||||
mature: boolean;
|
||||
};
|
||||
moisture: number;
|
||||
cropId: string
|
||||
progress: number
|
||||
mature: boolean
|
||||
}
|
||||
moisture: number
|
||||
}
|
||||
|
||||
export interface InventoryItem {
|
||||
id: string;
|
||||
count: number;
|
||||
id: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface GameState {
|
||||
cash: number;
|
||||
inventory: Record<string, number>;
|
||||
fieldSize: number;
|
||||
maxFieldSize: number;
|
||||
fieldUpgradeCosts: number[];
|
||||
plots: PlotState[][];
|
||||
gameSpeed: number;
|
||||
actionCooldown: number;
|
||||
cash: number
|
||||
inventory: Record<string, number>
|
||||
fieldSize: number
|
||||
maxFieldSize: number
|
||||
fieldUpgradeCosts: number[]
|
||||
plots: PlotState[][]
|
||||
gameSpeed: number
|
||||
actionCooldown: number
|
||||
}
|
||||
|
||||
export interface MarketTransaction {
|
||||
itemId: string;
|
||||
amount: number;
|
||||
type: 'buy' | 'sell';
|
||||
itemId: string
|
||||
amount: number
|
||||
type: 'buy' | 'sell'
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import { GameState } from '../types';
|
||||
import { GameState } from '../types'
|
||||
|
||||
const SAVE_SLOT_PREFIX = 'dionysian_idle_save_';
|
||||
const SAVE_SLOT_PREFIX = 'dionysian_idle_save_'
|
||||
|
||||
export const saveGame = (slot: number, state: GameState) => {
|
||||
try {
|
||||
const saveData = JSON.stringify(state);
|
||||
localStorage.setItem(`${SAVE_SLOT_PREFIX}${slot}`, saveData);
|
||||
return true;
|
||||
const saveData = JSON.stringify(state)
|
||||
localStorage.setItem(`${SAVE_SLOT_PREFIX}${slot}`, saveData)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to save game:', error);
|
||||
return false;
|
||||
console.error('Failed to save game:', error)
|
||||
return false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const loadGame = (slot: number): GameState | null => {
|
||||
try {
|
||||
const saveData = localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`);
|
||||
if (!saveData) return null;
|
||||
return JSON.parse(saveData);
|
||||
const saveData = localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`)
|
||||
if (!saveData) return null
|
||||
return JSON.parse(saveData)
|
||||
} catch (error) {
|
||||
console.error('Failed to load game:', error);
|
||||
return null;
|
||||
console.error('Failed to load game:', error)
|
||||
return null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const hasSaveInSlot = (slot: number): boolean => {
|
||||
return !!localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`);
|
||||
};
|
||||
return !!localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue