mirror of
https://github.com/kuhyx/slavic_game_jam.git
synced 2026-07-04 15:43:18 +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="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-warning">🔊 Hidden dangers end the game - listen for directions!</p>
|
||||||
<p class="audio-hint">🗣️ Speech directions: "left", "right", "up", "down"</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>
|
</div>
|
||||||
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
<canvas id="gameCanvas" width="800" height="600"></canvas>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
|||||||
43
src/game.js
43
src/game.js
@ -137,6 +137,10 @@ export class Game {
|
|||||||
this.inputHandler.onMove = (direction) => {
|
this.inputHandler.onMove = (direction) => {
|
||||||
this.movePlayer(direction);
|
this.movePlayer(direction);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.inputHandler.onProbe = (direction) => {
|
||||||
|
this.probeDirection(direction);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
movePlayer(direction) {
|
movePlayer(direction) {
|
||||||
@ -160,6 +164,11 @@ export class Game {
|
|||||||
this.handleAudioDanger();
|
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
|
// Check if player reached the exit
|
||||||
if (this.maze.isExit(newX, newY)) {
|
if (this.maze.isExit(newX, newY)) {
|
||||||
this.handleWin();
|
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) {
|
trackMove(x, y) {
|
||||||
// Track player move with timestamp
|
// Track player move with timestamp
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
@ -188,6 +212,25 @@ export class Game {
|
|||||||
this.gameOver();
|
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() {
|
gameOver() {
|
||||||
if (this.soundEnabled) {
|
if (this.soundEnabled) {
|
||||||
this.audioSystem.playGameOverSound();
|
this.audioSystem.playGameOverSound();
|
||||||
|
|||||||
39
src/input.js
39
src/input.js
@ -2,6 +2,7 @@ export class InputHandler {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.keys = new Set();
|
this.keys = new Set();
|
||||||
this.onMove = null; // Callback for movement
|
this.onMove = null; // Callback for movement
|
||||||
|
this.onProbe = null; // Callback for probing with Shift+direction
|
||||||
this.moveDelay = 150; // Milliseconds between moves
|
this.moveDelay = 150; // Milliseconds between moves
|
||||||
this.lastMoveTime = 0;
|
this.lastMoveTime = 0;
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ export class InputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let direction = null;
|
let direction = null;
|
||||||
|
const isShiftHeld = this.keys.has('ShiftLeft') || this.keys.has('ShiftRight');
|
||||||
|
|
||||||
// Check for movement keys (WASD or Arrow keys)
|
// Check for movement keys (WASD or Arrow keys)
|
||||||
if (this.keys.has('KeyW') || this.keys.has('ArrowUp')) {
|
if (this.keys.has('KeyW') || this.keys.has('ArrowUp')) {
|
||||||
@ -48,9 +50,16 @@ export class InputHandler {
|
|||||||
direction = { x: 1, y: 0 };
|
direction = { x: 1, y: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (direction && this.onMove) {
|
if (direction) {
|
||||||
this.onMove(direction);
|
if (isShiftHeld && this.onProbe) {
|
||||||
this.lastMoveTime = now;
|
// 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'),
|
down: this.keys.has('KeyS') || this.keys.has('ArrowDown'),
|
||||||
left: this.keys.has('KeyA') || this.keys.has('ArrowLeft'),
|
left: this.keys.has('KeyA') || this.keys.has('ArrowLeft'),
|
||||||
right: this.keys.has('KeyD') || this.keys.has('ArrowRight'),
|
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
|
// Clean up event listeners
|
||||||
destroy() {
|
destroy() {
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
document.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
|||||||
58
src/maze.js
58
src/maze.js
@ -12,7 +12,8 @@ export class Maze {
|
|||||||
this.SAFE = 1;
|
this.SAFE = 1;
|
||||||
this.DANGEROUS_VISUAL = 2; // Visually dangerous, no audio
|
this.DANGEROUS_VISUAL = 2; // Visually dangerous, no audio
|
||||||
this.DANGEROUS_AUDIO = 3; // Looks safe, has audio when near
|
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() {
|
generate() {
|
||||||
@ -89,16 +90,18 @@ export class Maze {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate target number of dangerous squares (30% of inner safe squares)
|
// Calculate target number of dangerous squares (40% of inner safe squares)
|
||||||
const totalDangerousCount = Math.floor(innerSafeSquares.length * 0.3);
|
const totalDangerousCount = Math.floor(innerSafeSquares.length * 0.4);
|
||||||
const visualDangerousCount = Math.floor(totalDangerousCount * 0.5);
|
const visualDangerousCount = Math.floor(totalDangerousCount * 0.33);
|
||||||
const audioDangerousCount = totalDangerousCount - visualDangerousCount;
|
const audioDangerousCount = Math.floor(totalDangerousCount * 0.33);
|
||||||
|
const hiddenDangerousCount = totalDangerousCount - visualDangerousCount - audioDangerousCount;
|
||||||
|
|
||||||
this.shuffle(innerSafeSquares);
|
this.shuffle(innerSafeSquares);
|
||||||
|
|
||||||
let addedDangerous = 0;
|
let addedDangerous = 0;
|
||||||
let addedVisual = 0;
|
let addedVisual = 0;
|
||||||
let addedAudio = 0;
|
let addedAudio = 0;
|
||||||
|
let addedHidden = 0;
|
||||||
|
|
||||||
// Add dangerous squares one by one, checking path after each addition
|
// Add dangerous squares one by one, checking path after each addition
|
||||||
for (let i = 0; i < innerSafeSquares.length && addedDangerous < totalDangerousCount; i++) {
|
for (let i = 0; i < innerSafeSquares.length && addedDangerous < totalDangerousCount; i++) {
|
||||||
@ -110,6 +113,8 @@ export class Maze {
|
|||||||
dangerType = this.DANGEROUS_VISUAL;
|
dangerType = this.DANGEROUS_VISUAL;
|
||||||
} else if (addedAudio < audioDangerousCount) {
|
} else if (addedAudio < audioDangerousCount) {
|
||||||
dangerType = this.DANGEROUS_AUDIO;
|
dangerType = this.DANGEROUS_AUDIO;
|
||||||
|
} else if (addedHidden < hiddenDangerousCount) {
|
||||||
|
dangerType = this.DANGEROUS_HIDDEN;
|
||||||
} else {
|
} else {
|
||||||
break; // We've added enough dangerous squares
|
break; // We've added enough dangerous squares
|
||||||
}
|
}
|
||||||
@ -125,8 +130,10 @@ export class Maze {
|
|||||||
addedDangerous++;
|
addedDangerous++;
|
||||||
if (dangerType === this.DANGEROUS_VISUAL) {
|
if (dangerType === this.DANGEROUS_VISUAL) {
|
||||||
addedVisual++;
|
addedVisual++;
|
||||||
} else {
|
} else if (dangerType === this.DANGEROUS_AUDIO) {
|
||||||
addedAudio++;
|
addedAudio++;
|
||||||
|
} else {
|
||||||
|
addedHidden++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Path blocked, revert this square to safe
|
// Path blocked, revert this square to safe
|
||||||
@ -220,6 +227,10 @@ export class Maze {
|
|||||||
return this.grid[y][x] === this.DANGEROUS_AUDIO;
|
return this.grid[y][x] === this.DANGEROUS_AUDIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDangerousHidden(x, y) {
|
||||||
|
return this.grid[y][x] === this.DANGEROUS_HIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
isNearAudioDanger(playerX, playerY, range = 1) {
|
isNearAudioDanger(playerX, playerY, range = 1) {
|
||||||
// Check if player is within range of any audio danger squares
|
// Check if player is within range of any audio danger squares
|
||||||
for (let dy = -range; dy <= range; dy++) {
|
for (let dy = -range; dy <= range; dy++) {
|
||||||
@ -335,6 +346,41 @@ export class Maze {
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case this.DANGEROUS_VISUAL:
|
||||||
// Visual-only danger squares - animated red appearance
|
// Visual-only danger squares - animated red appearance
|
||||||
const time = Date.now() * 0.003;
|
const time = Date.now() * 0.003;
|
||||||
|
|||||||
@ -80,6 +80,14 @@ h1 {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.probe-hint {
|
||||||
|
color: #dda0dd !important;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.8;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { opacity: 0.7; }
|
0%, 100% { opacity: 0.7; }
|
||||||
50% { opacity: 1; }
|
50% { opacity: 1; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user