aardio 文档
aardio 范例: 自绘小游戏 - 螺丝木板解谜(Screw Wood Puzzle)
import win.ui;
/*DSG{{*/
var winform = win.form(text="自绘小游戏 - 螺丝木板解谜(Screw Wood Puzzle)";right=1020;bottom=720;bgcolor=0x171821;border="dialog frame";max=false)
winform.add(
screwCanvas={cls="plus";left=0;top=0;right=1020;bottom=720;db=1;dl=1;dr=1;dt=1;notify=1;z=1}
)
/*}}*/
import gdip;
/*
螺丝木板解谜:
- 点击露出的彩色螺丝,把它们送入右侧 6 格工具盒;3 枚同色螺丝会自动收纳。
- 木板剩一枚螺丝时会围绕支点摆动,最后一枚被拆掉后会旋转坠落并揭开下层螺丝。
- 工具盒塞满且没有凑成三枚同色会触发一次紧急清盒并降低星级。
- H 提示最适合当前工具盒的螺丝,R 或右键重置;通关后单击、空格或回车进入下一关。
- 5 关分别包含 6 / 9 / 12 / 15 / 18 枚螺丝。最优拆解序列均已验证可见、可解且不会堵塞工具盒。
- 木板与螺丝使用 DPI 原尺寸透明精灵;逐帧仅贴图、变换少量木板并绘制短寿命粒子。
*/
var CANVAS_W=1020;
var CANVAS_H=720;
var WORK_X=28;
var WORK_Y=112;
var WORK_W=675;
var WORK_H=570;
var PANEL_X=724;
var PANEL_Y=104;
var PANEL_W=268;
var PANEL_H=568;
var TRAY_CAPACITY=6;
var UNSCREW_TIME=0.28;
var FLY_TIME=0.46;
var STATE_PLAYING=1;
var STATE_SOLVED=2;
var screwColors=[
0xFFFF5F6D,0xFF54DDF2,0xFFFFC857,
0xFFA879FF,0xFF55DB91,0xFFFF8B52
];
var woodStyles=[
{top=0xFFF0B868;bottom=0xFF9B572E;edge=0xFF6A351F;grain=0x7A6E361D;name="橡木"},
{top=0xFFB96B58;bottom=0xFF633340;edge=0xFF482431;grain=0x75622D2A;name="红木"},
{top=0xFF54B7A2;bottom=0xFF235F5D;edge=0xFF173F42;grain=0x68345D50;name="青木"},
{top=0xFF6D91D8;bottom=0xFF34466E;edge=0xFF232C4D;grain=0x69405277;name="蓝木"},
{top=0xFFC680D5;bottom=0xFF684472;edge=0xFF482C52;grain=0x6D59365F;name="紫木"},
{top=0xFFE19A5A;bottom=0xFF7B4930;edge=0xFF50301F;grain=0x78643825;name="胡桃木"}
];
// 六种木板模板共用同一工作台坐标;points 为多边形,pins 为三处可用螺孔。
var shapes={
A={
points=[{x=82;y=190},{x=646;y=205},{x=635;y=300},{x=72;y=284}];
pins=[{x=145;y=235},{x=355;y=244},{x=585;y=250}]
};
B={
points=[{x=126;y=218},{x=205;y=156},{x=594;y=575},{x=505;y=646}];
pins=[{x=275;y=320},{x=356;y=405},{x=526;y=570}]
};
C={
points=[{x=297;y=150},{x=407;y=164},{x=391;y=647},{x=283;y=631}];
pins=[{x=351;y=226},{x=347;y=402},{x=339;y=582}]
};
D={
points=[{x=73;y=430},{x=621;y=399},{x=637;y=505},{x=88;y=537}];
pins=[{x=145;y=484},{x=411;y=458},{x=573;y=447}]
};
E={
points=[{x=519;y=157},{x=601;y=224},{x=207;y=642},{x=123;y=570}];
pins=[{x=485;y=315},{x=360;y=405},{x=190;y=571},{x=420;y=350}]
};
F={
points=[{x=145;y=149},{x=246;y=145},{x=257;y=645},{x=149;y=650}];
pins=[{x=195;y=335},{x=205;y=460},{x=195;y=400}]
}
};
var P=function(slot,color,seq,cover){
return {slot=slot;color=color;seq=seq;cover=cover};
}
var B=function(key,style,pins){
return {key=key;style=style;pins=pins};
}
// boards 按从底层到顶层排列。seq 同时定义了无堵盒的推荐拆解序列。
var levels=[
{
name="初识摆臂";subtitle="先让三块木板学会摆动";accent=0xFFFFC857;
boards=[
B("E",3,[P(3,1,3),P(1,2,6)]),
B("B",2,[P(1,1,2),P(3,2,5)]),
B("A",1,[P(1,1,1),P(3,2,4)])
]
},
{
name="三色工具盒";subtitle="不要按木板顺序拆,要按颜色收纳";accent=0xFF55DFF2;
boards=[
B("E",5,[P(3,1,3),P(4,2,6),P(1,3,9)]),
B("B",3,[P(1,1,2),P(2,2,5),P(3,3,8)]),
B("A",1,[P(1,1,1),P(2,2,4),P(3,3,7)])
]
},
{
name="交叠机关";subtitle="上层木板坠落后,藏住的螺丝才会出现";accent=0xFFA879FF;
boards=[
B("D",6,[P(1,2,6),P(3,3,9),P(2,4,12,"B")]),
B("C",4,[P(3,1,3),P(1,3,8,"A"),P(2,4,11)]),
B("B",2,[P(1,1,2),P(2,2,5),P(3,4,10)]),
B("A",1,[P(1,1,1),P(2,2,4),P(3,3,7)])
]
},
{
name="五彩连锁";subtitle="让摆臂与坠落依次揭开下一层";accent=0xFF55DB91;
boards=[
B("E",5,[P(1,2,6),P(2,5,15,"C"),P(3,1,3)]),
B("D",6,[P(1,1,2),P(2,4,12,"B"),P(3,5,14)]),
B("C",4,[P(1,3,9,"A"),P(2,4,11),P(3,5,13)]),
B("B",2,[P(1,2,5),P(2,3,8),P(3,4,10)]),
B("A",1,[P(1,1,1),P(2,2,4),P(3,3,7)])
]
},
{
name="大师工坊";subtitle="六色、六层支点,完成最后的机械瀑布";accent=0xFFFF6F91;
boards=[
B("F",3,[P(1,1,3),P(2,6,18,"D"),P(3,2,6)]),
B("E",5,[P(1,6,17),P(2,5,15,"C"),P(3,1,2)]),
B("D",6,[P(1,5,14),P(2,4,12,"B"),P(3,6,16)]),
B("C",4,[P(1,3,9,"A"),P(2,4,11),P(3,5,13)]),
B("B",2,[P(1,2,5),P(2,3,8),P(3,4,10)]),
B("A",1,[P(1,1,1),P(2,2,4),P(3,3,7)])
]
}
];
var game={
state=STATE_PLAYING;
level=1;
cycle=1;
boards=[];
boardByKey={};
screws=[];
screwByKey={};
solution=[];
tray=[];
action=null;
removedScrews=0;
totalScrews=0;
moves=0;
matches=0;
jams=0;
misses=0;
hoverId=null;
hintId=null;
hintTimer=0;
blockedBoardKey=null;
blockedTimer=0;
statusText="点击露出的螺丝,同色三枚会自动收纳";
statusTime=2600;
particles=[];
pulse=0;
trayFlash=0;
solveGlow=0;
pendingSolve=false;
};
var CACHE_DPI_X=winform.dpiScale(1);
var CACHE_DPI_Y=CACHE_DPI_X;
var sceneCache;
var sceneDirty=true;
var screwSprites=[];
// 木板按 z 序分段缓存:连续静止木板合并为一张透明位图,
// 摆动或坠落木板保留为动态层,避免高 DPI 下逐帧贴 6 张大尺寸木板精灵。
var BOARD_CACHE_W=710;
var BOARD_CACHE_H=720;
var layerCaches=[];
var renderItems=[];
var layersDirty=true;
var sceneContainsBoards=false;
var sceneCachedBoardEnd=0;
var drawBoardRangeToGraphics;
var dustMotes=[];
for(i=1;58){
table.push(dustMotes,{
x=math.random()*CANVAS_W;y=math.random()*CANVAS_H;
size=0.5+math.random()*1.6;alpha=18+math.random(0,44)
});
}
import sys.midiOut;
var midiOut=sys.midiOut();
// -------------------- 基础工具 --------------------
var alphaColor=function(color,alpha){
return ((math.clamp(math.floor(alpha),0,255)<<24) | (color&0xFFFFFF));
}
var shadeColor=function(color,amount){
var r=math.clamp(((color>>16)&0xFF)+amount,0,255);
var g=math.clamp(((color>>8)&0xFF)+amount,0,255);
var b=math.clamp((color&0xFF)+amount,0,255);
return 0xFF000000 | (math.floor(r)<<16) | (math.floor(g)<<8) | math.floor(b);
}
var drawText=function(graphics,text,size,x,y,width,height,color,bold,align){
var textFont=gdip.font("Microsoft YaHei UI",size,bold ? 1/*_FontStyleBold*/ : 0,2/*_UnitPixel*/);
var format=gdip.stringFormat();
format.align=align;
format.lineAlign=1/*_StringAlignmentCenter*/;
var brush=gdip.solidBrush(color);
graphics.drawString(text,textFont,::RECTF(x,y,width,height),format,brush);
brush.delete();
format.delete();
textFont.delete();
}
var setStatus=function(text,milliseconds){
game.statusText=text;
game.statusTime=milliseconds;
sceneDirty=true;
}
var defaultStatus=function(){
if(game.state==STATE_SOLVED) return "机关已清空!单击或按空格进入下一关";
return "按颜色规划拆解:三枚同色自动收纳,六格塞满会堵盒";
}
var normalizeAngle=function(angle){
while(angle>180) angle-=360;
while(angle<-180) angle+=360;
return angle;
}
var boardPoint=function(board,x,y){
var rad=math.rad(board.angle);
var dx=x-board.baseCx;
var dy=y-board.baseCy;
return board.cx+dx*math.cos(rad)-dy*math.sin(rad),
board.cy+dx*math.sin(rad)+dy*math.cos(rad);
}
var screwPosition=function(screw){
return boardPoint(screw.board,screw.x,screw.y);
}
var traySlotPoint=function(index){
var col=(index-1)%3;
var row=math.floor((index-1)/3);
return 770+col*82,342+row*76;
}
var remainingScrews=function(board){
var remaining=[];
for(i,screwId in board.screwIds){
var screw=game.screws[screwId];
if(!screw.removed && !screw.removing) table.push(remaining,screw);
}
return remaining;
}
var pointInBoard=function(x,y,board){
if(!board || board.gone) return false;
var inside=false;
var j=#board.points;
for(i=1;#board.points){
var ax,ay=boardPoint(board,board.points[i].x,board.points[i].y);
var bx,by=boardPoint(board,board.points[j].x,board.points[j].y);
if(((ay>y)!=(by>y)) && (x<(bx-ax)*(y-ay)/(by-ay)+ax)) inside=!inside;
j=i;
}
return inside;
}
var isScrewVisible=function(screw){
if(!screw || screw.removed || screw.removing || screw.board.gone) return false;
if(screw.cover){
var coverBoard=game.boardByKey[screw.cover];
if(coverBoard && coverBoard.covering) return false;
}
var x,y=screwPosition(screw);
for(i=#game.boards;1;-1){
var board=game.boards[i];
if(pointInBoard(x,y,board)) return board==screw.board;
}
return false;
}
var allBoardsGone=function(){
for(i,board in game.boards){
if(!board.gone) return false;
}
return true;
}
var disposeLayerCaches=function(){
for(i,cache in layerCaches){
if(cache.bitmap) cache.bitmap.delete();
}
layerCaches=[];
renderItems=[];
}
var disposeSprites=function(){
disposeLayerCaches();
sceneContainsBoards=false;
sceneCachedBoardEnd=0;
for(i,board in game.boards){
if(board.sprite){
board.sprite.bitmap.delete();
board.sprite=null;
}
if(board.motionSprite){
board.motionSprite.bitmap.delete();
board.motionSprite=null;
}
}
for(i,sprite in screwSprites){
if(sprite) sprite.bitmap.delete();
}
screwSprites=[];
layersDirty=true;
}
// -------------------- 关卡与有限状态物理 --------------------
var initLevel=function(levelIndex){
disposeSprites();
game.level=levelIndex;
game.state=STATE_PLAYING;
game.boards=[];
game.boardByKey={};
game.screws=[];
game.screwByKey={};
game.solution=[];
game.tray=[];
game.action=null;
game.removedScrews=0;
game.totalScrews=0;
game.moves=0;
game.matches=0;
game.jams=0;
game.misses=0;
game.hoverId=null;
game.hintId=null;
game.hintTimer=0;
game.blockedBoardKey=null;
game.blockedTimer=0;
game.statusText="点击露出的螺丝,同色三枚会自动收纳";
game.statusTime=2800;
game.particles=[];
game.trayFlash=0;
game.solveGlow=0;
game.pendingSolve=false;
var levelData=levels[levelIndex];
for(i,boardDef in levelData.boards){
var shape=shapes[boardDef.key];
var minX=10000,minY=10000,maxX=-10000,maxY=-10000;
var sumX=0,sumY=0;
for(j,p in shape.points){
minX=math.min(minX,p.x);minY=math.min(minY,p.y);
maxX=math.max(maxX,p.x);maxY=math.max(maxY,p.y);
sumX+=p.x;sumY+=p.y;
}
var board={
id=i;key=boardDef.key;style=boardDef.style;
points=shape.points;pinPoints=shape.pins;
screwIds=[];
baseCx=sumX/#shape.points;baseCy=sumY/#shape.points;
cx=sumX/#shape.points;cy=sumY/#shape.points;
minX=minX;minY=minY;maxX=maxX;maxY=maxY;
angle=0;angularVel=0;targetAngle=0;settleTime=0;
mode="fixed";covering=true;gone=false;
vx=0;vy=0;omega=0;fallTime=0;
};
table.push(game.boards,board);
game.boardByKey[board.key]=board;
for(j,pinDef in boardDef.pins){
var pos=shape.pins[pinDef.slot];
var screw={
id=#game.screws+1;key=board.key++j;
board=board;boardKey=board.key;
x=pos.x;y=pos.y;color=pinDef.color;seq=pinDef.seq;cover=pinDef.cover;
removed=false;removing=false;
};
table.push(game.screws,screw);
table.push(board.screwIds,screw.id);
game.screwByKey[screw.key]=screw;
game.solution[screw.seq]=screw;
}
}
game.totalScrews=#game.screws;
sceneDirty=true;
layersDirty=true;
}
var nextLevel=function(){
var next=game.level+1;
if(next>#levels){
next=1;
game.cycle++;
}
initLevel(next);
if(midiOut) midiOut.play("changeInstrument(10), 1_,3_,5_", "C5", 55);
}
var spawnParticles=function(x,y,count,color,kind){
for(i=1;count){
var speed=kind=="dust" ? 55+math.random()*95 : 90+math.random()*190;
var angle=(kind=="dust" ? -math.pi*0.95+math.random()*math.pi*0.9 : math.random()*math.pi*2);
table.push(game.particles,{
x=x;y=y;
vx=math.cos(angle)*speed;vy=math.sin(angle)*speed;
gravity=kind=="dust" ? 150 : 210;
life=0.42+math.random()*0.48;maxLife=0.42+math.random()*0.48;
size=kind=="dust" ? 2+math.random()*3.6 : 1.5+math.random()*3;
color=color;kind=kind;spin=math.random()*math.pi*2
});
}
while(#game.particles>110) table.remove(game.particles,1);
}
var beginHanging=function(board,pivotScrew){
board.mode="hanging";
board.pivotLocalX=pivotScrew.x;
board.pivotLocalY=pivotScrew.y;
var pivotX,pivotY=screwPosition(pivotScrew);
board.pivotX=pivotX;
board.pivotY=pivotY;
var dx=board.baseCx-board.pivotLocalX;
var dy=board.baseCy-board.pivotLocalY;
var target=normalizeAngle(90-math.deg(math.atan2(dy,dx)));
if(math.abs(target)>124) target=target>0 ? 124 : -124;
if(math.abs(target)<24) target=(board.id%2 ? 1 : -1)*58;
board.targetAngle=target;
board.angularVel=(target>=0 ? 1 : -1)*(35+board.id*4);
board.settleTime=0;
layersDirty=true;
sceneDirty=true;
setStatus("木板只剩一个支点,正在围绕最后一枚螺丝摆动",1200);
}
var beginFalling=function(board){
board.mode="falling";
board.vx=(board.cx-(WORK_X+WORK_W/2))*0.12+(board.id%2 ? -18 : 18);
board.vy=35+board.id*5;
board.omega=(board.id%2 ? -1 : 1)*(42+board.id*7);
board.fallTime=0;
board.covering=true;
board.settleTime=0;
layersDirty=true;
sceneDirty=true;
var style=woodStyles[board.style];
spawnParticles(board.cx,board.cy,13,shadeColor(style.top,22),"dust");
setStatus("最后一个支点已释放,木板正在旋转坠落",1100);
if(midiOut) midiOut.play("changeInstrument(116), 1_,5_", "C3", 48);
}
var updateBoardState=function(board){
var remaining=remainingScrews(board);
if(#remaining==0){
beginFalling(board);
}
elseif(#remaining==1 && board.mode=="fixed"){
beginHanging(board,remaining[1]);
}
}
var detachScrew=function(action){
var screw=action.screw;
if(action.detached) return;
action.detached=true;
screw.removed=true;
screw.removing=false;
game.removedScrews++;
game.hoverId=null;
game.hintId=null;
game.hintTimer=0;
updateBoardState(screw.board);
spawnParticles(action.startX,action.startY,10,screwColors[screw.color],"spark");
action.targetSlot=#game.tray+1;
action.targetX,action.targetY=traySlotPoint(action.targetSlot);
sceneDirty=true;
layersDirty=true;
if(midiOut) midiOut.play("changeInstrument(13), 1_,5_", "C6", 34);
}
var removeMatchedColor=function(color){
var indexes=[];
for(i,value in game.tray){
if(value==color) table.push(indexes,i);
}
if(#indexes<3) return false;
for(i=#indexes;1;-1) table.remove(game.tray,indexes[i]);
game.matches++;
game.trayFlash=1;
spawnParticles(850,378,24,screwColors[color],"spark");
setStatus("三枚同色螺丝已自动收纳,工具盒腾出了空间",1450);
if(midiOut) midiOut.play("changeInstrument(10), 1_,3_,5_,1'_", "C5", 48);
return true;
}
var finishLevel=function(){
if(game.state==STATE_SOLVED) return;
game.state=STATE_SOLVED;
game.pendingSolve=false;
game.tray=[];
game.solveGlow=0;
setStatus("机关已清空!单击或按空格进入下一关",100000);
spawnParticles(WORK_X+WORK_W/2,WORK_Y+WORK_H/2,58,levels[game.level].accent,"spark");
if(midiOut) midiOut.play("changeInstrument(10), 1_,3_,5_,1'__,3'__,5'__", "C4", 78,70);
}
var finishScrewAction=function(action){
table.push(game.tray,action.screw.color);
var matched=removeMatchedColor(action.screw.color);
if(!matched && #game.tray>=TRAY_CAPACITY){
game.jams++;
game.tray=[];
game.trayFlash=-1;
spawnParticles(850,378,28,0xFFFF5F6D,"spark");
setStatus("工具盒堵住了!已紧急清盒,本关星级会下降",1900);
if(midiOut) midiOut.play("changeInstrument(47), '5_,'3_,'1__", "C3", 80);
}
elseif(!matched){
setStatus("螺丝已放入工具盒:继续寻找相同颜色",950);
}
game.action=null;
sceneDirty=true;
if(game.removedScrews>=game.totalScrews){
game.pendingSolve=true;
if(allBoardsGone()) finishLevel();
}
}
var startRemoveScrew=function(screw){
if(game.state!=STATE_PLAYING || game.action || !isScrewVisible(screw)) return;
var x,y=screwPosition(screw);
screw.removing=true;
game.action={
screw=screw;time=0;detached=false;
startX=x;startY=y;targetX=x;targetY=y;targetSlot=#game.tray+1
};
game.moves++;
game.hoverId=null;
setStatus("正在旋出彩色螺丝……",650);
if(midiOut) midiOut.play("changeInstrument(12), 1_,#1_", "C6", 30);
}
var findHintScrew=function(){
var counts=[];
for(i=1;#screwColors) counts[i]=0;
for(i,color in game.tray) counts[color]++;
var preferredColor=null;
var preferredCount=0;
for(color=1;#screwColors){
if(counts[color]>preferredCount){
preferredCount=counts[color];
preferredColor=color;
}
}
if(preferredColor){
for(i,screw in game.solution){
if(screw.color==preferredColor && isScrewVisible(screw)) return screw;
}
}
for(i,screw in game.solution){
if(isScrewVisible(screw)) return screw;
}
for(i,screw in game.screws){
if(isScrewVisible(screw)) return screw;
}
}
initLevel(1);
// -------------------- 输入 --------------------
var logicalMousePos=function(lParam){
var x,y=win.getMessagePos(lParam);
var dpiX,dpiY=winform.dpiScale(1,1);
return x/dpiX,y/dpiY;
}
var hitScrew=function(x,y,onlyVisible){
for(i=#game.screws;1;-1){
var screw=game.screws[i];
if(!screw.removed && !screw.removing && (!onlyVisible || isScrewVisible(screw))){
var sx,sy=screwPosition(screw);
if(math.dist(x,y,sx,sy)<=19) return screw;
}
}
}
winform.screwCanvas.onMouseMove=function(wParam,lParam){
if(game.action || game.state!=STATE_PLAYING){
game.hoverId=null;
return;
}
var x,y=logicalMousePos(lParam);
var screw=hitScrew(x,y,true);
game.hoverId=screw ? screw.id;
}
winform.screwCanvas.onMouseLeave=function(){
game.hoverId=null;
}
winform.screwCanvas.onMouseDown=function(wParam,lParam){
if(game.state==STATE_SOLVED){
nextLevel();
return;
}
if(game.action) return;
var x,y=logicalMousePos(lParam);
var screw=hitScrew(x,y,true);
if(screw){
startRemoveScrew(screw);
return;
}
var hidden=hitScrew(x,y,false);
if(hidden && !isScrewVisible(hidden)){
game.blockedBoardKey=hidden.cover;
game.blockedTimer=1500;
setStatus("这枚螺丝仍被上层木板压住,先释放发红光的木板",1500);
if(midiOut) midiOut.play("changeInstrument(116), 1_", "C3", 45);
}
else{
game.misses++;
setStatus("请点击露出的彩色螺丝头;木板本身不能直接拖动",1100);
}
}
winform.screwCanvas.onRightMouseUp=function(){
initLevel(game.level);
}
winform.screwCanvas.dlgCode=4/*_DLGC_WANTALLKEYS*/;
winform.screwCanvas.onKeyDown=function(keyCode,lParam,repeat){
if(repeat) return;
if(keyCode==0x52/*_VK_R*/){
initLevel(game.level);
}
elseif(keyCode==0x48/*_VK_H*/ && game.state==STATE_PLAYING && !game.action){
var screw=findHintScrew();
if(screw){
game.hintId=screw.id;
game.hintTimer=2300;
setStatus("提示:发出明亮脉冲的螺丝最适合当前工具盒",2300);
if(midiOut) midiOut.play("changeInstrument(10), 1_,3_", "C6", 42);
}
else{
setStatus("正在等待坠落木板移开,下一枚螺丝马上出现",1200);
}
}
elseif((keyCode==0x20/*_VK_SPACE*/ || keyCode==0x0D/*_VK_RETURN*/) && game.state==STATE_SOLVED){
nextLevel();
}
}
// -------------------- 木板与螺丝精灵 --------------------
var offsetPoints=function(points,offsetX,offsetY){
var result=[];
for(i,p in points) table.push(result,{x=p.x+offsetX;y=p.y+offsetY});
return result;
}
var drawBoardVector=function(graphics,board,offsetX,offsetY){
var style=woodStyles[board.style];
var points=offsetPoints(board.points,offsetX,offsetY);
var minX=board.minX+offsetX;
var minY=board.minY+offsetY;
var maxX=board.maxX+offsetX;
var maxY=board.maxY+offsetY;
var softShadowPoints=[];
for(i,p in points) table.push(softShadowPoints,{x=p.x+12;y=p.y+16});
var softShadow=gdip.solidBrush(0x36000000);
graphics.fillPolygon(softShadow,softShadowPoints);
softShadow.delete();
var shadowPoints=[];
for(i,p in points) table.push(shadowPoints,{x=p.x+7;y=p.y+10});
var shadow=gdip.solidBrush(0x82000000);
graphics.fillPolygon(shadow,shadowPoints);
shadow.delete();
var edge=gdip.pen(style.edge,7);
graphics.drawPolygon(edge,points);
edge.delete();
var path=gdip.path();
path.addPolygon(points);
var saved=graphics.save();
graphics.setClipPath(path);
var wood=gdip.lineBrush(::POINTF(minX,minY),::POINTF(maxX,maxY),style.top,style.bottom);
graphics.fillPath(wood,path);
wood.delete();
var warmBand=gdip.solidBrush(alphaColor(shadeColor(style.top,38),28));
graphics.fillPolygon(warmBand,[
{x=minX-30;y=minY+5},{x=maxX+30;y=minY+5},
{x=maxX+30;y=minY+(maxY-minY)*0.34},{x=minX-30;y=minY+(maxY-minY)*0.52}
]);
warmBand.delete();
for(lineIndex=1;7){
var ratio=lineIndex/8;
var y=minY+(maxY-minY)*ratio;
var wave=7*math.sin(board.id*1.7+lineIndex);
var grainColor=shadeColor(style.edge,22);
var grain=gdip.pen(alphaColor(grainColor,76+lineIndex*4),1.2+(lineIndex%2)*0.55);
graphics.drawBezier(grain,
minX-35,y,
minX+(maxX-minX)*0.32,y+wave,
minX+(maxX-minX)*0.67,y-wave*0.7,
maxX+35,y+wave*0.25
);
grain.delete();
if(lineIndex%2){
var grainLight=gdip.pen(alphaColor(shadeColor(style.top,55),58),0.8);
graphics.drawBezier(grainLight,
minX-30,y-3,
minX+(maxX-minX)*0.35,y+wave-4,
minX+(maxX-minX)*0.70,y-wave*0.55-3,
maxX+30,y+wave*0.2-3
);
grainLight.delete();
}
}
for(knot=1;2){
var kx=minX+(maxX-minX)*(0.28+knot*0.23);
var ky=minY+(maxY-minY)*(0.35+(board.id+knot)%3*0.15);
var knotPen=gdip.pen(alphaColor(style.grain,88),1.2);
graphics.drawEllipse(knotPen,kx-13,ky-6,26,12);
graphics.drawEllipse(knotPen,kx-6,ky-3,12,6);
knotPen.delete();
}
graphics.restore(saved);
path.delete();
var rim=gdip.pen(alphaColor(shadeColor(style.top,68),175),1.8);
graphics.drawPolygon(rim,points);
rim.delete();
var inner=gdip.pen(0x4DFFFFFF,1);
graphics.drawLine(inner,points[1],points[2]);
inner.delete();
for(i,screwId in board.screwIds){
var screw=game.screws[screwId];
var x=screw.x+offsetX;
var y=screw.y+offsetY;
var holeShadow=gdip.solidBrush(0x99000000);
graphics.fillCircle(holeShadow,x+1.8,y+3,19);
holeShadow.delete();
var holeBevel=gdip.lineBrush(::POINTF(x-18,y-18),::POINTF(x+18,y+18),
shadeColor(style.bottom,-34),shadeColor(style.top,30));
graphics.fillCircle(holeBevel,x,y,17.8);
holeBevel.delete();
var hole=gdip.lineBrush(::POINTF(x-11,y-11),::POINTF(x+11,y+11),0xFF0D0B0A,0xFF3C291E);
graphics.fillCircle(hole,x,y,11.5);
hole.delete();
var holeEdge=gdip.pen(0xA05B3A28,1.4);
graphics.drawCircle(holeEdge,x,y,17.2);
holeEdge.delete();
}
}
var getBoardSprite=function(board){
if(board.sprite && (math.abs(board.sprite.dpiX-CACHE_DPI_X)>0.001 || math.abs(board.sprite.dpiY-CACHE_DPI_Y)>0.001)){
board.sprite.bitmap.delete();
board.sprite=null;
}
if(board.sprite) return board.sprite;
var margin=24;
var x=board.minX-margin;
var y=board.minY-margin;
var width=board.maxX-board.minX+margin*2+12;
var height=board.maxY-board.minY+margin*2+14;
var bitmap=gdip.bitmap(math.ceil(width*CACHE_DPI_X),math.ceil(height*CACHE_DPI_Y));
var graphics=gdip.graphics(bitmap);
graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
graphics.scale(CACHE_DPI_X,CACHE_DPI_Y);
drawBoardVector(graphics,board,-x,-y);
graphics.delete();
board.sprite={bitmap=bitmap;x=x;y=y;width=width;height=height;dpiX=CACHE_DPI_X;dpiY=CACHE_DPI_Y};
return board.sprite;
}
var getMotionBoardSprite=function(board){
if(board.motionSprite && (math.abs(board.motionSprite.dpiX-CACHE_DPI_X)>0.001 || math.abs(board.motionSprite.dpiY-CACHE_DPI_Y)>0.001)){
board.motionSprite.bitmap.delete();
board.motionSprite=null;
}
if(board.motionSprite) return board.motionSprite;
// 大木板旋转时使用略低于屏幕 DPI 的临时精灵;运动中几乎看不出差异,
// 但可显著减少高 DPI 下旋转插值的像素量。静止后仍换回 DPI 原尺寸缓存。
var motionScale=math.min(CACHE_DPI_X,1.35);
var margin=24;
var x=board.minX-margin;
var y=board.minY-margin;
var width=board.maxX-board.minX+margin*2+12;
var height=board.maxY-board.minY+margin*2+14;
var bitmap=gdip.bitmap(math.ceil(width*motionScale),math.ceil(height*motionScale));
var graphics=gdip.graphics(bitmap);
graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
graphics.scale(motionScale,motionScale);
drawBoardVector(graphics,board,-x,-y);
graphics.delete();
board.motionSprite={bitmap=bitmap;x=x;y=y;width=width;height=height;dpiX=CACHE_DPI_X;dpiY=CACHE_DPI_Y};
return board.motionSprite;
}
var drawBoardSprite=function(graphics,board,motion){
if(board.gone) return;
var sprite=motion ? getMotionBoardSprite(board) : getBoardSprite(board);
var saved=graphics.save();
graphics.translate(board.cx,board.cy);
graphics.rotate(board.angle);
graphics.translate(-board.baseCx,-board.baseCy);
graphics.drawImageRect(sprite.bitmap,sprite.x,sprite.y,sprite.width,sprite.height);
graphics.restore(saved);
}
var drawScrewVector=function(graphics,colorIndex){
var color=screwColors[colorIndex];
var shadow=gdip.solidBrush(0x76000000);
graphics.fillCircle(shadow,2,3,16.5);
shadow.delete();
var washer=gdip.solidBrush(alphaColor(color,118));
graphics.fillCircle(washer,0,0,17.5);
washer.delete();
var outer=gdip.lineBrush(::POINTF(-14,-14),::POINTF(14,14),0xFFF5F8FA,0xFF59636B);
graphics.fillCircle(outer,0,0,14.8);
outer.delete();
var rim=gdip.pen(0xFF242B31,1.5);
graphics.drawCircle(rim,0,0,14.6);
rim.delete();
var enamel=gdip.lineBrush(::POINTF(-10,-10),::POINTF(10,10),shadeColor(color,62),shadeColor(color,-46));
graphics.fillCircle(enamel,0,0,10.7);
enamel.delete();
var enamelEdge=gdip.pen(alphaColor(shadeColor(color,-75),210),1.2);
graphics.drawCircle(enamelEdge,0,0,10.7);
enamelEdge.delete();
var slotShadow=gdip.pen(0xB0000000,3.5);
graphics.drawLine(slotShadow,-6,0,6,0);
graphics.drawLine(slotShadow,0,-6,0,6);
slotShadow.delete();
var slot=gdip.pen(0xD9F4F7F8,1.35);
graphics.drawLine(slot,-5.5,-0.8,5.5,-0.8);
graphics.drawLine(slot,-0.8,-5.5,-0.8,5.5);
slot.delete();
var shine=gdip.solidBrush(0xBFFFFFFF);
graphics.fillCircle(shine,-5,-5,2.1);
shine.delete();
}
var getScrewSprite=function(colorIndex){
var sprite=screwSprites[colorIndex];
if(sprite && (math.abs(sprite.dpiX-CACHE_DPI_X)>0.001 || math.abs(sprite.dpiY-CACHE_DPI_Y)>0.001)){
sprite.bitmap.delete();
screwSprites[colorIndex]=null;
sprite=null;
}
if(sprite) return sprite;
var size=44;
var bitmap=gdip.bitmap(math.ceil(size*CACHE_DPI_X),math.ceil(size*CACHE_DPI_Y));
var graphics=gdip.graphics(bitmap);
graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
graphics.scale(CACHE_DPI_X,CACHE_DPI_Y);
graphics.translate(size/2,size/2);
drawScrewVector(graphics,colorIndex);
graphics.delete();
sprite={bitmap=bitmap;size=size;dpiX=CACHE_DPI_X;dpiY=CACHE_DPI_Y};
screwSprites[colorIndex]=sprite;
return sprite;
}
var drawScrewSprite=function(graphics,colorIndex,x,y,angle,scale){
var sprite=getScrewSprite(colorIndex);
var saved=graphics.save();
graphics.translate(x,y);
if(angle) graphics.rotate(angle);
if(scale!=1) graphics.scale(scale,scale);
graphics.drawImageRect(sprite.bitmap,-sprite.size/2,-sprite.size/2,sprite.size,sprite.size);
graphics.restore(saved);
}
// -------------------- 静态场景缓存 --------------------
var starCount=function(){
if(game.jams==0) return 3;
if(game.jams==1) return 2;
return 1;
}
var drawBackground=function(graphics){
var bg=gdip.lineBrush(::POINTF(0,0),::POINTF(CANVAS_W,CANVAS_H),0xFF101C2A,0xFF25171B);
graphics.fillRect(bg,0,0,CANVAS_W,CANVAS_H);
bg.delete();
var amberGlow=gdip.solidBrush(0x1FFF9B48);
graphics.fillCircle(amberGlow,140,165,250);
amberGlow.delete();
var blueGlow=gdip.solidBrush(0x1654DDF2);
graphics.fillCircle(blueGlow,930,130,245);
blueGlow.delete();
for(i,mote in dustMotes){
var brush=gdip.solidBrush(alphaColor(0xFFFFE2B0,mote.alpha));
graphics.fillCircle(brush,mote.x,mote.y,mote.size);
brush.delete();
}
drawText(graphics,"螺 丝 木 板 解 谜",29,0,17,710,43,0xFFF8F4EE,true,1);
drawText(graphics,"SCREW • SWING • DROP",10,0,57,710,20,0x9DDBCCBC,false,1);
drawText(graphics,"按颜色收纳螺丝,让失去支点的木板摆动并坠落",13,0,82,710,26,0xC9E6DED5,false,1);
}
var drawWorkbench=function(graphics){
var shadow=gdip.solidBrush(0x72000000);
graphics.fillRoundRect(shadow,WORK_X+5,WORK_Y+8,WORK_W,WORK_H,24);
shadow.delete();
var frame=gdip.lineBrush(::POINTF(WORK_X,WORK_Y),::POINTF(WORK_X+WORK_W,WORK_Y+WORK_H),0xFF5B6670,0xFF20282F);
graphics.fillRoundRect(frame,WORK_X,WORK_Y,WORK_W,WORK_H,22);
frame.delete();
var frameEdge=gdip.pen(0x8AB7C0C7,1.4);
graphics.drawRoundRect(frameEdge,WORK_X,WORK_Y,WORK_W,WORK_H,22);
frameEdge.delete();
var plate=gdip.lineBrush(::POINTF(WORK_X+10,WORK_Y+10),::POINTF(WORK_X+10,WORK_Y+WORK_H-10),0xFF252B31,0xFF11171C);
graphics.fillRoundRect(plate,WORK_X+10,WORK_Y+10,WORK_W-20,WORK_H-20,16);
plate.delete();
var hole=gdip.solidBrush(0x6D05080A);
for(row=0;9){
for(col=0;11){
graphics.fillCircle(hole,WORK_X+45+col*52,WORK_Y+42+row*52,2.2);
}
}
hole.delete();
var blueprint=gdip.pen(0x174FDFF0,1.2);
graphics.drawCircle(blueprint,WORK_X+WORK_W/2,WORK_Y+WORK_H/2,88);
graphics.drawCircle(blueprint,WORK_X+WORK_W/2,WORK_Y+WORK_H/2,48);
for(i=1;12){
var angle=i*math.pi/6;
graphics.drawLine(blueprint,
WORK_X+WORK_W/2+math.cos(angle)*52,WORK_Y+WORK_H/2+math.sin(angle)*52,
WORK_X+WORK_W/2+math.cos(angle)*82,WORK_Y+WORK_H/2+math.sin(angle)*82
);
}
blueprint.delete();
var corner=gdip.lineBrush(::POINTF(0,0),::POINTF(12,12),0xFFB7C0C5,0xFF434A50);
for(i,pt in [
{x=WORK_X+24;y=WORK_Y+24},{x=WORK_X+WORK_W-24;y=WORK_Y+24},
{x=WORK_X+24;y=WORK_Y+WORK_H-24},{x=WORK_X+WORK_W-24;y=WORK_Y+WORK_H-24}
]){
graphics.fillCircle(corner,pt.x,pt.y,8);
}
corner.delete();
}
var drawProgressGauge=function(graphics){
var levelData=levels[game.level];
var cx=858,cy=252;
var back=gdip.pen(0x5A52616B,8);
graphics.drawArc(back,cx-46,cy-46,92,92,-220,260);
back.delete();
var ratio=game.totalScrews ? game.removedScrews/game.totalScrews : 0;
if(ratio>0){
var progress=gdip.pen(alphaColor(levelData.accent,230),8);
graphics.drawArc(progress,cx-46,cy-46,92,92,-220,260*ratio);
progress.delete();
}
var hub=gdip.lineBrush(::POINTF(cx-23,cy-23),::POINTF(cx+23,cy+23),0xFF40515E,0xFF121C24);
graphics.fillCircle(hub,cx,cy,27);
hub.delete();
var hubEdge=gdip.pen(alphaColor(levelData.accent,175),1.8);
graphics.drawCircle(hubEdge,cx,cy,27);
hubEdge.delete();
drawText(graphics,game.removedScrews++" / "++game.totalScrews,11,cx-40,cy-11,80,22,0xFFF2F6F8,true,1);
}
var drawTray=function(graphics){
drawText(graphics,"六格磁吸工具盒",11,751,300,214,22,0xFFE3EBEF,true,0);
var trayShadow=gdip.solidBrush(0x5E000000);
graphics.fillRoundRect(trayShadow,744,319,230,128,17);
trayShadow.delete();
var trayPlate=gdip.lineBrush(::POINTF(744,319),::POINTF(974,447),0xFF344650,0xFF111B21);
graphics.fillRoundRect(trayPlate,742,316,230,128,17);
trayPlate.delete();
var trayEdge=gdip.pen(0x8293A5AE,1.4);
graphics.drawRoundRect(trayEdge,742,316,230,128,17);
trayEdge.delete();
for(slot=1;TRAY_CAPACITY){
var x,y=traySlotPoint(slot);
var slotShadow=gdip.solidBrush(0x82000000);
graphics.fillCircle(slotShadow,x+2,y+3,25);
slotShadow.delete();
var slotBrush=gdip.lineBrush(::POINTF(x-22,y-22),::POINTF(x+22,y+22),0xFF30434E,0xFF080E12);
graphics.fillCircle(slotBrush,x,y,23);
slotBrush.delete();
var slotEdge=gdip.pen(0x9AA8B7BE,1.6);
graphics.drawCircle(slotEdge,x,y,23);
slotEdge.delete();
if(game.tray[slot]){
drawScrewSprite(graphics,game.tray[slot],x,y,0,0.82);
}
else{
var magnet=gdip.pen(0x536E858F,1.2);
graphics.drawLine(magnet,x-5,y,x+5,y);
graphics.drawLine(magnet,x,y-5,x,y+5);
magnet.delete();
}
}
}
var drawSidePanel=function(graphics){
var levelData=levels[game.level];
var shadow=gdip.solidBrush(0x65000000);
graphics.fillRoundRect(shadow,PANEL_X+5,PANEL_Y+7,PANEL_W,PANEL_H,22);
shadow.delete();
var panel=gdip.lineBrush(::POINTF(PANEL_X,PANEL_Y),::POINTF(PANEL_X+PANEL_W,PANEL_Y+PANEL_H),0xEF253542,0xF20C151C);
graphics.fillRoundRect(panel,PANEL_X,PANEL_Y,PANEL_W,PANEL_H,22);
panel.delete();
var border=gdip.pen(0x4B8195A1,1.2);
graphics.drawRoundRect(border,PANEL_X,PANEL_Y,PANEL_W,PANEL_H,22);
border.delete();
drawText(graphics,"第 "++game.level++" / "++#levels++" 关",11,744,119,228,20,alphaColor(levelData.accent,220),true,1);
drawText(graphics,levelData.name,22,742,140,232,35,0xFFF5F8FA,true,1);
drawText(graphics,levelData.subtitle,10,745,174,226,24,0xD0D7E1E6,false,1);
drawProgressGauge(graphics);
drawTray(graphics);
drawText(graphics,"操作 "++game.moves,10,750,454,68,24,0xFFDCE6EC,true,0);
drawText(graphics,"收纳 "++game.matches,10,825,454,68,24,0xFF9DEBBE,true,0);
drawText(graphics,"堵盒 "++game.jams,10,900,454,68,24,game.jams ? 0xFFFF9DA8 : 0xFFDCE6EC,true,0);
var statusBack=gdip.solidBrush(game.state==STATE_SOLVED ? alphaColor(levelData.accent,108) : 0x68101B23);
graphics.fillRoundRect(statusBack,748,489,220,67,13);
statusBack.delete();
var statusEdge=gdip.pen(game.state==STATE_SOLVED ? alphaColor(levelData.accent,238) : 0x354D7484,1.2);
graphics.drawRoundRect(statusEdge,748,489,220,67,13);
statusEdge.delete();
drawText(graphics,game.statusText,10,759,496,198,52,game.state==STATE_SOLVED ? 0xFFFFFFFF : 0xFFD9E4E9,game.state==STATE_SOLVED,1);
if(game.state==STATE_SOLVED){
var stars="";
for(i=1;3) stars++=(i<=starCount() ? '\u2605' : '\u2606');
drawText(graphics,stars,24,750,568,216,34,0xFFFFD46A,true,1);
drawText(graphics,"零堵盒可获得三星评价",10,750,605,216,20,0xD6E1E8EC,false,1);
}
else{
drawText(graphics,"H 提示",10,750,576,76,24,0xFFD0DDE4,true,0);
drawText(graphics,"R / 右键 重置",10,836,576,132,24,0xFFD0DDE4,true,0);
drawText(graphics,"三枚同色自动收纳",10,750,608,216,20,0xFFDCE6EC,false,1);
drawText(graphics,"六格无三连会紧急清盒",10,750,632,216,20,0xFFDCE6EC,false,1);
}
}
var buildSceneCache=function(){
var width=math.ceil(CANVAS_W*CACHE_DPI_X);
var height=math.ceil(CANVAS_H*CACHE_DPI_Y);
if(sceneCache) sceneCache.delete();
sceneCache=gdip.bitmap(width,height);
var graphics=gdip.graphics(sceneCache);
graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
graphics.textRenderingHint=4/*_TextRenderingHintAntiAlias*/;
graphics.scale(CACHE_DPI_X,CACHE_DPI_Y);
drawBackground(graphics);
drawWorkbench(graphics);
// 把最底部连续静止的木板直接并入完整场景缓存;
// 动态木板及其上层再按 z 序分段,可少贴一张大尺寸透明图。
sceneCachedBoardEnd=0;
for(index=1;#game.boards){
var board=game.boards[index];
if(board.mode=="hanging" || board.mode=="falling") break;
sceneCachedBoardEnd=index;
}
sceneContainsBoards=sceneCachedBoardEnd>=#game.boards;
if(sceneCachedBoardEnd>0 && drawBoardRangeToGraphics) drawBoardRangeToGraphics(graphics,1,sceneCachedBoardEnd);
drawSidePanel(graphics);
graphics.delete();
sceneDirty=false;
}
// -------------------- 动态绘图 --------------------
var drawBoardHighlight=function(graphics,board){
if(!board || board.gone) return;
var saved=graphics.save();
graphics.translate(board.cx,board.cy);
graphics.rotate(board.angle);
graphics.translate(-board.baseCx,-board.baseCy);
var pulse=3+2*math.sin(game.pulse/90);
var glow=gdip.pen(alphaColor(0xFFFF5F6D,125+55*math.sin(game.pulse/120)),3.4+pulse*0.25);
graphics.drawPolygon(glow,board.points);
glow.delete();
graphics.restore(saved);
}
var drawBoardScrews=function(graphics,board){
for(i,screwId in board.screwIds){
var screw=game.screws[screwId];
if(isScrewVisible(screw)){
var x,y=screwPosition(screw);
drawScrewSprite(graphics,screw.color,x,y,0,1);
}
}
}
drawBoardRangeToGraphics=function(graphics,startIndex,endIndex){
for(index=startIndex;endIndex){
var board=game.boards[index];
drawBoardSprite(graphics,board);
drawBoardScrews(graphics,board);
}
}
var drawScrewHighlights=function(graphics){
for(i,screw in game.screws){
if(isScrewVisible(screw)){
var isHover=game.hoverId==screw.id;
var isHint=game.hintId==screw.id && game.hintTimer>0;
if(isHover || isHint){
var x,y=screwPosition(screw);
var radius=isHint ? 23+4*math.sin(game.pulse/105) : 21+2*math.sin(game.pulse/130);
var alpha=isHint ? 105+65*math.sin(game.pulse/125) : 72;
var halo=gdip.solidBrush(alphaColor(screwColors[screw.color],alpha));
graphics.fillCircle(halo,x,y,radius);
halo.delete();
var ring=gdip.pen(alphaColor(0xFFFFFFFF,isHint ? 175 : 105),isHint ? 2.3 : 1.4);
graphics.drawCircle(ring,x,y,radius+2);
ring.delete();
}
}
}
}
var buildLayerCaches=function(){
disposeLayerCaches();
var addSegment=function(startIndex,endIndex){
if(!startIndex) return;
var bitmap=gdip.bitmap(math.ceil(BOARD_CACHE_W*CACHE_DPI_X),math.ceil(BOARD_CACHE_H*CACHE_DPI_Y));
var graphics=gdip.graphics(bitmap);
graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
graphics.interpolationMode=3/*_InterpolationModeBilinear*/;
graphics.scale(CACHE_DPI_X,CACHE_DPI_Y);
for(index=startIndex;endIndex){
var board=game.boards[index];
if(!board.gone){
drawBoardSprite(graphics,board);
drawBoardScrews(graphics,board);
}
}
graphics.delete();
var cache={bitmap=bitmap;startIndex=startIndex;endIndex=endIndex};
table.push(layerCaches,cache);
table.push(renderItems,{kind="cache";cache=cache});
}
var segmentStart=null;
var segmentEnd=null;
for(index=sceneCachedBoardEnd+1;#game.boards){
var board=game.boards[index];
var dynamic=board.mode=="hanging" || board.mode=="falling";
if(dynamic){
addSegment(segmentStart,segmentEnd);
segmentStart=null;
segmentEnd=null;
table.push(renderItems,{kind="board";board=board});
}
elseif(!board.gone){
if(!segmentStart) segmentStart=index;
segmentEnd=index;
}
}
addSegment(segmentStart,segmentEnd);
layersDirty=false;
}
var drawActionScrew=function(graphics){
var action=game.action;
if(!action) return;
var x=action.startX;
var y=action.startY;
var angle=0;
var scale=1;
if(action.time<UNSCREW_TIME){
var p=math.clamp(action.time/UNSCREW_TIME,0,1);
var eased=1-(1-p)*(1-p);
y-=eased*12;
angle=eased*760;
scale=1+0.12*math.sin(p*math.pi);
var glow=gdip.solidBrush(alphaColor(screwColors[action.screw.color],48+55*p));
graphics.fillCircle(glow,x,y,19+5*p);
glow.delete();
}
else{
var p=math.clamp((action.time-UNSCREW_TIME)/FLY_TIME,0,1);
var eased=p*p*(3-2*p);
var controlX=(action.startX+action.targetX)/2;
var controlY=math.min(action.startY,action.targetY)-110;
var inv=1-eased;
x=inv*inv*action.startX+2*inv*eased*controlX+eased*eased*action.targetX;
y=inv*inv*(action.startY-12)+2*inv*eased*controlY+eased*eased*action.targetY;
angle=760+eased*540;
scale=1-0.18*eased;
for(tail=1;3){
var old=math.max(0,eased-tail*0.08);
var oldInv=1-old;
var tx=oldInv*oldInv*action.startX+2*oldInv*old*controlX+old*old*action.targetX;
var ty=oldInv*oldInv*(action.startY-12)+2*oldInv*old*controlY+old*old*action.targetY;
var tailBrush=gdip.solidBrush(alphaColor(screwColors[action.screw.color],10+tail*9));
graphics.fillCircle(tailBrush,tx,ty,8+tail*2);
tailBrush.delete();
}
}
drawScrewSprite(graphics,action.screw.color,x,y,angle,scale);
}
var drawParticles=function(graphics){
for(i,p in game.particles){
var ratio=math.clamp(p.life/p.maxLife,0,1);
var size=p.size*(0.45+ratio*0.8);
var brush=gdip.solidBrush(alphaColor(p.color,ratio*(p.kind=="dust" ? 155 : 235)));
if(p.kind=="dust"){
graphics.fillCircle(brush,p.x,p.y,size);
}
else{
var cs=math.cos(p.spin);
var sn=math.sin(p.spin);
graphics.fillPolygon(brush,[
{x=p.x+cs*size*1.7;y=p.y+sn*size*1.7},
{x=p.x-sn*size*0.55;y=p.y+cs*size*0.55},
{x=p.x-cs*size*1.7;y=p.y-sn*size*1.7},
{x=p.x+sn*size*0.55;y=p.y-cs*size*0.55}
]);
}
brush.delete();
}
}
var drawDynamicEffects=function(graphics){
if(game.trayFlash!=0){
var positive=game.trayFlash>0;
var strength=math.abs(game.trayFlash);
var color=positive ? levels[game.level].accent : 0xFFFF5F6D;
var ring=gdip.pen(alphaColor(color,175*strength),2+5*(1-strength));
graphics.drawRoundRect(ring,742-8*(1-strength),292-8*(1-strength),232+16*(1-strength),170+16*(1-strength),20);
ring.delete();
}
if(game.state==STATE_SOLVED){
var glow=math.min(1,game.solveGlow);
var color=levels[game.level].accent;
var centerX=WORK_X+WORK_W/2;
var centerY=WORK_Y+WORK_H/2;
var ring=gdip.pen(alphaColor(color,(65+45*math.sin(game.pulse/145))*glow),3+glow*3);
graphics.drawCircle(ring,centerX,centerY,102+5*math.sin(game.pulse/170));
ring.delete();
var rays=gdip.pen(alphaColor(color,40*glow),2);
for(i=1;16){
var angle=i*math.pi/8+game.pulse/4500;
graphics.drawLine(rays,
centerX+math.cos(angle)*112,centerY+math.sin(angle)*112,
centerX+math.cos(angle)*(138+8*math.sin(game.pulse/150+i)),
centerY+math.sin(angle)*(138+8*math.sin(game.pulse/150+i))
);
}
rays.delete();
var bannerGlow=gdip.solidBrush(alphaColor(color,34+25*math.sin(game.pulse/160)));
graphics.fillRoundRect(bannerGlow,centerX-174,centerY-51,348,102,20);
bannerGlow.delete();
var banner=gdip.solidBrush(0xD9141D23);
graphics.fillRoundRect(banner,centerX-158,centerY-40,316,80,16);
banner.delete();
var bannerEdge=gdip.pen(alphaColor(color,210),1.8);
graphics.drawRoundRect(bannerEdge,centerX-158,centerY-40,316,80,16);
bannerEdge.delete();
drawText(graphics,"机 关 清 空",24,centerX-145,centerY-31,290,39,0xFFF8F5EE,true,1);
drawText(graphics,"LEVEL CLEAR",9,centerX-145,centerY+10,290,18,alphaColor(color,220),true,1);
}
}
winform.screwCanvas.onDrawBackground=function(graphics,rc,backgroundColor,color){
return true;
}
winform.screwCanvas.onDrawForeground=function(graphics,rc,foregroundRect,foregroundColor,color,font){
var dpiX,dpiY=winform.dpiScale(1,1);
if(math.abs(CACHE_DPI_X-dpiX)>0.001 || math.abs(CACHE_DPI_Y-dpiY)>0.001){
disposeSprites();
CACHE_DPI_X=dpiX;
CACHE_DPI_Y=dpiY;
sceneDirty=true;
}
if(sceneDirty || !sceneCache) buildSceneCache();
if(!sceneContainsBoards && (layersDirty || !#renderItems)) buildLayerCaches();
graphics.interpolationMode=3/*_InterpolationModeBilinear*/;
graphics.drawImageRect(sceneCache,0,0,rc.width,rc.height);
var saved=graphics.save();
graphics.scale(dpiX,dpiY);
graphics.smoothingMode=4/*_SmoothingModeAntiAlias*/;
if(!sceneContainsBoards){
for(i,item in renderItems){
if(item.kind=="cache"){
graphics.drawImageRect(item.cache.bitmap,0,0,BOARD_CACHE_W,BOARD_CACHE_H);
}
else{
drawBoardSprite(graphics,item.board,true);
drawBoardScrews(graphics,item.board);
}
}
}
drawScrewHighlights(graphics);
if(game.blockedBoardKey && game.blockedTimer>0) drawBoardHighlight(graphics,game.boardByKey[game.blockedBoardKey]);
drawActionScrew(graphics);
drawParticles(graphics);
drawDynamicEffects(graphics);
graphics.restore(saved);
}
// -------------------- 动画更新 --------------------
winform.screwCanvas.onAnimation=function(prevState,beginning,change,timestamp,duration,deltaMs){
deltaMs=math.min(deltaMs,60);
var dt=deltaMs/1000;
game.pulse+=deltaMs;
if(game.statusTime<90000){
game.statusTime=math.max(0,game.statusTime-deltaMs);
if(game.statusTime==0 && game.statusText!=defaultStatus()){
game.statusText=defaultStatus();
sceneDirty=true;
}
}
game.hintTimer=math.max(0,game.hintTimer-deltaMs);
game.blockedTimer=math.max(0,game.blockedTimer-deltaMs);
if(game.blockedTimer==0) game.blockedBoardKey=null;
if(game.trayFlash>0) game.trayFlash=math.max(0,game.trayFlash-dt*2.6);
elseif(game.trayFlash<0) game.trayFlash=math.min(0,game.trayFlash+dt*2.2);
if(game.state==STATE_SOLVED) game.solveGlow=math.min(1,game.solveGlow+dt*1.8);
if(game.action){
var action=game.action;
action.time+=dt;
if(!action.detached && action.time>=UNSCREW_TIME) detachScrew(action);
if(game.action && action.time>=UNSCREW_TIME+FLY_TIME) finishScrewAction(action);
}
for(i,board in game.boards){
if(board.mode=="hanging"){
var diff=normalizeAngle(board.targetAngle-board.angle);
board.angularVel+=diff*20*dt;
board.angularVel*=0.88**(dt*60);
board.angle+=board.angularVel*dt;
var rad=math.rad(board.angle);
var dx=board.baseCx-board.pivotLocalX;
var dy=board.baseCy-board.pivotLocalY;
board.cx=board.pivotX+dx*math.cos(rad)-dy*math.sin(rad);
board.cy=board.pivotY+dx*math.sin(rad)+dy*math.cos(rad);
if(math.abs(diff)<0.28 && math.abs(board.angularVel)<0.55) board.settleTime+=dt;
else board.settleTime=0;
if(board.settleTime>0.22){
board.mode="hangingSettled";
board.angularVel=0;
layersDirty=true;
sceneDirty=true;
}
}
elseif(board.mode=="falling"){
board.fallTime+=dt;
board.vy+=420*dt;
board.cx+=board.vx*dt;
board.cy+=board.vy*dt;
board.angle+=board.omega*dt;
if(board.fallTime>0.17 && board.covering){
board.covering=false;
layersDirty=true;
}
if(board.cy>CANVAS_H+250 || board.cx<-320 || board.cx>CANVAS_W+320){
board.gone=true;
board.covering=false;
board.mode="gone";
layersDirty=true;
sceneDirty=true;
}
}
}
for(i=#game.particles;1;-1){
var p=game.particles[i];
p.life-=dt;
p.vy+=p.gravity*dt;
p.x+=p.vx*dt;
p.y+=p.vy*dt;
p.vx*=0.985**(dt*60);
p.spin+=dt*5;
if(p.life<=0) table.remove(game.particles,i);
}
if(game.pendingSolve && allBoardsGone() && !game.action) finishLevel();
return true;
}
winform.screwCanvas.cursor="hand";
winform.screwCanvas.startAnimation(24);
winform.screwCanvas.setFocus();
winform.show();
win.loopMessage();
Markdown 格式