diff --git a/package-lock.json b/package-lock.json index 4fdb1ee..67f8639 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "idle-farm-game", + "name": "dionysian-idle", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "idle-farm-game", + "name": "dionysian-idle", "version": "0.0.0", "dependencies": { "@hookform/resolvers": "^5.0.1", @@ -70,6 +70,7 @@ "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.4", + "@wyw-in-js/vite": "^0.6.0", "autoprefixer": "^10.4.20", "eslint": "^9.15.0", "eslint-plugin-react-hooks": "^5.0.0", @@ -4863,6 +4864,72 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@wyw-in-js/transform": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@wyw-in-js/transform/-/transform-0.6.0.tgz", + "integrity": "sha512-/Rxw8e3KcmSjnqWs/6flmpcNTa4jLw8NViIyNwqem498AfSWXrPnYf3RyjWed9zDVL72q7Xjei6TcRqMupl3Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", + "@wyw-in-js/processor-utils": "0.6.0", + "@wyw-in-js/shared": "0.6.0", + "babel-merge": "^3.0.0", + "cosmiconfig": "^8.0.0", + "happy-dom": "^15.11.0", + "source-map": "^0.7.4", + "stylis": "^4.3.0", + "ts-invariant": "^0.10.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@wyw-in-js/transform/node_modules/happy-dom": { + "version": "15.11.7", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.11.7.tgz", + "integrity": "sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0", + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@wyw-in-js/transform/node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@wyw-in-js/vite": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@wyw-in-js/vite/-/vite-0.6.0.tgz", + "integrity": "sha512-nGeH/dFrjecHRJfxd5/71cJu4V2YWakUmnEoAUpS0iTnVVC4GWjKqt1dLtsJvtKZIm2OeNoTzzdFrP4bxyXYxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wyw-in-js/shared": "0.6.0", + "@wyw-in-js/transform": "0.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "vite": ">=3.2.7" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", diff --git a/package.json b/package.json index 5b47a32..5ea63b7 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.4", + "@wyw-in-js/vite": "^0.6.0", "autoprefixer": "^10.4.20", "eslint": "^9.15.0", "eslint-plugin-react-hooks": "^5.0.0", diff --git a/src/App.tsx b/src/App.tsx index 1995d8c..068a1ac 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/CustomTab import { useGameTick } from './hooks/useGameTick'; import Field from './components/Field'; import Warehouse from './components/Warehouse'; +import { ActionCooldown } from './components/ActionCooldown'; const appContainerStyle: React.CSSProperties = { maxWidth: '1200px', @@ -10,21 +11,6 @@ const appContainerStyle: React.CSSProperties = { padding: '2rem' }; -const headerStyle: React.CSSProperties = { - textAlign: 'center', - marginBottom: '2rem' -}; - -const titleStyle: React.CSSProperties = { - fontSize: '2.25rem', - fontWeight: 'bold', - marginBottom: '0.5rem' -}; - -const subtitleStyle: React.CSSProperties = { - color: '#6b7280' -}; - const tabsListStyles: React.CSSProperties = { display: 'grid', width: '100%', @@ -35,26 +21,24 @@ function App() { useGameTick(); return ( -
-
-

Idle Farm Game

-

Grow crops, harvest, and expand your farm!

-
- - - - Fields - Warehouse - Market - Temple - - - - - - - - +
+ +
+ + + Fields + Warehouse + Market + Temple + + + + + + + + +
); } diff --git a/src/components/ActionCooldown.tsx b/src/components/ActionCooldown.tsx new file mode 100644 index 0000000..4dbd629 --- /dev/null +++ b/src/components/ActionCooldown.tsx @@ -0,0 +1,59 @@ +import { useEffect, useState } from 'react'; +import { styled } from '@linaria/react'; + +import { useGameStore } from '../store/useGameStore'; + +const CooldownContainer = styled.div` + position: relative; + height: 1rem; + width: 100%; + background-color: #e5e7eb; + overflow: hidden; +`; + +const ProgressBar = styled.div<{ progress: string }>` + height: 100%; + background-color: #3b82f6; + width: ${props => props.progress}; +`; + +export const ActionCooldown = () => { + const { actionCooldown, setActionCooldown } = useGameStore(); + const [progress, setProgress] = useState(0); + + useEffect(() => { + if (actionCooldown <= 0) { + setProgress(0); + return; + } + + const startTime = Date.now(); + const duration = actionCooldown; + + const updateProgress = () => { + const elapsed = Date.now() - startTime; + const newProgress = Math.min(100, (elapsed / duration) * 100); + + if (newProgress >= 100) { + setProgress(0); + setActionCooldown(0); + return; + } + + setProgress(newProgress); + requestAnimationFrame(updateProgress); + }; + + const animationFrame = requestAnimationFrame(updateProgress); + + return () => { + cancelAnimationFrame(animationFrame); + }; + }, [actionCooldown, setActionCooldown]); + + return ( + + + + ); +}; diff --git a/src/components/Field.tsx b/src/components/Field.tsx index ddf8955..171b925 100644 --- a/src/components/Field.tsx +++ b/src/components/Field.tsx @@ -148,32 +148,12 @@ const FieldComponent: React.FC = () => { water, harvest, assignCrop, - lastPlantTime, - lastWaterTime, - lastHarvestTime, gameSpeed, - setGameSpeed + setGameSpeed, + actionCooldown } = useGameStore(); const [selectedCrop, setSelectedCrop] = useState(null); - const [cooldowns, setCooldowns] = useState({ - plant: false, - water: false, - harvest: false - }); - - useEffect(() => { - const interval = setInterval(() => { - const now = Date.now(); - setCooldowns({ - plant: now - lastPlantTime < 2000, - water: now - lastWaterTime < 2000, - harvest: now - lastHarvestTime < 2000 - }); - }, 100); - - return () => clearInterval(interval); - }, [lastPlantTime, lastWaterTime, lastHarvestTime]); const handlePlotClick = (row: number, col: number) => { if (selectedCrop) { @@ -187,6 +167,8 @@ const FieldComponent: React.FC = () => { return "#92400e"; }; + const availableSpeeds = [1, 2, 4, 8, 16, 32, 64] + return (

Fields

@@ -194,24 +176,15 @@ const FieldComponent: React.FC = () => {

Game Speed:

- - - + {availableSpeeds.map(speed => ( + + ))}
@@ -230,23 +203,23 @@ const FieldComponent: React.FC = () => {
diff --git a/src/hooks/useGameTick.ts b/src/hooks/useGameTick.ts index 2d3dfd6..5b7f385 100644 --- a/src/hooks/useGameTick.ts +++ b/src/hooks/useGameTick.ts @@ -1,26 +1,26 @@ -import { useEffect, useRef } from 'react'; -import { useGameStore } from '../store/useGameStore'; -import { TICK_INTERVAL } from '../constants'; +import { useEffect, useRef } from 'react' +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 intervalRef = useRef(null); + const tick = useGameStore(state => state.tick) + const gameSpeed = useGameStore(state => state.gameSpeed) + const intervalRef = useRef(null) useEffect(() => { if (intervalRef.current !== null) { - clearInterval(intervalRef.current); + clearInterval(intervalRef.current) } const adjustedInterval = TICK_INTERVAL / gameSpeed; intervalRef.current = window.setInterval(() => { - tick(); - }, adjustedInterval); + tick() + }, adjustedInterval) return () => { if (intervalRef.current !== null) { - clearInterval(intervalRef.current); + clearInterval(intervalRef.current) } - }; - }, [tick, gameSpeed]); // Add gameSpeed as dependency + } + }, [tick, gameSpeed]) }; diff --git a/src/store/useGameStore.ts b/src/store/useGameStore.ts index 587951f..1144e19 100644 --- a/src/store/useGameStore.ts +++ b/src/store/useGameStore.ts @@ -6,7 +6,8 @@ import { FIELD_UPGRADE_COSTS, INITIAL_INVENTORY, CROPS, - INITIAL_GAME_SPEED + INITIAL_GAME_SPEED, + COOLDOWN_DURATION } from '../constants'; import { GameState, PlotState } from '../types'; @@ -26,6 +27,7 @@ export const useGameStore = create void; upgradeField: () => void; setGameSpeed: (speed: number) => void; + setActionCooldown: (cooldown: number) => void; }>((set, get) => ({ cash: INITIAL_CASH, inventory: INITIAL_INVENTORY, @@ -33,10 +35,8 @@ export const useGameStore = create { set(produce(state => { @@ -45,10 +45,9 @@ export const useGameStore = create { - const now = Date.now(); - const { lastPlantTime, plots, inventory } = get(); + const { plots, inventory, actionCooldown } = get(); - if (now - lastPlantTime < 2000) { + if (actionCooldown > 0) { return; } @@ -68,7 +67,7 @@ export const useGameStore = create { - const now = Date.now(); - const { lastWaterTime, plots } = get(); + const { plots, actionCooldown } = get(); - if (now - lastWaterTime < 2000) { + if (actionCooldown > 0) { return; } @@ -102,16 +100,15 @@ export const useGameStore = create= 0 && driestCol >= 0) { set(produce(state => { state.plots[driestRow][driestCol].moisture = 1; - state.lastWaterTime = now; + state.actionCooldown = COOLDOWN_DURATION; })); } }, harvest: () => { - const now = Date.now(); - const { lastHarvestTime, plots } = get(); + const { plots, actionCooldown } = get(); - if (now - lastHarvestTime < 2000) { + if (actionCooldown > 0) { return; } @@ -127,7 +124,7 @@ export const useGameStore = create { state.plots[row][col].current = undefined; state.inventory[yieldItem] = (state.inventory[yieldItem] || 0) + yieldAmount; - state.lastHarvestTime = now; + state.actionCooldown = COOLDOWN_DURATION; })); return; } @@ -136,7 +133,15 @@ export const useGameStore = create { + const { actionCooldown } = get(); + set(produce(state => { + // Update cooldown + if (state.actionCooldown > 0) { + state.actionCooldown = Math.max(0, state.actionCooldown - (COOLDOWN_DURATION / 20)); + } + + // Update plots state.plots.forEach((row: PlotState[], rowIndex: number) => { row.forEach((plot: PlotState, colIndex: number) => { if (!plot.current || plot.current.mature) { @@ -184,5 +189,11 @@ export const useGameStore = create { state.gameSpeed = speed; })); + }, + + setActionCooldown: (cooldown) => { + set(produce(state => { + state.actionCooldown = cooldown; + })); } })); diff --git a/src/types/index.ts b/src/types/index.ts index 15424de..3e2efe5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -33,8 +33,6 @@ export interface GameState { maxFieldSize: number; fieldUpgradeCosts: number[]; plots: PlotState[][]; - lastPlantTime: number; - lastWaterTime: number; - lastHarvestTime: number; gameSpeed: number; + actionCooldown: number; } diff --git a/vite.config.ts b/vite.config.ts index 0f2a00a..55ea0a2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,12 +1,12 @@ import path from "path" -import react from "@vitejs/plugin-react" import { defineConfig } from "vite" -import linaria from "@linaria/vite" +import react from "@vitejs/plugin-react" +import wyw from '@wyw-in-js/vite' export default defineConfig({ plugins: [ react(), - linaria({ + wyw({ include: ['**/*.{ts,tsx}'], babelOptions: { presets: ['@babel/preset-typescript', '@babel/preset-react'],