Skip to content

Commit 0e495a8

Browse files
committed
Tutorial
1 parent 64b37d5 commit 0e495a8

File tree

8 files changed

+2988
-132
lines changed

8 files changed

+2988
-132
lines changed

improvements_summary.md

Lines changed: 393 additions & 0 deletions
Large diffs are not rendered by default.

interactive_test.html

Lines changed: 541 additions & 0 deletions
Large diffs are not rendered by default.

src/App.css

Lines changed: 851 additions & 0 deletions
Large diffs are not rendered by default.

src/App.tsx

Lines changed: 116 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import ProfileHeader from './components/ProfileHeader';
1212
import ProfileTab from './components/ProfileTab';
1313
import RulesPage from './components/RulesPage';
1414
import SettingsTab from './components/SettingsTab';
15+
import StepByStepTutorial, { BATTLE_TUTORIAL_STEPS } from './components/StepByStepTutorial';
16+
import TutorialTooltip, { COLLECTION_TUTORIAL_STEPS } from './components/TutorialTooltip';
1517
import {
1618
ELEMENTAL_TYPES,
1719
ELEMENTS,
@@ -94,6 +96,12 @@ const App: React.FC = () => {
9496
const [rulesPageOpen, setRulesPageOpen] = useState(false);
9597
const [musicVolume, setMusicVolume] = useState(0.2);
9698

99+
// Tutorial states
100+
const [showBattleTutorial, setShowBattleTutorial] = useState(false);
101+
const [showCollectionTutorial, setShowCollectionTutorial] = useState(false);
102+
const [hasSeenBattleTutorial, setHasSeenBattleTutorial] = useState(false);
103+
const [hasSeenCollectionTutorial, setHasSeenCollectionTutorial] = useState(false);
104+
97105
// Load game state from localStorage on mount
98106
useEffect(() => {
99107
const saved = localStorage.getItem('elementalGameState');
@@ -120,6 +128,18 @@ const App: React.FC = () => {
120128
// Failed to load audio settings - using defaults
121129
}
122130
}
131+
132+
// Load tutorial settings from localStorage
133+
const savedTutorialSettings = localStorage.getItem('tutorialSettings');
134+
if (savedTutorialSettings) {
135+
try {
136+
const settings = JSON.parse(savedTutorialSettings);
137+
setHasSeenBattleTutorial(settings.hasSeenBattleTutorial || false);
138+
setHasSeenCollectionTutorial(settings.hasSeenCollectionTutorial || false);
139+
} catch (error) {
140+
// Failed to load tutorial settings - using defaults
141+
}
142+
}
123143
}, []);
124144

125145
// Save game state to localStorage whenever player data changes
@@ -140,6 +160,38 @@ const App: React.FC = () => {
140160
);
141161
}, [musicVolume]);
142162

163+
// Save tutorial settings to localStorage whenever they change
164+
useEffect(() => {
165+
localStorage.setItem(
166+
'tutorialSettings',
167+
JSON.stringify({
168+
hasSeenBattleTutorial,
169+
hasSeenCollectionTutorial,
170+
})
171+
);
172+
}, [hasSeenBattleTutorial, hasSeenCollectionTutorial]);
173+
174+
// Auto-show tutorial for new users
175+
useEffect(() => {
176+
if (activeTab === 'battle' && !hasSeenBattleTutorial && gameState.gamePhase === 'menu') {
177+
const timer = setTimeout(() => {
178+
setShowBattleTutorial(true);
179+
}, 5000); // Show after 5 second delay (after loading screen)
180+
return () => clearTimeout(timer);
181+
}
182+
return undefined;
183+
}, [activeTab, hasSeenBattleTutorial, gameState.gamePhase]);
184+
185+
useEffect(() => {
186+
if (activeTab === 'collection' && !hasSeenCollectionTutorial) {
187+
const timer = setTimeout(() => {
188+
setShowCollectionTutorial(true);
189+
}, 500); // Show after 0.5 second delay
190+
return () => clearTimeout(timer);
191+
}
192+
return undefined;
193+
}, [activeTab, hasSeenCollectionTutorial]);
194+
143195
// Manage modal-open class for rules page
144196
useEffect(() => {
145197
if (rulesPageOpen) {
@@ -321,34 +373,12 @@ const App: React.FC = () => {
321373
gameState.player.selectedLocation as Location
322374
);
323375

324-
// For Free location, skip elemental selection and go directly to battle animation
325-
if (wager === 0) {
326-
const opponentElement = opponent.element || getRandomElement();
327-
const battleResult = calculateBattleResult(
328-
0, // baseWager is 0 for Free location
329-
gameState.player.selectedElement as Element,
330-
null, // no elemental for Free battles
331-
opponentElement,
332-
opponent.elemental || null,
333-
gameState.player.elementalCollection
334-
);
335-
336-
setGameState(prev => ({
337-
...prev,
338-
currentOpponent: opponent,
339-
opponentElement,
340-
initialBattleMana: prev.player.mana,
341-
battleResult: battleResult.winner,
342-
gamePhase: 'battleAnimation',
343-
}));
344-
} else {
345-
// For paid locations, show elemental selection
346-
setGameState(prev => ({
347-
...prev,
348-
currentOpponent: opponent,
349-
gamePhase: 'elementalSelection',
350-
}));
351-
}
376+
// All locations now go through elemental selection
377+
setGameState(prev => ({
378+
...prev,
379+
currentOpponent: opponent,
380+
gamePhase: 'elementalSelection',
381+
}));
352382
}, [
353383
gameState.player.selectedLocation,
354384
gameState.player.selectedElement,
@@ -685,11 +715,45 @@ const App: React.FC = () => {
685715
});
686716
}, []);
687717

718+
// Tutorial handlers
719+
const handleBattleTutorialComplete = useCallback(() => {
720+
setShowBattleTutorial(false);
721+
setHasSeenBattleTutorial(true);
722+
// Emit event to notify BattleComponent about tutorial completion
723+
window.dispatchEvent(new CustomEvent('battleTutorialCompleted'));
724+
}, []);
725+
726+
const handleBattleTutorialSkip = useCallback(() => {
727+
setShowBattleTutorial(false);
728+
setHasSeenBattleTutorial(true);
729+
// Emit event to notify BattleComponent about tutorial completion
730+
window.dispatchEvent(new CustomEvent('battleTutorialCompleted'));
731+
}, []);
732+
733+
const handleCollectionTutorialComplete = useCallback(() => {
734+
setShowCollectionTutorial(false);
735+
setHasSeenCollectionTutorial(true);
736+
}, []);
737+
738+
const handleCollectionTutorialSkip = useCallback(() => {
739+
setShowCollectionTutorial(false);
740+
setHasSeenCollectionTutorial(true);
741+
}, []);
742+
743+
const showBattleTutorialManually = useCallback(() => {
744+
setShowBattleTutorial(true);
745+
}, []);
746+
747+
const showCollectionTutorialManually = useCallback(() => {
748+
setShowCollectionTutorial(true);
749+
}, []);
750+
688751
// Reset cache and restore initial game state
689752
const resetGameCache = useCallback(() => {
690753
// Clear all localStorage data
691754
localStorage.removeItem('elementalGameState');
692755
localStorage.removeItem('audioSettings');
756+
localStorage.removeItem('tutorialSettings');
693757

694758
// Reset game state to initial values
695759
setGameState({
@@ -705,6 +769,12 @@ const App: React.FC = () => {
705769
setMusicEnabled(false);
706770
setUserInteracted(false);
707771

772+
// Reset tutorial settings
773+
setHasSeenBattleTutorial(false);
774+
setHasSeenCollectionTutorial(false);
775+
setShowBattleTutorial(false);
776+
setShowCollectionTutorial(false);
777+
708778
// Reset other states
709779
setNewAchievements([]);
710780
setLevelUp(null);
@@ -816,6 +886,8 @@ const App: React.FC = () => {
816886
setRulesPageOpen(true);
817887
}}
818888
onResetCache={resetGameCache}
889+
onShowBattleTutorial={showBattleTutorialManually}
890+
onShowCollectionTutorial={showCollectionTutorialManually}
819891
/>
820892
)}
821893

@@ -895,6 +967,22 @@ const App: React.FC = () => {
895967

896968
{/* Navigation outside of app container */}
897969
<Navigation activeTab={activeTab} onTabChange={handleTabChange} />
970+
971+
{/* Tutorial Components */}
972+
<StepByStepTutorial
973+
isActive={showBattleTutorial}
974+
currentPhase={gameState.gamePhase}
975+
steps={BATTLE_TUTORIAL_STEPS}
976+
onComplete={handleBattleTutorialComplete}
977+
onSkip={handleBattleTutorialSkip}
978+
/>
979+
980+
<TutorialTooltip
981+
steps={COLLECTION_TUTORIAL_STEPS}
982+
isActive={showCollectionTutorial}
983+
onComplete={handleCollectionTutorialComplete}
984+
onSkip={handleCollectionTutorialSkip}
985+
/>
898986
</>
899987
);
900988
};

0 commit comments

Comments
 (0)