{sprites[id] || sprites.egg}
;
};
// NES-style Background scene component – chunky pixels, limited palette
const SceneBackground = ({ isNight = false, anim = 0 }) => {
// NES color palette approximation
const p = isNight ? {
sky1: ‘#000058’, sky2: ‘#000038’, sky3: ‘#000028’,
mountain1: ‘#383878’, mountain2: ‘#282858’,
hill1: ‘#185818’, hill2: ‘#084808’, hill3: ‘#003800’,
grass: ‘#003800’, grassLight: ‘#005800’,
star: ‘#fcfcfc’, moon: ‘#fcfc68’
} : {
sky1: ‘#68a8fc’, sky2: ‘#88b8fc’, sky3: ‘#a8c8fc’,
mountain1: ‘#787878’, mountain2: ‘#585858’, snow: ‘#fcfcfc’,
hill1: ‘#00a800’, hill2: ‘#008800’, hill3: ‘#006800’,
grass: ‘#00a800’, grassLight: ‘#00c800’, grassDark: ‘#006800’,
sun: ‘#fcfc68’, sunOrange: ‘#fca044’
};
// Pixel size for NES chunky look
const px = 5;
if (isNight) {
return (
{/* Night sky – solid colors in bands */}
{/* Stars – single pixels */}
{[[15,10],[45,20],[80,8],[120,25],[140,12],[25,35],[60,30],[100,18],[135,38],[50,45],[90,40],[20,50],[70,15],[110,42]].map(([x,y], i) => (
))}
{/* Moon – pixelated circle */}
{/* Moon shadow for crescent */}
{/* Mountains – stepped pixels */}
{[…Array(32)].map((_, i) => {
const h = Math.max(0, 15 – Math.abs(i – 16)) * px;
return ;
})}
{[…Array(20)].map((_, i) => {
const h = Math.max(0, 10 – Math.abs(i – 10)) * px;
return ;
})}
{/* Rolling hills – NES style stepped */}
{[…Array(32)].map((_, i) => {
const h = Math.sin(i * 0.3) * 8 + Math.sin(i * 0.5) * 4;
return ;
})}
{[…Array(32)].map((_, i) => {
const h = Math.sin(i * 0.4 + 1) * 6 + Math.sin(i * 0.6) * 3;
return ;
})}
{/* Ground */}
{/* Grass tufts */}
{[5,25,45,65,85,105,125,145].map((x, i) => (
))}
);
}
return (
{/* Sky – NES blue gradient bands */}
{/* Sun – chunky pixel circle */}
{/* Sun rays */}
{/* Clouds – chunky NES style */}
{/* Far Mountain – stepped pixel art */}
{[…Array(28)].map((_, i) => {
const h = Math.max(0, 14 – Math.abs(i – 14)) * px;
return ;
})}
{/* Second mountain peak */}
{[…Array(18)].map((_, i) => {
const h = Math.max(0, 9 – Math.abs(i – 9)) * px;
return ;
})}
{/* Snow caps – white pixels at peak */}
{/* Rolling hills – multiple layers with NES-style stepping */}
{/* Back hill */}
{[…Array(32)].map((_, i) => {
const h = Math.sin(i * 0.25) * 10 + Math.sin(i * 0.4) * 5;
return ;
})}
{/* Front hill */}
{[…Array(32)].map((_, i) => {
const h = Math.sin(i * 0.35 + 2) * 8 + Math.sin(i * 0.55) * 4;
return ;
})}
{/* Ground */}
{/* Grass texture – NES style repeating pattern */}
{[0,20,40,60,80,100,120,140].map((x, i) => (
))}
{/* Ground detail – darker grass patches */}
{[10,35,55,75,95,115,135].map((x, i) => (
))}
);
};
const SAVE_KEY = ‘digimon_v6’;
const save = (s) => { try { localStorage.setItem(SAVE_KEY, JSON.stringify(s)); } catch(e) {} };
const load = () => { try { const s = localStorage.getItem(SAVE_KEY); return s ? JSON.parse(s) : null; } catch(e) { return null; } };
function App() {
const [gs, setGs] = useState(‘welcome’); const [digi, setDigi] = useState(null);
const [bp, setBp] = useState(0);
const [gameHours, setGameHours] = useState(0);
const [st, setSt] = useState({ hp:100, mHp:100, off:10, def:10, spd:10, brn:10, wt:15, hap:50, dis:50, hun:50, eng:100, mis:0, bat:0, age:0 });
const [poop, setPoop] = useState(0);
const [slp, setSlp] = useState(false);
const [sick, setSick] = useState(false);
const [menu, setMenu] = useState(‘main’);
const [msg, setMsg] = useState(”);
const [anim, setAnim] = useState(0);
const [battle, setBattle] = useState(null);
const [atk, setAtk] = useState(0);
const [playerAttacking, setPlayerAttacking] = useState(false);
const [enemyAttacking, setEnemyAttacking] = useState(false);
const [evoT, setEvoT] = useState(null);
const [ref, setRef] = useState(false);
const [refAct, setRefAct] = useState(null);
const [hunT, setHunT] = useState(0);
const [dirT, setDirT] = useState(0);
const [tirT, setTirT] = useState(0);
const [sickT, setSickT] = useState(0);
const [opC, setOpC] = useState(0);
const [hatchTaps, setHatchTaps] = useState(0);
const [eggPos, setEggPos] = useState(0);
const [petting, setPetting] = useState(false);
const [walkX, setWalkX] = useState(0);
const [walkDir, setWalkDir] = useState(1);
const [isWalking, setIsWalking] = useState(false);
const [walkFrame, setWalkFrame] = useState(0);
const [idleTime, setIdleTime] = useState(0);
const [eating, setEating] = useState(false);
const [eatBites, setEatBites] = useState(0);
const [training, setTraining] = useState(null); // ‘str’, ‘spd’, ‘brn’
const [trainFrame, setTrainFrame] = useState(0);
const [runX, setRunX] = useState(0);
const [showLightbulb, setShowLightbulb] = useState(false);
// Strength mini-game state
const [strGame, setStrGame] = useState({ active: false, presses: 0, lifts: 0, timeLeft: 5, started: false });
const [strResults, setStrResults] = useState(null); // { lifts, offGain, defGain, message }
// Speed mini-game state
const [spdGame, setSpdGame] = useState({ active: false, timeLeft: 5, started: false, hurdles: [], jumped: 0, missed: 0, isJumping: false, bgOffset: 0, hurdleCount: 0 });
const [spdResults, setSpdResults] = useState(null); // { jumped, missed, spdGain, brnGain, message, perfect, passed }
// Focus mini-game state (brain training)
const [focusGame, setFocusGame] = useState({ active: false, started: false, paddlePos: -45, direction: 1 });
const [focusResults, setFocusResults] = useState(null); // { accuracy, brnGain, spdGain, message, isPerfect, passed }
const focusAnimRef = useRef(null);
const [selectedEgg, setSelectedEgg] = useState(0); // 0-3 for botamon, poyomon, punimon, yuramon
const tickRef = useRef(0);
const EGG_TYPES = [
{ id: ‘egg_botamon’, baby: ‘botamon’, name: ‘Fire’, color: ‘#f8a030’ },
{ id: ‘egg_poyomon’, baby: ‘poyomon’, name: ‘Holy’, color: ‘#4878c8’ },
{ id: ‘egg_punimon’, baby: ‘punimon’, name: ‘Beast’, color: ‘#d088d0’ },
{ id: ‘egg_yuramon’, baby: ‘yuramon’, name: ‘Nature’, color: ‘#40b040’ },
];
useEffect(() => { if (gs === ‘playing’ && digi) save({ digi, bp, gameHours, st, poop, sick, hunT, dirT, tirT, sickT, opC }); }, [gs, digi, st, poop, gameHours, sick, sickT]);
const snd = useCallback((t) => {
try {
const c = new (window.AudioContext || window.webkitAudioContext)();
const o = c.createOscillator();
const g = c.createGain();
o.connect(g); g.connect(c.destination); g.gain.value = 0.08;
if (t === ‘beep’) { o.frequency.value = 800; o.start(); o.stop(c.currentTime + 0.08); }
else if (t === ‘ok’) { o.frequency.value = 523; o.start(); setTimeout(() => o.frequency.value = 784, 100); o.stop(c.currentTime + 0.2); }
else if (t === ‘hit’) { o.type = ‘sawtooth’; o.frequency.value = 150; o.start(); o.stop(c.currentTime + 0.1); }
else if (t === ‘evo’) { o.frequency.value = 440; o.start(); setTimeout(() => o.frequency.value = 880, 300); o.stop(c.currentTime + 0.6); }
else if (t === ‘no’) { o.type = ‘square’; o.frequency.value = 200; o.start(); o.stop(c.currentTime + 0.15); }
else if (t === ‘grunt’) { o.type = ‘sine’; o.frequency.value = 280 + Math.random() * 80; g.gain.value = 0.12; o.start(); setTimeout(() => o.frequency.value = 200 + Math.random() * 60, 50); o.stop(c.currentTime + 0.12); }
} catch(e) {}
}, []);
const start = useCallback((ld = false) => {
if (ld) { const s = load(); if (s) { setDigi(s.digi); setBp(s.bp); setGameHours(s.gameHours || 0); setSt(s.st); setPoop(s.poop); setSick(s.sick); setHunT(s.hunT||0); setDirT(s.dirT||0); setTirT(s.tirT||0); setSickT(s.sickT||0); setOpC(s.opC||0); setMsg(‘Welcome back!’); setGs(‘playing’); snd(‘ok’); return; } }
setSelectedEgg(0);
setGs(‘eggSelect’);
snd(‘ok’);
}, [snd]);
const confirmEggSelection = useCallback(() => {
setDigi(‘egg’); setBp(0); setGameHours(0); setSt({ hp:100, mHp:100, off:10, def:10, spd:10, brn:10, wt:15, hap:50, dis:50, hun:50, eng:100, mis:0, bat:0, age:0 }); setPoop(0); setSlp(false); setSick(false); setMenu(‘main’); setMsg(‘Tap to hatch!’); setHunT(0); setDirT(0); setTirT(0); setSickT(0); setOpC(0); setHatchTaps(0); setEggPos(0); tickRef.current = 0; setGs(‘hatching’); snd(‘ok’);
}, [snd]);
const digimonDeath = useCallback(() => {
snd(‘no’);
setMsg(‘Died…’);
setTimeout(() => {
setSelectedEgg(0);
setGs(‘eggSelect’);
}, 2000);
}, [snd]);
const tapEgg = useCallback(() => {
snd(‘grunt’);
setEggPos(p => p === 0 ? 1 : p === 1 ? -1 : 1);
setHatchTaps(t => {
const newT = t + 1;
if (newT >= 15) {
setTimeout(() => {
const freshDigimon = EGG_TYPES[selectedEgg].baby;
setEvoT(freshDigimon);
setGs(‘evolving’);
snd(‘evo’);
}, 300);
}
return newT;
});
}, [snd, selectedEgg]);
useEffect(() => {
if (gs !== ‘playing’ || slp) return;
const t = setInterval(() => {
tickRef.current += 1;
if (tickRef.current >= 150) {
tickRef.current = 0;
setGameHours(h => h + 1);
setSt(s => ({ …s, age: s.age + 1, hun: Math.max(0, s.hun – 5), eng: Math.max(0, s.eng – 3), hap: Math.max(0, s.hap – 1) }));
if (Math.random() Math.min(p + 1, 8));
}
setSt(s => { if (s.hun t + 1); else setHunT(0); if (s.eng t + 1); else setTirT(0); return s; });
if (poop >= 3) setDirT(t => t + 1); else setDirT(0);
if (sick) setSickT(t => t + 1); else setSickT(0);
}, 1000);
return () => clearInterval(t);
}, [gs, slp, poop, sick]);
useEffect(() => {
if (gs !== ‘playing’) return;
if (hunT >= 60) { setSt(s => ({ …s, mis: s.mis + 1 })); setHunT(0); setMsg(‘Mistake!’); if (!sick && Math.random() = 60) { setSt(s => ({ …s, mis: s.mis + 1 })); setDirT(0); setMsg(‘Mistake!’); if (!sick && Math.random() = 60) { setSt(s => ({ …s, mis: s.mis + 1 })); setTirT(0); setMsg(‘Mistake!’); }
if (st.eng ({ …s, mis: s.mis + 1, eng: 5 })); setMsg(‘Exhausted!’); }
if (st.mis >= 10 && DIGIMON_DATA[digi]?.stage === ‘rookie’) { setDigi(‘numemon’); setMsg(‘Devolved!’); snd(‘hit’); }
// Death: hungry for 5 game hours (750 seconds)
if (hunT >= 750 && digi !== ‘egg’) { digimonDeath(); return; }
// Death: sick for 3 game hours (450 seconds)
if (sickT >= 450 && digi !== ‘egg’) { digimonDeath(); return; }
// Numemon: 6+ poop AND dirty for 1 game hour (150 seconds)
if (poop >= 6 && dirT >= 150 && digi !== ‘numemon’ && digi !== ‘egg’ && DIGIMON_DATA[digi]?.stage !== ‘fresh’ && DIGIMON_DATA[digi]?.stage !== ‘intraining’) {
setDigi(‘numemon’);
setMsg(‘Too dirty!’);
snd(‘hit’);
setPoop(0);
setDirT(0);
}
}, [hunT, dirT, tirT, sickT, st.eng, st.mis, gs, digi, sick, poop, snd, digimonDeath]);
useEffect(() => {
const a = setInterval(() => {
setAnim(f => (f + 1) % 2);
setWalkFrame(f => (f + 1) % 4);
}, 300);
return () => clearInterval(a);
}, []);
// Idle walking behavior
useEffect(() => {
if (gs !== ‘playing’ || slp || ref || petting || menu !== ‘main’) {
setIdleTime(0);
return;
}
const t = setInterval(() => {
setIdleTime(it => {
const newTime = it + 1;
// Start walking after 5 seconds of idle
if (newTime >= 5 && !isWalking) {
setIsWalking(true);
setWalkDir(Math.random() clearInterval(t);
}, [gs, slp, ref, petting, menu, isWalking]);
// Walking movement
useEffect(() => {
if (!isWalking || gs !== ‘playing’ || slp) return;
const t = setInterval(() => {
setWalkX(x => {
const newX = x + walkDir * 2;
// Turn around at edges or randomly
if (Math.abs(newX) > 35 || Math.random() -d);
}
return Math.max(-40, Math.min(40, newX));
});
}, 150);
return () => clearInterval(t);
}, [isWalking, walkDir, gs, slp]);
// Return to center on interaction
const returnToCenter = useCallback(() => {
setWalkX(0);
setIsWalking(false);
setIdleTime(0);
}, []);
useEffect(() => { if (ref) { const t = setTimeout(() => { setRef(false); setRefAct(null); }, 1500); return () => clearTimeout(t); } }, [ref]);
const willRef = useCallback(() => st.hap < 30 && Math.random() {
if (!digi || gs !== ‘playing’) return;
const d = DIGIMON_DATA[digi];
if (!d || d.evolvesTo.length === 0) return;
if (digi === ‘egg’ && bp >= 15) { setEvoT(d.evolvesTo[Math.floor(Math.random() * d.evolvesTo.length)]); setGs(‘evolving’); snd(‘evo’); return; }
if (st.age >= d.evolveTime) { const t = st.mis = 50 ? d.evolvesTo[0] : d.evolvesTo[Math.floor(Math.random() * d.evolvesTo.length)]; setEvoT(t); setGs(‘evolving’); snd(‘evo’); }
}, [digi, bp, st, gs, snd]);
useEffect(() => { checkEvo(); }, [st.age, bp, checkEvo]);
const completeEvo = useCallback(() => {
if (!evoT) return;
setDigi(evoT); setSt(s => ({ …s, age: 0, mHp: s.mHp + 50, hp: s.mHp + 50, off: s.off + 20, def: s.def + 20, spd: s.spd + 15, brn: s.brn + 15 }));
setMsg(`${DIGIMON_DATA[evoT].name}!`); setEvoT(null); setGs(‘playing’);
}, [evoT]);
const feed = useCallback((type, force = false) => {
returnToCenter();
snd(‘beep’); setBp(p => p + 1);
if (st.hun >= 90 && !force) { setRef(true); setRefAct(‘feed’); setMsg(“Full!”); snd(‘no’); return; }
if (st.hun >= 90 && force) { setSt(s => ({ …s, hun: Math.min(100, s.hun + 30), mis: s.mis + 1, hap: Math.max(0, s.hap – 10) })); setMsg(‘Mistake!’); return; }
// Start eating animation for meat
if (type === ‘meat’) {
setEating(true);
setEatBites(0);
const biteInterval = setInterval(() => {
setEatBites(b => {
if (b >= 3) {
clearInterval(biteInterval);
setEating(false);
return 0;
}
snd(‘beep’);
return b + 1;
});
}, 400);
setSt(s => ({ …s, hun: Math.min(100, s.hun + 30), wt: s.wt + 1, hp: Math.min(s.mHp, s.hp + 10), hap: Math.min(100, s.hap + 3) }));
} else {
setSt(s => ({ …s, hun: Math.min(100, s.hun + 10), off: s.off + 1, def: s.def + 1, hap: Math.min(100, s.hap + 2) }));
}
setMsg(‘Yum!’);
}, [st.hun, snd, returnToCenter]);
const train = useCallback((type, force = false) => {
returnToCenter();
if (st.eng p + 1);
// Start training animation
setTraining(type);
setTrainFrame(0);
setShowLightbulb(false);
if (type === ‘str’) {
// Strength: Weight lifting mini-game
setStrGame({ active: true, presses: 0, lifts: 0, timeLeft: 5, started: false });
setMsg(‘MASH!’);
} else if (type === ‘spd’) {
// Speed: Hurdles mini-game
setSpdGame({ active: true, timeLeft: 5, started: false, hurdles: [], jumped: 0, missed: 0, isJumping: false, bgOffset: 0, hurdleCount: 0 });
setMsg(‘JUMP!’);
} else {
// Brain: Focus mini-game
setFocusGame({ active: true, started: false, paddlePos: -45, direction: 1 });
setMsg(‘FOCUS!’);
}
setMsg(‘Trained!’);
}, [st.eng, willRef, snd, returnToCenter]);
// Get stage multiplier for rewards (25% increase per stage advancement)
const getStageMultiplier = useCallback(() => {
const stage = DIGIMON_DATA[digi]?.stage;
switch(stage) {
case ‘fresh’: return 1.0;
case ‘intraining’: return 1.25;
case ‘rookie’: return 1.5;
case ‘champion’: return 1.75;
case ‘ultimate’: return 2.0;
default: return 1.0;
}
}, [digi]);
// Handle strength mini-game button press
const handleStrPress = useCallback(() => {
if (!strGame.active) return;
// Start timer on first press
if (!strGame.started) {
setStrGame(g => ({ …g, started: true }));
}
snd(‘beep’);
setStrGame(g => {
const newPresses = g.presses + 1;
const newLifts = Math.min(4, Math.floor(newPresses / 10));
return { …g, presses: newPresses, lifts: newLifts };
});
}, [strGame.active, strGame.started, snd]);
// Strength mini-game timer
useEffect(() => {
if (!strGame.active || !strGame.started) return;
if (strGame.timeLeft = 4) {
message = ‘PERFECT!’;
snd(‘evo’);
} else if (lifts >= 3) {
message = ‘Great!’;
snd(‘ok’);
} else if (lifts >= 1) {
message = ‘OK!’;
snd(‘ok’);
} else {
message = ‘Weak…’;
snd(‘no’);
}
// Store results and show results screen
setStrResults({ lifts, offGain, defGain, message });
setStrGame({ active: false, presses: 0, lifts: 0, timeLeft: 5, started: false });
return;
}
const timer = setTimeout(() => {
setStrGame(g => ({ …g, timeLeft: g.timeLeft – 1 }));
}, 1000);
return () => clearTimeout(timer);
}, [strGame.active, strGame.started, strGame.timeLeft, strGame.lifts, getStageMultiplier, snd]);
// Confirm strength results and apply stats
const confirmStrResults = useCallback(() => {
if (!strResults) return;
setSt(s => ({
…s,
off: s.off + strResults.offGain,
def: s.def + strResults.defGain,
eng: s.eng – 20,
hap: Math.max(0, s.hap – 5),
hun: Math.max(0, s.hun – 8)
}));
setMsg(strResults.message);
setStrResults(null);
setTraining(null);
snd(‘ok’);
}, [strResults, snd]);
// Handle speed mini-game jump
const handleSpdJump = useCallback(() => {
if (!spdGame.active) return;
// Start timer on first press and spawn first hurdle
if (!spdGame.started) {
setSpdGame(g => ({
…g,
started: true,
hurdles: [{ x: 100, id: Date.now(), cleared: false }],
hurdleCount: 1
}));
return;
}
// Don’t allow jumping while already jumping
if (spdGame.isJumping) return;
snd(‘beep’);
setSpdGame(g => ({ …g, isJumping: true }));
// Land after jump animation – longer jump window for easier gameplay
setTimeout(() => {
setSpdGame(g => ({ …g, isJumping: false }));
}, 500);
}, [spdGame.active, spdGame.started, spdGame.isJumping, snd]);
// Speed mini-game timer and hurdle spawning
useEffect(() => {
if (!spdGame.active || !spdGame.started) return;
if (spdGame.timeLeft = 3; // Need 3+ to pass (easier)
const perfect = spdGame.missed === 0 && spdGame.jumped >= 3;
const multiplier = getStageMultiplier();
// Base rewards: 3 speed, 1 brain for passing
// Failing (less than 5 jumps): just +1 speed
let spdGain, brnGain;
if (passed) {
const baseSpd = 3;
const baseBrn = 1;
spdGain = Math.max(1, Math.round(baseSpd * multiplier));
brnGain = Math.round(baseBrn * multiplier);
} else {
spdGain = 1;
brnGain = 0;
}
let message = ”;
if (perfect) {
message = ‘PERFECT!’;
snd(‘evo’);
} else if (passed) {
message = ‘Great!’;
snd(‘ok’);
} else {
message = ‘Failed…’;
snd(‘no’);
}
// Store results and show results screen
setSpdResults({ jumped: spdGame.jumped, missed: spdGame.missed, spdGain, brnGain, message, perfect, passed });
setSpdGame({ active: false, timeLeft: 5, started: false, hurdles: [], jumped: 0, missed: 0, isJumping: false, bgOffset: 0, hurdleCount: 0 });
return;
}
// Timer countdown
const timer = setTimeout(() => {
setSpdGame(g => ({ …g, timeLeft: g.timeLeft – 1 }));
}, 1000);
return () => clearTimeout(timer);
}, [spdGame.active, spdGame.started, spdGame.timeLeft, spdGame.jumped, spdGame.missed, getStageMultiplier, snd]);
// Hurdle spawning and movement
useEffect(() => {
if (!spdGame.active || !spdGame.started || spdGame.timeLeft {
setSpdGame(g => {
// Move background
const newBgOffset = (g.bgOffset + 3) % 160;
// Move hurdles – moderate speed
let newHurdles = g.hurdles.map(h => ({ …h, x: h.x – 4 }));
let jumped = g.jumped;
let missed = g.missed;
// Check for cleared/missed hurdles – WIDE collision zone for forgiveness (x=5-40)
newHurdles = newHurdles.map(h => {
// Hurdle is in the jump zone – very wide window
if (h.x 0 && !h.cleared) {
// Check if player was jumping when hurdle was in zone
if (g.isJumping) {
jumped++;
shouldPlayJump = true;
return { …h, cleared: true };
}
}
return h;
});
// Check for hurdles that passed without being cleared
newHurdles = newHurdles.filter(h => {
if (h.x <= -5 && !h.cleared) {
missed++;
shouldPlayMiss = true;
return false;
}
// Remove hurdles that went off screen
if (h.x 0 ? Math.max(…newHurdles.map(h => h.x)) : -100;
const canSpawn = hurdleCount < 8 && rightmostHurdle !h.cleared).length < 3;
// Random spawn – more frequent to ensure enough hurdles
if (canSpawn && Math.random() clearInterval(gameLoop);
}, [spdGame.active, spdGame.started, spdGame.timeLeft, snd]);
// Confirm speed results and apply stats
const confirmSpdResults = useCallback(() => {
if (!spdResults) return;
setSt(s => ({
…s,
spd: s.spd + spdResults.spdGain,
brn: s.brn + spdResults.brnGain,
eng: s.eng – 20,
hap: Math.max(0, s.hap – 5),
hun: Math.max(0, s.hun – 8)
}));
setMsg(spdResults.message);
setSpdResults(null);
setTraining(null);
snd(‘ok’);
}, [spdResults, snd]);
// Focus mini-game – start the paddle movement
const startFocusGame = useCallback(() => {
if (!focusGame.active) return;
snd(‘beep’);
setFocusGame(g => ({ …g, started: true }));
}, [focusGame.active, snd]);
// Focus mini-game animation loop
useEffect(() => {
if (!focusGame.active || !focusGame.started) return;
const speed = 4; // pixels per frame
const animate = () => {
setFocusGame(g => {
let newPos = g.paddlePos + (speed * g.direction);
let newDir = g.direction;
// Bounce off walls
if (newPos >= 45) {
newPos = 45;
newDir = -1;
} else if (newPos {
if (focusAnimRef.current) {
cancelAnimationFrame(focusAnimRef.current);
}
};
}, [focusGame.active, focusGame.started]);
// Focus mini-game – stop paddle and calculate results
const handleFocusStop = useCallback(() => {
if (!focusGame.active || !focusGame.started) return;
// Stop animation
if (focusAnimRef.current) {
cancelAnimationFrame(focusAnimRef.current);
}
// Calculate accuracy based on distance from center
const distance = Math.abs(focusGame.paddlePos);
let accuracy;
if (distance <= 3) accuracy = 100;
else if (distance <= 8) accuracy = 100 – (distance – 3) * 4;
else if (distance <= 15) accuracy = 80 – (distance – 8) * 3;
else if (distance = 95) {
brnGain = Math.max(1, Math.round(5 * multiplier));
spdGain = Math.round(2 * multiplier);
message = ‘PERFECT!’;
isPerfect = true;
passed = true;
snd(‘evo’);
} else if (accuracy >= 80) {
brnGain = Math.max(1, Math.round(4 * multiplier));
spdGain = Math.round(1 * multiplier);
message = ‘Great!’;
isPerfect = false;
passed = true;
snd(‘ok’);
} else if (accuracy >= 60) {
brnGain = Math.max(1, Math.round(3 * multiplier));
spdGain = Math.round(1 * multiplier);
message = ‘Good!’;
isPerfect = false;
passed = true;
snd(‘ok’);
} else if (accuracy >= 40) {
brnGain = Math.max(1, Math.round(2 * multiplier));
spdGain = 0;
message = ‘OK…’;
isPerfect = false;
passed = true;
snd(‘beep’);
} else {
brnGain = 1;
spdGain = 0;
message = ‘Missed…’;
isPerfect = false;
passed = false;
snd(‘no’);
}
// Show results after brief pause
setTimeout(() => {
setFocusResults({ accuracy: Math.round(accuracy), brnGain, spdGain, message, isPerfect, passed });
setFocusGame({ active: false, started: false, paddlePos: -45, direction: 1 });
}, 300);
}, [focusGame.active, focusGame.started, focusGame.paddlePos, getStageMultiplier, snd]);
// Confirm focus results and apply stats
const confirmFocusResults = useCallback(() => {
if (!focusResults) return;
setSt(s => ({
…s,
brn: s.brn + focusResults.brnGain,
spd: s.spd + focusResults.spdGain,
eng: s.eng – 15,
hap: Math.max(0, s.hap – 4),
hun: Math.max(0, s.hun – 6)
}));
setMsg(focusResults.message);
setFocusResults(null);
setTraining(null);
snd(‘ok’);
}, [focusResults, snd]);
const clean = useCallback(() => { returnToCenter(); if (poop > 0) { setPoop(0); setDirT(0); setMsg(‘Clean!’); snd(‘ok’); } }, [poop, snd, returnToCenter]);
const doSleep = useCallback((force = false) => { returnToCenter(); if (willRef() && !force) { setRef(true); setRefAct(‘sleep’); setMsg(“Won’t!”); snd(‘no’); return; } setSlp(true); setTirT(0); setMsg(‘Zzz…’); setTimeout(() => { setSlp(false); setSt(s => ({ …s, eng: 100, age: s.age + 0.25 })); setGameHours(h => h + 0.25); setMsg(‘Rested!’); }, 5000); }, [willRef, snd, returnToCenter]);
const heal = useCallback((force = false) => { returnToCenter(); if (!sick) return; if (willRef() && !force) { setRef(true); setRefAct(‘heal’); setMsg(“Won’t!”); snd(‘no’); return; } setSick(false); setSickT(0); setSt(s => ({ …s, hp: s.mHp })); setMsg(‘Healed!’); snd(‘ok’); }, [sick, willRef, snd, returnToCenter]);
const praise = useCallback(() => {
returnToCenter();
snd(‘beep’); setBp(p => p + 1);
if (st.hap >= 100) {
setOpC(c => { const n = c + 1; if (n > 5) { setSt(s => ({ …s, mis: s.mis + 1 })); setMsg(‘Mistake!’); return 0; } setMsg(`(${n}/5)`); return n; });
return;
}
setOpC(0);
setPetting(true);
setTimeout(() => setPetting(false), 1200);
setSt(s => ({ …s, hap: Math.min(100, s.hap + 10) }));
setMsg(‘Happy!’);
}, [st.hap, snd, returnToCenter]);
const scold = useCallback(() => { returnToCenter(); snd(‘beep’); setBp(p => p + 1); if (st.hap >= 100) { setSt(s => ({ …s, mis: s.mis + 1, hap: Math.max(0, s.hap – 15) })); setMsg(‘Mistake!’); return; } if (ref && refAct) { setRef(false); setSt(s => ({ …s, dis: Math.min(100, s.dis + 5), hap: Math.min(100, s.hap + 5) })); setMsg(‘OK!’); setTimeout(() => { if (refAct === ‘train’) train(‘str’, true); else if (refAct === ‘sleep’) doSleep(true); else if (refAct === ‘heal’) heal(true); else if (refAct === ‘feed’) feed(‘meat’, true); setRefAct(null); }, 500); return; } if (st.hun >= 90) { setMsg(‘Force…’); setTimeout(() => feed(‘meat’, true), 500); return; } setSt(s => ({ …s, dis: Math.min(100, s.dis + 10), hap: Math.max(0, s.hap – 5) })); setMsg(‘Scolded!’); }, [st.hap, st.hun, ref, refAct, snd, train, doSleep, heal, feed, returnToCenter]);
const startBattle = useCallback(() => {
returnToCenter();
const d = DIGIMON_DATA[digi];
if (!d?.attacks) { setMsg(“Can’t!”); return; }
// Energy cost based on stage
const energyCost = d.stage === ‘rookie’ ? 30 : d.stage === ‘champion’ ? 20 : 10;
if (st.eng ({ …s, eng: s.eng – energyCost }));
const eP = ENEMIES[d.stage] || ENEMIES.rookie;
const eId = eP[Math.floor(Math.random() * eP.length)];
setBattle({ enemy: eId, eHp: st.mHp * 0.8, eMax: st.mHp * 0.8, pHp: st.hp, turn: ‘p’, log: [`${DIGIMON_DATA[eId].name}!`] });
setAtk(0);
setGs(‘battle’);
snd(‘beep’);
}, [digi, st, snd, returnToCenter]);
const doAttack = useCallback(() => { if (!battle || battle.turn !== ‘p’) return; const d = DIGIMON_DATA[digi]; const dmg = Math.floor(st.off * (1 + Math.random() * 0.5)); setPlayerAttacking(true); setTimeout(() => setPlayerAttacking(false), 400); snd(‘hit’); const nE = Math.max(0, battle.eHp – dmg); const nL = […battle.log, `${dmg}dmg!`]; if (nE ({ …s, bat: s.bat + 1, hap: Math.min(100, s.hap + 10), off: s.off + 2 })); setBattle({ …battle, eHp: 0, log: […nL, ‘WIN!’], turn: ‘end’ }); snd(‘ok’); setTimeout(() => { setGs(‘playing’); setBattle(null); setMsg(‘Won!’); }, 2000); } else { setBattle({ …battle, eHp: nE, log: nL, turn: ‘e’ }); setTimeout(() => { const eDmg = Math.floor(st.off * 0.6 * (1 + Math.random() * 0.3)); const nP = Math.max(0, battle.pHp – eDmg); setEnemyAttacking(true); setTimeout(() => setEnemyAttacking(false), 400); snd(‘hit’); if (nP ({ …s, hp: Math.floor(s.mHp * 0.3), hap: Math.max(0, s.hap – 20), mis: s.mis + 1 })); setBattle(pr => ({ …pr, pHp: 0, log: […pr.log, ‘LOSE’], turn: ‘end’ })); setTimeout(() => { setGs(‘playing’); setBattle(null); setMsg(‘Lost…’); }, 2000); } else { setBattle(pr => ({ …pr, pHp: nP, log: […pr.log, `${eDmg}dmg!`], turn: ‘p’ })); } }, 1000); } }, [battle, digi, st, snd]);
const run = useCallback(() => { if (Math.random() < 0.5) { setGs('playing'); setBattle(null); setMsg('Escaped!'); } else setMsg("Can't!"); }, []);
const hasSave = load() !== null;
const d = DIGIMON_DATA[digi];
const warn = st.hun <= 20 || st.eng = 3 || sick || poop >= 6;
const BrickShell = ({ children }) => (
{/* Keychain loop at top – PS1 style chunky metal */}
{/* Main brick body – PS1/32-bit realistic red brick texture */}
{/* 32-bit brick texture – chunky PS1 style stone blocks */}
{/* PS1 style chunky brick pattern */}
{/* Individual brick blocks with beveled edges */}
{/* Row 1 */}
{/* Row 2 – offset */}
{/* Mortar lines */}
{/* Surface wear pattern */}
{/* PS1 dithered shadow overlay */}
{children}
{/* Outer broken cage frame – dark metal with missing bars */}
{/* Inner screen bezel – dark weathered metal */}
);
// 16-bit style button icons
const ButtonIcon = ({ type, size = 20 }) => {
const icons = {
// 32-bit Steak – detailed juicy T-bone with rich shading
meat:
{/* Bone – detailed with depth */}
{/* Bone marrow detail */}
{/* Main meat – gradient base */}
{/* Highlight edge – top sear */}
{/* Deep shadow edge */}
{/* Fat marbling – realistic distribution */}
{/* Grill marks – dark char lines */}
{/* Juice/glisten highlights */}
,
// 32-bit Vitamin Capsule – detailed with 3D shading and shine
vitamin:
{/* Shadow underneath */}
{/* Red half – back shadow */}
{/* Red half – main body */}
{/* Red half – highlight */}
{/* Red half – top shine */}
{/* Blue half – back shadow */}
{/* Blue half – main body */}
{/* Blue half – highlight */}
{/* Blue half – top shine */}
{/* Center band – metallic separator */}
{/* Center shine line */}
{/* Rounded end caps */}
,
// Train icon – now a dumbbell
train:
{/* Left weight plates */}
{/* Right weight plates */}
{/* Bar */}
{/* Inner plates */}
,
// Strength – same dumbbell style
strength:
,
// Running man
speed:
,
// Open Book instead of brain
brain:
{/* Left page */}
{/* Right page */}
{/* Spine */}
{/* Text lines on left page */}
{/* Text lines on right page */}
{/* Page highlight */}
,
// Red cross (hospital)
care:
,
// Syringe for healing
heal:
,
// Smiley face for praise
praise:
,
// Sad face for scold
scold:
,
// Angry face (when refusing)
angry:
,
// Shower head for cleaning
clean:
{/* Shower head */}
{/* Shower holes */}
{/* Pipe/arm */}
{/* Water drops */}
{/* More drops */}
,
// Three Z’s for sleep
sleep:
,
// Stats/Chart icon
stats:
,
// 32-bit Sword – detailed with metallic blade, ornate hilt, gleam
fight:
{/* Blade shadow */}
{/* Blade – main steel body */}
{/* Blade – edge highlight (left edge) */}
{/* Blade – center fuller (groove) */}
{/* Blade – bright edge highlights */}
{/* Blade tip */}
{/* Cross-guard – ornate gold */}
{/* Cross-guard gems */}
{/* Grip – wrapped leather */}
{/* Grip wrap lines */}
{/* Pommel – gold cap */}
{/* Pommel gem */}
{/* Gleam effect on blade */}
,
// Back arrow
back:
,
// Run icon
run:
,
// New game icon – plus sign
newgame:
,
// Load game icon
load:
,
// OK/Confirm icon – checkmark
ok:
,
// Left arrow for navigation
arrowLeft:
,
// Right arrow for navigation
arrowRight:
,
};
return icons[type] || null;
};
const RoundButton = ({ onClick, icon, disabled = false, pulse = false }) => (
);
if (gs === ‘welcome’) return (
{/* PS1 style metal texture */}
{/* Broken cage bars – vertical bars with gaps (missing bars) */}
{/* Top horizontal bar */}
{/* Bottom horizontal bar */}
{/* Left vertical bars – some broken/missing */}
{/* Broken stub at top */}
{/* Right vertical bars – some broken/missing */}
{/* Broken stub */}
{/* Icon row above screen – carved into metal */}
{/* The actual LCD screen – kept at original resolution */}
{/* Bottom icons row */}
{showScene && }
{!showScene && (
)}
{children}
DIGIMON
setGs(‘title’)} icon=”ok” />
DIGITAL
MONSTER
start(false)} icon=”newgame” />
{hasSave && start(true)} icon=”load” />}
SELECT EGG
{/* Egg carousel display */}
{/* Left preview egg (smaller, faded) */}
{/* Center selected egg (large, animated) */}
{/* Right preview egg (smaller, faded) */}
{/* Egg type name with colored background */}
{EGG_TYPES[selectedEgg].name}
{EGG_TYPES.map((egg, i) => (
))}
{ setSelectedEgg(e => (e + 3) % 4); snd(‘beep’); }}
icon=”arrowLeft”
/>
{ setSelectedEgg(e => (e + 1) % 4); snd(‘beep’); }}
icon=”arrowRight”
/>
TAP TO HATCH!
{hatchTaps}/15
{hatchTaps > 0 && hatchTaps < 15 &&*crack crack*
}EVOLUTION!
{evoT && DIGIMON_DATA[evoT]?.name}
{d?.name}
{battle.pHp}/{st.mHp}
{enemyData?.name}
{Math.floor(battle.eHp)}/{Math.floor(battle.eMax)}
{battle.log.slice(-3).map((l, i) =>
{l}
)}
{battle.turn === ‘p’ && (
{d?.attacks?.slice(0, 2).map((a, i) => (
))}
)}
{battle.turn === ‘e’ &&
…
}
{gameHours.toFixed(1)}h
{d?.name || ‘Egg’}
β{st.mis}
{warn && {st.hun <= 20 && π{Math.max(0, Math.floor((750-hunT)/150))}h}{st.eng <= 20 && π΄{Math.max(0, 60-tirT)}}{poop >= 3 && π©{poop >= 6 ? `!${Math.max(0, Math.floor((150-dirT)/60))}m` : Math.max(0, 60-dirT)}}{sick && π€{Math.max(0, Math.floor((450-sickT)/150))}h}
}
{/* Speed Mini-Game Track with Hurdles */}
{spdGame.active && spdGame.started && (
)}
{/* Focus mini-game overlay */}
{focusGame.active && (
{/* Scrolling ground/track */}
{/* Hurdles – positioned on the track, taller */}
{spdGame.hurdles.map(hurdle => (
)}
{(() => {
const stage = d?.stage || ‘fresh’;
const isEarlyStage = stage === ‘fresh’ || stage === ‘intraining’;
// Squash and stretch for early stages
const squashX = isEarlyStage && isWalking ? (walkFrame % 2 === 0 ? 1.15 : 0.9) : 1;
const squashY = isEarlyStage && isWalking ? (walkFrame % 2 === 0 ? 0.85 : 1.1) : 1;
// Bounce for all stages
const bounceY = anim * 3;
// Walking tilt for later stages
const tilt = !isEarlyStage && isWalking ? (walkFrame % 2 === 0 ? 3 : -3) * walkDir : 0;
// Flip sprite based on direction
const flipX = walkDir === -1 ? -1 : 1;
// Speed training position – stay on left during minigame, positioned lower on track
const isInSpeedGame = spdGame.active && spdGame.started;
const trainXOffset = training === ‘spd’ ? (spdGame.active ? -25 : runX) : 0;
// Speed minigame jumping – clear jump height above hurdles
const isSpeedJumping = spdGame.active && spdGame.isJumping;
const jumpHeight = isSpeedJumping ? -45 : 0;
// Position Digimon much lower during speed game so it’s on the track with hurdles
const speedGameYOffset = isInSpeedGame ? 20 : 0;
return (
{/* Success indicator when cleared */}
{hurdle.cleared && (
))}
β
)}
{/* Strength training weight in front (mini-game) */}
{training === ‘str’ && strGame.active && (
)}
{/* Brain training lightbulb above head */}
{training === ‘brn’ && showLightbulb && (
)}
{ref ? : slp ?
)}
{/* Brain training book in front */}
{training === ‘brn’ && !showLightbulb && (
)}
{petting && (
)}
);
})()}
π€
: sick ? π€
: }
{/* Eating steak animation */}
{eating && (
{[…Array(poop)].map((_, i) => π©)}
{msg && {msg}
}
{/* Strength mini-game overlay */}
{strGame.active && (
β±οΈ{strGame.timeLeft}
ποΈ{strGame.lifts}/4
{/* Focus game area */}
)}
{/* Target zones background */}
{/* Center line */}
{/* Moving paddle */}
{/* Status text */}
{focusGame.started ? ‘β‘ STOP! β‘’ : ‘π§ FOCUS!’}
β€οΈ{st.hp}/{st.mHp}
π{st.hun}%
<div className={`h-full rounded ${st.hun
β‘{st.eng}%
<div className={`h-full rounded ${st.eng
π{st.hap}%
<div className={`h-full rounded ${st.hap
{/* Keyhole element – PS1 style metallic plate with keyhole cutout */}
)}
{/* Speed Mini-Game Button */}
{spdGame.active && (
);
}
ReactDOM.render(, document.getElementById(‘root’));
{/* Keyhole shape – classic skeleton key style */}
{/* Strength Mini-Game Button */}
{strGame.active && (
{/* Circle part of keyhole */}
{/* Slot part of keyhole */}
{/* Metal highlight */}
{/* Timer and Lifts display */}
{/* MASH button */}
{/* Instructions */}
{!strGame.started && (
)}
{/* Strength Results Screen */}
{strResults && (
β±οΈ {strGame.timeLeft}s
ποΈ {strGame.lifts}/4
{/* Lift progress bar */}
{10 – (strGame.presses % 10)} more to lift!
Press to start!
)}
{/* Title */}
{/* OK Button */}
= 4 ? ‘#ffd700’ : strResults.lifts >= 3 ? ‘#90EE90’ : strResults.lifts >= 1 ? ‘#87CEEB’ : ‘#ff6b6b’
}}>
{strResults.message}
{/* Lifts completed */}
ποΈ {strResults.lifts}/4 Lifts
{/* Stat gains */}
STAT GAINS
βοΈ
+{strResults.offGain}
π‘οΈ
+{strResults.defGain}
{/* Timer and Jumps display */}
{/* JUMP button */}
{/* Instructions */}
{!spdGame.started && (
)}
{/* Speed Results Screen */}
{spdResults && (
)}
{/* Focus Mini-Game Button */}
{focusGame.active && (
)}
{/* Buttons area */}
{menu === ‘main’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
setMenu(‘main’)} icon=”back” />
)}
β±οΈ {spdGame.timeLeft}s
β
{spdGame.jumped}/3+
{/* Progress bar showing jumps toward goal */}
= 3 ? ‘bg-gradient-to-r from-green-500 to-green-400’ : ‘bg-gradient-to-r from-yellow-500 to-orange-500’}`}
style={{ width: `${Math.min(100, (spdGame.jumped / 3) * 100)}%` }}
/>
{spdGame.started ? (spdGame.isJumping ? ‘π¦ JUMPING!’ : `π ${3 – spdGame.jumped > 0 ? `${3 – spdGame.jumped} more to pass!` : ‘β Keep going!’}`) : ‘Press START!’}
Jump 3+ hurdles to pass!
)}
{/* Title */}
{/* Perfect bonus indicator */}
{spdResults.perfect && (
{/* OK Button */}
{spdResults.message}
{/* Hurdles jumped */}
π {spdResults.jumped}/3+ Cleared ({spdResults.missed} missed)
{/* Stat gains */}
STAT GAINS
π¨
+{spdResults.spdGain}
π§
+{spdResults.brnGain}
β PERFECT BONUS! β
)}
{!spdResults.passed && (
Need 3+ jumps to pass!
)}
{/* Status display */}
{/* START/STOP button */}
{/* Instructions */}
{!focusGame.started && (
)}
{/* Focus Results Screen */}
{focusResults && (
π§ FOCUS!
{focusGame.started ? ‘Press STOP!’ : ‘Align paddle with center’}
Press to start!
)}
{/* Title */}
{/* Perfect bonus indicator */}
{focusResults.isPerfect && (
{/* OK Button */}
{focusResults.message}
{/* Accuracy */}
π― {focusResults.accuracy}% Accuracy
{/* Stat gains */}
STAT GAINS
π§
+{focusResults.brnGain}
π¨
+{focusResults.spdGain}
β PERFECT BONUS! β
)}
{!focusResults.passed && (
Need 40%+ to pass!
)}
{ returnToCenter(); setMenu(‘feed’); }} icon=”meat” />
{ returnToCenter(); setMenu(‘train’); }} icon=”train” />
{ returnToCenter(); setMenu(‘care’); }} icon=”care” />
{ returnToCenter(); setMenu(‘stats’); }} icon=”stats” />
doSleep()} pulse={st.eng
)}
{menu === ‘feed’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
feed(‘meat’)} icon=”meat” />
feed(‘vit’)} icon=”vitamin” />
setMenu(‘main’)} icon=”back” />
)}
{menu === ‘train’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
train(‘str’)} icon=”strength” />
train(‘spd’)} icon=”speed” />
train(‘brn’)} icon=”brain” />
setMenu(‘main’)} icon=”back” />
)}
{menu === ‘care’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
= 3} icon=”clean” />
heal()} disabled={!sick} icon=”heal” />
setMenu(‘main’)} icon=”back” />
)}
{menu === ‘stats’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (βοΈ {st.off}
π‘οΈ {st.def}
π¨ {st.spd}
π§ {st.brn}
Age: {st.age}h