aardio 文档

aardio 范例: 拼图

import fsys.dlg;
import fonts.fontAwesome;
import win.ui;
/*DSG{{*/
var winform = win.form(text='\uD83E\uDDE9 拼图 - 自绘小游戏';right=918;bottom=587;bgcolor=0xF0F0F0;border="thin";max=false)
winform.add(
btnOpen={cls="button";text='\uD83D\uDCC2 选择图片';left=718;top=11;right=809;bottom=39;z=1};
btnStart={cls="button";text='\u25B7 开始游戏';left=821;top=11;right=912;bottom=39;z=2};
cbDifficulty={cls="combobox";left=611;top=12;right=706;bottom=38;edge=1;items={"3 x 3","4 x 4","5 x 5","6 x 6"};mode="dropdown";z=3};
dragOrphan={cls="plus";left=664;top=723;right=823;bottom=889;clipBk=false;transparent=1;z=9};
gameCanvas={cls="plus";left=0;top=0;right=570;bottom=587;db=1;dl=1;dt=1;edge=1;notify=1;z=4};
lblStatus={cls="plus";text="请选择图片并开始计时";left=581;top=339;right=911;bottom=369;align="left";clip=1;color=0x666666;font=LOGFONT(h=-14);notify=1;z=7};
lblTime={cls="plus";text="时间: 0s";left=581;top=304;right=911;bottom=332;align="left";color=0x333333;font=LOGFONT(h=-16);z=6};
previewBox={cls="plus";left=582;top=51;right=912;bottom=291;border={color=0xFFCCCCCC;radius=4;width=1};z=5};
sideArea={cls="plus";left=581;top=376;right=911;bottom=584;bgcolor=0xE8E8E8;border={color=0xFFCCCCCC;radius=4;width=1};db=1;dl=1;dr=1;z=8}
)
/*}}*/

winform.cbDifficulty.selIndex = 1;

var gameState = {
    imagePath = null;
    srcBitmap = null;
    blurBitmap = null;
    gridN = 3;
    cellW = 0;
    cellH = 0;
    boardX = 0;
    boardY = 0;
    boardW = 600;
    boardH = 600;
    knobRatio = 0.22;
    pieces = {};
    dragging = null;
    dragOffX = 0;
    dragOffY = 0;
    gameStep = 0;
    gameCompleted = false;
    startTime = 0;
    elapsedTime = 0;
    placedCount = 0;
    totalPieces = 0;
};

import sys.midiOut;
var midiOut = sys.midiOut();
var snapThreshold;

// 生成边信息表
var generateEdges = function(n) {
    var edges = {};
    for(row=1; n) {
        edges[row] = {};
        for(col=1; n) {
            edges[row][col] = {top=0; right=0; bottom=0; left=0};
        }
    }

    for(row=1; n) {
        for(col=1; n) {
            if(col < n) {
                var v = (math.random(0,1) == 0) ? 1 : -1;
                edges[row][col].right = v;
                edges[row][col+1].left = -v;
            }
            if(row < n) {
                var v = (math.random(0,1) == 0) ? 1 : -1;
                edges[row][col].bottom = v;
                edges[row+1][col].top = -v;
            }
        }
    }
    return edges;
};

// 生成边的路径点序列
var buildEdgePoints = function(x0, y0, x1, y1, edgeType, knobSize) {
    if(edgeType == 0) {
        return { {x=x1; y=y1} };
    }

    var dx = x1 - x0;
    var dy = y1 - y0;
    var len = math.sqrt(dx*dx + dy*dy);
    if(len < 1) return { {x=x1; y=y1} };

    var ux = dx / len;
    var uy = dy / len;
    var nx = -uy;
    var ny = ux;

    var dir = edgeType;
    var ks = knobSize * len;

    var t1 = 0.35;
    var t2 = 0.65;
    var neckW = 0.10 * len;
    var headH = ks;

    var p1x = x0 + dx * t1;
    var p1y = y0 + dy * t1;
    var p2x = p1x + nx * dir * neckW * 0.3;
    var p2y = p1y + ny * dir * neckW * 0.3;
    var p3x = x0 + dx * (t1 - 0.05) + nx * dir * headH;
    var p3y = y0 + dy * (t1 - 0.05) + ny * dir * headH;
    var p4x = x0 + dx * 0.5 + nx * dir * headH;
    var p4y = y0 + dy * 0.5 + ny * dir * headH;
    var p5x = x0 + dx * (t2 + 0.05) + nx * dir * headH;
    var p5y = y0 + dy * (t2 + 0.05) + ny * dir * headH;
    var p6x = x0 + dx * t2 + nx * dir * neckW * 0.3;
    var p6y = y0 + dy * t2 + ny * dir * neckW * 0.3;
    var p7x = x0 + dx * t2;
    var p7y = y0 + dy * t2;

    return {
        {x=p1x; y=p1y}, {x=p2x; y=p2y}, {x=p3x; y=p3y}, {x=p4x; y=p4y},
        {x=p5x; y=p5y}, {x=p6x; y=p6y}, {x=p7x; y=p7y}, {x=x1; y=y1}
    };
};

// 构建拼图块路径
var buildPiecePath = function(row, col, cellW, cellH, edgeInfo, knobRatio) {
    var x0, y0 = 0, 0;
    var x1, y1 = cellW, cellH;

    var path = gdip.path();

    var topPts = buildEdgePoints(x0, y0, x1, y0, edgeInfo.top, knobRatio);
    var rightPts = buildEdgePoints(x1, y0, x1, y1, edgeInfo.right, knobRatio);
    var bottomPts = buildEdgePoints(x1, y1, x0, y1, edgeInfo.bottom, knobRatio);
    var leftPts = buildEdgePoints(x0, y1, x0, y0, edgeInfo.left, knobRatio);

    var addEdge = function(pts, startX, startY) {
        if(#pts == 1) {
            path.addLine(startX, startY, pts[1].x, pts[1].y);
        }
        else {
            path.addLine(startX, startY, pts[1].x, pts[1].y);
            path.addBezier(pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y, pts[4].x, pts[4].y);
            path.addBezier(pts[4].x, pts[4].y, pts[5].x, pts[5].y, pts[6].x, pts[6].y, pts[7].x, pts[7].y);
            path.addLine(pts[7].x, pts[7].y, pts[8].x, pts[8].y);
        }
    };

    addEdge(topPts, x0, y0);
    addEdge(rightPts, x1, y0);
    addEdge(bottomPts, x1, y1);
    addEdge(leftPts, x0, y1);

    path.closeFigure();
    return path;
};

// 切割拼图块位图
var cutPieceBitmap = function(srcBmp, row, col, cellW, cellH, piecePath, knobRatio) {
    var margin = math.ceil(cellW * knobRatio * 1.2);
    var marginH = math.ceil(cellH * knobRatio * 1.2);
    if(marginH > margin) margin = marginH;

    var bmpW = cellW + margin * 2;
    var bmpH = cellH + margin * 2;

    var pieceBmp = gdip.bitmap(bmpW, bmpH);
    var g = gdip.graphics(pieceBmp);
    g.smoothingMode = 4;
    g.interpolationMode = 7;
    g.pixelOffsetMode = 4;

    g.translate(margin, margin);
    g.setClipPath(piecePath);

    var srcX = (col - 1) * cellW;
    var srcY = (row - 1) * cellH;
    g.drawImageRectRect(srcBmp, -margin, -margin, bmpW, bmpH, srcX - margin, srcY - margin, bmpW, bmpH);

    g.resetClip();
    g.resetTransform();
    g.translate(margin, margin);

    var pen = gdip.pen(0x80000000, 1.5);
    g.drawPath(pen, piecePath);
    pen.delete();

    g.delete();
    return pieceBmp, margin;
};

// 碰撞检测
var hitTestPiece = function(piece, mx, my) {
    var p = piece;
    var px = p.currentX - p.margin;
    var py = p.currentY - p.margin;
    return mx >= px && mx <= px + p.bmpW && my >= py && my <= py + p.bmpH;
};

// 吸附检测
var checkSnap = function(piece) {
    if(piece.rotation % 360 != 0) return false;

    var dx = math.abs(piece.currentX - piece.targetX);
    var dy = math.abs(piece.currentY - piece.targetY);

    if(!snapThreshold) snapThreshold = gameState.cellW * 0.25;
    return dx < snapThreshold && dy < snapThreshold;
};

// 执行吸附并返回是否成功
var trySnapPiece = function(piece) {
    if(checkSnap(piece)) {
        piece.currentX = piece.targetX;
        piece.currentY = piece.targetY;
        piece.rotation = 0;
        piece.placed = true;
        gameState.placedCount++;

        if(midiOut) {
            midiOut.play("changeInstrument(14), 5_", "C5", 80);
        }

        if(gameState.placedCount >= gameState.totalPieces) {
            gameState.gameCompleted = true;
            gameState.elapsedTime = math.floor((time.tick() - gameState.startTime) / 1000);
            winform.lblStatus.text = string.format("恭喜完成!用时 %d 秒", gameState.elapsedTime);
            winform.btnStart.text = "▷ 开始游戏";
        }
        return true;
    }
    return false;
};

var constrainPiecePosition = function(piece) {
    var gs = gameState;
    var canvasRc = winform.gameCanvas.getClientRect();
    var canvasW = canvasRc.right;
    var canvasH = canvasRc.bottom;

    // 确保至少有一部分图块在画布内可见
    var minVisible = math.min(gs.cellW, gs.cellH) * 0.4;

    // 左边界
    if(piece.currentX + gs.cellW < minVisible) {
        piece.currentX = minVisible - gs.cellW * 0.5;
    }
    // 右边界
    if(piece.currentX > canvasW - minVisible) {
        piece.currentX = canvasW - minVisible - gs.cellW * 0.5;
    }
    // 上边界
    if(piece.currentY + gs.cellH < minVisible) {
        piece.currentY = minVisible - gs.cellH * 0.5;
    }
    // 下边界
    if(piece.currentY > canvasH - minVisible) {
        piece.currentY = canvasH - minVisible - gs.cellH * 0.5;
    }
};

// 绘制拼图块
var drawPieceOnGraphics = function(graphics, piece) {
    var p = piece;
    var drawX = p.currentX - p.margin;
    var drawY = p.currentY - p.margin;

    if(p.rotation % 360 == 0) {
        graphics.drawImage(p.bitmap, drawX, drawY);
    }
    else {
        var cx = drawX + p.bmpW / 2;
        var cy = drawY + p.bmpH / 2;
        graphics.save();
        graphics.translate(cx, cy);
        graphics.rotate(p.rotation);
        graphics.translate(-cx, -cy);
        graphics.drawImage(p.bitmap, drawX, drawY);
        graphics.restore();
    }
};

// 计算 dragOrphan 的精确屏幕位置
var calcDragOrphanPos = function(piece) {
    var p = piece;
    var size = math.max(p.bmpW, p.bmpH) + 20;

    var bmpOffsetX = (size - p.bmpW) / 2;
    var bmpOffsetY = (size - p.bmpH) / 2;

    var canvasDrawX = p.currentX - p.margin;
    var canvasDrawY = p.currentY - p.margin;

    var screenX, screenY = win.toScreen(winform.gameCanvas.hwnd, canvasDrawX, canvasDrawY);

    var dragX = screenX - bmpOffsetX;
    var dragY = screenY - bmpOffsetY;

    return dragX, dragY, size;
};

var updateDragWindow = function(piece) {
    if(!piece) {
        winform.dragOrphan.show(false);
        return;
    }

    var p = piece;
    var size = math.max(p.bmpW, p.bmpH) + 20;

    var dragBmp = gdip.bitmap(size, size);
    var g = gdip.graphics(dragBmp);
    g.smoothingMode = 4;
    g.interpolationMode = 7;

    g.save();
    g.translate(size / 2, size / 2);
    if(p.rotation % 360 != 0) g.rotate(p.rotation);
    g.translate(-p.bmpW / 2, -p.bmpH / 2);
    g.drawImage(p.bitmap, 0, 0);
    g.restore();

    g.delete();

    winform.dragOrphan.background = dragBmp;

    var dragX, dragY, dragSize = calcDragOrphanPos(p);
    winform.dragOrphan.setPos(dragX, dragY, dragSize, dragSize);
    winform.dragOrphan.show(4/*_SW_SHOWNOACTIVATE*/);
};

// 随机散布拼图块
var scatterPieces = function() {
    var gs = gameState;
    var canvasRc = winform.gameCanvas.getClientRect();
    var canvasW = canvasRc.right;
    var canvasH = canvasRc.bottom;

    for(i=1; #gs.pieces) {
        var p = gs.pieces[i];
        if(!p.placed) {
            p.rotation = math.random(0, 3) * 90;
            var maxX = canvasW - gs.cellW;
            var maxY = canvasH - gs.cellH;
            p.currentX = math.random(10, math.max(10, maxX));
            p.currentY = math.random(10, math.max(10, maxY));
        }
    }

    for(i=#gs.pieces; 1; -1) {
        var j = math.random(1, i);
        gs.pieces[i], gs.pieces[j] = gs.pieces[j], gs.pieces[i];
    }
};

var cleanupPieces = function() {
    var gs = gameState;
    for(i=1; #gs.pieces) {
        var p = gs.pieces[i];
        if(p.bitmap) { p.bitmap.delete(); p.bitmap = null; }
        if(p.path) { p.path.delete(); p.path = null; }
    }
    gs.pieces = {};
};

var initPuzzle = function() {
    var gs = gameState;

    cleanupPieces();
    gs.placedCount = 0;
    gs.gameCompleted = false;
    gs.elapsedTime = 0;
    gs.dragging = null;

    if(!gs.srcBitmap) return;

    var n = gs.gridN;
    var canvasRc = winform.gameCanvas.getClientRect();
    var canvasW = canvasRc.right;
    var canvasH = canvasRc.bottom;

    var boardMargin = 30;
    var boardSize = math.min(canvasW, canvasH) - boardMargin * 2;
    gs.boardW = boardSize;
    gs.boardH = boardSize;
    gs.boardX = (canvasW - boardSize) / 2;
    gs.boardY = (canvasH - boardSize) / 2;

    gs.cellW = boardSize / n;
    gs.cellH = boardSize / n;

    snapThreshold = gs.cellW * 0.25;

    var scaledBmp = gdip.bitmap(boardSize, boardSize);
    var sg = gdip.graphics(scaledBmp);
    sg.interpolationMode = 7;
    sg.drawImageRect(gs.srcBitmap, 0, 0, boardSize, boardSize);
    sg.delete();

    var edges = generateEdges(n);

    gs.totalPieces = n * n;
    var pieceIndex = 0;

    for(row=1; n) {
        for(col=1; n) {
            pieceIndex++;

            var edgeInfo = edges[row][col];
            var pPath = buildPiecePath(row, col, gs.cellW, gs.cellH, edgeInfo, gs.knobRatio);
            var pBmp, margin = cutPieceBitmap(scaledBmp, row, col, gs.cellW, gs.cellH, pPath, gs.knobRatio);

            var targetX = gs.boardX + (col - 1) * gs.cellW;
            var targetY = gs.boardY + (row - 1) * gs.cellH;

            var piece = {
                index = pieceIndex;
                row = row;
                col = col;
                bitmap = pBmp;
                path = pPath;
                margin = margin;
                bmpW = pBmp.width;
                bmpH = pBmp.height;
                targetX = targetX;
                targetY = targetY;
                currentX = 0;
                currentY = 0;
                rotation = 0;
                placed = false;
            };

            table.push(gs.pieces, piece);
        }
    }

    scaledBmp.delete();
    scatterPieces();
};

winform.gameCanvas.onDrawBackground = function(graphics,rc,backgroundColor,foregroundColor){
    var p1 = ::POINTF(0, 0);
    var p2 = ::POINTF(rc.right, rc.bottom);
    var brush = gdip.lineBrush(p1, p2, 0xFF2c3e50, 0xFF34495e);
    graphics.fillRectangle(brush, rc);
    brush.delete();
};

winform.gameCanvas.onDrawContent = function(graphics,rc,txtColor,rcContent,foregroundColor,font){
    var gs = gameState;

    if(gs.gameStep < 2 && #gs.pieces == 0) {
        var font = gdip.font("Microsoft YaHei", 16, 0, 3);
        var fmt = gdip.stringformat();
        fmt.align = 1;
        fmt.lineAlign = 1;
        var brush = gdip.solidBrush(0x80FFFFFF);

        var tip;
        if(gs.gameStep) {
            tip = "尽快记住图片,点这里「开始拼图」"    
        } else {
            tip = gs.srcBitmap ? "图片已加载,点这里「开始游戏」" : "点这里「选择图片」"
        }

        graphics.drawString(tip, font, ::RECTF(0, 0, rc.right, rc.bottom), fmt, brush);
        brush.delete(); fmt.delete(); font.delete();
        return;
    }

    if(#gs.pieces == 0) return;

    var boardBrush = gdip.solidBrush(0x20FFFFFF);
    graphics.fillRectangle(boardBrush, ::RECTF(gs.boardX, gs.boardY, gs.boardW, gs.boardH));
    boardBrush.delete();

    // 绘制网格线
    var gridPen = gdip.pen(0x40FFFFFF, 1);
    gridPen.setDashStyle(2);
    var n = gs.gridN;
    for(i=0; n) {
        var x = gs.boardX + i * gs.cellW;
        graphics.drawLine(gridPen, x, gs.boardY, x, gs.boardY + gs.boardH);
    }
    for(i=0; n) {
        var y = gs.boardY + i * gs.cellH;
        graphics.drawLine(gridPen, gs.boardX, y, gs.boardX + gs.boardW, y);
    }
    gridPen.delete();

    // 先绘制已放置的拼图块
    for(i=1; #gs.pieces) {
        var p = gs.pieces[i];
        if(p.placed) {
            drawPieceOnGraphics(graphics, p);
        }
    }

    // 再绘制未放置的拼图块(不含正在拖拽的)
    for(i=1; #gs.pieces) {
        var p = gs.pieces[i];
        if(!p.placed && !(gs.dragging && gs.dragging.index == p.index)) {
            drawPieceOnGraphics(graphics, p);
        }
    }

    var borderPen = gdip.pen(0x80FFFFFF, 2);
    graphics.drawRectangle(borderPen, gs.boardX, gs.boardY, gs.boardW, gs.boardH);
    borderPen.delete();

    // 游戏结束提示
    if(gs.gameCompleted) {
        var maskBrush = gdip.solidBrush(0xA000b894);
        graphics.fillRectangle(maskBrush, ::RECTF(0, rc.bottom * 0.35, rc.right, rc.bottom * 0.3));
        maskBrush.delete();

        var font = gdip.font("Microsoft YaHei", 22, 0, 3);
        var fmt = gdip.stringformat();
        fmt.align = 1;
        fmt.lineAlign = 1;

        var msg = string.format("恭喜完成!用时 %d 秒", gs.elapsedTime);
        var msgBrush = gdip.solidBrush(0xFFFFFFFF);
        graphics.drawString(msg, font, ::RECTF(0, rc.bottom * 0.35, rc.right, rc.bottom * 0.3), fmt, msgBrush);

        msgBrush.delete(); fmt.delete(); font.delete();

        winform.btnStart.text = "▷ 开始游戏";
        winform.btnStart.disabled = false;
        winform.btnOpen.disabled = false;
        winform.previewBox.redraw();

        winform.lblStatus.text = "已完成,点击「开始游戏」重新开始";

        if(midiOut) midiOut.play("changeInstrument(10), 1_,3_,5_,1'__", "C4", 150);
    }
};

winform.gameCanvas.onMouseDown = function(wParam, lParam) {
    var gs = gameState;
    if((gs.gameStep != 2) || gs.gameCompleted) {

        if( !gs.gameSte ){
            if(! gs.srcBitmap) return winform.btnOpen.oncommand(); 
            return winform.btnStart.oncommand();    
        }

    }

    var mx, my = win.getMessagePos(lParam);

    for(i=#gs.pieces; 1; -1) {
        var p = gs.pieces[i];
        if(!p.placed && hitTestPiece(p, mx, my)) {
            gs.dragging = p;
            gs.dragOffX = mx - p.currentX;
            gs.dragOffY = my - p.currentY;

            table.remove(gs.pieces, i);
            table.push(gs.pieces, p);

            win.setCapture(winform.gameCanvas.hwnd);

            updateDragWindow(p);
            owner.redraw();
            return;
        }
    }
};

winform.gameCanvas.onMouseDrag = function(wParam, lParam) {
    var gs = gameState;
    if(!gs.dragging) return;

    var mx, my = win.getMessagePos(lParam);
    gs.dragging.currentX = mx - gs.dragOffX;
    gs.dragging.currentY = my - gs.dragOffY;

    // 只更新悬浮窗位置,不重绘画布
    var p = gs.dragging;
    var dragX, dragY, size = calcDragOrphanPos(p);
    winform.dragOrphan.setPos(dragX, dragY, size, size);
};

winform.gameCanvas.onMouseUp = function(wParam, lParam) {
    var gs = gameState;
    win.releaseCapture();

    if(!gs.dragging) return;

    var p = gs.dragging;
    var mx, my = win.getMessagePos(lParam);
    p.currentX = mx - gs.dragOffX;
    p.currentY = my - gs.dragOffY;

    constrainPiecePosition(p);
    trySnapPiece(p);

    gs.dragging = null;

    // 先隐藏悬浮窗再重绘画布
    winform.dragOrphan.show(false);
    owner.redraw();
};

winform.gameCanvas.onRightMouseDown = function(wParam, lParam) {
    var gs = gameState;
    if((gs.gameStep != 2) || gs.gameCompleted) return;

    var mx, my = win.getMessagePos(lParam);

    for(i=#gs.pieces; 1; -1) {
        var p = gs.pieces[i];
        if(!p.placed && hitTestPiece(p, mx, my)) {
            p.rotation = (p.rotation + 90) % 360;

            if(trySnapPiece(p)) {
                owner.redraw();
                return;
            }

            if(gs.dragging && gs.dragging.index == p.index) {
                updateDragWindow(p);
            }

            owner.redraw();

            if(midiOut) {
                midiOut.play("changeInstrument(115), 3_", "C4", 60);
            }
            return;
        }
    }
};

winform.previewBox.onDrawContent = function(graphics, rc) {
    graphics.smoothingMode = 4;
    graphics.interpolationMode = 7;

    var gs = gameState;
    var img = (gs.gameStep == 1 || gs.gameCompleted) ? gs.srcBitmap : gs.blurBitmap;
    if(!img) {
        var font = gdip.font("Microsoft YaHei", 11, 0, 3);
        var fmt = gdip.stringformat();
        fmt.align = 1; fmt.lineAlign = 1;
        var brush = gdip.solidBrush(0xFF999999);
        graphics.drawString("预览图", font, ::RECTF(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top), fmt, brush);
        brush.delete(); fmt.delete(); font.delete();
        return;
    }

    graphics.drawImageScale(img, rc);
};

winform.setInterval(function() {
    var gs = gameState;
    if(gs.gameStep > 0 && !gs.gameCompleted) {
        gs.elapsedTime = math.floor((time.tick() - gs.startTime) / 1000);
        winform.lblTime.text = string.format("⏱ 时间: %d 秒", gs.elapsedTime);
        if(gs.gameStep > 1) {
            winform.lblStatus.text = string.format("已完成: %d / %d", gs.placedCount, gs.totalPieces);
        } else {
            winform.lblStatus.text = "记住图片并点击开始拼图";
        }
    }
}, 1000);

winform.btnOpen.oncommand = function() { 
    var path = fsys.dlg.open("图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif||",,"选择拼图图片");
    if(!path) return;

    if(gameState.srcBitmap) {
        gameState.srcBitmap.delete();
        gameState.srcBitmap = null;
    }
    if(gameState.blurBitmap) {
        gameState.blurBitmap.delete();
        gameState.blurBitmap = null;
    }

    gameState.imagePath = path;
    gameState.srcBitmap = gdip.bitmap(path);
    gameState.gameCompleted = false;
    gameState.gameStep = 0;

    if(!gameState.srcBitmap) {
        winform.msgboxErr("无法加载图片文件");
        return;
    }

    gameState.blurBitmap = gameState.srcBitmap.getThumbnailImage(
        winform.previewBox.width * 0.02, winform.previewBox.height * 0.02, true);
    winform.previewBox.redraw();

    winform.lblStatus.text = "图片已加载,点击「开始游戏」";
    winform.gameCanvas.redraw();
};

winform.btnStart.oncommand = function() {
    var gs = gameState;

    if(!gs.srcBitmap) {
        winform.msgbox("请先选择一张图片", "提示");
        return;
    }

    winform.btnOpen.disabled = true;

    if(gs.gameStep == 0 || gs.gameCompleted) {
        cleanupPieces();

        gs.gameCompleted = false;
        gs.placedCount = 0;
        gs.dragging = null;
        gs.gameStep = 1;
        gs.startTime = time.tick();
        gs.elapsedTime = 0;

        winform.btnStart.text = "🧩 开始拼图";
        winform.btnStart.disabled = false;
        winform.gameCanvas.redraw();
        winform.previewBox.redraw();
        return; 
    }

    // gameStep==1 时,开始拼图
    if(gs.gameStep != 1) {
        return;
    }

    var selIdx = winform.cbDifficulty.selIndex;
    var difficulties = {3, 4, 5, 6};
    gs.gridN = difficulties[selIdx] : 3;

    snapThreshold = null;

    initPuzzle();
    gs.gameStep = 2;
    winform.previewBox.redraw();
    winform.btnStart.text = "🧩 正在拼图";
    winform.btnStart.disabled = true;

    winform.lblStatus.text = string.format("拼图进行中!%dx%d = %d 块", gs.gridN, gs.gridN, gs.totalPieces);
    winform.gameCanvas.redraw();

    if(midiOut) {
        midiOut.play("changeInstrument(10), 1_,2_,3_,4_,5_", "C4", 80);
    }
};

winform.sideArea.onDrawContent = function(graphics, rc) {
    graphics.smoothingMode = 4;

    var font = gdip.font("Segoe UI Emoji", 10, 0, 3);
    var fmt = gdip.stringformat();
    fmt.align = 1;
    var brush = gdip.solidBrush(0xFF666666);

    var tips = {
        "🖱 左键拖拽拼图块";
        "🔄 右键旋转拼图块";
        "📌 靠近正确位置自动吸附";
        "🎯 旋转角度正确才能吸附";
    };

    var dpiX, dpiY = winform.dpiScale(1, 1);
    var lineHeight = 35 * dpiY;
    var y = 20 * dpiY;

    for(i=1; #tips) {
        graphics.drawString(tips[i], font, ::RECTF(rc.left, y, rc.right - rc.left, 30 * dpiY), fmt, brush);
        y += lineHeight;
    }

    brush.delete(); fmt.delete(); font.delete();
};

//winform.gameCanvas.dlgCode = 4/*_DLGC_WANTALLKEYS*/
winform.gameCanvas.cursor = ::User32.LoadCursor(null,32649/*_IDC_HAND*/);

winform.show();

//悬浮窗口,独立窗口,与所有者窗口(原来的父窗口)保持相对位置
winform.dragOrphan.orphanWindow(true/*转为透明分层窗口*/);
win.loopMessage();
Markdown 格式