from pathlib import Path root=Path(r'G:\项目\NumberSwipe-Variants') p=root/'tools/engine-template.mjs' s=p.read_text(encoding='utf-8-sig') s=s.replace(" for(let i=0;i<(level.gates||[]).length;i++)result.push", " for(let i=0;i<(level.walls||[]).length;i++)result.push({key:'wall:'+i,kind:'wall',edge:level.walls[i]});\n for(let i=0;i<(level.doors||[]).length;i++)result.push({key:'door:'+i,kind:'door',...level.doors[i]});\n for(let i=0;i<(level.gates||[]).length;i++)result.push") a=s.index('export function directionAt(');b=s.index('export function violations(',a) s=s[:a]+'''export function directionAt(level,path,k){ if(k===0||k===path.length-1)return null; const [a,b,c]=[path[k-1],path[k],path[k+1]].map(id=>level.cells.find(x=>x.id===id)); const side=(x)=>x.xb.x?'r':x.ya.filter(j=>!alternating||g.cells[i].category!==g.cells[j].category));", " const adj=g.adj.map((a,i)=>a.filter(j=>(!alternating||g.cells[i].category!==g.cells[j].category)&&!active.some(r=>r.kind==='wall'&&r.edge.includes(g.cells[i].id)&&r.edge.includes(g.cells[j].id))));") s=s.replace("let blocked=false;for(const r of active)", "let blocked=edgeBlocked(level,path,g.cells[i].id,g.cells[j].id,{omit});for(const r of active)") s=s.replace("!['straight','turn'].includes(r.value)", "!Object.keys(directionNames).includes(r.value)") s=s.replace(" if(r.kind==='gate'&&", " if((r.kind==='wall'||r.kind==='door')&&(r.edge.length!==2||!g.adj[g.ids.get(r.edge[0])].includes(g.ids.get(r.edge[1]))))throw Error('墙或门不在共边');\n if(r.kind==='gate'&&") s=s.replace(" const pairs=[];", " if((level.doors||[]).length>1)throw Error('步进门只允许一道门');\n const physicalEdges=[...(level.walls||[]),...(level.doors||[]).map(d=>d.edge)].map(e=>[...e].sort().join('|'));if(new Set(physicalEdges).size!==physicalEdges.length)throw Error('墙门共边重复');\n const pairs=[];") p.write_text(s,encoding='utf-8') p=root/'tools/generate.mjs';s=p.read_text(encoding='utf-8-sig') a=s.index(" }else if(def.id==='direction'){");b=s.index(" }else if(def.id.includes('alternation'))",a) s=s[:a]+''' }else if(def.id==='direction'){ const kinds=['lr','du','ru','dr','dl','lu'];const pool={};for(const kind of kinds)pool[kind]=[]; for(let i=1;ipool[k].length):[requested];if(!available.length)return null;const kind=pick(available);if(!pool[kind].length)return null;const id=pick(pool[kind]);pool[kind].splice(pool[kind].indexOf(id),1);level.cells.find(c=>c.id===id).direction=kind;selected.push(kind);} if(index===3&&selected[0]===selected[1])return null; ''' +s[b:] s=s.replace(" }else if(def.id.includes('alternation'))for(let i=0;ic.id===author[i]).category=i%2?'triangle':'circle';", " }else if(def.id.includes('alternation')){for(let i=0;ic.id===author[i]).category=i%2?'triangle':'circle';if(def.geometry==='hex'){const g=graph(level);let edges=[];for(let a=0;a=2:i>=Math.floor(n/3))eligible.push(i);if(!eligible.length)return null;const e=pick(eligible);level.doors=[{edge:[at(e),at(e+1)],initialOpen:false}];}") s=s.replace("r.value==='straight'?'直行':'转弯'", "({lr:'左右直行',du:'上下直行',ru:'上与右',dr:'右与下',dl:'下与左',lu:'左与上'})[r.value]") s=s.replace("if(r.kind==='gate')return", "if(r.kind==='wall')return `永久墙 ${r.edge.join('—')} 断开表面邻接,需绕行`;if(r.kind==='door'){const crossing=author.findIndex((id,i)=>i>0&&r.edge.includes(id)&&r.edge.includes(author[i-1]));return `第 ${crossing} 格后门开,从 ${author[crossing-1]} 走向 ${author[crossing]},落格后门关;空盘门关、起步也切换`;}if(r.kind==='gate')return") p.write_text(s,encoding='utf-8')