Skip to content

Commit 35d00fe

Browse files
Merge pull request steam-bell-92#764 from siri-004/fix/snake-audio-loop
fix : stop infinite game-over audio looping in snake game
2 parents a39affe + 11b78ea commit 35d00fe

2 files changed

Lines changed: 156 additions & 37 deletions

File tree

web-app/js/audioManager.js

Lines changed: 139 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,165 @@
11
const AudioManager = (() => {
22
let _ctx = null;
3+
let soundLocks = new Set();
4+
35
function ctx() {
46
if (!_ctx) _ctx = new (window.AudioContext || window.webkitAudioContext)();
57
if (_ctx.state === 'suspended') _ctx.resume();
68
return _ctx;
79
}
10+
811
function enabled() {
912
const v = localStorage.getItem('soundEnabled');
1013
return v === null ? true : v === 'true';
1114
}
12-
function beep(freq, dur, type='sine', gain=0.3, t=null) {
13-
const c = ctx(); const now = t ?? c.currentTime;
14-
const osc = c.createOscillator(), g = c.createGain();
15-
osc.connect(g); g.connect(c.destination);
16-
osc.type = type; osc.frequency.setValueAtTime(freq, now);
15+
16+
function beep(freq, dur, type = 'sine', gain = 0.3, t = null) {
17+
const c = ctx();
18+
const now = t ?? c.currentTime;
19+
20+
const osc = c.createOscillator(),
21+
g = c.createGain();
22+
23+
osc.connect(g);
24+
g.connect(c.destination);
25+
26+
osc.type = type;
27+
osc.frequency.setValueAtTime(freq, now);
28+
1729
g.gain.setValueAtTime(0, now);
18-
g.gain.linearRampToValueAtTime(gain, now+0.01);
19-
g.gain.exponentialRampToValueAtTime(0.001, now+dur);
20-
osc.start(now); osc.stop(now+dur+0.01);
30+
g.gain.linearRampToValueAtTime(gain, now + 0.01);
31+
g.gain.exponentialRampToValueAtTime(0.001, now + dur);
32+
33+
osc.start(now);
34+
osc.stop(now + dur + 0.01);
2135
}
22-
function noise(dur=0.1, gain=0.2) {
23-
const c = ctx(), buf = c.createBuffer(1, c.sampleRate*dur, c.sampleRate);
36+
37+
function noise(dur = 0.1, gain = 0.2) {
38+
const c = ctx(),
39+
buf = c.createBuffer(1, c.sampleRate * dur, c.sampleRate);
40+
2441
const d = buf.getChannelData(0);
25-
for (let i=0;i<d.length;i++) d[i]=Math.random()*2-1;
26-
const src = c.createBufferSource(); src.buffer = buf;
27-
const g = c.createGain(), f = c.createBiquadFilter();
28-
f.type='highpass'; f.frequency.value=1000;
29-
src.connect(f); f.connect(g); g.connect(c.destination);
42+
for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1;
43+
44+
const src = c.createBufferSource();
45+
src.buffer = buf;
46+
47+
const g = c.createGain(),
48+
f = c.createBiquadFilter();
49+
50+
f.type = 'highpass';
51+
f.frequency.value = 1000;
52+
53+
src.connect(f);
54+
f.connect(g);
55+
g.connect(c.destination);
56+
3057
const t = c.currentTime;
3158
g.gain.setValueAtTime(gain, t);
32-
g.gain.exponentialRampToValueAtTime(0.001, t+dur);
33-
src.start(t); src.stop(t+dur+0.01);
59+
g.gain.exponentialRampToValueAtTime(0.001, t + dur);
60+
61+
src.start(t);
62+
src.stop(t + dur + 0.01);
3463
}
64+
3565
const sounds = {
36-
snake_eat() { const c=ctx(),n=c.currentTime; beep(300,.06,'square',.25,n); beep(600,.08,'square',.20,n+.05); },
37-
snake_die() { const c=ctx(),n=c.currentTime; beep(400,.12,'sawtooth',.3,n); beep(200,.20,'sawtooth',.25,n+.10); beep(100,.30,'sine',.20,n+.25); },
38-
mole_hit() { noise(.08,.35); const c=ctx(); beep(180,.10,'sine',.2,c.currentTime+.02); },
39-
card_flip() { noise(.05,.12); const c=ctx(); beep(800,.04,'sine',.08,c.currentTime+.01); },
40-
card_deal() { noise(.07,.18); const c=ctx(); beep(300,.06,'sine',.12,c.currentTime+.02); },
41-
game_win() { const c=ctx(),n=c.currentTime; [523,659,784,1047].forEach((f,i)=>beep(f,.15,'sine',.25,n+i*.12)); },
42-
game_over() { const c=ctx(),n=c.currentTime; beep(440,.15,'sawtooth',.3,n); beep(349,.20,'sawtooth',.28,n+.18); beep(262,.35,'sine',.25,n+.40); },
43-
score_point() { beep(880,.08,'sine',.2); },
44-
wrong() { beep(150,.15,'sawtooth',.2); },
45-
click() { beep(600,.04,'sine',.1); },
66+
snake_eat() {
67+
const c = ctx(),
68+
n = c.currentTime;
69+
beep(300, 0.06, 'square', 0.25, n);
70+
beep(600, 0.08, 'square', 0.2, n + 0.05);
71+
},
72+
73+
snake_die() {
74+
const c = ctx(),
75+
n = c.currentTime;
76+
beep(400, 0.12, 'sawtooth', 0.3, n);
77+
beep(200, 0.2, 'sawtooth', 0.25, n + 0.1);
78+
beep(100, 0.3, 'sine', 0.2, n + 0.25);
79+
},
80+
81+
mole_hit() {
82+
noise(0.08, 0.35);
83+
const c = ctx();
84+
beep(180, 0.1, 'sine', 0.2, c.currentTime + 0.02);
85+
},
86+
87+
card_flip() {
88+
noise(0.05, 0.12);
89+
const c = ctx();
90+
beep(800, 0.04, 'sine', 0.08, c.currentTime + 0.01);
91+
},
92+
93+
card_deal() {
94+
noise(0.07, 0.18);
95+
const c = ctx();
96+
beep(300, 0.06, 'sine', 0.12, c.currentTime + 0.02);
97+
},
98+
99+
game_win() {
100+
const c = ctx(),
101+
n = c.currentTime;
102+
[523, 659, 784, 1047].forEach((f, i) =>
103+
beep(f, 0.15, 'sine', 0.25, n + i * 0.12)
104+
);
105+
},
106+
107+
game_over() {
108+
const c = ctx(),
109+
n = c.currentTime;
110+
beep(440, 0.15, 'sawtooth', 0.3, n);
111+
beep(349, 0.2, 'sawtooth', 0.28, n + 0.18);
112+
beep(262, 0.35, 'sine', 0.25, n + 0.4);
113+
},
114+
115+
score_point() {
116+
beep(880, 0.08, 'sine', 0.2);
117+
},
118+
119+
wrong() {
120+
beep(150, 0.15, 'sawtooth', 0.2);
121+
},
122+
123+
click() {
124+
beep(600, 0.04, 'sine', 0.1);
125+
},
46126
};
127+
47128
return {
48129
play(name) {
49130
if (!enabled()) return false;
50-
if (sounds[name]) { try { sounds[name](); } catch(e){} return true; }
131+
132+
// 🛑 Strict permanent lock until game reset explicitly clears it
133+
if (name === "snake_die" || name === "game_over") {
134+
if (soundLocks.has(name)) return false;
135+
soundLocks.add(name);
136+
}
137+
138+
if (sounds[name]) {
139+
try {
140+
sounds[name]();
141+
} catch (e) {}
142+
return true;
143+
}
144+
51145
return false;
52146
},
53-
setEnabled(v) { localStorage.setItem('soundEnabled', String(!!v)); },
54-
isEnabled() { return enabled(); },
55-
list() { return Object.keys(sounds); },
147+
148+
setEnabled(v) {
149+
localStorage.setItem('soundEnabled', String(!!v));
150+
},
151+
152+
isEnabled() {
153+
return enabled();
154+
},
155+
156+
list() {
157+
return Object.keys(sounds);
158+
},
159+
160+
reset() {
161+
soundLocks.clear();
162+
}
56163
};
57164
})();
58-
window.AudioManager = AudioManager;
165+
window.AudioManager = AudioManager;

web-app/js/projects/snake.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,12 @@ function getSnakeGameHTML() {
198198

199199
// --- GAME LOGIC STATE ---
200200
let direction = { x: 0, y: 0 };
201-
let speed = 9; // System baseline operational configuration state
202-
let scoreMultiplier = 2; // Baseline multiplier scalar state
201+
let speed = 9;
202+
let scoreMultiplier = 2;
203203
let score = 0;
204204
let lastPaintTime = 0;
205205
let isPaused = false;
206+
let isGameOver = false; // 👈 Added tracking state flag
206207
let snakeArr = [{ x: 13, y: 10 }];
207208
let food = { x: 6, y: 7 };
208209

@@ -215,7 +216,7 @@ const CONFIG_DIFFICULTY = {
215216

216217
function main(ctime) {
217218
const canvas = document.getElementById('snakeCanvas');
218-
if (!canvas) return; // Exit loop if the game modal has been closed
219+
if (!canvas) return;
219220

220221
window.requestAnimationFrame(main);
221222
if ((ctime - lastPaintTime) / 1000 < 1 / speed) {
@@ -268,7 +269,12 @@ function gameEngine() {
268269
direction = { x: 0, y: 0 };
269270
document.getElementById('final-score').innerHTML = score;
270271
document.getElementById("game-over-overlay").classList.remove("hidden");
271-
if (window.AudioManager) AudioManager.play("snake_die");
272+
273+
// 🚨 CRITICAL FIX: Only play the sound ONCE right when impact happens
274+
if (!isGameOver) {
275+
if (window.AudioManager) AudioManager.play("snake_die");
276+
isGameOver = true;
277+
}
272278

273279
// Execute persistent local evaluations
274280
checkAndSaveHighScore();
@@ -282,7 +288,7 @@ function gameEngine() {
282288

283289
// Eating food
284290
if (snakeArr[0].y === food.y && snakeArr[0].x === food.x) {
285-
score += (1 * scoreMultiplier); // Scaled multiplier calculations
291+
score += (1 * scoreMultiplier);
286292
document.getElementById('score').innerHTML = score;
287293
snakeArr.unshift({ x: snakeArr[0].x + direction.x, y: snakeArr[0].y + direction.y });
288294
if (window.AudioManager) AudioManager.play("snake_eat");
@@ -332,8 +338,14 @@ function restartGame() {
332338
snakeArr = [{ x: 13, y: 10 }];
333339
score = 0;
334340
isPaused = false;
341+
isGameOver = false; // 👈 Reset the tracking flag state
335342
lastPaintTime = 0;
336343

344+
// 🚨 CRITICAL FIX: Clear the AudioManager permanent sound locks on restart
345+
if (window.AudioManager) {
346+
window.AudioManager.reset();
347+
}
348+
337349
// Reset UI
338350
const pauseBtn = document.getElementById('pauseGameBtn');
339351
if (pauseBtn) pauseBtn.textContent = 'Pause';

0 commit comments

Comments
 (0)