# aardio 范例: 五子棋 - 自绘小游戏



```aardio
import win.ui;
/*DSG{{*/
var winform = win.form(text="五子棋 - 自绘小游戏";right=580;bottom=640;bgcolor=0xFFFFFF;border="dialog frame";max=false)
winform.add(
btnRestart={cls="plus";text="重新开始";left=40;top=585;right=140;bottom=620;bgcolor=0x50AF4C;border={radius=6};color=0xFFFFFF;font=LOGFONT(h=-14;weight=600);z=2};
btnUndo={cls="plus";text="悔棋";left=161;top=585;right=261;bottom=620;bgcolor=0x0098FF;border={radius=6};color=0xFFFFFF;font=LOGFONT(h=-14;weight=600);z=3};
lblStatus={cls="plus";text="黑方执子";left=300;top=585;right=540;bottom=620;color=0x333333;font=LOGFONT(h=-16;weight=600);z=4};
plus={cls="plus";left=15;top=15;right=565;bottom=565;border={color=0x60A4D4;radius=8;width=3};clipBk=false;notify=1;z=1}
)
/*}}*/

// 游戏配置
var boardSize = 15;

// 动态布局参数
var layout = {
    cellSize = 36;
    padding = 18;
    pieceRadius = 15;
};

// 计算布局（根据控件大小自适应）
var calculateLayout = function(width, height) {
    var size = math.min(width, height);
    var margin = 20;
    var availableSize = size - margin * 2;
    
    layout.cellSize = math.floor(availableSize / (boardSize - 1));
    layout.padding = math.floor((size - layout.cellSize * (boardSize - 1)) / 2);
    layout.pieceRadius = math.floor(layout.cellSize * 0.42);
}

// 游戏状态
var board = {};
var currentPlayer = 1; // 1=黑, 2=白
var gameOver = false;
var winner = 0;
var moveHistory = {};
var hoverX, hoverY = 0, 0;
var winLine = {};

// 初始化棋盘
var initBoard = function() {
    board = {};
    for(y=1; boardSize) {
        board[y] = {};
        for(x=1; boardSize) {
            board[y][x] = 0;
        }
    }
    currentPlayer = 1;
    gameOver = false;
    winner = 0;
    moveHistory = {};
    winLine = {};
    winform.lblStatus.text = "☯ 黑方执子";
}

// 屏幕坐标转棋盘坐标
var screenToBoard = function(sx, sy) {
    var x = math.floor((sx - layout.padding + layout.cellSize/2) / layout.cellSize) + 1;
    var y = math.floor((sy - layout.padding + layout.cellSize/2) / layout.cellSize) + 1;
    if(x >= 1 && x <= boardSize && y >= 1 && y <= boardSize) {
        return x, y;
    }
    return 0, 0;
}

// 棋盘坐标转屏幕坐标
var boardToScreen = function(bx, by) {
    return layout.padding + (bx - 1) * layout.cellSize, 
           layout.padding + (by - 1) * layout.cellSize;
}

// 检查某方向是否五子连珠
var checkDirection = function(x, y, dx, dy, player) {
    var count = 1;
    var line = {{x=x, y=y}};
    
    var nx, ny = x + dx, y + dy;
    while(nx >= 1 && nx <= boardSize && ny >= 1 && ny <= boardSize && board[ny][nx] == player) {
        count = count + 1;
        table.push(line, {x=nx, y=ny});
        nx = nx + dx;
        ny = ny + dy;
    }
    
    nx, ny = x - dx, y - dy;
    while(nx >= 1 && nx <= boardSize && ny >= 1 && ny <= boardSize && board[ny][nx] == player) {
        count = count + 1;
        table.push(line, {x=nx, y=ny});
        nx = nx - dx;
        ny = ny - dy;
    }
    
    return count, line;
}

// 检查是否获胜
var checkWin = function(x, y, player) {
    var directions = {{1,0}, {0,1}, {1,1}, {1,-1}};
    
    for(i, dir in directions) {
        var count, line = checkDirection(x, y, dir[1], dir[2], player);
        if(count >= 5) {
            return true, line;
        }
    }
    return false, {};
}

// 检查是否平局
var checkDraw = function() {
    for(y=1; boardSize) {
        for(x=1; boardSize) {
            if(board[y][x] == 0) return false;
        }
    }
    return true;
}

// 落子
var placePiece = function(x, y) {
    if(gameOver) return false;
    if(x < 1 || x > boardSize || y < 1 || y > boardSize) return false;
    if(board[y][x] != 0) return false;
    
    board[y][x] = currentPlayer;
    table.push(moveHistory, {x=x, y=y, player=currentPlayer});
    
    var won, line = checkWin(x, y, currentPlayer);
    if(won) {
        gameOver = true;
        winner = currentPlayer;
        winLine = line;
        winform.lblStatus.text = (winner == 1) ? "黑方获胜！" : "白方获胜！";
    }
    elseif(checkDraw()) {
        gameOver = true;
        winner = 0;
        winform.lblStatus.text = "平局！";
    }
    else {
        currentPlayer = (currentPlayer == 1) ? 2 : 1;
        winform.lblStatus.text = (currentPlayer == 1) ? "☯ 黑方执子" : "○ 白方执子";
    }
    
    return true;
}

// 悔棋
var undoMove = function() {
    if(#moveHistory == 0) return;
    if(gameOver) {
        gameOver = false;
        winner = 0;
        winLine = {};
    }
    
    var lastMove = table.pop(moveHistory);
    board[lastMove.y][lastMove.x] = 0;
    currentPlayer = lastMove.player;
    winform.lblStatus.text = (currentPlayer == 1) ? "☯ 黑方执子" : "○ 白方执子";
}

initBoard();

// 按钮样式
winform.btnRestart.skin({
    background = {default=0xFF4CAF50; hover=0xFF45A049; active=0xFF388E3C};
    color = {default=0xFFFFFFFF};
});

winform.btnUndo.skin({
    background = {default=0xFFFF9800; hover=0xFFF57C00; active=0xFFE65100};
    color = {default=0xFFFFFFFF};
});

// 绘制棋子（柔和高光）
var drawPiece = function(graphics, sx, sy, isBlack) {
    var r = layout.pieceRadius;
    
    // 阴影
    var shadowPath = gdip.path();
    shadowPath.addEllipse(sx - r + 2, sy - r + 2, r * 2, r * 2);
    var shadowBrush = gdip.pathGradientBrush(shadowPath);
    shadowBrush.setCenterColor(0x50000000);
    shadowBrush.setSurroundColors(0x00000000);
    graphics.fillPath(shadowBrush, shadowPath);
    shadowBrush.delete();
    shadowPath.delete();
    
    // 棋子主体
    var path = gdip.path();
    path.addEllipse(sx - r, sy - r, r * 2, r * 2);
    
    var pathBrush = gdip.pathGradientBrush(path);
    if(isBlack) {
        pathBrush.setCenterColor(0xFF454545);
        pathBrush.setSurroundColors(0xFF0A0A0A);
    }
    else {
        pathBrush.setCenterColor(0xFFFFFFFF);
        pathBrush.setSurroundColors(0xFFAAAAAA);
    }
    pathBrush.setCenterPoint(sx - r * 0.3, sy - r * 0.3);
    
    graphics.fillPath(pathBrush, path);
    pathBrush.delete();
    path.delete();
    
    // 柔和高光（路径渐变，边缘透明）
    var hlPath = gdip.path();
    hlPath.addEllipse(sx - r * 0.55, sy - r * 0.65, r * 0.85, r * 0.55);
    
    var hlBrush = gdip.pathGradientBrush(hlPath);
    if(isBlack) {
        hlBrush.setCenterColor(0x38FFFFFF);
        hlBrush.setSurroundColors(0x00FFFFFF);
    }
    else {
        hlBrush.setCenterColor(0x70FFFFFF);
        hlBrush.setSurroundColors(0x00FFFFFF);
    }
    hlBrush.setCenterPoint(sx - r * 0.25, sy - r * 0.4);
    
    graphics.fillPath(hlBrush, hlPath);
    hlBrush.delete();
    hlPath.delete();
    
    // 小亮点（更自然的反光）
    var dotPath = gdip.path();
    dotPath.addEllipse(sx - r * 0.35, sy - r * 0.45, r * 0.25, r * 0.18);
    
    var dotBrush = gdip.pathGradientBrush(dotPath);
    dotBrush.setCenterColor(isBlack ? 0x50FFFFFF : 0x90FFFFFF);
    dotBrush.setSurroundColors(0x00FFFFFF);
    
    graphics.fillPath(dotBrush, dotPath);
    dotBrush.delete();
    dotPath.delete();
}

// 绘制背景
winform.plus.onDrawBackground = function(graphics,rc,backgroundColor,color){
    var width = rc.right - rc.left;
    var height = rc.bottom - rc.top;
    calculateLayout(width, height);
    
    // 棋盘底色渐变
    var p1 = ::POINTF(0, 0);
    var p2 = ::POINTF(rc.right, rc.bottom);
    var brush = gdip.lineBrush(p1, p2, 0xFFDEB887, 0xFFD2A064, 1);
    graphics.fillRectangle(brush, rc);
    brush.delete();
    
    // 木纹纹理
    var texturePen = gdip.pen(0x12000000, 1);
    for(i=0; 60) {
        var y = i * 10 + math.sin(i * 0.5) * 4;
        graphics.drawLine(texturePen, 0, y, rc.right, y + 2);
    }
    texturePen.delete();
}

// 绘制内容
winform.plus.onDrawForeground = function(graphics,rc,foregroundRect,foregroundColor,color,font){

    graphics.smoothingMode = 4;
    
    var width = rc.right - rc.left;
    var height = rc.bottom - rc.top;
    
    // 绘制网格线
    var linePen = gdip.pen(0xFF5D4037, 1);
    for(i=1; boardSize) {
        var x1, y1 = boardToScreen(i, 1);
        var x2, y2 = boardToScreen(i, boardSize);
        graphics.drawLine(linePen, x1, y1, x2, y2);
        
        x1, y1 = boardToScreen(1, i);
        x2, y2 = boardToScreen(boardSize, i);
        graphics.drawLine(linePen, x1, y1, x2, y2);
    }
    linePen.delete();
    
    // 星位（天元和四角星）
    var starPoints = {{8,8}, {4,4}, {4,12}, {12,4}, {12,12}};
    var starSize = math.max(3, layout.cellSize / 10);
    var starBrush = gdip.solidBrush(0xFF5D4037);
    for(i, pt in starPoints) {
        var sx, sy = boardToScreen(pt[1], pt[2]);
        graphics.fillEllipse(starBrush, sx - starSize, sy - starSize, starSize * 2, starSize * 2);
    }
    starBrush.delete();
    
    // 悬停提示
    if(!gameOver && hoverX > 0 && hoverY > 0 && board[hoverY][hoverX] == 0) {
        var hx, hy = boardToScreen(hoverX, hoverY);
        var r = layout.pieceRadius;
        
        var borderColor = (currentPlayer == 1) ? 0x70000000 : 0x70A0A0A0;
        var hoverColor = (currentPlayer == 1) ? 0x45000000 : 0x45FFFFFF;
        if(winform.plus.state.active){
        	borderColor = borderColor | 0x30000000;
      		hoverColor = hoverColor | 0x30000000;
        }
       
        var hoverBrush = gdip.solidBrush(hoverColor);
        graphics.fillEllipse(hoverBrush, hx - r, hy - r, r * 2, r * 2);
        hoverBrush.delete();
        
        
        var borderPen = gdip.pen(borderColor, 2);
        
        
        graphics.drawEllipse(borderPen, hx - r, hy - r, r * 2, r * 2);
        borderPen.delete();
    }
    
    // 绘制所有棋子
    for(y=1; boardSize) {
        for(x=1; boardSize) {
            if(board[y][x] != 0) {
                var sx, sy = boardToScreen(x, y);
                drawPiece(graphics, sx, sy, board[y][x] == 1);
            }
        }
    }
    
    // 标记最后一步
    if(#moveHistory > 0) {
        var lastMove = moveHistory[#moveHistory];
        var lx, ly = boardToScreen(lastMove.x, lastMove.y);
        var markSize = math.max(4, layout.cellSize / 7);
        var markPen = gdip.pen(0xFFFF0000, 2);
        graphics.drawLine(markPen, lx - markSize, ly, lx + markSize, ly);
        graphics.drawLine(markPen, lx, ly - markSize, lx, ly + markSize);
        markPen.delete();
    }
    
    // 获胜连线
	if(gameOver && winner != 0 && #winLine >= 5) {
    	var minIdx, maxIdx = 1, 1;
    	
    	for(i, pt in winLine) {
        	var minPt = winLine[minIdx];
        	var maxPt = winLine[maxIdx];
        	
        	// 按 x 排序，x 相同则按 y 排序
        	if(pt.x < minPt.x || (pt.x == minPt.x && pt.y < minPt.y)) {
            	minIdx = i;
        	}
        	if(pt.x > maxPt.x || (pt.x == maxPt.x && pt.y > maxPt.y)) {
            	maxIdx = i;
        	}
    	}
    	
    	var sx1, sy1 = boardToScreen(winLine[minIdx].x, winLine[minIdx].y);
    	var sx2, sy2 = boardToScreen(winLine[maxIdx].x, winLine[maxIdx].y);
    	
    	// 光晕
    	var glowPen = gdip.pen(0x50FF4444, layout.cellSize / 2.5);
    	graphics.drawLine(glowPen, sx1, sy1, sx2, sy2);
    	glowPen.delete();
    	
    	// 主线
    	var winPen = gdip.pen(0xE0FF0000, 3);
    	graphics.drawLine(winPen, sx1, sy1, sx2, sy2);
    	winPen.delete();
	}
    
    // 游戏结束遮罩
    if(gameOver) {
        var maskBrush = gdip.solidBrush(0x35000000);
        graphics.fillRectangle(maskBrush, rc);
        maskBrush.delete();
        
        var resultText = (winner == 1) ? "黑方获胜！" : ((winner == 2) ? "白方获胜！" : "平局！");
        var fontSize = math.max(20, layout.cellSize * 0.8);
        var font = gdip.font("Tahoma", fontSize);
        var format = gdip.stringFormat();
        format.align = 1;
        format.lineAlign = 1;
        
        // 文字阴影
        var shadowBrush = gdip.solidBrush(0x90000000);
        graphics.drawString(resultText, font, ::RECTF(3, 3, width, height), format, shadowBrush);
        shadowBrush.delete();
        
        // 文字主体
        var textColor = (winner == 1) ? 0xFFFFD700 : ((winner == 2) ? 0xFFFFFFFF : 0xFF90EE90);
        var textBrush = gdip.solidBrush(textColor);
        graphics.drawString(resultText, font, ::RECTF(0, 0, width, height), format, textBrush);
        textBrush.delete();
        
        format.delete();
        font.delete();
    }
}

// 鼠标移动
winform.plus.onMouseMove = function(wParam, lParam) {
    var x, y = win.getMessagePos(lParam);
    var bx, by = screenToBoard(x, y);
    
    if(bx != hoverX || by != hoverY) {
        hoverX, hoverY = bx, by;
        owner.redraw();
    }
}

// 鼠标离开
winform.plus.onMouseLeave = function() {
    hoverX, hoverY = 0, 0;
    owner.redraw();
}

import sys.midiOut;
var midiOut = sys.midiOut();

winform.plus.onMouseDown = function(wParam, lParam) {
	owner.redraw();
}

// 鼠标点击落子
winform.plus.onMouseUp = function(wParam, lParam) {
    if(gameOver) return;
    
    var x, y = win.getMessagePos(lParam);
    var bx, by = screenToBoard(x, y);
    
    if(bx > 0 && by > 0) {
        if(placePiece(bx, by)) {
            owner.redraw();
            
            if(midiOut) {
               midiOut.play("changeInstrument(115), 5_", "C5", 80);
            }
        }
    }
}

// 按钮事件
winform.btnRestart.oncommand = function() {
    initBoard();
    winform.plus.redraw();
}

winform.btnUndo.oncommand = function() {
    undoMove();
    winform.plus.redraw();
}

winform.show();
win.loopMessage();
```

