import fs from 'node:fs';import path from 'node:path';import {fileURLToPath} from 'node:url';import {definitions} from './definitions.mjs';import {units,constraints} from './engine-template.mjs'; const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..'); const esc=s=>String(s).replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); function diagram(l,author=null){let shapes=[],centers=new Map();for(const c of l.cells){if(l.geometry==='hex'){const x=1.5*c.r,y=Math.sqrt(3)*(c.q+c.r/2);centers.set(c.id,[x,y]);let p=[];for(let a=0;a<6;a++)p.push([x+Math.cos(a*Math.PI/3),y+Math.sin(a*Math.PI/3)]);shapes.push({id:c.id,p});}else{const u=units(c);centers.set(c.id,[u.reduce((s,p)=>s+p[0]+.5,0)/u.length,u.reduce((s,p)=>s+p[1]+.5,0)/u.length]);for(const [x,y]of u)shapes.push({id:c.id,p:[[x,y],[x+1,y],[x+1,y+1],[x,y+1]]});}} const all=shapes.flatMap(s=>s.p),xmin=Math.min(...all.map(p=>p[0])),xmax=Math.max(...all.map(p=>p[0])),ymin=Math.min(...all.map(p=>p[1])),ymax=Math.max(...all.map(p=>p[1])),s=Math.min(340/(xmax-xmin),410/(ymax-ymin)),tr=([x,y])=>[(x-xmin)*s+20,(y-ymin)*s+20];const width=(xmax-xmin)*s+40,height=(ymax-ymin)*s+40; let svg=``; for(const item of shapes){const c=l.cells.find(c=>c.id===item.id);svg+=``;} if(author)svg+=``; for(let i=0;i<(l.gates||[]).length;i++){const r=l.gates[i],[a,b]=r.edge.map(id=>tr(centers.get(id))),x=(a[0]+b[0])/2,y=(a[1]+b[1])/2,angle=Math.atan2(b[1]-a[1],b[0]-a[0])*180/Math.PI;svg+=`门${i+1}`;} for(const c of l.cells){const [x,y]=tr(centers.get(c.id)),w=(l.windows||[]).find(r=>r.cell===c.id),sw=(l.gates||[]).findIndex(r=>r.switchCell===c.id);let label=author?String(author.indexOf(c.id)+1):c.clue===undefined?'':String(c.clue);let tags=[w?`${w.min}–${w.max}`:'',c.direction==='straight'?'↔':c.direction==='turn'?'↱':'',c.category==='circle'?'○':c.category==='triangle'?'△':'',sw>=0?`开${sw+1}`:''].filter(Boolean).join(' ');svg+=`${esc(label||tags)}${c.id}${label&&tags?' · '+esc(tags):''}`;} return svg+'';} for(const def of definitions){const folder=path.join(root,'modes',def.id),pack=JSON.parse(fs.readFileSync(path.join(folder,'levels.json'),'utf8')),notes=JSON.parse(fs.readFileSync(path.join(folder,'design.json'),'utf8'));let articles='';for(let i=0;i<8;i++){const l=pack.levels[i],n=notes[i];fs.writeFileSync(path.join(folder,l.id+'-board.svg'),diagram(l));fs.writeFileSync(path.join(folder,l.id+'-solution.svg'),diagram(l,n.author)); articles+=`

${l.id} · ${l.difficulty} · ${esc(l.title)}

${diagram(l)}
原始盘面 · 格id用于对照JSON
展开作者解与路线
${diagram(l,n.author)}

${n.author.join(' → ')}

推理依据

关键分叉:第 ${n.keyFork.step} 步应走 ${n.keyFork.correct},诱惑路线走 ${n.keyFork.tempting}。排除依据:${esc(n.reason)}。

逐项机制必要性反例${n.witnesses.map(w=>`

解除 ${w.constraint} 后:${w.path.join(' → ')}

`).join('')}
`;} fs.writeFileSync(path.join(folder,'DESIGN.html'),`${def.title} · 设计评审进入试玩

${def.title} · 8关设计评审

${esc(def.rule)}

原始盘面和作者解分开展示。标题是设计任务,真人推理路径与难度仍需试玩验证。数字、窗口、方向、类别和开关显示在格内;步差与先后关系见逐关推理依据。

${articles}`);} console.log('Generated 56 original boards, 56 solutions and 7 design review pages.');