diff --git a/web-app/js/hero-canvas.js b/web-app/js/hero-canvas.js index 050f377..c6f7134 100644 --- a/web-app/js/hero-canvas.js +++ b/web-app/js/hero-canvas.js @@ -3,9 +3,11 @@ Fixed: canvas now fills 100% of hero section ═══════════════════════════════════════════ */ const canvas = document.getElementById('boardCanvas'); -const ctx = canvas.getContext('2d'); -/* Force canvas to fill the hero section absolutely */ +if (canvas) { + const ctx = canvas.getContext('2d'); + + /* Force canvas to fill the hero section absolutely */ (function positionCanvas() { const hero = canvas.closest('.hero-section') || canvas.parentElement; if (hero && getComputedStyle(hero).position === 'static') { @@ -289,6 +291,7 @@ function loop() { tokens.forEach(t => { t.update(); t.draw(); }); drawDice(); requestAnimationFrame(loop); -} + } -loop(); \ No newline at end of file + loop(); +} \ No newline at end of file diff --git a/web-app/js/projects.js b/web-app/js/projects.js index 8028753..afc1da6 100644 --- a/web-app/js/projects.js +++ b/web-app/js/projects.js @@ -3471,31 +3471,6 @@ function getTowerOfHanoiHTML() { `; } -function initTowerOfHanoi() { - const canvas = document.getElementById('hanoiCanvas'); - const ctx = canvas.getContext('2d'); - const diskCountInput = document.getElementById('diskCount'); - const solveBtn = document.getElementById('solveBtn'); - const resetBtn = document.getElementById('resetHanoi'); - const moveCountEl = document.getElementById('moveCount'); - const optimalMovesEl = document.getElementById('optimalMoves'); - - let towers = [[], [], []]; - let diskCount = 3; - let moveCount = 0; - let isAnimating = false; - let shouldStop = false; - - const towerX = [200, 400, 600]; - const baseY = 350; - const diskHeight = 20; - const maxDiskWidth = 120; - const colors = ['#ff6b6b', '#f59e0b', '#10b981', '#06b6d4', '#6366f1', '#8b5cf6', '#ec4899']; - - function initTowers() { - towers = [[], [], []]; - moveCount = 0; - diskCount = parseInt(diskCountInput.value) || 3; //Reset animation state isAnimating = false; @@ -3841,7 +3816,6 @@ function initializeProject(projectName) { initializers[projectName](); } } -} //Removed Redundant game and project Logics and seperated them to different individual files located at (web-app/js/projects/) diff --git a/web-app/js/projects/whack-a-mole.js b/web-app/js/projects/whack-a-mole.js index 7eeecc1..c3a3bc3 100644 --- a/web-app/js/projects/whack-a-mole.js +++ b/web-app/js/projects/whack-a-mole.js @@ -45,6 +45,8 @@ function initWhackaMole() { let timeLeft = 30; let gameActive = false; let activeIndex = -1; + let lastActiveIndex = -1; + let lastActiveTime = 0; let timerId = null; let moleId = null; @@ -52,32 +54,80 @@ function initWhackaMole() { const button = document.createElement('button'); button.type = 'button'; button.className = 'whack-hole'; + button.textContent = '🕳️'; button.setAttribute('aria-label', `Hole ${index + 1}`); - button.addEventListener('click', () => { - if (!gameActive || index !== activeIndex) return; + + const handleHit = (e) => { + if (e) e.preventDefault(); + if (!gameActive) return; + + const isDirectHit = (index === activeIndex); + const isGraceHit = (index === lastActiveIndex && (Date.now() - lastActiveTime) < 150); + + if (!isDirectHit && !isGraceHit) return; + score += 1; scoreEl.textContent = String(score); - messageEl.textContent = 'Hit!'; + messageEl.textContent = 'Hit! 🔨'; + button.textContent = '💥'; + button.classList.remove('active'); + + // Set to -1 immediately to prevent double hits + activeIndex = -1; + lastActiveIndex = -1; + clearTimeout(moleId); - showMole(); - }); + moleId = setTimeout(showMole, 250); + }; + + button.addEventListener('pointerdown', handleHit); + button.addEventListener('click', handleHit); + board.appendChild(button); return button; }); function showMole() { - holes.forEach(hole => hole.classList.remove('active')); - activeIndex = Math.floor(Math.random() * holes.length); + if (!gameActive) return; + + // Save previous active mole state for grace period + if (activeIndex !== -1) { + lastActiveIndex = activeIndex; + lastActiveTime = Date.now(); + } + + holes.forEach(hole => { + hole.classList.remove('active'); + hole.textContent = '🕳️'; + }); + + // Choose a new hole, ensuring it is different from the current one + let newIndex; + do { + newIndex = Math.floor(Math.random() * holes.length); + } while (newIndex === activeIndex && holes.length > 1); + + activeIndex = newIndex; holes[activeIndex].classList.add('active'); + holes[activeIndex].textContent = '🐭'; + moleId = setTimeout(showMole, 850); } function stopGame(finalMessage) { gameActive = false; clearInterval(timerId); + timerId = null; clearTimeout(moleId); - holes.forEach(hole => hole.classList.remove('active')); + moleId = null; + activeIndex = -1; + lastActiveIndex = -1; + holes.forEach(hole => { + hole.classList.remove('active'); + hole.textContent = '🕳️'; + }); messageEl.textContent = finalMessage; + startBtn.disabled = false; } function startGame() { @@ -87,9 +137,9 @@ function initWhackaMole() { scoreEl.textContent = '0'; timeEl.textContent = '30'; messageEl.textContent = 'Go!'; + startBtn.disabled = true; clearInterval(timerId); clearTimeout(moleId); - showMole(); timerId = setInterval(() => { timeLeft -= 1; timeEl.textContent = String(timeLeft); @@ -97,19 +147,27 @@ function initWhackaMole() { stopGame(`Time! Final score: ${score}`); } }, 1000); + showMole(); } startBtn.addEventListener('click', startGame); resetBtn.addEventListener('click', () => { clearInterval(timerId); + timerId = null; clearTimeout(moleId); + moleId = null; score = 0; timeLeft = 30; gameActive = false; activeIndex = -1; - holes.forEach(hole => hole.classList.remove('active')); + lastActiveIndex = -1; + holes.forEach(hole => { + hole.classList.remove('active'); + hole.textContent = '🕳️'; + }); scoreEl.textContent = '0'; timeEl.textContent = '30'; messageEl.textContent = 'Hit the mole when it appears.'; + startBtn.disabled = false; }); } \ No newline at end of file