8 lines
3.7 KiB
JavaScript
8 lines
3.7 KiB
JavaScript
import {graph,violations,edgeBlocked,sideBetween,solve} from '../rules.mjs';
|
|
export function measure(l,answer=null,{depthLimit=8,stateLimit=12000}={}){const g=graph(l),author=answer||solve(l)[0],n=g.cells.length,cat=new Map(l.cells.map(c=>[c.id,c.category])),cells=new Map(l.cells.map(c=>[c.id,c]));const staticAdj=g.adj.map((a,i)=>a.filter(j=>!(l.walls||[]).some(e=>e.includes(g.cells[i].id)&&e.includes(g.cells[j].id))&&(!l.features.includes('alternation')||cat.get(g.cells[i].id)!==cat.get(g.cells[j].id))));let probes=0,capped=false;
|
|
function candidates(p){const source=p.length?g.adj[g.ids.get(p.at(-1))].map(j=>g.cells[j].id):l.cells.filter(c=>!g.clues.has(1)||c.clue===1).map(c=>c.id);return source.filter(id=>{if(p.includes(id))return false;const c=cells.get(id);if(c.clue!==undefined&&c.clue!==p.length+1)return false;if(p.length&&(edgeBlocked(l,p,p.at(-1),id)||l.features.includes('alternation')&&cat.get(p.at(-1))===cat.get(id)))return false;if(c.direction&&p.length&&!c.direction.includes(sideBetween(l,id,p.at(-1))))return false;return !violations(l,[...p,id]).length;});}
|
|
function simple(p){if(violations(l,p).length)return false;if(p.length===n)return true;const cur=g.ids.get(p.at(-1)),available=new Set(g.cells.map((c,i)=>i).filter(i=>!p.includes(g.cells[i].id)));available.add(cur);const q=[cur],seen=new Set(q);let leaves=0;for(let k=0;k<q.length;k++){const i=q[k],next=staticAdj[i].filter(j=>available.has(j));if(!next.length)return false;if(i!==cur&&next.length===1){if(++leaves>1)return false;if(g.cells[i].direction)return false;}for(const j of next)if(!seen.has(j)){seen.add(j);q.push(j);}}if(seen.size!==available.size)return false;for(const c of l.cells)if(c.clue!==undefined&&!p.includes(c.id)){const target=g.ids.get(c.id),dist=new Map([[cur,0]]),walk=[cur];for(let k=0;k<walk.length&&!dist.has(target);k++)for(const j of staticAdj[walk[k]])if(available.has(j)&&!dist.has(j)){dist.set(j,dist.get(walk[k])+1);walk.push(j);}if(!dist.has(target)||dist.get(target)>c.clue-p.length)return false;}return true;}
|
|
function probe(p,d,budget){if(++probes>budget){capped=true;return {depth:d,survived:true,capped:true};}if(!simple(p))return {depth:d,survived:false};if(d>=depthLimit)return {depth:d,survived:true};let best={depth:d,survived:false};for(const id of candidates(p)){const r=probe([...p,id],d+1,budget);if(r.depth>best.depth)best=r;if(r.survived)return r;}return best;}
|
|
let decisions=0,forced=0,longestForced=0,run=0,wrongBranches=0,hard=0;const traps=[],branches=[];for(let k=0;k<author.length;k++){const prefix=author.slice(0,k),all=candidates(prefix),plausible=all.filter(id=>simple([...prefix,id]));if(plausible.length<=1){forced++;longestForced=Math.max(longestForced,++run);}else{decisions++;run=0;}for(const id of plausible.filter(id=>id!==author[k])){const before=probes,r=probe([...prefix,id],0,probes+stateLimit);wrongBranches++;if(r.depth>=3)hard++;traps.push(r.depth);branches.push({step:k+1,tempting:id,correct:author[k],survivesAtLeast:r.depth,depthCapped:r.survived,probeStates:probes-before});}}
|
|
const avg=traps.length?traps.reduce((a,b)=>a+b,0)/traps.length:0;const raw=2*Math.log2(1+n/3)+5*Math.log2(1+decisions)+4*Math.log2(1+hard)+6*avg+3*Math.log2(1+probes/10)-5*forced/n;const score=Math.max(0,Math.min(100,Math.round(raw)));let turns=0,last=null;for(let i=1;i<author.length;i++){const a=cells.get(author[i-1]),b=cells.get(author[i]),v=l.geometry==='hex'?[b.q-a.q,b.r-a.r]:[Math.sign(b.x-a.x),Math.sign(b.y-a.y)];const key=v.join();if(last&&key!==last)turns++;last=key;}return {version:'reasoner-v1',score,nodeCount:n,decisions,forcedFraction:forced/n,longestForcedRun:longestForced,wrongBranches,nonlocalBranches:hard,meanWrongDepth:Number(avg.toFixed(2)),maxWrongDepth:Math.max(0,...traps),probeStates:probes,depthLimit,stateLimit,capped,turns,branches};}
|