mirror of
https://github.com/kuhyx/slavic_game_jam.git
synced 2026-07-04 11:43:04 +02:00
wip: vibrate danger
This commit is contained in:
parent
13123234da
commit
89b689e2f0
@ -15,6 +15,7 @@
|
||||
<p class="visual-warning">⚠️ Red squares are deadly - avoid them!</p>
|
||||
<p class="audio-warning">🔊 Hidden dangers end the game - listen for directions!</p>
|
||||
<p class="audio-hint">🗣️ Speech directions: "left", "right", "up", "down"</p>
|
||||
<p class="probe-hint">👁️ Hold Shift + direction to detect invisible dangers!</p>
|
||||
</div>
|
||||
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
||||
<div class="controls">
|
||||
|
||||
43
src/game.js
43
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();
|
||||
|
||||
39
src/input.js
39
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);
|
||||
|
||||
58
src/maze.js
58
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;
|
||||
|
||||
@ -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; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user