feat: version campaigns and document original puzzle design

Restore v0.1 and v0.2 archives with separate progress, publish the curved v0.3 campaign, and document the original 24-level design with reproducible evidence. Refine board presentation and movement, and keep video lab tooling within its own directory.
This commit is contained in:
cabinw
2026-09-09 13:17:30 +08:00
parent 70b021d4cc
commit e92cccba82
52 changed files with 76449 additions and 15076 deletions
+6 -6
View File
@@ -1,10 +1,10 @@
import { attachBoardViewport } from './board-viewport.js';
import { generateLaneLevel } from './campaign-generator.js';
import { createStudyLevel } from './study-levels.js';
import { TapQueue } from './tap-queue.js';
import { exitTiming } from './exit-animation.js';
import { LEVEL_THEMES, PIGMENTS, MOTION, themeWithColors } from './design-tokens.js';
import { LEVELS } from './levels.js';
import { Game, available, solveRotations } from './core.js';
import { Game, available, solveRotations, trace } from './core.js';
import { layout, hitCell } from './geometry.js';
import { CanvasPainter, drawScene, drawTile } from './renderer.js';
import { ICONS, ICON_SIZE, ICON_ATLAS } from './ui-skin.js';
@@ -13,8 +13,8 @@ const taps=new TapQueue();
const $=id=>document.getElementById(id),canvas=$('preview'),ctx=canvas.getContext('2d'),painter=new CanvasPainter(ctx);
const reduced=matchMedia('(prefers-reduced-motion: reduce)').matches;
let boardViewport;
const regularLevel=LEVELS.find(l=>l.tiles.length>=16)||LEVELS.find(l=>l.tiles.length>=12)||LEVELS[0];
let studyLevel=generateLaneLevel(606,{radius:5,target:42,axis:0});
const regularLevel=createStudyLevel(3);
let studyLevel=createStudyLevel(5);
let colorCount=7;
let theme=themeWithColors(LEVEL_THEMES[0],colorCount),game=new Game(studyLevel),animation=null,rotation=null,frame=0,view=null,hint=null,afterRotation=null;
const atlas=new Image();atlas.src=TILE_ATLAS;atlas.onload=()=>{painter.atlas=atlas;draw();paintSamples();};
@@ -72,7 +72,7 @@ function demonstrate(){
if(animation||rotation)return;
if(game.won||game.moves<=0)reset();
const free=available(game.level,game.tiles);
if(free.length){move(free[0]);return;}
if(free.length){free.sort((a,b)=>trace(game.level,b,game.tiles).steps.length-trace(game.level,a,game.tiles).steps.length);move(free[0]);return;}
const action=solveRotations({...game.level,tiles:game.tiles,moveLimit:game.moves}).actions[0];
if(action?.type==='rotate')turn(action.direction,demonstrate);else{reset();$('motion-status').textContent='演示已重置,请再次播放。';}
}
@@ -86,7 +86,7 @@ $('study-close').onclick=()=>$('study-dialog').close();
$('study-dialog-content').onclick=e=>{const b=e.target.closest('[data-level]');if(b){studyLevel=LEVELS[Number(b.dataset.level)];document.querySelector('.phone-top strong').textContent=`LEVEL ${String(Number(b.dataset.level)+1).padStart(2,'0')}`;boardViewport.camera.reset();reset();resize();document.querySelectorAll('.density-card .mini-scene>span').forEach(el=>el.textContent=`${el.closest('[data-count]').dataset.count} 色 / ${studyLevel.tiles.length} 枚棋子`);$('study-dialog').close();}if(e.target.closest('#study-fit')){boardViewport.camera.reset();resize();$('study-dialog').close();}};
$('study-dialog-content').onchange=e=>{if(e.target.id==='study-slow')$('slow').checked=e.target.checked;};
boardViewport=attachBoardViewport(canvas,{changed:resize,tap:({x,y})=>{if(!view)return;const cell=hitCell(game.level.cells,view,x,y);const tile=cell&&game.tiles.find(t=>t.q===cell.q&&t.r===cell.r);if(tile)move(tile);}});
$('board-size').onchange=()=>{studyLevel=$('board-size').value==='regular'?regularLevel:generateLaneLevel(606,{radius:Number($('board-size').value),target:$('board-size').value==='5'?42:60,axis:0});boardViewport.camera.reset();reset();resize();document.querySelectorAll('.density-card .mini-scene>span').forEach(el=>el.textContent=`${el.closest('[data-count]').dataset.count} 色 / ${studyLevel.tiles.length} 枚棋子`);};
$('board-size').onchange=()=>{studyLevel=$('board-size').value==='regular'?regularLevel:createStudyLevel(Number($('board-size').value));boardViewport.camera.reset();reset();resize();document.querySelectorAll('.density-card .mini-scene>span').forEach(el=>el.textContent=`${el.closest('[data-count]').dataset.count} 色 / ${studyLevel.tiles.length} 枚棋子`);};
$('density-grid').innerHTML=[4,7,9,12].map(n=>`<button class="theme-card density-card" data-count="${n}" aria-pressed="${n===colorCount}"><div class="mini-scene"><span>${n} 色 / ${studyLevel.tiles.length} 枚棋子</span><canvas data-density="${n}" aria-label="${n}色棋盘对照"></canvas></div><strong>${n===4?'简洁':n===7?'丰富':n===9?'缤纷':'满色'} · ${n} 色</strong><small>${n===7?'建议优先体验':n===12?'观察相近色带来的复杂感':'同一布局,比较视觉密度'}</small></button>`).join('');
document.querySelectorAll('[data-count]').forEach(b=>b.onclick=()=>{colorCount=Number(b.dataset.count);document.querySelectorAll('[data-count]').forEach(x=>x.setAttribute('aria-pressed',String(Number(x.dataset.count)===colorCount)));setTheme(LEVEL_THEMES.find(t=>t.id===theme.id));reset();});
document.querySelector('.phone-top strong').textContent='测试关 01';