Show code
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Dynamic Replanning Robot</title>
<style>
:root{color-scheme:dark}
*{box-sizing:border-box}
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:radial-gradient(120% 120% at 50% 0%,#141c34 0%,#070a14 70%);color:#e6ecff;font:14px/1.4 ui-sans-serif,system-ui,"Segoe UI",Roboto,sans-serif;padding:18px}
.wrap{width:min(760px,100%)}
h1{margin:0 0 10px;font-size:17px;letter-spacing:.02em;font-weight:650}
.hud{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}
.p{background:#151d33;border:1px solid #263252;border-radius:8px;padding:6px 10px;font-variant-numeric:tabular-nums}
.p b{color:#8fa4d4;font-weight:500;margin-right:6px;font-size:12px;text-transform:uppercase;letter-spacing:.06em}
canvas{width:100%;height:auto;display:block;border:1px solid #263252;border-radius:10px;background:#0b1020}
.leg{margin-top:9px;color:#8fa4d4;font-size:12px}
</style></head>
<body><div class="wrap">
<h1>Robot replanning on a changing map</h1>
<div class="hud"><div class="p"><b>Steps</b><span id="s">0</span></div><div class="p"><b>Replans</b><span id="r">0</span></div><div class="p"><b>Walls</b><span id="w">0</span></div><div class="p"><b>Status</b><span id="t">planning</span></div></div>
<canvas id="c"></canvas>
<div class="leg">Breadth-first shortest path on a 30×20 grid. New walls appear ahead of the robot; a blocked route triggers an immediate replan from its current cell, drawn in a fresh colour.</div>
</div>
<script>
const W=30,H=20,CS=25,COL=["#38bdf8","#f472b6","#facc15","#4ade80","#c084fc","#fb923c","#22d3ee"];
const cv=document.getElementById("c");cv.width=W*CS;cv.height=H*CS;const ctx=cv.getContext("2d");
const el=i=>document.getElementById(i);
let g,path,pi,rob,goalI,steps,replans,nwalls,t,ci,pulse,done,status;
const ix=(x,y)=>y*W+x,X=i=>i%W,Y=i=>(i/W)|0,ri=n=>(Math.random()*n)|0;
function bfs(s,e){const pv=new Int32Array(W*H).fill(-1),q=[s];pv[s]=s;let h=0;
while(h<q.length){const c=q[h++];if(c===e){const p=[];let k=c;while(k!==pv[k]){p.push(k);k=pv[k]}p.push(k);return p.reverse()}
const cx=X(c),cy=Y(c);
for(const[dx,dy]of[[1,0],[0,1],[-1,0],[0,-1]]){const nx=cx+dx,ny=cy+dy;if(nx<0||ny<0||nx>=W||ny>=H)continue;const n=ix(nx,ny);if(g[n]||pv[n]>=0)continue;pv[n]=c;q.push(n)}}
return null}
function init(){for(;;){g=new Uint8Array(W*H);nwalls=0;
for(let i=0;i<W*H;i++)if(Math.random()<.13){g[i]=1;nwalls++}
const s=ix(1+ri(3),1+ri(H-2)),e=ix(W-2-ri(3),1+ri(H-2));if(s===e)continue;
g[s]=0;g[e]=0;const p=bfs(s,e);if(!p)continue;
rob=s;goalI=e;path=p;pi=0;steps=0;replans=0;t=0;ci=0;pulse=[];done=0;status="following path";return}}
function remainingBlocked(){for(let k=pi;k<path.length;k++)if(g[path[k]])return true;return false}
function dropWall(){const ahead=[];for(let k=pi+2;k<path.length-1;k++)ahead.push(path[k]);
let c=-1;
if(ahead.length&&Math.random()<.85)c=ahead[ri(Math.min(ahead.length,6))];
else{for(let a=0;a<40;a++){const q=ix(ri(W),ri(H));if(!g[q]&&q!==goalI&&q!==rob){c=q;break}}}
if(c<0||c===goalI||c===rob)return;g[c]=1;nwalls++;pulse.push({c,t:.7})}
function replan(){const p=bfs(rob,goalI);
if(!p){status="trapped - new map";done=1.1;return}
path=p;pi=0;replans++;ci=(ci+1)%COL.length;status="replanned";}
function stepDone(){pi++;steps++;rob=path[pi];
if(rob===goalI){status="goal reached";done=1.4;return}
if(steps%3===0)dropWall();
if(remainingBlocked())replan();else if(status==="replanned")status="following path"}
function cell(i,pad,col,rad){ctx.fillStyle=col;ctx.beginPath();ctx.roundRect(X(i)*CS+pad,Y(i)*CS+pad,CS-2*pad,CS-2*pad,rad);ctx.fill()}
function draw(now){ctx.fillStyle="#0b1020";ctx.fillRect(0,0,cv.width,cv.height);
ctx.strokeStyle="rgba(140,170,255,.07)";ctx.lineWidth=1;ctx.beginPath();
for(let x=0;x<=W;x++){ctx.moveTo(x*CS+.5,0);ctx.lineTo(x*CS+.5,H*CS)}
for(let y=0;y<=H;y++){ctx.moveTo(0,y*CS+.5);ctx.lineTo(W*CS,y*CS+.5)}ctx.stroke();
for(let i=0;i<W*H;i++)if(g[i])cell(i,1,"#2b3a5e",3);
const c=COL[ci];
for(let k=pi;k<path.length;k++)cell(path[k],CS*.36,c+"55",2);
for(const p of pulse){const a=p.t/.7;ctx.strokeStyle="rgba(255,90,90,"+a+")";ctx.lineWidth=2;ctx.strokeRect(X(p.c)*CS+1.5,Y(p.c)*CS+1.5,CS-3,CS-3)}
const gp=.5+.5*Math.sin(now/260);
ctx.strokeStyle="rgba(74,222,128,"+(.45+.5*gp)+")";ctx.lineWidth=2;
ctx.strokeRect(X(goalI)*CS+3.5,Y(goalI)*CS+3.5,CS-7,CS-7);
cell(goalI,CS*.34,"#4ade80",2);
let px=X(rob),py=Y(rob);
if(!done&&pi+1<path.length){const n=path[pi+1];px+=(X(n)-px)*t;py+=(Y(n)-py)*t}
const rx=px*CS+CS/2,ry=py*CS+CS/2;
ctx.fillStyle=c+"33";ctx.beginPath();ctx.arc(rx,ry,CS*.72,0,7);ctx.fill();
ctx.fillStyle=c;ctx.beginPath();ctx.arc(rx,ry,CS*.34,0,7);ctx.fill();
ctx.fillStyle="#0b1020";ctx.beginPath();ctx.arc(rx,ry,CS*.13,0,7);ctx.fill()}
let last=0;
function frame(ts){const dt=Math.min(.05,last?(ts-last)/1000:0);last=ts;
if(done>0){done-=dt;if(done<=0)init()}
else{t+=dt*8.5;let guard=0;while(t>=1&&!done&&guard++<4){t-=1;stepDone()}if(done)t=0}
for(const p of pulse)p.t-=dt;pulse=pulse.filter(p=>p.t>0);
el("s").textContent=steps;el("r").textContent=replans;el("w").textContent=nwalls;el("t").textContent=status;
draw(ts);requestAnimationFrame(frame)}
init();requestAnimationFrame(frame);
</script></body></html>