from pathlib import Path root=Path(r'G:\项目\NumberSwipe-Variants') p=root/'tools/ui-template.mjs';s=p.read_text(encoding='utf-8-sig') s=s.replace('canAppend,hint}', 'canAppend,hint,doorOpen,directionNames}') s=s.replace("for(const c of level.cells)if(c.direction)add(c.direction==='straight'?'↔ 必须直行':'↱ 必须转弯',[c.id]);", "for(const c of level.cells)if(c.direction)add(directionNames[c.direction]+' · 可反向',[c.id]);") s=s.replace("add('○ → △ → ○ · 每步换一类',[]);", "add('🔵 蓝 → 🟠 橙 → 🔵 蓝 · 每步换颜色',[]);") s=s.replace(" for(let i=0;i<(level.gates||[]).length;i++)add", " for(let i=0;i<(level.walls||[]).length;i++)add('黑黄墙 · 永久封闭',level.walls[i]);\n for(const d of level.doors||[])add(doorOpen(d,path.length)?'门开 · 再落一格就关':'门关 · 再落一格就开',d.edge);\n for(let i=0;i<(level.gates||[]).length;i++)add") s=s.replace("ctx.fillStyle=badIds.has(c.id)?'#ffe0dc':visited?'#ddd8ff':c.category==='circle'?'#e4f3ff':c.category==='triangle'?'#fff0d4':'#f0f0f6';", "ctx.fillStyle=c.category==='circle'?'#64acf3':c.category==='triangle'?'#ffae55':badIds.has(c.id)?'#ffe0dc':visited?'#ddd8ff':'#f0f0f6';") s=s.replace(" for(let i=0;i<(level.gates||[]).length;i++){const gate=", " for(const edge of level.walls||[])drawBarrier(edge,'wall');\n for(const d of level.doors||[])drawBarrier(d.edge,doorOpen(d,path.length)?'open':'closed');\n for(let i=0;i<(level.gates||[]).length;i++){const gate=") s=s.replace("if(c.direction)symbol=c.direction==='straight'?'↔':'↱';", "if(c.direction)symbol='';") s=s.replace(" ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillStyle=", " if(c.direction)drawChannel(c.direction,x,y-(step?font*.72:0),Math.min(unit*.28,step?font*.4:20));\n ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillStyle=") s=s.replace(" if(bad.length)setStatus", " if(level.doors?.length)renderConstraints();\n if(bad.length)setStatus") a=s.index('function inside(') s=s[:a]+'''function drawChannel(kind,x,y,r){const vector={l:[-1,0],r:[1,0],u:[0,-1],d:[0,1]};const [a,b]=kind.split('').map(k=>vector[k]);ctx.beginPath();ctx.moveTo(x+a[0]*r,y+a[1]*r);ctx.lineTo(x,y);ctx.lineTo(x+b[0]*r,y+b[1]*r);ctx.lineCap='round';ctx.lineJoin='round';ctx.lineWidth=9;ctx.strokeStyle='#c1daf7';ctx.stroke();ctx.lineWidth=4;ctx.strokeStyle='#15497b';ctx.stroke();} function drawBarrier(edge,kind){const left=polygons.filter(p=>p.id===edge[0]),right=polygons.filter(p=>p.id===edge[1]);let common=null;for(const a of left)for(const b of right)for(let i=0;i[a[0]+(b[0]-a[0])*f,a[1]+(b[1]-a[1])*f];ctx.lineCap='butt';ctx.lineWidth=9;ctx.strokeStyle=kind==='open'?'#23895f':kind==='wall'?'#272638':'#aa4d25';ctx.beginPath();if(kind==='open'){ctx.moveTo(...point(.05));ctx.lineTo(...point(.26));ctx.moveTo(...point(.74));ctx.lineTo(...point(.95));}else{ctx.moveTo(...point(.05));ctx.lineTo(...point(.95));}ctx.stroke();if(kind==='wall'){ctx.lineWidth=4;ctx.strokeStyle='#ffc542';ctx.setLineDash([5,4]);ctx.beginPath();ctx.moveTo(...point(.05));ctx.lineTo(...point(.95));ctx.stroke();ctx.setLineDash([]);}if(kind!=='wall'){const [x,y]=point(.5);ctx.font='700 11px system-ui';ctx.textAlign='center';ctx.fillStyle=kind==='open'?'#12623f':'#993b1e';ctx.fillText(kind==='open'?'开':'关',x+10,y-8);}} ''' +s[a:] s=s.replace("markError(id,path.length?'此处类别相同或门尚未打开。':'请从数字 1 开始。');", "markError(id,path.length?(level.walls?.some(e=>e.includes(path.at(-1))&&e.includes(id))?'这是永久墙,不能穿过。':level.doors?.some(d=>d.edge.includes(path.at(-1))&&d.edge.includes(id))?'门现在关闭;绕行落格后会切换。':'同色格不能连续经过,请换另一种颜色。'):'请从数字 1 开始。');") s=s.replace("openedGates:(level.gates||[]).map(g=>path.includes(g.switchCell))", "openedGates:(level.gates||[]).map(g=>path.includes(g.switchCell)),doors:(level.doors||[]).map(d=>doorOpen(d,path.length))") p.write_text(s,encoding='utf-8') p=root/'tools/build.mjs';s=p.read_text(encoding='utf-8-sig').replace('Built 7 standalone pages + index.','Built 5 standalone pages + index.').replace('一笔路径,七种推理','一笔路径,五种推理').replace('56 个机制验证候选关','40 个机制验证候选关').replace('门和类别限制会阻止移动','永久墙、关闭的门和同色移动会阻止移动') s=s.replace("${esc(d.rule)}

", "${esc(d.rule)}

") s=s.replace('