From 89b689e2f0aa745f3170203e12e4beeb8c72769b Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Sat, 2 Aug 2025 13:01:45 +0200 Subject: [PATCH] wip: vibrate danger --- index.html | 1 + src/game.js | 43 ++++++++++++++++++++++++++++++++++++++ src/input.js | 39 +++++++++++++++++++++++++++++++--- src/maze.js | 58 +++++++++++++++++++++++++++++++++++++++++++++------ src/style.css | 8 +++++++ 5 files changed, 140 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index 29b4d90..ad30756 100644 --- a/index.html +++ b/index.html @@ -15,6 +15,7 @@

⚠️ Red squares are deadly - avoid them!

🔊 Hidden dangers end the game - listen for directions!

🗣️ Speech directions: "left", "right", "up", "down"

+

👁️ Hold Shift + direction to detect invisible dangers!

diff --git a/src/game.js b/src/game.js index 232da01..f54bf86 100644 --- a/src/game.js +++ b/src/game.js @@ -137,6 +137,10 @@ export class Game { this.inputHandler.onMove = (direction) => { this.movePlayer(direction); }; + + this.inputHandler.onProbe = (direction) => { + this.probeDirection(direction); + }; } movePlayer(direction) { @@ -160,6 +164,11 @@ export class Game { this.handleAudioDanger(); } + // Check if player stepped on a hidden danger square + if (this.maze.isDangerousHidden(newX, newY)) { + this.handleHiddenDanger(); + } + // Check if player reached the exit if (this.maze.isExit(newX, newY)) { this.handleWin(); @@ -167,6 +176,21 @@ export class Game { } } + probeDirection(direction) { + const probeX = this.player.x + direction.x; + const probeY = this.player.y + direction.y; + + // Check if probe position is within bounds + if (probeX < 0 || probeX >= this.maze.cols || probeY < 0 || probeY >= this.maze.rows) { + return; + } + + // Check if the probed square has a hidden danger + if (this.maze.isDangerousHidden(probeX, probeY)) { + this.handleHiddenDangerDetected(); + } + } + trackMove(x, y) { // Track player move with timestamp const timestamp = Date.now(); @@ -188,6 +212,25 @@ export class Game { this.gameOver(); } + handleHiddenDanger() { + // Hidden danger squares end the game + this.gameOver(); + } + + handleHiddenDangerDetected() { + // Player detected a hidden danger with Shift+direction + console.log('Hidden danger detected!'); + + // Try vibration first + if (this.vibrationEnabled && navigator.vibrate) { + navigator.vibrate([200, 100, 200]); // Danger detection pattern + } else { + // Fallback to caps lock toggle + this.inputHandler.toggleCapsLock(); + console.log('Caps Lock toggled as vibration fallback'); + } + } + gameOver() { if (this.soundEnabled) { this.audioSystem.playGameOverSound(); diff --git a/src/input.js b/src/input.js index 9351b02..a39b8a5 100644 --- a/src/input.js +++ b/src/input.js @@ -2,6 +2,7 @@ export class InputHandler { constructor() { this.keys = new Set(); this.onMove = null; // Callback for movement + this.onProbe = null; // Callback for probing with Shift+direction this.moveDelay = 150; // Milliseconds between moves this.lastMoveTime = 0; @@ -36,6 +37,7 @@ export class InputHandler { } let direction = null; + const isShiftHeld = this.keys.has('ShiftLeft') || this.keys.has('ShiftRight'); // Check for movement keys (WASD or Arrow keys) if (this.keys.has('KeyW') || this.keys.has('ArrowUp')) { @@ -48,9 +50,16 @@ export class InputHandler { direction = { x: 1, y: 0 }; } - if (direction && this.onMove) { - this.onMove(direction); - this.lastMoveTime = now; + if (direction) { + if (isShiftHeld && this.onProbe) { + // Shift is held, this is a probe action + this.onProbe(direction); + this.lastMoveTime = now; + } else if (!isShiftHeld && this.onMove) { + // Normal movement + this.onMove(direction); + this.lastMoveTime = now; + } } } @@ -68,9 +77,33 @@ export class InputHandler { down: this.keys.has('KeyS') || this.keys.has('ArrowDown'), left: this.keys.has('KeyA') || this.keys.has('ArrowLeft'), right: this.keys.has('KeyD') || this.keys.has('ArrowRight'), + shift: this.keys.has('ShiftLeft') || this.keys.has('ShiftRight'), }; } + // Toggle Caps Lock as fallback for vibration + toggleCapsLock() { + // Create a temporary input to simulate caps lock toggle + const temp = document.createElement('input'); + temp.style.position = 'absolute'; + temp.style.left = '-9999px'; + document.body.appendChild(temp); + temp.focus(); + + // Simulate caps lock key press + const event = new KeyboardEvent('keydown', { + key: 'CapsLock', + code: 'CapsLock', + keyCode: 20, + which: 20 + }); + temp.dispatchEvent(event); + + setTimeout(() => { + document.body.removeChild(temp); + }, 100); + } + // Clean up event listeners destroy() { document.removeEventListener('keydown', this.handleKeyDown); diff --git a/src/maze.js b/src/maze.js index f19ec2b..0e7f8cf 100644 --- a/src/maze.js +++ b/src/maze.js @@ -12,7 +12,8 @@ export class Maze { this.SAFE = 1; this.DANGEROUS_VISUAL = 2; // Visually dangerous, no audio this.DANGEROUS_AUDIO = 3; // Looks safe, has audio when near - this.EXIT = 4; + this.DANGEROUS_HIDDEN = 4; // Invisible, only detectable with Shift+direction + this.EXIT = 5; } generate() { @@ -89,16 +90,18 @@ export class Maze { } } - // Calculate target number of dangerous squares (30% of inner safe squares) - const totalDangerousCount = Math.floor(innerSafeSquares.length * 0.3); - const visualDangerousCount = Math.floor(totalDangerousCount * 0.5); - const audioDangerousCount = totalDangerousCount - visualDangerousCount; + // Calculate target number of dangerous squares (40% of inner safe squares) + const totalDangerousCount = Math.floor(innerSafeSquares.length * 0.4); + const visualDangerousCount = Math.floor(totalDangerousCount * 0.33); + const audioDangerousCount = Math.floor(totalDangerousCount * 0.33); + const hiddenDangerousCount = totalDangerousCount - visualDangerousCount - audioDangerousCount; this.shuffle(innerSafeSquares); let addedDangerous = 0; let addedVisual = 0; let addedAudio = 0; + let addedHidden = 0; // Add dangerous squares one by one, checking path after each addition for (let i = 0; i < innerSafeSquares.length && addedDangerous < totalDangerousCount; i++) { @@ -110,6 +113,8 @@ export class Maze { dangerType = this.DANGEROUS_VISUAL; } else if (addedAudio < audioDangerousCount) { dangerType = this.DANGEROUS_AUDIO; + } else if (addedHidden < hiddenDangerousCount) { + dangerType = this.DANGEROUS_HIDDEN; } else { break; // We've added enough dangerous squares } @@ -125,8 +130,10 @@ export class Maze { addedDangerous++; if (dangerType === this.DANGEROUS_VISUAL) { addedVisual++; - } else { + } else if (dangerType === this.DANGEROUS_AUDIO) { addedAudio++; + } else { + addedHidden++; } } else { // Path blocked, revert this square to safe @@ -220,6 +227,10 @@ export class Maze { return this.grid[y][x] === this.DANGEROUS_AUDIO; } + isDangerousHidden(x, y) { + return this.grid[y][x] === this.DANGEROUS_HIDDEN; + } + isNearAudioDanger(playerX, playerY, range = 1) { // Check if player is within range of any audio danger squares for (let dy = -range; dy <= range; dy++) { @@ -335,6 +346,41 @@ export class Maze { } break; + case this.DANGEROUS_HIDDEN: + if (debugMode) { + // In debug mode, show hidden danger squares with a purple overlay + ctx.fillStyle = '#ffffff'; + ctx.fillRect(pixelX, pixelY, cellSize, cellSize); + + // Add purple debug overlay with pulsing effect + const hiddenTime = Date.now() * 0.004; + const hiddenPulse = (Math.sin(hiddenTime + x + y) + 1) * 0.5; + const hiddenAlpha = 0.3 + hiddenPulse * 0.4; + ctx.fillStyle = `rgba(128, 0, 128, ${hiddenAlpha})`; + ctx.fillRect(pixelX, pixelY, cellSize, cellSize); + + // Add debug border + ctx.strokeStyle = '#800080'; + ctx.lineWidth = 2; + ctx.strokeRect(pixelX, pixelY, cellSize, cellSize); + + // Add hidden symbol + ctx.fillStyle = '#ffffff'; + ctx.font = `${cellSize * 0.4}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('👁️', pixelX + cellSize / 2, pixelY + cellSize / 2); + } else { + // In normal mode, hidden danger squares look identical to safe squares + ctx.fillStyle = '#ffffff'; + ctx.fillRect(pixelX, pixelY, cellSize, cellSize); + // Add subtle grid lines + ctx.strokeStyle = '#e0e0e0'; + ctx.lineWidth = 1; + ctx.strokeRect(pixelX, pixelY, cellSize, cellSize); + } + break; + case this.DANGEROUS_VISUAL: // Visual-only danger squares - animated red appearance const time = Date.now() * 0.003; diff --git a/src/style.css b/src/style.css index 4ee8228..2096ad3 100644 --- a/src/style.css +++ b/src/style.css @@ -80,6 +80,14 @@ h1 { opacity: 0.8; } +.probe-hint { + color: #dda0dd !important; + font-size: 0.9em; + font-style: italic; + opacity: 0.8; + font-weight: bold; +} + @keyframes pulse { 0%, 100% { opacity: 0.7; } 50% { opacity: 1; }