38 lines
6.5 KiB
Python
38 lines
6.5 KiB
Python
from pathlib import Path
|
|
root=Path(r'G:\项目\NumberSwipe-Variants');dest=root/'campaign-50'
|
|
s=(root/'tools/ui-template.mjs').read_text(encoding='utf-8-sig')
|
|
s=s.replace("level.mode.includes('alternation')", "level.features.includes('alternation')")
|
|
s=s.replace("const memoryKey='variant:'+pack.mode;", "const memoryKey='variant:campaign-50:v1';")
|
|
s=s.replace("const labels=new Map();const metrics={};let started=0,record=null;", "const labels=new Map();let started=0,firstDrawAt=null,record=null;let records=[];try{records=JSON.parse(localStorage.getItem(memoryKey+':records')||'[]');}catch{}\nfunction saveRecord(){if(!record)return;record.reasoning=$('reasoning').value;record.elapsedSeconds=Math.round((performance.now()-started)/1000);record.completed=cleared;const copy={...record,endedAt:new Date().toISOString()};const at=records.findIndex(r=>r.attemptId===copy.attemptId);if(at>=0)records[at]=copy;else records.push(copy);try{localStorage.setItem(memoryKey+':records',JSON.stringify(records.slice(-500)));}catch{}}")
|
|
s=s.replace("return {mode:pack.mode,level:level.id,difficulty:level.difficulty,title:level.title,startedAt:new Date().toISOString(),", "return {attemptId:Date.now().toString(36)+'-'+Math.random().toString(36).slice(2,8),mode:pack.mode,level:level.id,contentRevision:level.contentRevision,order:level.order,chapter:level.chapter,title:level.title,startedAt:new Date().toISOString(),thinkingBeforeFirstDrawSeconds:null,")
|
|
s=s.replace("function start(i){index=i;", "function start(i){if(record)saveRecord();index=i;")
|
|
s=s.replace("started=performance.now();record=makeRecord();", "started=performance.now();firstDrawAt=null;record=makeRecord();")
|
|
s=s.replace("$('rule-text').textContent=pack.rule;", "$('rule-text').textContent=rulesFor(level);try{localStorage.setItem(memoryKey+':current',level.id);}catch{}")
|
|
a=s.index('function renderSelectors(){');b=s.index('function renderConstraints(){',a)
|
|
s=s[:a]+'''function renderSelectors(){
|
|
$('difficulties').replaceChildren();for(const chapter of pack.chapters){const b=document.createElement('button');b.textContent='第'+chapter.id+'章';b.className=level.chapter===chapter.id?'active':'';b.onclick=()=>start(pack.levels.findIndex(l=>l.chapter===chapter.id));$('difficulties').append(b);}
|
|
$('levels').replaceChildren();for(let i=0;i<pack.levels.length;i++){const l=pack.levels[i];if(l.chapter!==level.chapter)continue;const b=document.createElement('button');b.textContent=String(l.order)+(completed.includes(l.id)?' ✓':'');b.className=i===index?'active':'';b.onclick=()=>start(i);$('levels').append(b);}
|
|
$('position').textContent='第 '+level.order+'/50 关 · '+level.chapterTitle;
|
|
$('progress').textContent='已完成 '+completed.length+'/50';
|
|
}
|
|
function rulesFor(l){const a=['每格恰好经过一次,共边才能走;固定数字是访问步号。'];if(l.geometry==='blocks')a.push('大格和异形整块只算一步。');if(l.geometry==='hex')a.push('六边形有六个共边方向。');if(l.features.includes('direction'))a.push('通道连接哪两边,就从哪两边进出,可反向;通道不能是端点。');if(l.features.includes('alternation'))a.push('蓝→橙→蓝,每一步换颜色;○蓝,△橙。');if(l.features.includes('parity'))a.push('奇/偶标记要求该格访问步号的奇偶,不按面积计数。');if(l.features.includes('walls'))a.push('黑黄墙永久封闭这条共边。');if(l.features.includes('rhythm-door'))a.push('门空盘关,起点也切换,每落一格切换一次;移动前看当前门位,撤销恢复。');return a.join(' ');}
|
|
''' +s[b:]
|
|
s=s.replace(" for(const c of level.cells)if(c.direction)add", " for(const c of level.cells)if(c.parity)add(c.parity==='odd'?'奇 · 第1、3、5…步':'偶 · 第2、4、6…步',[c.id]);\n for(const c of level.cells)if(c.direction)add")
|
|
s=s.replace("if(c.direction)symbol='';", "if(c.direction)symbol='';if(c.parity)symbol=c.parity==='odd'?'奇':'偶';")
|
|
s=s.replace("index===7", "index===pack.levels.length-1")
|
|
s=s.replace(" if(!path.length)started=performance.now();path.push(id);", " if(firstDrawAt===null){firstDrawAt=performance.now();record.thinkingBeforeFirstDrawSeconds=Math.round((firstDrawAt-started)/1000);}path.push(id);")
|
|
s=s.replace("renderSelectors();}draw();}", "saveRecord();renderSelectors();}draw();}")
|
|
s=s.replace("started=performance.now();setStatus('已重开。');", "setStatus('已重开。');")
|
|
s=s.replace("selected=pack.levels[index+1].difficulty;start(index+1);", "if(index+1<pack.levels.length)start(index+1);")
|
|
a=s.index("$('export').onclick=");b=s.index("window.addEventListener('resize'",a)
|
|
s=s[:a]+'''$('export').onclick=()=>{saveRecord();const payload={version:pack.version,exportedAt:new Date().toISOString(),clearedIds:completed,records};const blob=new Blob([JSON.stringify(payload,null,2)],{type:'application/json'}),a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='NumberSwipe-DEMO50-试玩记录.json';a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000);setStatus('已导出全部试玩记录。');};
|
|
window.addEventListener('pagehide',saveRecord);
|
|
''' +s[b:]
|
|
s=s.replace("window.addEventListener('resize',draw);start(0);", "window.addEventListener('resize',draw);let initialId=null;try{initialId=localStorage.getItem(memoryKey+':current');}catch{}let initial=pack.levels.findIndex(l=>l.id===initialId);start(initial>=0?initial:0);")
|
|
s=s.replace("level:level.id,path:", "level:level.id,order:level.order,chapter:level.chapter,records:records.length,path:")
|
|
a=s.index('function drawBarrier(');b=s.index('function inside(',a);barrier=s[a:b];barrier=barrier.replace('let common=null','let commons=[]').replace('common=[p,q]','commons.push([p,q])').replace('if(!common)return;const [a,b]=common;', 'for(const common of commons){const [a,b]=common;');end=barrier.rfind('}');barrier=barrier[:end]+'}'+barrier[end:];s=s[:a]+barrier+s[b:]
|
|
(dest/'ui.mjs').write_text(s,encoding='utf-8')
|
|
source=(root/'tools/build.mjs').read_text(encoding='utf-8-sig');css=source.split('const css=`',1)[1].split('`;',1)[0];css+=' #difficulties{grid-template-columns:repeat(5,1fr)}#levels{display:grid;grid-template-columns:repeat(10,1fr);gap:5px}#levels button{padding:8px 2px;min-width:0}#progress{font-size:13px;color:#77638d}#next{display:block;width:100%;margin-top:10px}#next[hidden]{display:none}@media(max-width:480px){#levels{grid-template-columns:repeat(5,1fr)}#difficulties button{padding:8px 2px;font-size:13px}}'
|
|
(dest/'style.css').write_text(css,encoding='utf-8')
|
|
print('Campaign UI with per-level rules, chapter selection, parity and local records created.')
|