Files
MatchMaster_level/scripts/build-recognition-guide.py

52 lines
7.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Render the maintained Markdown guide into a self-contained HTML document."""
from pathlib import Path
import re,html,json,base64
ROOT=Path(__file__).resolve().parents[1]
source=ROOT/'colorBlock-level-editor/docs/SCREENSHOT_RECOGNITION_EXPLAINED.md'
# This small renderer supports precisely the tables/headings/lists/code in this guide.
def inline(s):
s=html.escape(s);s=re.sub(r'`([^`]+)`',r'<code>\1</code>',s);return re.sub(r'\*\*([^*]+)\*\*',r'<strong>\1</strong>',s)
lines=source.read_text().splitlines();out=[];toc=[];i=0
while i<len(lines):
line=lines[i]
if not line.strip():i+=1;continue
if line.startswith('```'):
i+=1;block=[]
while i<len(lines) and not lines[i].startswith('```'):block.append(lines[i]);i+=1
out.append('<pre><code>'+html.escape('\n'.join(block))+'</code></pre>');i+=1;continue
if line.startswith('#'):
level=len(line)-len(line.lstrip('#'));title=line[level:].strip();id='section-'+str(len(toc)) if level==2 else 'h-'+str(i)
if level==2:toc.append((id,title))
if level>1:out.append(f'<h{level} id="{id}">{inline(title)}</h{level}>')
i+=1;continue
if line.startswith('|'):
rows=[]
while i<len(lines) and lines[i].startswith('|'):
cols=[c.strip() for c in lines[i].strip('|').split('|')]
if not all(re.fullmatch(r':?-+:?',c) for c in cols):rows.append(cols)
i+=1
out.append('<div class="table"><table><thead><tr>'+''.join('<th>'+inline(c)+'</th>' for c in rows[0])+'</tr></thead><tbody>'+''.join('<tr>'+''.join('<td>'+inline(c)+'</td>' for c in row)+'</tr>' for row in rows[1:])+'</tbody></table></div>');continue
if re.match(r'(- |\d+\. )',line):
tag='ul' if line.startswith('-') else 'ol';items=[]
while i<len(lines) and re.match(r'(- |\d+\. )',lines[i]):items.append(re.sub(r'^(- |\d+\. )','',lines[i]));i+=1
out.append('<'+tag+'>'+''.join('<li>'+inline(c)+'</li>' for c in items)+'</'+tag+'>');continue
paragraph=[]
while i<len(lines) and lines[i].strip() and not re.match(r'^(#|\||```|- |\d+\. )',lines[i]):paragraph.append(lines[i]);i+=1
out.append('<p>'+inline(' '.join(paragraph))+'</p>')
result=json.loads((ROOT/'outputs/editor-level19-20261015/result.json').read_text())
grid=result['inspections'][0]['grid'];old={'cols':6,'rows':10,'corners':[[152.5197,338],[646.7146,338],[661.6781,1130],[127.5033,1130]],'rowCurve':-.154803758}
def point(g,c,r):
u=c/g['cols'];t=r/g['rows'];v=t+g['rowCurve']*t*(t-1);a,b,d,e=g['corners'];return [(1-v)*((1-u)*a[j]+u*b[j])+v*((1-u)*e[j]+u*d[j]) for j in (0,1)]
def grid_svg(g):
poly=[]
for c in range(g['cols']+1):poly.append([point(g,c,r/4) for r in range(g['rows']*4+1)])
for r in range(g['rows']+1):poly.append([point(g,0,r),point(g,g['cols'],r)])
return ''.join('<polyline points="'+' '.join(f'{x:.2f},{y:.2f}' for x,y in ps)+'"/>' for ps in poly)
img=base64.b64encode(Path('/Users/ruoruo/Desktop/截屏2026-10-15 19.37.51.png').read_bytes()).decode()
figure=f'''<section class="comparison"><div><div class="eyebrow">用这关看网格边界</div><h2>门在网格外,方块在网格内。</h2><p>切换查看同一原图上的修复前后网格。旧网格把顶门算入高度,造成6×10;修复后为6×9,并保留轻微透视。</p><div class="switch"><button data-view="new" aria-pressed="true">修复后 · 6×9</button><button data-view="old" aria-pressed="false">修复前 · 6×10</button></div><p class="note">这是原始图片与程序计算网格的叠加示意;不替代浏览器操作验收。下方记录完整算法与限制。</p></div><svg viewBox="65 300 665 895" role="img" aria-label="第19关修复前后棋盘网格"><image href="data:image/png;base64,{img}" width="784" height="1208"/><g id="grid-new" fill="none" stroke="#fff" stroke-width="1.8">{grid_svg(grid)}</g><g id="grid-old" fill="none" stroke="#ffea70" stroke-width="1.8" style="display:none">{grid_svg(old)}</g></svg></section>'''
css='''*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:#f3f1ea;color:#223236;font:16px/1.85 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif}header{background:#133c39;color:#f6f3e6;padding:64px max(24px,calc((100vw - 1120px)/2)) 42px}header h1{font-size:38px;line-height:1.4;margin:16px 0}header p{max-width:720px;color:#d1e1d8}.eyebrow{letter-spacing:2px;font-size:12px;text-transform:uppercase;color:#95b4a6}.metrics{display:flex;gap:12px;flex-wrap:wrap;margin-top:30px}.metrics div{padding:14px 25px;border:1px solid #54736a;border-radius:8px}.metrics strong{font-size:27px;margin-right:8px}.comparison{max-width:1120px;margin:32px auto;padding:32px;background:#fffdf7;display:grid;grid-template-columns:1fr 380px;gap:36px;border-radius:12px}.comparison h2{border:0;margin-top:16px}.comparison svg{width:100%;max-height:510px;border-radius:8px}.switch{display:flex;gap:12px;flex-wrap:wrap}button{font:inherit;padding:10px 16px;border:1px solid #739488;background:transparent;color:#234740;border-radius:6px;cursor:pointer}button[aria-pressed=true]{background:#164d43;color:white}.note{font-size:13px;color:#67766d}.layout{display:grid;grid-template-columns:220px minmax(0,850px);gap:40px;max-width:1120px;margin:40px auto 100px}nav{position:sticky;top:24px;align-self:start;font-size:13px}nav a{display:block;padding:7px 0;color:#35584c;text-decoration:none}nav a:hover{text-decoration:underline}article{background:#fffdf7;border-radius:12px;padding:32px 42px;min-width:0}h2{font-size:24px;line-height:1.5;border-top:1px solid #d5ddd4;padding-top:30px;margin-top:44px;scroll-margin-top:20px}h3{font-size:18px;color:#266251;margin-top:28px}p{margin:16px 0}strong{color:#204e42}header strong{color:#fff5d8}code{font:13px/1.6 ui-monospace,Menlo,monospace;background:#eaf0e8;padding:2px 5px;border-radius:4px;overflow-wrap:anywhere}pre{overflow-x:auto;background:#1c3735;color:#e1f0e2;padding:22px;border-radius:8px}pre code{background:none;color:inherit;padding:0;overflow-wrap:normal}.table{overflow-x:auto}table{border-collapse:collapse;font-size:13px;min-width:100%}th,td{padding:11px 12px;text-align:left;border-bottom:1px solid #d9dfd5}th{background:#e9efe5;color:#264b40}li{padding:4px 0}footer{text-align:center;padding:26px;font-size:13px;color:#637269}@media(max-width:900px){.layout{display:block;margin:20px}.comparison{grid-template-columns:1fr;margin:20px;padding:24px}.comparison svg{max-height:480px}nav{position:static;margin-bottom:24px;columns:2}article{padding:20px}header{padding:30px 24px}header h1{font-size:30px}}@media print{nav,.switch{display:none}.layout{display:block}.comparison{break-inside:avoid}body,article{background:white}header{padding:25px}article{padding:0}h2{break-after:avoid}tr{break-inside:avoid}}'''
page='<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>本地截图识别 · 方法与第19关修复</title><style>'+css+'</style><header><div class="eyebrow">ColorBlock Studio / 0.19.3 / 2026.10.19</div><h1>一张截图,怎样变成可编辑关卡</h1><p>从像素、透视网格到方块与门。以第19关解释完整实现、错误原因、修复方式,以及补图真正能提供的信息。</p><div class="metrics"><div><strong>6 × 9</strong>可玩网格</div><div><strong>27</strong>方块</div><div><strong>8</strong>门</div><div><strong>10 / 10</strong>冻结块初值</div></div></header>'+figure+'<div class="layout"><nav aria-label="章节目录">'+''.join(f'<a href="#{id}">{html.escape(t)}</a>' for id,t in toc)+'</nav><article>'+''.join(out)+'</article></div><footer>本地离线文档 · 不加载外部资源 · 原图不修改 · 开发样本不等于独立准确率</footer><script>document.querySelectorAll("[data-view]").forEach(b=>b.onclick=()=>{const current=b.dataset.view;["new","old"].forEach(v=>document.getElementById("grid-"+v).style.display=v===current?"":"none");document.querySelectorAll("[data-view]").forEach(x=>x.setAttribute("aria-pressed",String(x===b)));});</script></html>'
(ROOT/'colorBlock-level-editor/docs/SCREENSHOT_RECOGNITION_EXPLAINED.html').write_text(page)
print('Wrote self-contained guide',len(page),'characters')