clicker-clicker2/src/store/gameStore.ts

534 lines
16 KiB
TypeScript

import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { playPurchaseSound, playLevelUpSound, playUpgradeSound } from '../utils/soundUtils'
// Building information interface
export interface BuildingInfo {
id?: string
cost: number
levelRequirement: number
title: string
description: string
production: {
points?: number
techParts?: number
}
}
interface GameState {
// Resources
points: number
// Buildings
mouseFarms: number
keyboardFactories: number
monitorDisplays: number
officeSpace: number
serverRooms: number
dataCenters: number
dataCities: number
dataCountries: number
dataContinents: number
dataWorlds: number
dataMoons: number
dataSolarSystems: number
dataGalaxies: number
dataUniverses: number
dataGods: number
// Building Levels
mouseFarmsLevel: number
keyboardFactoriesLevel: number
monitorDisplaysLevel: number
officeSpaceLevel: number
serverRoomsLevel: number
dataCentersLevel: number
dataCitiesLevel: number
dataCountriesLevel: number
dataContinentsLevel: number
dataWorldsLevel: number
dataMoonsLevel: number
dataSolarSystemsLevel: number
dataGalaxiesLevel: number
dataUniversesLevel: number
dataGodsLevel: number
// Upgrades
autoClickers: number
clickPower: number
clickPowerUpgrades: number
// Production rates
pointsPerSecond: number
// Player level
playerLevel: number
// Actions
click: () => void
buyBuilding: (buildingType: BuildingType) => void
buyUpgrade: (upgradeType: UpgradeType) => void
resetGame: () => void
tick: () => void
upgradeBuilding: (buildingType: BuildingType) => void
getClickPowerUpgradeCost: () => number
canUpgradeBuilding: (buildingType: BuildingType) => boolean
getBuildingUpgradeCost: (buildingType: BuildingType) => number
// New getter for available buildings
getAvailableBuildings: () => BuildingInfo[]
}
// Define tiers based on level requirements for buildings
type BuildingTier = 'early' | 'mid' | 'late';
// Building information with costs and requirements
export const BUILDING_INFO: Record<string, Omit<BuildingInfo, 'id'>> = {
mouseFarms: {
title: 'Mouse Farm',
description: 'A basic facility that produces computer mice. Each mouse farm generates points automatically.',
production: { points: 0.1 },
cost: 10,
levelRequirement: 1
},
keyboardFactories: {
title: 'Keyboard Factory',
description: 'Manufactures mechanical keyboards and generates tech parts. Essential for advanced upgrades.',
production: { points: 0.2 },
cost: 50,
levelRequirement: 1
},
monitorDisplays: {
title: 'Monitor Display',
description: 'High-resolution displays that generate both points and tech parts. A balanced production facility.',
production: { points: 0.2 },
cost: 100,
levelRequirement: 1
},
serverRooms: {
title: 'Server Room',
description: 'A powerful facility that generates significant amounts of both resources. Requires proper cooling.',
production: { points: 5 },
cost: 1000,
levelRequirement: 1
},
dataCenters: {
title: 'Data Center',
description: 'The ultimate production facility. Generates massive amounts of resources but requires significant investment.',
production: { points: 25 },
cost: 5000,
levelRequirement: 5
},
dataCities: {
title: 'Data City',
description: 'A massive network of data centers spanning an entire city. Requires level 10 to unlock.',
production: { points: 100 },
cost: 25000,
levelRequirement: 10
},
dataCountries: {
title: 'Data Country',
description: 'A country-wide network of data cities. Requires level 20 to unlock.',
production: { points: 500 },
cost: 100000,
levelRequirement: 20
},
dataContinents: {
title: 'Data Continent',
description: 'A continent-spanning network of data countries. Requires level 30 to unlock.',
production: { points: 2500 },
cost: 500000,
levelRequirement: 30
},
dataWorlds: {
title: 'Data World',
description: 'A world-wide network of data continents. Requires level 40 to unlock.',
production: { points: 10000 },
cost: 2000000,
levelRequirement: 40
},
dataMoons: {
title: 'Data Moon',
description: 'A moon-sized data processing facility. Requires level 50 to unlock.',
production: { points: 50000 },
cost: 10000000,
levelRequirement: 50
},
dataSolarSystems: {
title: 'Data Solar System',
description: 'A solar system-wide network of data moons. Requires level 60 to unlock.',
production: { points: 250000 },
cost: 50000000,
levelRequirement: 60
},
dataGalaxies: {
title: 'Data Galaxy',
description: 'A galaxy-spanning network of data solar systems. Requires level 70 to unlock.',
production: { points: 1000000 },
cost: 200000000,
levelRequirement: 70
},
dataUniverses: {
title: 'Data Universe',
description: 'A universe-wide network of data galaxies. Requires level 80 to unlock.',
production: { points: 5000000 },
cost: 1000000000,
levelRequirement: 80
},
dataGods: {
title: 'Data God',
description: 'The ultimate data processing entity. Requires level 90 to unlock.',
production: { points: 25000000 },
cost: 5000000000,
levelRequirement: 90
}
}
const initialState = {
points: 0,
mouseFarms: 0,
keyboardFactories: 0,
monitorDisplays: 0,
officeSpace: 0,
serverRooms: 0,
dataCenters: 0,
dataCities: 0,
dataCountries: 0,
dataContinents: 0,
dataWorlds: 0,
dataMoons: 0,
dataSolarSystems: 0,
dataGalaxies: 0,
dataUniverses: 0,
dataGods: 0,
mouseFarmsLevel: 1,
keyboardFactoriesLevel: 1,
monitorDisplaysLevel: 1,
officeSpaceLevel: 1,
serverRoomsLevel: 1,
dataCentersLevel: 1,
dataCitiesLevel: 1,
dataCountriesLevel: 1,
dataContinentsLevel: 1,
dataWorldsLevel: 1,
dataMoonsLevel: 1,
dataSolarSystemsLevel: 1,
dataGalaxiesLevel: 1,
dataUniversesLevel: 1,
dataGodsLevel: 1,
autoClickers: 0,
clickPower: 1,
clickPowerUpgrades: 0,
pointsPerSecond: 0,
playerLevel: 1,
}
// Production rates per building
const PRODUCTION_RATES = {
mouseFarms: { points: 0.1 },
keyboardFactories: { points: 0.2 },
monitorDisplays: { points: 0.2 },
officeSpace: { points: 1 },
serverRooms: { points: 5 },
dataCenters: { points: 25 },
dataCities: { points: 100 },
dataCountries: { points: 500 },
dataContinents: { points: 2500 },
dataWorlds: { points: 10000 },
dataMoons: { points: 50000 },
dataSolarSystems: { points: 250000 },
dataGalaxies: { points: 1000000 },
dataUniverses: { points: 5000000 },
dataGods: { points: 25000000 },
}
// Building costs
const BUILDING_COSTS = {
mouseFarms: 10,
keyboardFactories: 50,
monitorDisplays: 100,
officeSpace: 500,
serverRooms: 1000,
dataCenters: 5000,
dataCities: 25000,
dataCountries: 100000,
dataContinents: 500000,
dataWorlds: 2000000,
dataMoons: 10000000,
dataSolarSystems: 50000000,
dataGalaxies: 200000000,
dataUniverses: 1000000000,
dataGods: 5000000000,
}
// Building level requirements
const BUILDING_LEVEL_REQUIREMENTS = {
mouseFarms: 1,
keyboardFactories: 1,
monitorDisplays: 1,
officeSpace: 1,
serverRooms: 1,
dataCenters: 5,
dataCities: 10,
dataCountries: 20,
dataContinents: 30,
dataWorlds: 40,
dataMoons: 50,
dataSolarSystems: 60,
dataGalaxies: 70,
dataUniverses: 80,
dataGods: 90,
}
// Helper function to determine building tier based on level requirement
const getBuildingTier = (buildingType: BuildingType): BuildingTier => {
const levelRequirement = BUILDING_LEVEL_REQUIREMENTS[buildingType];
if (levelRequirement <= 5) {
return 'early';
} else if (levelRequirement <= 50) {
return 'mid';
} else {
return 'late';
}
}
// Get tier-based upgrade cost multiplier
const getTierMultiplier = (tier: BuildingTier): number => {
switch (tier) {
case 'early': return 1.75; // Steeper curve for early buildings
case 'mid': return 1.6; // Medium curve for mid-game
case 'late': return 1.5; // Unchanged for late game
}
}
// Get tier-based production scaling power
const getTierScalingPower = (tier: BuildingTier): number => {
switch (tier) {
case 'early': return 0.9; // Sub-linear scaling for early buildings
case 'mid': return 0.95; // Slightly sub-linear for mid-game
case 'late': return 1; // Linear scaling for late game
}
}
// Calculate upgrade cost based on current level and building tier
const calculateUpgradeCost = (buildingType: BuildingType, currentLevel: number): number => {
const baseCost = BUILDING_COSTS[buildingType]
const tier = getBuildingTier(buildingType)
const multiplier = getTierMultiplier(tier)
// Formula: baseCost * multiplier^(currentLevel - 1)
// This ensures costs scale exponentially with level
const cost = Math.floor(baseCost * Math.pow(multiplier, currentLevel - 1))
// For debugging
console.log(`Upgrade cost for ${buildingType} at level ${currentLevel}: ${cost} points, tier: ${tier}, multiplier: ${multiplier}`)
return cost
}
// Calculate building production based on level, count, and tier
const calculateProduction = (buildingType: BuildingType, level: number, count: number): number => {
const baseRate = PRODUCTION_RATES[buildingType].points || 0
const tier = getBuildingTier(buildingType)
const scalingPower = getTierScalingPower(tier)
// Apply tier-based scaling to level
const effectiveLevel = Math.pow(level, scalingPower)
return baseRate * effectiveLevel * count
}
// Helper function to calculate click power upgrade cost
const calculateClickPowerUpgradeCost = (upgrades: number): number => {
return Math.floor(20 * Math.pow(1.5, upgrades));
}
type BuildingType = keyof typeof BUILDING_COSTS
type UpgradeType = 'autoClickers' | 'clickPower'
// Helper function to get available buildings
const getAvailableBuildings = (playerLevel: number): BuildingInfo[] => {
return Object.entries(BUILDING_INFO)
.filter(([_, building]) => building.levelRequirement <= playerLevel)
.map(([id, building]) => ({
...building,
id
}))
.sort((a, b) => a.cost - b.cost)
}
export const useGameStore = create<GameState>()(
persist(
(set, get) => ({
...initialState,
click: () => {
const { clickPower, autoClickers } = get()
const pointsPerClick = clickPower * (1 + autoClickers * 0.1)
set((state) => ({
points: state.points + pointsPerClick,
}))
},
buyBuilding: (buildingType: BuildingType) => {
const state = get()
const cost = BUILDING_COSTS[buildingType]
// Get building info for level requirement check
const info = BUILDING_INFO[buildingType];
// Check if player meets level requirement
if (state.playerLevel < info.levelRequirement) {
return;
}
if (state.points >= cost) {
// Play purchase sound
playPurchaseSound()
set((state) => {
const newCount = state[buildingType] + 1
const level = state[`${buildingType}Level` as keyof GameState] as number
// Use the new calculateProduction function
const newProduction = calculateProduction(buildingType, level, newCount)
const oldProduction = calculateProduction(buildingType, level, newCount - 1)
const productionDifference = newProduction - oldProduction
return {
points: state.points - cost,
[buildingType]: newCount,
pointsPerSecond: state.pointsPerSecond + productionDifference,
}
})
}
},
buyUpgrade: (type: UpgradeType) => {
const state = get();
if (type === 'clickPower') {
const cost = get().getClickPowerUpgradeCost();
// Check if we have enough points
if (state.points < cost) return;
// Apply the upgrade
set({
points: state.points - cost,
clickPower: state.clickPower + 1,
clickPowerUpgrades: state.clickPowerUpgrades + 1
});
// Play upgrade sound instead of purchase sound
playUpgradeSound();
}
},
getClickPowerUpgradeCost: () => {
const { clickPowerUpgrades } = get()
return calculateClickPowerUpgradeCost(clickPowerUpgrades)
},
getBuildingUpgradeCost: (buildingType: BuildingType) => {
const state = get()
const currentLevel = state[`${buildingType}Level` as keyof GameState] as number
return calculateUpgradeCost(buildingType, currentLevel)
},
canUpgradeBuilding: (buildingType: BuildingType) => {
const state = get()
// Check if player owns at least one building
if (state[buildingType] <= 0) {
return false
}
// Get current level and calculate upgrade cost
const currentLevel = state[`${buildingType}Level` as keyof GameState] as number
const cost = calculateUpgradeCost(buildingType, currentLevel)
// Check if player has enough points
return state.points >= cost
},
upgradeBuilding: (buildingType: BuildingType) => {
const state = get()
const currentLevel = state[`${buildingType}Level` as keyof GameState] as number
const cost = calculateUpgradeCost(buildingType, currentLevel)
const ownedCount = state[buildingType]
console.log(`Attempting to upgrade ${buildingType}:
- Current level: ${currentLevel}
- Upgrade cost: ${cost}
- Player points: ${state.points}
- Buildings owned: ${ownedCount}
- Can afford: ${state.points >= cost}
- Owns building: ${ownedCount > 0}
`)
if (state.points >= cost && state[buildingType] > 0) {
// Play upgrade sound instead of purchase sound
playUpgradeSound()
set((state) => {
const newLevel = (state[`${buildingType}Level` as keyof GameState] as number) + 1
const count = state[buildingType]
// Use the new calculateProduction function for both old and new production
const oldProduction = calculateProduction(buildingType, currentLevel, count)
const newProduction = calculateProduction(buildingType, newLevel, count)
const productionDifference = newProduction - oldProduction
console.log(`Upgrade successful! New level: ${newLevel}, Production increase: +${productionDifference}/s`)
return {
points: state.points - cost,
[`${buildingType}Level`]: newLevel,
pointsPerSecond: state.pointsPerSecond + productionDifference,
}
})
} else {
// Log why upgrade failed
console.warn(`Upgrade failed! ${state.points < cost ? 'Not enough points' : 'No buildings owned'}`)
}
},
resetGame: () => {
set(initialState)
},
tick: () => {
const state = get()
// Add production from buildings
if (state.pointsPerSecond > 0) {
set((state) => ({
points: state.points + state.pointsPerSecond / 10, // 10 ticks per second
}))
}
// Update player level based on points per second
const currentLevel = state.playerLevel
const newLevel = Math.max(1, Math.floor(Math.log10(state.pointsPerSecond) + 1))
if (newLevel !== currentLevel) {
// If level has increased, play the level up sound
if (newLevel > currentLevel) {
playLevelUpSound()
}
set({ playerLevel: newLevel })
}
},
getAvailableBuildings: () => {
const { playerLevel } = get()
return getAvailableBuildings(playerLevel)
},
}),
{
name: 'clicker-game',
}
)
)