Skip to content

Commit 4334bea

Browse files
author
sharma.ankit2
committed
feat: fix solver, add audio feedback, auto-solve with speed presets
- Replace broken greedy solver with guaranteed inverse-scramble solver - Add audio feedback (move tones, solved fanfare, sound toggle) - Add Auto Solve with Slow/Medium/Fast speed presets - Clean up unused props and imports
1 parent a777a71 commit 4334bea

5 files changed

Lines changed: 253 additions & 387 deletions

File tree

src/features/games/rubiks-cube/RubiksCubePage.tsx

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { useEffect, useRef } from 'react';
1+
import { useEffect, useRef, useCallback } from 'react';
22
import { Link } from 'react-router-dom';
3-
import { ArrowLeft, Shuffle, RotateCcw, BookOpen, Trophy, Gamepad2, HelpCircle } from 'lucide-react';
3+
import { ArrowLeft, Shuffle, RotateCcw, BookOpen, Trophy, Gamepad2, HelpCircle, Volume2, VolumeX } from 'lucide-react';
44
import Cube3D from './components/Cube3D';
55
import MoveControls from './components/MoveControls';
66
import SolvePanel from './components/SolvePanel';
77
import ChallengePanel from './components/ChallengePanel';
88
import { useRubiksCube } from './hooks/useRubiksCube';
9+
import { useRubiksSound } from './hooks/useRubiksSound';
910
import type { GameMode, CubeState } from './engine/types';
1011
import { COLOR_MAP } from './engine/types';
1112

@@ -39,16 +40,42 @@ export default function RubiksCubePage() {
3940
stepForward, stepBackward, play, pause, tick, setMode, setSpeed, clearAnimation,
4041
} = useRubiksCube();
4142

43+
const sound = useRubiksSound();
44+
const soundRef = useRef(sound);
45+
soundRef.current = sound;
46+
4247
const tickRef = useRef(tick);
4348
tickRef.current = tick;
4449

50+
// Track previous solved state to trigger sound
51+
const prevSolvedRef = useRef(state.isSolved);
52+
useEffect(() => {
53+
if (state.isSolved && !prevSolvedRef.current) {
54+
soundRef.current.playSolved();
55+
}
56+
prevSolvedRef.current = state.isSolved;
57+
}, [state.isSolved]);
58+
59+
// Play sound on each animated move
60+
useEffect(() => {
61+
if (state.animatingMove) {
62+
soundRef.current.playMove(state.animatingMove);
63+
}
64+
}, [state.animatingMove]);
65+
4566
// Auto-play timer
4667
useEffect(() => {
4768
if (!state.isPlaying) return;
4869
const id = setInterval(() => tickRef.current(), state.speed);
4970
return () => clearInterval(id);
5071
}, [state.isPlaying, state.speed]);
5172

73+
// Auto-solve: set speed then start playing
74+
const handleAutoSolve = useCallback((speed: number) => {
75+
setSpeed(speed);
76+
play();
77+
}, [play, setSpeed]);
78+
5279
return (
5380
<div className="flex-1 overflow-y-auto">
5481
{/* Header */}
@@ -71,11 +98,20 @@ export default function RubiksCubePage() {
7198
3D interactive cube with step-by-step solving, guided tutorials & timed challenges
7299
</p>
73100
</div>
74-
{state.isSolved && state.mode !== 'challenge' && (
75-
<div className="flex items-center gap-1.5 rounded-full bg-emerald-500/10 px-3 py-1 ring-1 ring-emerald-500/30">
76-
<span className="text-xs font-semibold text-emerald-400">✓ Solved</span>
77-
</div>
78-
)}
101+
<div className="flex items-center gap-2">
102+
<button
103+
onClick={() => sound.setEnabled(!sound.isEnabled())}
104+
className="p-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white transition-colors border border-slate-700/50"
105+
title={sound.isEnabled() ? 'Mute' : 'Unmute'}
106+
>
107+
{sound.isEnabled() ? <Volume2 size={14} /> : <VolumeX size={14} />}
108+
</button>
109+
{state.isSolved && state.mode !== 'challenge' && (
110+
<div className="flex items-center gap-1.5 rounded-full bg-emerald-500/10 px-3 py-1 ring-1 ring-emerald-500/30">
111+
<span className="text-xs font-semibold text-emerald-400">✓ Solved</span>
112+
</div>
113+
)}
114+
</div>
79115
</div>
80116
</div>
81117
</section>
@@ -175,11 +211,10 @@ export default function RubiksCubePage() {
175211
isPlaying={state.isPlaying}
176212
onStepForward={stepForward}
177213
onStepBackward={stepBackward}
178-
onPlay={play}
179214
onPause={pause}
180215
onReset={() => setMode('guided')}
216+
onAutoSolve={handleAutoSolve}
181217
speed={state.speed}
182-
onSpeedChange={setSpeed}
183218
guidedPhase={state.guidedPhase}
184219
/>
185220
</div>
@@ -205,11 +240,10 @@ export default function RubiksCubePage() {
205240
isPlaying={state.isPlaying}
206241
onStepForward={stepForward}
207242
onStepBackward={stepBackward}
208-
onPlay={play}
209243
onPause={pause}
210244
onReset={reset}
245+
onAutoSolve={handleAutoSolve}
211246
speed={state.speed}
212-
onSpeedChange={setSpeed}
213247
guidedPhase={state.guidedPhase}
214248
/>
215249
</div>

src/features/games/rubiks-cube/components/SolvePanel.tsx

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
import type { SolveStep } from '../engine/types';
2-
import { Play, Pause, SkipForward, SkipBack, RotateCcw } from 'lucide-react';
2+
import { Pause, SkipForward, SkipBack, RotateCcw, Wand2, Turtle, Rabbit, Zap } from 'lucide-react';
3+
4+
const SPEED_PRESETS = [
5+
{ label: 'Slow', value: 1000, icon: Turtle, color: 'from-emerald-600 to-green-600 hover:from-emerald-500 hover:to-green-500' },
6+
{ label: 'Medium', value: 400, icon: Rabbit, color: 'from-violet-600 to-purple-600 hover:from-violet-500 hover:to-purple-500' },
7+
{ label: 'Fast', value: 120, icon: Zap, color: 'from-amber-600 to-orange-600 hover:from-amber-500 hover:to-orange-500' },
8+
] as const;
39

410
interface SolvePanelProps {
511
steps: SolveStep[];
612
currentStep: number;
713
isPlaying: boolean;
814
onStepForward: () => void;
915
onStepBackward: () => void;
10-
onPlay: () => void;
1116
onPause: () => void;
1217
onReset: () => void;
18+
onAutoSolve?: (speed: number) => void;
1319
speed: number;
14-
onSpeedChange: (speed: number) => void;
1520
guidedPhase: string;
1621
}
1722

1823
const PHASE_COLORS: Record<string, string> = {
19-
'White Cross': 'text-white',
20-
'White Corners': 'text-slate-200',
21-
'Middle Layer': 'text-blue-400',
22-
'Yellow Cross': 'text-yellow-400',
23-
'Last Layer': 'text-yellow-300',
24+
'Undo Last Moves': 'text-amber-400',
25+
'Restoring Middle': 'text-blue-400',
26+
'Final Restoration': 'text-emerald-400',
2427
};
2528

2629
export default function SolvePanel({
2730
steps, currentStep, isPlaying, onStepForward, onStepBackward,
28-
onPlay, onPause, onReset, speed, onSpeedChange, guidedPhase,
31+
onPause, onReset, onAutoSolve, speed, guidedPhase,
2932
}: SolvePanelProps) {
3033
const progress = steps.length > 0 ? ((currentStep + 1) / steps.length) * 100 : 0;
3134
const currentDesc = currentStep >= 0 && currentStep < steps.length
@@ -76,7 +79,47 @@ export default function SolvePanel({
7679
)}
7780
</div>
7881

79-
{/* Controls */}
82+
{/* Auto Solve with speed presets */}
83+
{onAutoSolve && currentStep < steps.length - 1 && !isPlaying && (
84+
<div className="space-y-1.5">
85+
<div className="flex items-center gap-1.5 text-[10px] text-slate-500 uppercase font-semibold">
86+
<Wand2 size={10} /> Auto Solve
87+
</div>
88+
<div className="grid grid-cols-3 gap-1.5">
89+
{SPEED_PRESETS.map(({ label, value, icon: Icon, color }) => (
90+
<button
91+
key={label}
92+
onClick={() => onAutoSolve(value)}
93+
className={`flex flex-col items-center gap-0.5 py-2 rounded-lg
94+
bg-gradient-to-r ${color}
95+
text-white text-[10px] font-semibold transition-all`}
96+
>
97+
<Icon size={14} />
98+
{label}
99+
</button>
100+
))}
101+
</div>
102+
</div>
103+
)}
104+
105+
{/* During auto-solve, show pause */}
106+
{isPlaying && (
107+
<div className="flex items-center gap-2">
108+
<button
109+
onClick={onPause}
110+
className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-lg
111+
bg-gradient-to-r from-red-600 to-rose-600 hover:from-red-500 hover:to-rose-500
112+
text-white text-xs font-semibold transition-all"
113+
>
114+
<Pause size={14} /> Pause
115+
</button>
116+
<div className="text-[10px] text-slate-500">
117+
{speed <= 150 ? '⚡ Fast' : speed <= 500 ? '🐇 Medium' : '🐢 Slow'}
118+
</div>
119+
</div>
120+
)}
121+
122+
{/* Step controls */}
80123
<div className="flex items-center gap-1.5">
81124
<button
82125
onClick={onReset}
@@ -87,50 +130,22 @@ export default function SolvePanel({
87130
</button>
88131
<button
89132
onClick={onStepBackward}
90-
disabled={currentStep < 0}
133+
disabled={currentStep < 0 || isPlaying}
91134
className="p-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white
92135
disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
93136
title="Step Back"
94137
>
95138
<SkipBack size={14} />
96139
</button>
97140
<button
98-
onClick={isPlaying ? onPause : onPlay}
99-
disabled={steps.length === 0 || currentStep >= steps.length - 1}
141+
onClick={onStepForward}
142+
disabled={currentStep >= steps.length - 1 || isPlaying}
100143
className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-lg
101144
bg-gradient-to-r from-cyan-600 to-teal-600 hover:from-cyan-500 hover:to-teal-500
102145
text-white text-xs font-semibold disabled:opacity-30 disabled:cursor-not-allowed transition-all"
103146
>
104-
{isPlaying ? <><Pause size={14} /> Pause</> : <><Play size={14} /> Play</>}
147+
<SkipForward size={14} /> Next Step
105148
</button>
106-
<button
107-
onClick={onStepForward}
108-
disabled={currentStep >= steps.length - 1}
109-
className="p-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white
110-
disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
111-
title="Step Forward"
112-
>
113-
<SkipForward size={14} />
114-
</button>
115-
</div>
116-
117-
{/* Speed control */}
118-
<div className="space-y-1">
119-
<div className="flex justify-between text-[10px] text-slate-500">
120-
<span>Speed</span>
121-
<span>{speed}ms</span>
122-
</div>
123-
<input
124-
type="range"
125-
min={100}
126-
max={2000}
127-
step={100}
128-
value={speed}
129-
onChange={e => onSpeedChange(Number(e.target.value))}
130-
className="w-full h-1 bg-slate-700 rounded-lg appearance-none cursor-pointer
131-
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
132-
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-cyan-500"
133-
/>
134149
</div>
135150

136151
{/* Steps list */}

0 commit comments

Comments
 (0)