Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dijkstra vs A* on the same weighted grid</title>
<style>
html,body{margin:0;height:100%;background:#0b0f16;color:#e6edf3;display:flex;align-items:center;justify-content:center;
font:13px/1.4 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
canvas{background:#141b25;border-radius:12px;box-shadow:0 18px 60px #000a;max-width:98vw;height:auto}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const W=30,H=20,CS=18,N=W*H,PAD=16,TOP=48,PW=W*CS,PH=H*CS,S=0,G=N-1;
const c=document.getElementById('c'),x=c.getContext('2d');
c.width=PAD*3+PW*2;c.height=TOP+PH+PAD+26;
let cost,gmin,A,B,hold=0;
function gen(){
let f=new Float64Array(N);
for(let i=0;i<N;i++)f[i]=Math.random();
for(let p=0;p<2;p++){const g=new Float64Array(N);
for(let y=0;y<H;y++)for(let X=0;X<W;X++){let s=0,n=0;
for(let dy=-1;dy<=1;dy++)for(let dx=-1;dx<=1;dx++){const a=X+dx,b=y+dy;
if(a<0||b<0||a>=W||b>=H)continue;s+=f[b*W+a];n++}
g[y*W+X]=s/n}
f=g}
let mn=1e9,mx=-1e9;for(const v of f){if(v<mn)mn=v;if(v>mx)mx=v}
cost=new Int32Array(N);
for(let i=0;i<N;i++)cost[i]=1+Math.round(Math.pow((f[i]-mn)/(mx-mn||1),1.35)*8);
cost[S]=1;cost[G]=1;gmin=9;for(const v of cost)if(v<gmin)gmin=v;
}
function mk(useH){
const st={dist:new Float64Array(N).fill(1e18),prev:new Int32Array(N).fill(-1),
done:new Uint8Array(N),inOpen:new Uint8Array(N),open:[S],exp:0,path:null,fin:false,useH:useH};
st.dist[S]=0;st.inOpen[S]=1;return st;
}
function hh(n){return (Math.abs(n%W-G%W)+Math.abs((n/W|0)-(G/W|0)))*gmin}
function trace(st){const p=[];let u=G;while(u>=0){p.push(u);u=st.prev[u]}return p.reverse()}
function step(st){
if(st.fin)return;
if(!st.open.length){st.fin=true;return}
let bi=0,bf=1e18;
for(let i=0;i<st.open.length;i++){const n=st.open[i],f=st.dist[n]+(st.useH?hh(n):0);
if(f<bf){bf=f;bi=i}}
const u=st.open[bi];st.open.splice(bi,1);st.inOpen[u]=0;
if(st.done[u])return;
st.done[u]=1;st.exp++;
if(u===G){st.fin=true;st.path=trace(st);return}
const ux=u%W,uy=u/W|0;
const nb=[[1,0],[-1,0],[0,1],[0,-1]];
for(const d of nb){const a=ux+d[0],b=uy+d[1];
if(a<0||b<0||a>=W||b>=H)continue;
const v=b*W+a,nd=st.dist[u]+cost[v];
if(nd<st.dist[v]-1e-9){st.dist[v]=nd;st.prev[v]=u;
if(!st.done[v]&&!st.inOpen[v]){st.inOpen[v]=1;st.open.push(v)}}}
}
function cell(i){return[(i%W)*CS,(i/W|0)*CS]}
function panel(ox,st,title){
for(let i=0;i<N;i++){const p=cell(i),px=ox+p[0],py=TOP+p[1],v=cost[i];
x.fillStyle='hsl('+(142-v*9)+','+(24+v*3)+'%,'+(80-v*6.6)+'%)';
x.fillRect(px,py,CS-1,CS-1);
if(st.done[i]){x.fillStyle='rgba(56,139,253,.46)';x.fillRect(px,py,CS-1,CS-1)}
else if(st.inOpen[i]){x.fillStyle='rgba(255,193,58,.82)';x.fillRect(px,py,CS-1,CS-1)}}
if(st.path){
x.lineJoin='round';x.lineCap='round';x.beginPath();
for(let k=0;k<st.path.length;k++){const p=cell(st.path[k]);
const px=ox+p[0]+CS/2-.5,py=TOP+p[1]+CS/2-.5;k?x.lineTo(px,py):x.moveTo(px,py)}
x.strokeStyle='#ff4f6d';x.lineWidth=5;x.stroke();
x.strokeStyle='rgba(255,255,255,.9)';x.lineWidth=1.6;x.stroke()}
for(const m of [[S,'#22e08a'],[G,'#ff2f6a']]){const p=cell(m[0]);
x.fillStyle=m[1];x.fillRect(ox+p[0],TOP+p[1],CS-1,CS-1);
x.strokeStyle='#fff';x.lineWidth=1.5;x.strokeRect(ox+p[0]+.75,TOP+p[1]+.75,CS-2.5,CS-2.5)}
x.strokeStyle='rgba(255,255,255,.12)';x.lineWidth=1;
x.strokeRect(ox-.5,TOP-.5,PW,PH);
x.fillStyle='#e6edf3';x.font='bold 15px ui-monospace,monospace';x.fillText(title,ox,20);
x.font='12px ui-monospace,monospace';x.fillStyle='#8fa3b8';
x.fillText('expanded '+st.exp+' · path cost '+(st.path?st.dist[G]:'…')+
(st.fin&&!st.path?' (no path)':''),ox,38);
}
function draw(){
x.clearRect(0,0,c.width,c.height);
panel(PAD,A,'Dijkstra');
panel(PAD*2+PW,B,'A* (Manhattan × '+gmin+')');
const y=TOP+PH+19;
const key=[['#5aa0f5','visited'],['#ffc13a','frontier'],['#ff4f6d','shortest path']];
let kx=PAD;x.font='12px ui-monospace,monospace';
for(const k of key){x.fillStyle=k[0];x.fillRect(kx,y-9,10,10);
x.fillStyle='#8fa3b8';x.fillText(k[1],kx+15,y);kx+=25+x.measureText(k[1]).width}
const same=A.path&&B.path&&Math.abs(A.dist[G]-B.dist[G])<1e-9;
x.fillStyle=same?'#22e08a':'#8fa3b8';
const msg=same?'identical cost '+A.dist[G]+' · A* expanded '+(A.exp-B.exp)+' fewer cells ('+
Math.round(100-B.exp/A.exp*100)+'% less work)':'searching the same random terrain (cost 1–9)…';
x.fillText(msg,c.width-PAD-x.measureText(msg).width,y);
}
function frame(){
requestAnimationFrame(frame);
if(hold>0){if(--hold===0){gen();A=mk(0);B=mk(1)}}
else{for(let k=0;k<7;k++){step(A);step(B)}
if(A.fin&&B.fin)hold=120}
draw();
}
gen();A=mk(0);B=mk(1);requestAnimationFrame(frame);
</script>
</body>
</html>