Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.

Commit 8d1d11d

Browse files
authored
Update mazegame.html
1 parent 2a3e1a4 commit 8d1d11d

1 file changed

Lines changed: 148 additions & 125 deletions

File tree

website/mazegame.html

Lines changed: 148 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
6-
<title>Perfect Maze — Always Solvable</title>
6+
<title>Maze Game v2</title>
77
<link rel="icon" type="image/x-icon" href="https://usrx215.github.io/scgames/mazegame.png">
88
<style>
99
:root { --bg:#0b0e14; --panel:#11151f; --ink:#e6e6e6; --muted:#9aa4b2; --accent:#6ee7ff; --accent2:#a7f3d0; }
@@ -18,8 +18,9 @@
1818
.controls button { cursor:pointer; transition: transform .05s ease; }
1919
.controls button:active { transform: translateY(1px); }
2020
.controls .hint { color:var(--muted); font-size:12px; margin-left:6px; }
21-
main { display:grid; place-items:center; padding:12px; }
22-
canvas { background:#05070c; border-radius:16px; box-shadow: 0 10px 40px #00000066, inset 0 0 0 1px #1d2433; image-rendering: pixelated; }
21+
main { display:grid; place-items:center; padding:12px; position:relative; }
22+
canvas { background:#05070c; border-radius:12px; box-shadow: 0 10px 40px #00000066, inset 0 0 0 1px #1d2433; image-rendering: pixelated; }
23+
#minimap { position:absolute; top:20px; right:20px; background:#000; border:1px solid #444; border-radius:8px; image-rendering: pixelated; }
2324
footer { text-align:center; color:var(--muted); font-size:12px; padding:8px 0 12px; }
2425
.kbd { padding:1px 6px; border:1px solid #32415f; border-bottom-width:2px; border-radius:6px; background:#0f1422; color:#c9d2e1; font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; font-size:12px; }
2526
</style>
@@ -29,233 +30,255 @@
2930
<h1>Maze Game v2</h1>
3031
<div class="controls">
3132
<label>Width (cells)
32-
<input id="w" type="number" min="5" step="2" value="32" />
33+
<input id="w" type="number" min="5" step="2" value="41" />
3334
</label>
3435
<label>Height (cells)
35-
<input id="h" type="number" min="5" step="2" value="32" />
36+
<input id="h" type="number" min="5" step="2" value="41" />
3637
</label>
3738
<label>Cell px
38-
<input id="cell" type="number" min="4" max="32" value="20" />
39+
<input id="cell" type="number" min="4" max="64" value="16" />
3940
</label>
4041
<label>Seed
4142
<input id="seed" type="text" placeholder="random" />
4243
</label>
4344
<label><input id="showPath" type="checkbox" /> Show solution</label>
4445
<button id="regen">Regenerate</button>
4546
<button id="reset">Reset Player</button>
47+
<button id="showSeed">Show Seed</button>
4648
<span class="hint">Controls: <span class="kbd">WASD</span>/<span class="kbd">↑←↓→</span>, <span class="kbd">R</span> regen</span>
4749
</div>
4850
</header>
4951
<main>
5052
<canvas id="maze"></canvas>
53+
<canvas id="minimap" width="160" height="160"></canvas>
5154
</main>
55+
<footer>
56+
Perfect maze — camera locked to player, minimap reveals visited tiles.
57+
</footer>
5258

5359
<script>
54-
// --- Utilities: PRNG with seed (Mulberry32) ---
60+
// PRNG (seedable)
5561
function xmur3(str){ let h=1779033703^str.length; for(let i=0;i<str.length;i++){h=Math.imul(h^str.charCodeAt(i),3432918353); h=h<<13|h>>>19;} return ()=>{h=Math.imul(h^h>>>16,2246822507); h=Math.imul(h^h>>>13,3266489909); return (h^h>>>16)>>>0;}; }
5662
function mulberry32(a){ return function(){ let t=a+=0x6D2B79F5; t=Math.imul(t^t>>>15,t|1); t^=t+Math.imul(t^t>>>7,t|61); return ((t^t>>>14)>>>0)/4294967296; } }
5763
function makeRng(seedStr){ if(!seedStr) return Math.random; const seedFn=xmur3(seedStr); return mulberry32(seedFn()); }
5864

59-
// Grid helpers: We use odd coordinates as "air" cells, even coordinates as walls.
60-
// This produces a classic perfect maze where all air spaces are connected and solvable.
61-
6265
const canvas = document.getElementById('maze');
6366
const ctx = canvas.getContext('2d');
67+
const minimap = document.getElementById('minimap');
68+
const mctx = minimap.getContext('2d');
6469

65-
let grid, W, H, CELL, rng, player, goal;
70+
let grid, W, H, CELL, rng, player, goal, visited;
71+
const VIEW = 21; // visible tile window (odd preferred)
72+
let currentSeed = null;
6673

6774
function initGrid(widthCells, heightCells){
68-
// Force odd dimensions to maintain the wall/cell pattern.
6975
W = widthCells|0; H = heightCells|0; if(W%2===0) W++; if(H%2===0) H++;
70-
grid = Array.from({length:H}, (_,y)=>Array.from({length:W},(_,x)=>1)); // 1=wall, 0=air
71-
// Frame walls remain 1; we'll carve internal passages with randomized DFS.
76+
grid = Array.from({length:H}, ()=>Array(W).fill(1));
77+
visited = Array.from({length:H}, ()=>Array(W).fill(false));
7278
}
7379

74-
function shuffle(arr){ for(let i=arr.length-1;i>0;i--){ const j=(rng()* (i+1))|0; [arr[i],arr[j]]=[arr[j],arr[i]]; } return arr; }
80+
function shuffle(arr){ for(let i=arr.length-1;i>0;i--){const j=(rng()*(i+1))|0; [arr[i],arr[j]]=[arr[j],arr[i]];} return arr; }
7581

7682
function carveMaze(){
77-
// Start at (1,1), carve passages by DFS jumping 2 cells at a time, knocking down walls between.
78-
const stack = [[1,1]]; grid[1][1]=0;
83+
const stack = [[1,1]]; grid[1][1] = 0;
7984
while(stack.length){
8085
const [cx,cy] = stack[stack.length-1];
81-
// candidates: N S E W two steps away within bounds, still walls (unvisited cells)
8286
const dirs = shuffle([[0,-2],[0,2],[2,0],[-2,0]]);
8387
let carved = false;
8488
for(const [dx,dy] of dirs){
85-
const nx=cx+dx, ny=cy+dy;
89+
const nx = cx+dx, ny = cy+dy;
8690
if(ny>0 && ny<H-1 && nx>0 && nx<W-1 && grid[ny][nx]===1){
87-
// Knock down the wall between (cx,cy) and (nx,ny)
88-
grid[cy+dy/2][cx+dx/2]=0; // middle wall
89-
grid[ny][nx]=0; // new cell
90-
stack.push([nx,ny]);
91-
carved = true;
92-
break;
91+
grid[cy+dy/2][cx+dx/2] = 0; grid[ny][nx] = 0;
92+
stack.push([nx,ny]); carved = true; break;
9393
}
9494
}
9595
if(!carved) stack.pop();
9696
}
97-
// Select start/goal on opposite edges of the carved region for nice paths
98-
player = {x:1, y:1};
97+
player = {x:1,y:1};
9998
goal = farthestFrom(player);
100-
// Optionally, place player at farthest from goal, to maximize path length
10199
player = farthestFrom(goal);
102100
}
103101

104-
function farthestFrom(start){
105-
const dist = bfsDistances(start.x, start.y);
106-
let max= -1, pos=start;
107-
for(let y=1;y<H;y+=2){
108-
for(let x=1;x<W;x+=2){
109-
if(dist[y][x]!==Infinity && dist[y][x]>max){ max=dist[y][x]; pos={x,y}; }
110-
}
111-
}
112-
return pos;
113-
}
114-
115102
function bfsDistances(sx,sy){
116-
const dist = Array.from({length:H},()=>Array(W).fill(Infinity));
117-
const q=[[sx,sy]]; dist[sy][sx]=0;
118-
const moves=[[1,0],[-1,0],[0,1],[0,-1]];
103+
const dist = Array.from({length:H}, ()=>Array(W).fill(Infinity));
104+
const q = [[sx,sy]]; dist[sy][sx]=0;
105+
const moves = [[1,0],[-1,0],[0,1],[0,-1]];
119106
while(q.length){
120-
const [x,y]=q.shift();
107+
const [x,y] = q.shift();
121108
for(const [dx,dy] of moves){
122-
const nx=x+dx, ny=y+dy;
123-
if(nx>=0&&nx<W&&ny>=0&&ny<H && grid[ny][nx]===0 && dist[ny][nx]===Infinity){
124-
dist[ny][nx]=dist[y][x]+1; q.push([nx,ny]);
109+
const nx = x+dx, ny = y+dy;
110+
if(nx>=0 && ny>=0 && nx<W && ny<H && grid[ny][nx]===0 && dist[ny][nx]===Infinity){
111+
dist[ny][nx] = dist[y][x] + 1; q.push([nx,ny]);
125112
}
126113
}
127114
}
128115
return dist;
129116
}
130117

118+
function farthestFrom(start){
119+
const dist = bfsDistances(start.x, start.y);
120+
let max = -1, pos = {x:start.x,y:start.y};
121+
for(let y=1;y<H;y+=2){ for(let x=1;x<W;x+=2){ if(dist[y][x]!==Infinity && dist[y][x] > max){ max = dist[y][x]; pos = {x,y}; } } }
122+
return pos;
123+
}
124+
131125
function shortestPath(from, to){
132-
// Reconstruct one shortest path via BFS parents
133-
const dist=bfsDistances(from.x, from.y);
126+
const dist = bfsDistances(from.x, from.y);
134127
if(dist[to.y][to.x]===Infinity) return [];
135-
const path=[[to.x,to.y]];
136-
const moves=[[1,0],[-1,0],[0,1],[0,-1]];
137-
let [x,y]=[to.x,to.y];
128+
const path = [[to.x,to.y]];
129+
const moves = [[1,0],[-1,0],[0,1],[0,-1]];
130+
let [x,y] = [to.x,to.y];
138131
while(x!==from.x || y!==from.y){
139-
let best=null, bestd=Infinity;
132+
let best = null, bestd = Infinity;
140133
for(const [dx,dy] of moves){
141-
const nx=x+dx, ny=y+dy;
142-
if(nx>=0&&nx<W&&ny>=0&&ny<H && dist[ny][nx] < bestd){
143-
bestd=dist[ny][nx]; best=[nx,ny];
144-
}
134+
const nx = x+dx, ny = y+dy;
135+
if(nx>=0 && ny>=0 && nx<W && ny<H && dist[ny][nx] < bestd){ bestd = dist[ny][nx]; best = [nx,ny]; }
145136
}
146-
if(!best) break; // should not happen in perfect maze
147-
[x,y]=best; path.push([x,y]);
137+
if(!best) break;
138+
[x,y] = best; path.push([x,y]);
148139
}
149140
return path.reverse();
150141
}
151142

152143
function draw(){
153-
const pad=8; canvas.width = W*CELL + pad*2; canvas.height = H*CELL + pad*2;
154-
ctx.fillStyle = '#03060b'; ctx.fillRect(0,0,canvas.width, canvas.height);
155-
156-
// Draw maze
157-
for(let y=0;y<H;y++){
158-
for(let x=0;x<W;x++){
159-
const px = pad + x*CELL, py = pad + y*CELL;
160-
if(grid[y][x]===1){
161-
// walls next to air by construction; render them slightly glossy
162-
ctx.fillStyle = '#101826';
163-
ctx.fillRect(px,py,CELL,CELL);
164-
ctx.fillStyle = 'rgba(255,255,255,0.04)';
165-
ctx.fillRect(px,py, CELL, Math.max(1, CELL*0.15));
166-
} else {
167-
ctx.fillStyle = '#0c1220';
168-
ctx.fillRect(px,py,CELL,CELL);
169-
}
144+
canvas.width = VIEW * CELL;
145+
canvas.height = VIEW * CELL;
146+
ctx.fillStyle = '#03060b'; ctx.fillRect(0,0,canvas.width,canvas.height);
147+
const half = VIEW >> 1;
148+
for(let dy=-half; dy<=half; dy++){
149+
for(let dx=-half; dx<=half; dx++){
150+
const worldX = player.x + dx;
151+
const worldY = player.y + dy;
152+
const sx = (dx + half) * CELL;
153+
const sy = (dy + half) * CELL;
154+
if(worldX >= 0 && worldY >= 0 && worldX < W && worldY < H){
155+
if(grid[worldY][worldX] === 1){ ctx.fillStyle = '#101826'; ctx.fillRect(sx, sy, CELL, CELL); ctx.fillStyle='rgba(255,255,255,0.04)'; ctx.fillRect(sx, sy, CELL, Math.max(1, CELL*0.12)); }
156+
else { ctx.fillStyle = '#0c1220'; ctx.fillRect(sx, sy, CELL, CELL); }
157+
} else { ctx.fillStyle = '#0b0f16'; ctx.fillRect(sx, sy, CELL, CELL); }
170158
}
171159
}
172-
173-
// Draw solution path if toggled
174160
if(document.getElementById('showPath').checked){
175161
const path = shortestPath(player, goal);
176-
ctx.globalAlpha = 0.9;
177-
for(const [x,y] of path){
178-
const px = pad + x*CELL, py = pad + y*CELL;
179-
ctx.fillStyle = '#6ee7ff';
180-
ctx.fillRect(px+CELL*0.25, py+CELL*0.25, CELL*0.5, CELL*0.5);
162+
ctx.globalAlpha = 0.95;
163+
for(const [px,py] of path){
164+
const dx = px - player.x; const dy = py - player.y;
165+
if(Math.abs(dx) <= half && Math.abs(dy) <= half){
166+
const sx = (dx + half) * CELL + CELL*0.25;
167+
const sy = (dy + half) * CELL + CELL*0.25;
168+
ctx.fillStyle = '#6ee7ff'; ctx.fillRect(sx, sy, CELL*0.5, CELL*0.5);
169+
}
181170
}
182171
ctx.globalAlpha = 1;
183172
}
184-
185-
// Draw goal
186-
;{
187-
const gx = pad + goal.x*CELL, gy = pad + goal.y*CELL;
188-
ctx.fillStyle = '#a7f3d0';
189-
ctx.beginPath();
190-
ctx.roundRect(gx+CELL*0.15, gy+CELL*0.15, CELL*0.7, CELL*0.7, 4);
191-
ctx.fill();
173+
const gdx = goal.x - player.x, gdy = goal.y - player.y;
174+
if(Math.abs(gdx) <= half && Math.abs(gdy) <= half){
175+
const gsx = (gdx + half) * CELL + CELL*0.15;
176+
const gsy = (gdy + half) * CELL + CELL*0.15;
177+
ctx.fillStyle = '#a7f3d0'; ctx.fillRect(gsx, gsy, CELL*0.7, CELL*0.7);
192178
}
179+
const center = half * CELL;
180+
ctx.fillStyle = '#e5e7eb'; ctx.fillRect(center + CELL*0.2, center + CELL*0.2, CELL*0.6, CELL*0.6);
181+
visited[player.y][player.x] = true;
182+
drawMinimap();
183+
}
193184

194-
// Draw player
195-
const px = pad + player.x*CELL, py = pad + player.y*CELL;
196-
ctx.fillStyle = '#e5e7eb';
197-
ctx.beginPath();
198-
ctx.roundRect(px+CELL*0.2, py+CELL*0.2, CELL*0.6, CELL*0.6, 4);
199-
ctx.fill();
185+
function drawMinimap(){
186+
const mw = minimap.width, mh = minimap.height;
187+
const scaleX = mw / W, scaleY = mh / H;
188+
mctx.fillStyle = '#000'; mctx.fillRect(0,0,mw,mh);
189+
for(let y=0;y<H;y++){
190+
for(let x=0;x<W;x++){
191+
if(!visited[y][x]) continue;
192+
mctx.fillStyle = grid[y][x]===0 ? '#0c1220' : '#101826';
193+
mctx.fillRect(x*scaleX, y*scaleY, scaleX, scaleY);
194+
}
195+
}
196+
mctx.fillStyle = '#e5e7eb'; mctx.fillRect(player.x*scaleX, player.y*scaleY, scaleX, scaleY);
197+
mctx.fillStyle = '#a7f3d0'; mctx.fillRect(goal.x*scaleX, goal.y*scaleY, scaleX, scaleY);
200198
}
201199

202200
function regenerate(){
203-
const w = Math.max(5, parseInt(document.getElementById('w').value,10)||31);
204-
const h = Math.max(5, parseInt(document.getElementById('h').value,10)||31);
205-
CELL = Math.max(4, Math.min(32, parseInt(document.getElementById('cell').value,10)||16));
201+
const w = Math.max(5, parseInt(document.getElementById('w').value,10) || 31);
202+
const h = Math.max(5, parseInt(document.getElementById('h').value,10) || 31);
203+
CELL = Math.max(4, Math.min(64, parseInt(document.getElementById('cell').value,10) || 16));
206204
const seedStr = document.getElementById('seed').value.trim();
207-
rng = makeRng(seedStr || null);
208-
initGrid(w, h);
205+
currentSeed = seedStr || (Math.random() + '').slice(2);
206+
rng = makeRng(seedStr || currentSeed);
207+
208+
initGrid(w,h);
209209
carveMaze();
210+
211+
visited = Array.from({length:H}, ()=>Array(W).fill(false));
212+
visited[player.y][player.x] = true;
213+
214+
canvas.width = VIEW * CELL; canvas.height = VIEW * CELL;
215+
210216
draw();
211217
}
212218

213219
function tryMove(dx,dy){
214-
const nx = player.x + dx; const ny = player.y + dy;
215-
if(nx<0||ny<0||nx>=W||ny>=H) return; // out of bounds
216-
if(grid[ny][nx]===0){ player.x=nx; player.y=ny; draw(); checkWin(); }
220+
const nx = player.x + dx, ny = player.y + dy;
221+
if(nx<0||ny<0||nx>=W||ny>=H) return;
222+
if(grid[ny][nx]===0){ player.x = nx; player.y = ny; draw(); checkWin(); }
217223
}
218224

219-
function checkWin(){
220-
if(player.x===goal.x && player.y===goal.y){
221-
flashWin();
222-
}
223-
}
225+
function checkWin(){ if(player.x===goal.x && player.y===goal.y) flashWin(); }
224226

225227
function flashWin(){
226-
// Simple celebratory pulse
227-
let t=0, id=null;
228+
let t=0;
228229
function step(){
229-
t+=0.05; draw();
230-
const pad=8; const x = 8 + player.x*CELL + CELL/2; const y=8 + player.y*CELL + CELL/2;
231-
ctx.save(); ctx.translate(x,y); ctx.globalAlpha = Math.max(0, 1.2 - t);
232-
ctx.beginPath(); ctx.arc(0,0, Math.max(0,CELL*1.2*t), 0, Math.PI*2);
233-
ctx.strokeStyle = '#6ee7ff'; ctx.lineWidth = 3; ctx.stroke(); ctx.restore();
234-
if(t<1.2) id=requestAnimationFrame(step);
230+
draw();
231+
const half = VIEW>>1;
232+
const cx = half*CELL + CELL*0.5, cy = cx;
233+
ctx.save();
234+
ctx.globalAlpha = Math.max(0,1.2 - t);
235+
ctx.strokeStyle = '#6ee7ff';
236+
ctx.lineWidth = 3;
237+
ctx.beginPath();
238+
ctx.arc(cx, cy, CELL*1.2*t, 0, Math.PI*2);
239+
ctx.stroke();
240+
ctx.restore();
241+
t+=0.06;
242+
if(t<1.2) requestAnimationFrame(step);
243+
else {
244+
document.getElementById('seed').value = '';
245+
currentSeed = (Math.random() + '').slice(2);
246+
rng = makeRng(currentSeed);
247+
regenerate();
248+
}
235249
}
236250
step();
237251
}
238252

239-
// Input
253+
// movement
240254
window.addEventListener('keydown', (e)=>{
241-
const k=e.key.toLowerCase();
255+
const k = e.key.toLowerCase();
242256
if(k==='arrowup' || k==='w') tryMove(0,-1);
243257
else if(k==='arrowdown' || k==='s') tryMove(0,1);
244258
else if(k==='arrowleft' || k==='a') tryMove(-1,0);
245259
else if(k==='arrowright' || k==='d') tryMove(1,0);
246-
else if(k==='r'){ regenerate(); }
260+
else if(k==='r') regenerate();
247261
});
248262

249263
document.getElementById('regen').addEventListener('click', regenerate);
250-
document.getElementById('reset').addEventListener('click', ()=>{ player=farthestFrom(goal); draw(); });
264+
document.getElementById('reset').addEventListener('click', ()=>{ player = {x:1,y:1}; visited = Array.from({length:H}, ()=>Array(W).fill(false)); visited[player.y][player.x]=true; draw();});
251265
document.getElementById('showPath').addEventListener('change', draw);
266+
document.getElementById('showSeed').addEventListener('click', () => {
267+
document.getElementById('seed').value = currentSeed;
268+
});
252269

253-
// Kick off
254270
(function start(){
255-
document.getElementById('w').value = 31;
256-
document.getElementById('h').value = 31;
271+
document.getElementById('w').value = 41;
272+
document.getElementById('h').value = 41;
257273
document.getElementById('cell').value = 16;
258-
regenerate();
274+
currentSeed = (Math.random() + '').slice(2);
275+
rng = makeRng(currentSeed);
276+
initGrid(41,41);
277+
carveMaze();
278+
CELL = parseInt(document.getElementById('cell').value,10) || 16;
279+
canvas.width = VIEW * CELL; canvas.height = VIEW * CELL;
280+
visited[player.y][player.x] = true;
281+
draw();
259282
})();
260283
</script>
261284
</body>

0 commit comments

Comments
 (0)