feat: admin auto-solve button for quick level testing
- Add adminMode toggle in AdminPanel (green "Admin ON/OFF" button) - Pass adminMode through GameApp → PuzzleView - Show purple "🛠 Resolver" button in puzzle toolbar when admin is active - Auto-solve gives 3 stars instantly and shows completion overlay - Lets admin skip through all 96 levels for rapid testing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { loadProgress, saveProgress, resetProgress } from './gameState.js';
|
import { loadProgress, saveProgress, resetProgress } from './gameState.js';
|
||||||
|
|
||||||
export default function AdminPanel({ worlds, onClose }) {
|
export default function AdminPanel({ worlds, onClose, adminMode, onToggleAdmin }) {
|
||||||
const [, refresh] = useState(0);
|
const [, refresh] = useState(0);
|
||||||
const p = loadProgress();
|
const p = loadProgress();
|
||||||
const totalStars = Object.values(p.completedLevels).reduce((s, l) => s + (l.stars || 0), 0);
|
const totalStars = Object.values(p.completedLevels).reduce((s, l) => s + (l.stars || 0), 0);
|
||||||
@@ -63,6 +63,12 @@ export default function AdminPanel({ worlds, onClose }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="admin-actions">
|
<div className="admin-actions">
|
||||||
|
<button
|
||||||
|
className={`admin-action-btn ${adminMode ? 'active' : ''}`}
|
||||||
|
onClick={onToggleAdmin}
|
||||||
|
>
|
||||||
|
{adminMode ? '🛠 Admin ON' : '🛠 Admin OFF'}
|
||||||
|
</button>
|
||||||
<button className="admin-action-btn gold" onClick={giveAllStars}>★★★ Todo</button>
|
<button className="admin-action-btn gold" onClick={giveAllStars}>★★★ Todo</button>
|
||||||
<button className="admin-action-btn danger" onClick={handleReset}>Reset Progreso</button>
|
<button className="admin-action-btn danger" onClick={handleReset}>Reset Progreso</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default function GameApp({ onSwitchToSandbox }) {
|
|||||||
const [currentLevelIndex, setCurrentLevelIndex] = useState(0);
|
const [currentLevelIndex, setCurrentLevelIndex] = useState(0);
|
||||||
const [currentWorld, setCurrentWorld] = useState(null);
|
const [currentWorld, setCurrentWorld] = useState(null);
|
||||||
const [showAdmin, setShowAdmin] = useState(false);
|
const [showAdmin, setShowAdmin] = useState(false);
|
||||||
|
const [adminMode, setAdminMode] = useState(false);
|
||||||
|
|
||||||
const handleSelectLevel = useCallback((level, world) => {
|
const handleSelectLevel = useCallback((level, world) => {
|
||||||
const idx = world.levels.findIndex(l => l.id === level.id);
|
const idx = world.levels.findIndex(l => l.id === level.id);
|
||||||
@@ -67,6 +68,7 @@ export default function GameApp({ onSwitchToSandbox }) {
|
|||||||
worldLevels={currentWorld.levels}
|
worldLevels={currentWorld.levels}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onNextLevel={handleNextLevel}
|
onNextLevel={handleNextLevel}
|
||||||
|
adminMode={adminMode}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -82,6 +84,8 @@ export default function GameApp({ onSwitchToSandbox }) {
|
|||||||
<AdminPanel
|
<AdminPanel
|
||||||
worlds={allWorlds}
|
worlds={allWorlds}
|
||||||
onClose={() => setShowAdmin(false)}
|
onClose={() => setShowAdmin(false)}
|
||||||
|
adminMode={adminMode}
|
||||||
|
onToggleAdmin={() => setAdminMode(a => !a)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import LevelComplete from './LevelComplete.jsx';
|
|||||||
import { completeLevel, saveLevelPatch, getLevelPatch, markHintUsed, wasHintUsed } from './gameState.js';
|
import { completeLevel, saveLevelPatch, getLevelPatch, markHintUsed, wasHintUsed } from './gameState.js';
|
||||||
import { playLevelComplete, playFail, playHint, playEngineStart, playEngineStop, playNav } from '../engine/uiSounds.js';
|
import { playLevelComplete, playFail, playHint, playEngineStart, playEngineStop, playNav } from '../engine/uiSounds.js';
|
||||||
|
|
||||||
export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onNextLevel }) {
|
export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onNextLevel, adminMode }) {
|
||||||
const [, forceUpdate] = useState(0);
|
const [, forceUpdate] = useState(0);
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const portPositions = useRef({});
|
const portPositions = useRef({});
|
||||||
@@ -285,6 +285,17 @@ export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onN
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Admin auto-solve — gives 3 stars instantly
|
||||||
|
const handleAutoSolve = () => {
|
||||||
|
const checks = level.checks.map(check => ({
|
||||||
|
...check,
|
||||||
|
passed: true,
|
||||||
|
}));
|
||||||
|
completeLevel(level.id, 3);
|
||||||
|
setResult({ stars: 3, checks, hintPenalty: false });
|
||||||
|
playLevelComplete();
|
||||||
|
};
|
||||||
|
|
||||||
const isLastLevel = levelIndex >= worldLevels.length - 1;
|
const isLastLevel = levelIndex >= worldLevels.length - 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -312,6 +323,11 @@ export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onN
|
|||||||
<button className="gm-btn check" onClick={handleCheck}>
|
<button className="gm-btn check" onClick={handleCheck}>
|
||||||
✓ Comprobar
|
✓ Comprobar
|
||||||
</button>
|
</button>
|
||||||
|
{adminMode && (
|
||||||
|
<button className="gm-btn admin-solve" onClick={handleAutoSolve} title="Admin: resolver nivel">
|
||||||
|
🛠 Resolver
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -715,9 +715,17 @@ html, body, #root {
|
|||||||
}
|
}
|
||||||
.admin-action-btn.gold { border-color: var(--yellow); color: var(--yellow); }
|
.admin-action-btn.gold { border-color: var(--yellow); color: var(--yellow); }
|
||||||
.admin-action-btn.gold:hover { background: var(--yellow); color: var(--bg); }
|
.admin-action-btn.gold:hover { background: var(--yellow); color: var(--bg); }
|
||||||
|
.admin-action-btn.active { border-color: var(--green); color: var(--green); background: rgba(68, 255, 136, 0.1); }
|
||||||
|
.admin-action-btn.active:hover { background: var(--green); color: var(--bg); }
|
||||||
.admin-action-btn.danger { border-color: var(--red); color: var(--red); }
|
.admin-action-btn.danger { border-color: var(--red); color: var(--red); }
|
||||||
.admin-action-btn.danger:hover { background: var(--red); color: #fff; }
|
.admin-action-btn.danger:hover { background: var(--red); color: #fff; }
|
||||||
|
|
||||||
|
/* Admin auto-solve button in puzzle bar */
|
||||||
|
.gm-btn.admin-solve {
|
||||||
|
background: rgba(170, 85, 255, 0.15); border-color: var(--purple); color: var(--purple);
|
||||||
|
}
|
||||||
|
.gm-btn.admin-solve:hover { background: var(--purple); color: #fff; }
|
||||||
|
|
||||||
.admin-world { margin-bottom: 16px; }
|
.admin-world { margin-bottom: 16px; }
|
||||||
.admin-world-header {
|
.admin-world-header {
|
||||||
display: flex; align-items: center; gap: 8px;
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
|||||||
Reference in New Issue
Block a user