101 lines
21 KiB
Python
101 lines
21 KiB
Python
from pathlib import Path
|
|
import json,csv,hashlib,uuid,html,statistics
|
|
root=Path(r'G:\项目\NumberSwipe-Variants');out=root/'design/demo-50'
|
|
# number/title/geometry/features/N/constraints/layout/goal/wrong-route/reason
|
|
rows=[
|
|
(1,'第一笔','square',[],5,'数字1、5','紧凑小盘;一个浅分叉,终点清晰','理解全格覆盖与固定步号','直接滑向终点,留下一个未走格','终点到达时仍有剩余格,必须先覆盖'),
|
|
(2,'先收短支路','square',[],8,'数字1+一个中段数字','主区旁有短支路;支路不是视觉隐藏格','根据端点与剩余区域判断访问顺序','先进入主区,支路失去可续接入口','完整覆盖要求保留支路访问机会'),
|
|
(3,'大块一步','blocks',['big'],8,'一个2×2大格;数字1+中段数字','两条候选路线视觉长度相近,经过拼块数不同','按拼块而非面积计步','把大格误当多步,选择面积更短的一侧','实际步号对不上中段数字'),
|
|
(4,'枢纽留后','blocks',['big'],10,'一个多出口大格;少量数字','两片区域通过两个通口连接,大格位于路线中段','决定何时使用大格枢纽','过早进入大格并从近出口离开','剩余区域无法接续或不能按时到数字'),
|
|
(5,'认清异形共边','blocks',['big'],11,'一个L形格+早期引导数字;不加奇偶','凹口接触邻格;只新增异形读盘,不同时增加未知起点','异形整块一步,辨认哪些边实际共边','按包围盒把凹口当成块内,错误认定邻接','以真实占地共边判邻接,不能跨空洞'),
|
|
(6,'边上的墙','square',['walls'],9,'一堵必要墙;数字1','墙封住明显直达边;绕行仍有两种选择','认识永久封边,靠数字选绕行方向','把墙当作一个格,或尝试穿过','墙只封边,另一条真实共边才可通行'),
|
|
(7,'近路要绕','square',['walls'],11,'一至两堵必要墙;一个中段数字','空间近而路径远,墙后保留区域出口','先观察墙改变了哪些邻接','先绕进近侧,过早占用唯一续接节点','后段覆盖或步号不成立'),
|
|
(8,'第一座桥','blocks',['big','walls'],13,'一个大格;两堵必要墙','两片区域有两个通口,墙与大格共同限制跨区顺序','大格必须留到完成一侧之后','先走枢纽,再试图回补区域','大格只能一次,剩余区域被切断'),
|
|
(9,'绕过去就好','square',['walls'],9,'一堵必要墙;起点给定','小盘面,墙排除一个清楚的诱惑方向','以一次短推理完成释放关','忽略墙尝试直达','正确绕行只需一个局部判断'),
|
|
(10,'对称小花园','blocks',['big','walls'],12,'一个大格;一堵必要墙','近似对称外形,内部线索清楚打破对称','迁移已学规则并顺畅结束章节','按外形镜像照走','线索和墙形成可见的不对称依据'),
|
|
(11,'左右通道','square',['direction'],9,'一个lr通道;起点给定','通道还有上下几何邻格','认识左右两边连接、可反向通过','从上或下进入通道','图示只连接左右边'),
|
|
(12,'上下通道','square',['direction'],10,'一个du通道','把横向题的结构改为纵向应用,增加一处分叉','独立区分横线和竖线','从左侧切入纵向通道','进出边必须是上与下'),
|
|
(13,'认清拐角','square',['direction'],11,'一个明确拐角;中段数字','拐角格至少有三个邻格,图形指向两边','辨认拐角连接边,不把它理解成单向右转','使用另一种转弯方向','图形未连接的边不能成为进出边'),
|
|
(14,'两角相接','square',['direction','walls'],13,'两个不同拐角;一堵必要墙','前后通道共享一片区域的入口出口选择','用后段出口反推前段入口','先满足第一个通道,却留错第二个入口','两个通道要求必须同时成立'),
|
|
(15,'直线与拐角','square',['direction','walls'],14,'一横或竖通道+一拐角;一堵墙','旧结构换成新方向布局,保持两三个有效分叉','巩固明确边配对与区域收尾','通道局部满足但走过区域出口','剩余区域无法完整覆盖'),
|
|
(16,'蓝橙第一步','blocks',['big','alternation'],9,'一个大格;两色全盘;起点给定','至少一处同色共边,色彩鲜明','认识每步换颜色,大格仍一步','走向共享边的同色邻格','下一步必须另一种颜色'),
|
|
(17,'留下另一种颜色','blocks',['big','alternation'],12,'大格双色;一个必要中段数字','大格有多个异色出口,只有一个能续接全盘','不能仅以颜色合法判断下一步','走向合法异色近出口','后段区域缺少异色续接机会'),
|
|
(18,'双色跨区','blocks',['big','alternation','walls'],15,'两个多单位块;两堵必要墙','两个区域的跨区出口同时受颜色和墙影响','先定访问顺序,再选异色出口','选当前合法的异色出口,提前跨区','另一区域无法按交替顺序收尾'),
|
|
(19,'短短的接力','blocks',['big','alternation'],10,'一个大格;少量数字','小盘面,一个明确的同色诱惑与一个收尾判断','以已掌握规则降低认知负担','误走同色共边或留错最后异色格','颜色与覆盖条件给出直接排除'),
|
|
(20,'双色回环','blocks',['big','alternation'],13,'一个长条或大格;起点给定','视觉呈回环,但路径不闭合;出口清楚','稳定应用双色和一步大格,顺畅收束','为追求形状闭合而提前封住尾段','完成要求是覆盖路径,不是闭环'),
|
|
(21,'偶数落脚','blocks',['big','parity'],10,'一个必要偶标记;一个大格;数字1','两种区域顺序使标记落在不同奇偶步','认识访问步号的偶数要求','按面积或颜色想象奇偶','整块一步,实际访问步号必须为偶'),
|
|
(22,'奇数绕行','blocks',['big','parity'],12,'一个必要奇标记;长条','换一类结构,保留两个访问顺序','独立应用奇数要求','选择空间较短但步号为偶的路线','拼块步数决定奇偶,距离不决定'),
|
|
(23,'凹口的步号','blocks',['big','parity'],13,'L形或T形;一至两个必要奇偶','凹口新增真实邻接,但不新增块内步数','迁移奇偶到异形区域','把凹口内单位方格拆开数步','异形整块只访问一次'),
|
|
(24,'墙后先收尾','blocks',['big','parity','walls'],15,'两个必要奇偶;一至两堵墙','区域有两个通口,墙限制一侧,奇偶限制次序','让墙影响区域顺序,再由奇偶筛掉另一选择','先满足一个奇偶却过早离开区域','另一条件或剩余覆盖不成立'),
|
|
(25,'少量线索也够','blocks',['big','parity'],12,'一个必要奇偶;一个中段数字','比24关更短但保留一个可解释分叉','巩固奇偶,避免连续多条件压力','机械沿边走,忽略到达步号','奇偶直接排除这一条完整路线'),
|
|
(26,'门随脚步开关','square',['rhythm-door'],9,'一道门;空盘关;数字1','门在前段;能看到另一侧入口选择','起步也切换,移动前看门位','把门当静态墙或等待自动打开','只有落格才切换,等待无效'),
|
|
(27,'从另一侧通过','square',['rhythm-door'],12,'一道门;一个中段数字','普通方格中从正确一侧接近门','用步号判断通过方向,不假设绕路能改目标奇偶','从当前关闭的一侧接近,试图靠等待或同侧绕圈补拍','正交单位格固定起点的格奇偶不随绕路改变'),
|
|
(28,'大块改变节拍','blocks',['big','rhythm-door'],15,'一道门;一至两个大格','经过不同拼块的区域顺序影响到门时的步号','在大格图中规划真实到达状态','选择视觉近但落格次数不合适的接近路线','门在出发前状态关闭'),
|
|
(29,'墙和门分开看','blocks',['big','rhythm-door','walls'],12,'一道门+一堵墙;短盘面','永久墙只阻一条边,门保留清楚的跨区判断','以短组合应用释放认知负担','把黑黄墙也当作会切换的门','永久墙始终不可跨越'),
|
|
(30,'顺着节拍收尾','blocks',['big','rhythm-door'],14,'一道门;起点给定','近似对称两区,关键经过时机清楚','巩固大格计步与门位,完成顺畅章节收束','门开就立刻跨区,留下本侧格','需要先完成本侧再合法跨门'),
|
|
(31,'六向邻接','hex',['walls'],10,'一堵墙;数字1','紧凑六边盘,保留三角邻接','只新增六向几何,复用熟悉墙规则','漏看斜向共边,或认为碰角也能走','以实际六边共边判断邻接'),
|
|
(32,'六边留出口','hex',['walls'],13,'两堵必要墙;一个中段数字','更多六向出口中保留后续区域连接','识别多出口枢纽,规划区域收尾','先走看似安全的近出口','覆盖余区所需通口被用掉'),
|
|
(33,'六边换颜色','hex',['alternation'],13,'蓝橙全盘;起点给定','三角邻接有同色边,两个异色候选都可起手','把交替迁移到六向盘面','以六向相邻为由走同色格','仍必须每步换色'),
|
|
(34,'颜色之外还有墙','hex',['alternation','walls'],15,'两堵必要墙,均在异色共边上','颜色先筛候选,墙再影响区域出口','分清颜色阻挡与墙阻挡,两者联动','选异色格却忽略边墙','异色仍可能被永久墙封边'),
|
|
(35,'六边小花园','hex',['alternation','walls'],12,'一堵必要墙;起点给定','比34更短,几何主题清楚,不重教规则','巩固六边迁移,以一次区域判断完成','追随主题外形,留错末端','末端颜色与区域覆盖共同排除'),
|
|
(36,'六边也有奇偶','hex',['parity'],14,'一至两个必要奇偶;无双色','保留三角邻接,可改变路线步号关系','把奇偶迁移到非天然黑白图','照搬单位方格天然奇偶规律','三角邻接允许不同访问奇偶,须按路线计步'),
|
|
(37,'起点没有写出','hex',['parity'],12,'一个必要奇偶;不给1;其他条件减少','有可辨认的端点候选,盘面缩小','首次只增加起点未知的信息负担','任意格起手再穷举','端点结构与奇偶可排除起点候选'),
|
|
(38,'找起点再跨区','hex',['parity','walls'],17,'两个必要奇偶;两堵墙;不给1','区域连接清楚,但两端和方向需要联合判断','先定端点,再定访问方向与区域次序','选对几何端点却朝错误方向走','奇偶与墙共同排除反向完整路线'),
|
|
(39,'给回起点的短题','hex',['parity','walls'],12,'一个必要奇偶;一堵墙;恢复1','短盘面,只保留一个区域收尾问题','移除起点负担,形成明确释放','提前经过跨区口','区域覆盖与奇偶给出短排除'),
|
|
(40,'六边收束','hex',['parity','walls'],15,'一至两个必要奇偶;一堵墙','对称主题外形,线索给出清楚解题入口','熟练应用已学条件,顺畅结束章节','照镜像选择次序','明确线索打破对称'),
|
|
(41,'通道再接力','square',['direction','walls'],15,'两个必要通道;一至两堵墙','横竖与拐角配合,两片区域相互制约','用熟悉机制开始综合阶段','只满足近侧通道,留错远侧入口','后段方向要求反推前段入口'),
|
|
(42,'两个双色枢纽','blocks',['big','alternation','walls'],16,'两个大格或异形;两堵墙','两个枢纽连接区域,颜色限制各自出口','两个局部选择不能独立处理','先使用更近枢纽的异色出口','远侧区域失去可交替续接通路'),
|
|
(43,'奇偶分配区域','blocks',['big','parity','walls'],17,'两个必要奇偶;两堵墙','两片区域的访问先后影响两个标记步号','协调两处步数条件,不能各自贪心满足','满足第一个条件后直接跨区','第二个奇偶或完整覆盖冲突'),
|
|
(44,'六边出口互锁','hex',['alternation','walls'],18,'三堵必要墙;少量数字','三片逻辑区域由至少两类可选通口衔接,避免三枝单枢纽无解','一个区域选择影响另一个区域的出口','抢先用掉当前合法异色出口','后续区域无法持续交替覆盖'),
|
|
(45,'方向决定门路','square',['direction','rhythm-door'],16,'两个必要通道+一道门','方向配对迫使从特定一侧接近门','后段通道反推穿门方向','通道局部满足,但从关闭侧到门','门与通道联合排除路线'),
|
|
(46,'先定奇偶,再定节拍','blocks',['big','parity','rhythm-door'],17,'两个必要奇偶+一道门','奇偶格远离门口,避免直接重复门条件','前段奇偶选择影响后段门位','把两处条件分别满足,却忽略次序依赖','路线必须同时满足远处标记与跨门状态'),
|
|
(47,'墙通道门三者联动','square',['direction','walls','rhythm-door'],18,'三个必要通道;两堵墙;一道门','多步入口出口依赖,起点仍给定','从后段限制逐步反推,而非起手试画','走到中段才发现后段通道进不去','需要提前保留满足通道与门的接近路线'),
|
|
(48,'六边综合峰值','hex',['parity','walls','rhythm-door'],19,'两个必要奇偶;两堵墙;一道门','保留三角邻接,跨区时机与前段次序联动;不给额外新规则','端点、区域、步号和门状态构成可解释依赖链','局部条件都似乎满足,最后跨门状态不对','前段次序必须为后段合法穿门服务'),
|
|
(49,'回到双色小题','blocks',['big','alternation','walls'],12,'一个大格;一堵墙;恢复清楚起点','一个异色出口保留判断,明显短于峰值','在挑战后用已掌握能力快速完成','随便选合法颜色的出口','后续收尾给出一个短排除'),
|
|
(50,'继续还有新花园','hex',['alternation','walls'],16,'一至两堵墙;起点给定','新的主题布局,包含两个短推理,但不做终极考卷','展示机制的可持续变化,顺畅完成DEMO','照搬44关的访问次序','新布局的墙与颜色要求有不同收尾顺序')
|
|
]
|
|
scores=[12,16,20,22,26,30,35,40,24,28,24,28,32,35,38,26,32,46,32,36,30,34,39,43,36,32,38,50,40,42,34,38,43,47,40,44,49,58,42,46,44,48,52,56,58,60,65,72,48,54]
|
|
chapters=[{'id':1,'title':'覆盖、大格与墙','introduced':['big','walls'],'focus':'结束操作教学,建立计步与区域收尾','peak':8}, {'id':2,'title':'方向与双色','introduced':['direction','alternation'],'focus':'明确进出边,再规划异色续接','peak':18}, {'id':3,'title':'奇偶与步进门','introduced':['parity','rhythm-door'],'focus':'真实步号与到达状态,五关单元引入','peak':28}, {'id':4,'title':'六边迁移与起点推理','introduced':['hex','unknown-start'],'focus':'旧规则迁移到六向几何,适度减少起点信息','peak':38}, {'id':5,'title':'组合应用与收束','introduced':[],'focus':'增加推理依赖,不继续增加规则','peak':48}]
|
|
names={'big':'大格/异形','walls':'永久墙','direction':'方向通道','alternation':'双色交替','parity':'奇偶','rhythm-door':'步进门'}
|
|
roles=['演示/热身','练习','变化','组合','巩固','演示/迁移','练习/挑战','章节峰值','释放','收束']
|
|
intro={1:'操作',3:'大格一步',5:'异形共边',6:'永久墙',11:'横向通道',12:'纵向通道',13:'明确拐角',16:'双色交替',21:'奇偶',26:'步进门',31:'六边邻接',37:'起点未知'}
|
|
unknown={37,38};levels=[]
|
|
for row,score in zip(rows,scores):
|
|
n,title,geo,features,count,qty,layout,goal,wrong,reason=row;ch=(n-1)//10+1;slot=(n-1)%10+1
|
|
role=roles[slot-1];target_range=[max(5,score-4),min(100,score+4)]
|
|
# Targets refer to active planning time, not measured performance or visible game timer.
|
|
time_range=([8,18] if n<=2 else [12,30] if n<=10 else [15,40] if n<=20 else [20,55] if n<=40 else [25,65])
|
|
if slot==8:time_range=[20,45] if n<=20 else [30,75] if n<=40 else [45,100]
|
|
if slot in (9,10):time_range=[12,28] if n<=20 else [18,40] if n<=40 else [20,45]
|
|
if n in intro and n>2:time_range=[12,35]
|
|
dependencies=1 if score<28 else 2 if score<45 else 3 if score<60 else 4
|
|
decision_range=[1,2] if score<28 else [2,3] if score<45 else [3,4] if score<60 else [4,5]
|
|
evidence='先说明诱惑方向,再用'+reason+'排除;最后检查数字、覆盖和全部机制。'
|
|
runtime='已有单机制可参考,组合需独立主线支持'
|
|
if 'parity' in features:runtime='待实现:Variants奇偶判定、提示与校验;不读取原项目运行代码'
|
|
reference=[]
|
|
if 'direction' in features:reference=['direction/01','direction/02','direction/04']
|
|
elif geo=='hex' and 'alternation' in features and 'walls' in features:reference=['hex-alternation/04','hex-alternation/07']
|
|
elif 'rhythm-door' in features:reference=['rhythm-door/01','rhythm-door/05']
|
|
elif 'alternation' in features:reference=['block-alternation/03','block-alternation/05']
|
|
elif 'walls' in features:reference=['walls/02','walls/05']
|
|
level={'levelId':str(uuid.uuid5(uuid.NAMESPACE_URL,'numberswipe/demo-design-v1/'+str(n))), 'designKey':'demo-'+str(n).zfill(3),'displayOrder':n,'title':title,'chapter':ch,'chapterSlot':slot,'role':role,'geometry':geo,'features':features,'nodeCountTarget':count,'constraintsBudget':qty,'unknownStart':n in unknown,'introduces':intro.get(n,''),'layoutIntent':layout,'teachingGoal':goal,'temptingRouteIntent':wrong,'rejectionReason':reason,'reasoningOutline':evidence,'targetDifficulty':score,'targetDifficultyBand':target_range,'targetDecisionCount':decision_range,'targetDependencyDepth':dependencies,'thinkingSecondsP50Target':time_range,'sourceCandidates':reference,'reusePolicy':'仅参考构造,未确认可直接复用','implementationNote':runtime,'status':'design-only','geometryValidated':False,'uniqueSolutionValidated':False,'humanDifficultyCalibrated':False,'measuredFeatures':None}
|
|
levels.append(level)
|
|
orderA=[l['levelId'] for l in levels];orderB=[]
|
|
# Within each chapter, take the same peak puzzle and move it from slot8 to slot10.
|
|
for c in range(5):chunk=orderA[c*10:(c+1)*10];orderB+=chunk[:7]+chunk[8:]+[chunk[7]]
|
|
source=Path(r'G:\下载\NumberSwipe_Research_H5.html');digest=hashlib.sha256(source.read_bytes()).hexdigest()
|
|
plan={'schemaVersion':1,'designVersion':'demo50-design-2026-10-14-v1','scope':'50关设计与目标曲线;不含可玩盘面或真人实测','researchSource':{'path':str(source),'sha256':digest,'usedAs':'用户提供研究;未复核外部500关统计'},'projectSource':str(root),'curvePolicy':'外层缓慢提高,章内第8关峰值、9释放、10收束;为待测假设','difficultyModelVersion':'target-rubric-v1','scoreSemantics':'0–100策划目标,不是算法测得难度、概率或真人时间','chapterSize':10,'chapters':chapters,'levels':levels,'orders':{'A':{'title':'第8关峰值,末两关释放','levelIds':orderA},'B':{'title':'章末峰值对照','levelIds':orderB}},'hardConstraints':['每格一次、共边移动、固定数字为绝对访问步号','完整有序解唯一;解除任意新约束后至少两解','原始同格数字与奇偶互斥','不给固定起点的双色+奇偶组合','方向通道仅用于单位方格整盘','每关最多一道步进门;起点也切换、移动前判门位、撤销恢复','N最多20,按拼块数量而非面积','不上新传送、限时、体力、分数或已放弃机制']}
|
|
extraPaths=[root.parent/'NumberSwipe/docs/一笔画竞品调研与关卡编排方案.md',root.parent/'NumberSwipe/docs/一笔画深度调研-制作与难度证据.md',root.parent/'NumberSwipe/docs/research/level-structure-audit.json']
|
|
plan['additionalResearchSources']=[{'path':str(p),'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'scope':'原项目45关研究,不等于Variants40关验收'} for p in extraPaths]
|
|
plan['pilotDesignKeys']=['demo-005','demo-013','demo-018','demo-021','demo-024','demo-028']
|
|
for l in levels:
|
|
l['pilotPriority']=l['designKey'] in plan['pilotDesignKeys']
|
|
introFeature={'大格一步':'big','永久墙':'walls','横向通道':'direction','纵向通道':'direction','明确拐角':'direction','双色交替':'alternation','奇偶':'parity','步进门':'rhythm-door'};l['prerequisiteSkills']=([] if l['displayOrder']==1 else ['共边移动','每格一次','精确步号'])+[names[f] for f in l['features'] if f!=introFeature.get(l['introduces'])];l['prerequisiteSkills']+=['端点推理'] if l['unknownStart'] else []
|
|
plan['skillDependencies']={'big':'共边/覆盖/步号→大格一步→大格出口→异形共边','direction':'共边/覆盖→横竖端口→明确拐角→方向联合','alternation':'覆盖/大格一步→双色交替→异色出口保留','parity':'精确步号/实际拼块邻接→单奇偶→双奇偶协作','rhythm-door':'步号/撤销→起步切换→穿门方向→大格到达状态','hex':'共边/墙→六向邻接→旧规则迁移','unknown-start':'覆盖/端点/步号推理→小盘起点未知→组合反推'}
|
|
(out/'demo-design.json').write_text(json.dumps(plan,ensure_ascii=False,indent=2),encoding='utf-8')
|
|
with (out/'difficulty-curve.csv').open('w',encoding='utf-8-sig',newline='') as f:
|
|
writer=csv.DictWriter(f,fieldnames=['design_key','level_id','position_a','position_b','chapter','role','title','geometry','features','node_target','target_difficulty','target_low','target_high','thinking_p50_low','thinking_p50_high','measured_difficulty']);writer.writeheader()
|
|
for l in levels:writer.writerow(dict(design_key=l['designKey'],level_id=l['levelId'],position_a=l['displayOrder'],position_b=orderB.index(l['levelId'])+1,chapter=l['chapter'],role=l['role'],title=l['title'],geometry=l['geometry'],features='+'.join(l['features']) or '数字',node_target=l['nodeCountTarget'],target_difficulty=l['targetDifficulty'],target_low=l['targetDifficultyBand'][0],target_high=l['targetDifficultyBand'][1],thinking_p50_low=l['thinkingSecondsP50Target'][0],thinking_p50_high=l['thinkingSecondsP50Target'][1],measured_difficulty=''))
|
|
print(json.dumps({'levels':len(levels),'chapterPeaks':[max(scores[i:i+10]) for i in range(0,50,10)],'mechanismCoverage':{k:sum(k in l['features'] for l in levels) for k in names},'sourceHash':digest},ensure_ascii=False))
|