From 83fd159b0feb3e271a0d011be0accb06fa4027e9 Mon Sep 17 00:00:00 2001 From: ac15cr Date: Tue, 13 May 2025 03:20:34 -0400 Subject: [PATCH] game semi-working --- src/game.tsx | 178 ++++++++++++++++++++++++--------------------------- 1 file changed, 84 insertions(+), 94 deletions(-) diff --git a/src/game.tsx b/src/game.tsx index fbdf38c..a801098 100644 --- a/src/game.tsx +++ b/src/game.tsx @@ -1,24 +1,22 @@ import React, { useState, useEffect, createContext, useRef } from 'react'; -// Game context definition const GameContext = createContext(); -// Game state provider const GameProvider = ({ children }) => { const [gameState, setGameState] = useState({ screen: 'title', // 'title', 'map', 'location', 'dialogue', 'cooking', 'notebook', 'festival' currentRegion: null, - unlockedRegions: ['coastal'], // Start with just the coastal region unlocked + unlockedRegions: ['coastal'], // coastal region unlocked at start reputation: { coastal: 0, southwest: 0, midwest: 0 }, - recipes: [], // Unlocked recipes - dialogueProgress: {}, // Track NPC conversations - currentNpc: null, // Current NPC in dialogue - currentDialogueId: null, // Current dialogue id - currentRecipe: null, // Current recipe being cooked + recipes: [], // unlocked recipes + dialogueProgress: {}, // NPC conversations + currentNpc: null, // current NPC in dialogue + currentDialogueId: null, // current dialogue id + currentRecipe: null, // current recipe being cooked }); const updateGameState = (newState) => { @@ -32,16 +30,16 @@ const GameProvider = ({ children }) => { ); }; -// Regional data structure with descriptions, visuals, and unique characteristics +// regional data structure const regionData = { coastal: { name: "Coastal Town", fullName: "Bayshore Harbor", description: "A charming New England coastal town with a rich maritime history. The local cuisine focuses on fresh seafood prepared with simple techniques that highlight natural flavors.", - background: "coastal-background.jpg", // Placeholder + background: "coastal-background.jpg", backgroundColor: "bg-blue-100", accent: "blue", - unlockRequirement: null, // Starting region + unlockRequirement: null, // starting region specialFeature: "Fresh seafood market with daily catches", culturalNote: "The town's annual Seafood Festival attracts visitors from across the region, celebrating centuries of fishing traditions that have shaped the local identity." }, @@ -49,7 +47,7 @@ const regionData = { name: "Southwest City", fullName: "Mesa Verde", description: "A vibrant desert city where Mexican, Native American, and frontier traditions blend. The cuisine features bold flavors, chiles, and slow-cooking techniques.", - background: "southwest-background.jpg", // Placeholder + background: "southwest-background.jpg", backgroundColor: "bg-orange-100", accent: "orange", unlockRequirement: { region: "coastal", reputation: 3 }, @@ -60,7 +58,7 @@ const regionData = { name: "Midwest Suburb", fullName: "Heartland Hills", description: "A friendly Midwestern community where comfort food reigns supreme. Hearty casseroles, farm-fresh ingredients, and family recipes passed through generations define the local cuisine.", - background: "midwest-background.jpg", // Placeholder + background: "midwest-background.jpg", backgroundColor: "bg-green-100", accent: "green", unlockRequirement: { region: "southwest", reputation: 3 }, @@ -69,7 +67,7 @@ const regionData = { } }; -// Recipe data with cultural context +// recipe data const recipeData = { new_england_clam_chowder: { name: "New England Clam Chowder", @@ -154,7 +152,7 @@ const recipeData = { } }; -// Dialogue data structure +// dialogue data structure const dialogueData = { coastal: { chef: { @@ -210,7 +208,7 @@ const dialogueData = { { id: "increase-reputation", text: "You know, it's refreshing to meet someone who genuinely cares about food traditions. Most tourists just want a quick seafood fix. If you're serious about learning our coastal cuisine, I'd be happy to share more insights and techniques.", - reputation: 1, // This dialogue increases reputation + reputation: 1, options: [ { text: "I'd love to learn how to make the chowder.", nextId: "teach" }, { text: "Thank you! I'll come back later.", nextId: "end" } @@ -219,7 +217,7 @@ const dialogueData = { { id: "teach", text: "I'd be delighted to teach you our chowder recipe! Let me add it to your notebook. The key is patience - let those flavors develop slowly. When you're ready to try cooking it yourself, just let me know and we can get started right away.", - recipe: "new_england_clam_chowder", // This dialogue unlocks a recipe + recipe: "new_england_clam_chowder", options: [ { text: "Let's start cooking now!", nextId: "start_cooking" }, { text: "I'll check my notebook first.", nextId: "end" } @@ -228,7 +226,7 @@ const dialogueData = { { id: "start_cooking", text: "Excellent! Let's head to the kitchen and get started on that chowder. I'll guide you through each step.", - cooking: true, // This triggers the cooking minigame + cooking: true, options: [ { text: "Let's do it!", nextId: "end" } ] @@ -374,7 +372,6 @@ const dialogueData = { { text: "I'd like to learn a recipe.", nextId: "teach" } ] }, - // More dialogue options would be added here ] }, historian: { @@ -391,7 +388,6 @@ const dialogueData = { { text: "Nice to meet you. I'll explore more later.", nextId: "end" } ] }, - // More dialogue options would be added here ] }, vendor: { @@ -408,7 +404,6 @@ const dialogueData = { { text: "Just browsing, thanks.", nextId: "end" } ] }, - // More dialogue options would be added here ] } }, @@ -427,7 +422,6 @@ const dialogueData = { { text: "I'd like to learn how to make one.", nextId: "teach" } ] }, - // More dialogue options would be added here ] }, historian: { @@ -444,7 +438,6 @@ const dialogueData = { { text: "Nice to meet you. I'll chat more later.", nextId: "end" } ] }, - // More dialogue options would be added here ] }, vendor: { @@ -461,13 +454,12 @@ const dialogueData = { { text: "Just looking around for now, thanks.", nextId: "end" } ] }, - // More dialogue options would be added here ] } } }; -// Cooking challenges for each recipe +// cooking challenges const cookingChallenges = { new_england_clam_chowder: [ { action: "chop", key: "C", description: "Chop the vegetables", duration: 4000 }, @@ -492,7 +484,7 @@ const cookingChallenges = { ] }; -// Title Screen Component +// title screen component const TitleScreen = () => { const { updateGameState } = React.useContext(GameContext); @@ -535,19 +527,19 @@ const TitleScreen = () => { ); }; -// Map Screen Component +// map screen component const MapScreen = () => { const { gameState, updateGameState } = React.useContext(GameContext); const [selectedRegion, setSelectedRegion] = useState(null); - // Handle region selection + // handle region selection const handleRegionSelect = (regionId) => { if (gameState.unlockedRegions.includes(regionId)) { setSelectedRegion(regionId); } }; - // Handle travel to region + // handle travel to region const handleTravelToRegion = () => { if (selectedRegion) { updateGameState({ @@ -558,7 +550,7 @@ const MapScreen = () => { } }; - // Get unlock status text + // get unlock status text const getUnlockStatus = (regionId) => { const region = regionData[regionId]; @@ -647,7 +639,7 @@ const MapScreen = () => { {/* Region background image placeholder */}
- [Image of {regionData[selectedRegion].fullName}] + [{regionData[selectedRegion].fullName} Image]
@@ -741,16 +733,16 @@ const MapScreen = () => { ); }; -// Location Screen Component +// location screen component const LocationScreen = () => { const { gameState, updateGameState } = React.useContext(GameContext); const region = regionData[gameState.currentRegion]; - // Handle NPC click + // handle NPC click const handleNpcClick = (npcType) => { const npcData = dialogueData[gameState.currentRegion][npcType]; - // Check if we have dialogueProgress for this NPC, if not start with intro + // check if we have dialogueProgress for this NPC, if not start with intro const dialogueId = gameState.dialogueProgress[`${gameState.currentRegion}-${npcType}`] || 'intro'; updateGameState({ @@ -855,16 +847,16 @@ const LocationScreen = () => { ); }; -// Dialogue Screen Component +// dialogue screen component const DialogueScreen = () => { const { gameState, updateGameState } = React.useContext(GameContext); - // Get current NPC and dialogue data + // get current NPC and dialogue data const npcData = dialogueData[gameState.currentRegion][gameState.currentNpc]; const currentDialogue = npcData.dialogues.find(d => d.id === gameState.currentDialogueId); const handleOptionClick = (option) => { - // If there's no next dialogue, return to location screen + // no next dialogue, return to location screen if (!option.nextId) { updateGameState({ screen: 'location', @@ -874,32 +866,32 @@ const DialogueScreen = () => { return; } - // Find the next dialogue + // find the next dialogue const nextDialogue = npcData.dialogues.find(d => d.id === option.nextId); - // Update dialogue progress for this NPC + // update dialogue progress for this NPC const newDialogueProgress = { ...gameState.dialogueProgress, [`${gameState.currentRegion}-${gameState.currentNpc}`]: option.nextId }; - // Check if the next dialogue awards reputation + // check if the next dialogue awards reputation let newReputation = { ...gameState.reputation }; if (nextDialogue.reputation) { newReputation[gameState.currentRegion] += nextDialogue.reputation; - // Cap reputation at 5 + // cap reputation at 5 if (newReputation[gameState.currentRegion] > 5) { newReputation[gameState.currentRegion] = 5; } } - // Check if the next dialogue unlocks a recipe + // check if the next dialogue unlocks recipe let newRecipes = [...gameState.recipes]; if (nextDialogue.recipe && !newRecipes.includes(nextDialogue.recipe)) { newRecipes.push(nextDialogue.recipe); } - // Update game state + // update game state updateGameState({ currentDialogueId: option.nextId, dialogueProgress: newDialogueProgress, @@ -907,7 +899,7 @@ const DialogueScreen = () => { recipes: newRecipes }); - // If dialogue triggers cooking, go to cooking screen + // if dialogue triggers cooking, go to cooking screen if (nextDialogue.cooking) { setTimeout(() => { updateGameState({ @@ -915,7 +907,7 @@ const DialogueScreen = () => { currentNpc: null, currentDialogueId: null }); - }, 1500); // Give player time to read dialogue before transition + }, 1500); // give player time to read dialogue before transition } }; @@ -972,15 +964,15 @@ const NotebookScreen = () => { const { gameState, updateGameState } = React.useContext(GameContext); const [selectedRecipe, setSelectedRecipe] = useState(null); - // Get all unlocked recipes + // get all unlocked recipes const unlockedRecipes = gameState.recipes.map(recipeId => recipeData[recipeId]); - // Handle recipe selection + // handle recipe selection const handleRecipeClick = (recipeId) => { setSelectedRecipe(recipeId); }; - // Handle cooking button + // handle cooking button const handleCook = () => { updateGameState({ screen: 'cooking', @@ -1093,14 +1085,13 @@ const NotebookScreen = () => { ); }; -// Cooking Screen Component +// cooking screen component const CookingScreen = () => { const { gameState, updateGameState } = React.useContext(GameContext); - // If no specific recipe is selected, default to the first unlocked recipe const currentRecipe = gameState.currentRecipe || (gameState.recipes.length > 0 ? gameState.recipes[0] : 'new_england_clam_chowder'); - // States for the cooking minigame + // states for the cooking minigame const [gamePhase, setGamePhase] = useState('intro'); // 'intro', 'playing', 'result' const [currentStep, setCurrentStep] = useState(0); const [score, setScore] = useState(0); @@ -1109,14 +1100,14 @@ const CookingScreen = () => { const [timeLeft, setTimeLeft] = useState(0); const [result, setResult] = useState(null); // 'perfect', 'good', 'needsWork' - // Timer ref + // timer ref const timerRef = useRef(null); - // Recipe data + // recipe data const recipe = recipeData[currentRecipe]; const challenges = cookingChallenges[currentRecipe]; - // Start the game + // start the game const startGame = () => { setGamePhase('playing'); setCurrentStep(0); @@ -1124,10 +1115,10 @@ const CookingScreen = () => { nextStep(0); }; - // Next cooking step + // next cooking step const nextStep = (stepIndex) => { if (stepIndex >= challenges.length) { - // Game complete, calculate result + // game complete, calculate result const finalScore = score; let finalResult; @@ -1142,24 +1133,24 @@ const CookingScreen = () => { setResult(finalResult); setGamePhase('result'); - // Increase reputation based on result + // increase reputation based on result const reputationGain = finalResult === 'perfect' ? 2 : (finalResult === 'good' ? 1 : 0); const newReputation = { ...gameState.reputation }; newReputation[gameState.currentRegion] += reputationGain; - // Cap reputation at 5 + // cap reputation at 5 if (newReputation[gameState.currentRegion] > 5) { newReputation[gameState.currentRegion] = 5; } updateGameState({ reputation: newReputation }); - // Check if this unlocks the next region + // check if this unlocks the next region if (newReputation[gameState.currentRegion] >= 3 && gameState.currentRegion === 'coastal' && !gameState.unlockedRegions.includes('southwest')) { - // Unlock southwest region + // southwest region const newUnlockedRegions = [...gameState.unlockedRegions, 'southwest']; updateGameState({ unlockedRegions: newUnlockedRegions }); @@ -1167,7 +1158,7 @@ const CookingScreen = () => { gameState.currentRegion === 'southwest' && !gameState.unlockedRegions.includes('midwest')) { - // Unlock midwest region + // midwest region const newUnlockedRegions = [...gameState.unlockedRegions, 'midwest']; updateGameState({ unlockedRegions: newUnlockedRegions }); } @@ -1175,21 +1166,21 @@ const CookingScreen = () => { return; } - // Set up the next step + // set up the next step setCurrentStep(stepIndex); setShowKey(false); setKeyPressed(false); - // Start the timer for this step + // start the timer for this step const duration = challenges[stepIndex].duration; setTimeLeft(duration); - // Show the key after a random delay + // show the key after a random delay const keyDelay = Math.floor(Math.random() * (duration * 0.6)) + (duration * 0.2); setTimeout(() => { setShowKey(true); - // Hide the key after a short time if not pressed + // hide the key after a short time if not pressed setTimeout(() => { if (!keyPressed) { setShowKey(false); @@ -1197,41 +1188,40 @@ const CookingScreen = () => { }, 1200); }, keyDelay); - // Set a timer for the entire step duration + // set a timer for the entire step duration timerRef.current = setTimeout(() => { - // Move to next step regardless of success + // move to next step regardless of success -- // TODO: broken? nextStep(stepIndex + 1); }, duration); }; - // Handle key press + // handle key press const handleKeyPress = (keyCode) => { - // Only process if we're showing a key and in playing phase + if (showKey && gamePhase === 'playing' && !keyPressed) { const expectedKey = challenges[currentStep].key; - // Check if the correct key was pressed + // check if the correct key was pressed if (keyCode.toUpperCase() === expectedKey) { - // Correct key! Add to score + + // correct setScore(score + 1); setKeyPressed(true); setShowKey(false); - // Clear the current timer clearTimeout(timerRef.current); - // Move to next step after a short delay setTimeout(() => { nextStep(currentStep + 1); }, 800); } else { - // Wrong key, just hide the prompt + // wrong setShowKey(false); } } }; - // Set up keyboard listener + // set up keyboard listener useEffect(() => { const keyListener = (event) => { handleKeyPress(event.key); @@ -1245,7 +1235,7 @@ const CookingScreen = () => { }; }, [currentStep, showKey, gamePhase, keyPressed]); - // Timer effect + // timer effect useEffect(() => { if (gamePhase === 'playing' && timeLeft > 0) { const interval = setInterval(() => { @@ -1256,7 +1246,7 @@ const CookingScreen = () => { } }, [gamePhase, timeLeft]); - // Render the appropriate game phase + // render the appropriate game phase const renderGameContent = () => { switch(gamePhase) { case 'intro': @@ -1329,7 +1319,7 @@ const CookingScreen = () => { const reputationGain = result === 'perfect' ? 2 : (result === 'good' ? 1 : 0); const regionName = gameState.currentRegion.charAt(0).toUpperCase() + gameState.currentRegion.slice(1); - // Check if we unlocked a new region + // unlocked a new region? const unlockedNewRegion = (gameState.currentRegion === 'coastal' && gameState.reputation.coastal >= 3 && !gameState.unlockedRegions.includes('southwest')) || (gameState.currentRegion === 'southwest' && gameState.reputation.southwest >= 3 && !gameState.unlockedRegions.includes('midwest')); @@ -1343,7 +1333,7 @@ const CookingScreen = () => { {/* Result image placeholder */}
- [Image of {result} {recipe.name}] + [{result} {recipe.name} Image]
@@ -1423,15 +1413,15 @@ const CookingScreen = () => { ); }; -// Game completion check +// game completion check const checkGameCompletion = (gameState) => { - // Check if all regions have at least 4 reputation + // check if all regions have at least 4 reputation const allRegionsHighRep = gameState.unlockedRegions.includes('coastal') && gameState.reputation.coastal >= 4 && gameState.unlockedRegions.includes('southwest') && gameState.reputation.southwest >= 4 && gameState.unlockedRegions.includes('midwest') && gameState.reputation.midwest >= 4; - // Check if at least one recipe from each region is known + // check if at least one recipe from each region is known const hasCoastalRecipe = gameState.recipes.some(recipe => recipeData[recipe].region === 'coastal'); const hasSouthwestRecipe = gameState.recipes.some(recipe => recipeData[recipe].region === 'southwest'); const hasMidwestRecipe = gameState.recipes.some(recipe => recipeData[recipe].region === 'midwest'); @@ -1441,12 +1431,12 @@ const checkGameCompletion = (gameState) => { return allRegionsHighRep && allRegionRecipes; }; -// Final Festival Component +// final festival component const FinalFestivalScreen = () => { const { gameState, updateGameState } = React.useContext(GameContext); const [festivalPhase, setFestivalPhase] = useState('intro'); - // Total reputation across all regions + // total reputation across all regions const totalReputation = gameState.reputation.coastal + gameState.reputation.southwest + @@ -1455,7 +1445,7 @@ const FinalFestivalScreen = () => { const maxReputation = 15; // 5 per region const reputationPercentage = Math.round((totalReputation / maxReputation) * 100); - // Festival outcome levels + // festival outcome levels const getFestivalOutcome = () => { if (reputationPercentage >= 90) return "Outstanding"; if (reputationPercentage >= 75) return "Excellent"; @@ -1463,9 +1453,9 @@ const FinalFestivalScreen = () => { return "Modest"; }; - // Handle return to title + // handle return to title const handleReturnToTitle = () => { - // Reset game state for a new game + // reset game state for a new game updateGameState({ screen: 'title', currentRegion: null, @@ -1482,7 +1472,7 @@ const FinalFestivalScreen = () => { }); }; - // Render the appropriate festival phase + // render the appropriate festival phase const renderFestivalContent = () => { switch(festivalPhase) { case 'intro': @@ -1653,19 +1643,19 @@ const FinalFestivalScreen = () => { ); }; -// Enhanced Map Screen that checks for game completion +// enhanced Map Screen that checks for game completion const EnhancedMapScreen = (props) => { const { gameState, updateGameState } = React.useContext(GameContext); - // Check for game completion when entering the map screen + // check for game completion when entering the map screen useEffect(() => { if (checkGameCompletion(gameState)) { - // If conditions are met, show the final festival + // if conditions are met, show the final festival updateGameState({ screen: 'festival' }); } }, []); - // Render the original MapScreen + // render the original MapScreen return ; }; @@ -1673,7 +1663,7 @@ const EnhancedMapScreen = (props) => { const FoodTruckFrontier = () => { const { gameState } = React.useContext(GameContext); - // Render the appropriate screen based on game state + // render the appropriate screen based on game state const renderScreen = () => { switch (gameState.screen) { case 'title': @@ -1702,7 +1692,7 @@ const FoodTruckFrontier = () => { ); }; -// Export the wrapped game with context +// export wrapped game + context export default function Game() { return (