利用aardio制作简单的泡泡龙游戏

Mr_MAO 1天前 56

import win.ui;
import gdip;
/*DSG{{*/
var winform = win.form(text="泡泡龙游戏 (By Mr_MAO)";right=480;bottom=640;bgcolor=0xFFFFFF;border="dialog frame";max=false)
winform.add(
canvas={cls="plus";left=0;top=0;right=480;bottom=640;db=1;dl=1;dr=1;dt=1;notify=1;z=1}
)
/*}}*/

// --- 游戏常量 ---
var RADIUS = 20;            // 泡泡半径
var DIAMETER = RADIUS * 2;
var ROW_HEIGHT = RADIUS * math.sqrt(3); // 六边形行高
var COLS = 11;              // 列数 (偶数行11个, 奇数行10个)
var ROWS = 15;              // 网格最大行数
var BOARD_WIDTH = COLS * DIAMETER; 

// 颜色定义
var colors = {
    0xFFFF0000; // 红
    0xFF00FF00; // 绿
    0xFF0000FF; // 蓝
    0xFFFFFF00; // 黄
    0xFFFF00FF; // 紫
    0xFF00FFFF; // 青
};

// --- 游戏状态 ---
var grid = {};       // 网格二维数组 grid[row][col] = colorIndex
var bullet = null;   // 当前飞行的泡泡 {x, y, dx, dy, color}
var nextColor = 0;   // 下一个发射的颜色
var currColor = 0;   // 当前发射的颜色
var angle = -90;     // 发射角度
var isGameOver = false;

// --- 工具函数:获取网格坐标对应的屏幕像素位置 ---
var getPixelPos = function(r, c){
    var x = c * DIAMETER + RADIUS;
    // 奇数行向右偏移半个身位
    if(r % 2 != 0){
        x = x + RADIUS;
    }
    var y = r * ROW_HEIGHT + RADIUS;
    return x, y;
}

// --- 工具函数:计算两个圆心的距离平方 ---
var getDistSq = function(x1, y1, x2, y2){
    return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
}

// --- 初始化游戏 ---
function initGame(){
    grid = {};
    isGameOver = false;
    
    // 初始化网格,生成几行初始泡泡
    for(r=0; 5){ 
        grid[r] = {};
        // 偶数行COLS个,奇数行COLS-1个
        var count = (r % 2 == 0) ? COLS : COLS - 1;
        for(c=0; count-1){
            grid[r][c] = math.random(1, #colors);
        }
    }
    
    currColor = math.random(1, #colors);
    nextColor = math.random(1, #colors);
    bullet = null;
    winform.canvas.redraw();
}

// --- 查找相连的同色泡泡 (递归/DFS) ---
findCluster = function(r, c, matchColor, foundList, visited){
    var key = r + "," + c;
    if(visited[key]) return;
    
    // 边界检查
    if(!grid[r] || !grid[r][c]) return;
    if(grid[r][c] != matchColor) return;
    
    visited[key] = true;
    table.push(foundList, {r=r; c=c});
    
    // 六个方向的邻居偏移量 (偶数行和奇数行不同)
    var offsets = (r % 2 == 0) 
        ? {{r-1,c-1}, {r-1,c}, {r,c-1}, {r,c+1}, {r+1,c-1}, {r+1,c}}  // 偶数行邻居
        : {{r-1,c}, {r-1,c+1}, {r,c-1}, {r,c+1}, {r+1,c}, {r+1,c+1}}; // 奇数行邻居
        
    for(i=1; #offsets){
        var nr, nc = offsets[i][1], offsets[i][2];
        findCluster(nr, nc, matchColor, foundList, visited);
    }
}

// --- 查找根节点 (用于悬空检测) ---
var findFloating = function(){
    var visited = {};
    var queue = {};
    
    // 1. 将第一行的所有泡泡加入队列
    if(grid[0]){
        for(c, color in grid[0]){
            table.push(queue, {r=0; c=c});
            visited["0," + c] = true;
        }
    }
    
    // 2. BFS 遍历所有连接到顶部的泡泡
    var head = 1;
    while(head <= #queue){
        var curr = queue[head];
        var r, c = curr.r, curr.c;
        head++;
        
        var offsets = (r % 2 == 0) 
            ? {{r-1,c-1}, {r-1,c}, {r,c-1}, {r,c+1}, {r+1,c-1}, {r+1,c}}
            : {{r-1,c}, {r-1,c+1}, {r,c-1}, {r,c+1}, {r+1,c}, {r+1,c+1}};
            
        for(i=1; #offsets){
            var nr, nc = offsets[i][1], offsets[i][2];
            if(grid[nr] && grid[nr][nc]){
                var key = nr + "," + nc;
                if(!visited[key]){
                    visited[key] = true;
                    table.push(queue, {r=nr; c=nc});
                }
            }
        }
    }
    
    // 3. 移除未被访问到的泡泡 (即悬空的)
    for(r, row in grid){
        for(c, color in row){
            if(!visited[r + "," + c]){
                grid[r][c] = null; // 移除
            }
        }
    }
}

// --- 处理碰撞后的逻辑 ---
function snapBubble(){
    // 简单的碰撞处理:找到距离子弹最近的一个空网格位置
    var minDist = 999999;
    var bestR, bestC = -1, -1;
    
    // 遍历所有可能的网格位置(稍微有些低效但逻辑简单)
    for(r=0; ROWS){
        var cols = (r % 2 == 0) ? COLS : COLS - 1;
        for(c=0; cols-1){
            if( (!grid[r] || !grid[r][c]) ){ // 必须是空格
                var px, py = getPixelPos(r, c);
                var dist = getDistSq(bullet.x, bullet.y, px, py);
                if(dist < minDist){
                    minDist = dist;
                    bestR, bestC = r, c;
                }
            }
        }
    }
    
    // 只有当距离足够近时才吸附 (防止吸附到太远的地方)
    if(bestR != -1 && minDist < DIAMETER*DIAMETER){
        if(!grid[bestR]) grid[bestR] = {};
        grid[bestR][bestC] = bullet.color;
        
        // 1. 检查消除
        var matches = {};
        findCluster(bestR, bestC, bullet.color, matches, {});
        
        if(#matches >= 3){
            // 消除
            for(i=1; #matches){
                var m = matches[i];
                grid[m.r][m.c] = null;
            }
            // 2. 检查悬空并掉落
            findFloating();
        }
        
        // 检查游戏结束 (泡泡到底部)
        if(bestR >= ROWS - 2) isGameOver = true;
    }
    
    bullet = null; // 销毁子弹
    currColor = nextColor;
    nextColor = math.random(1, #colors);
    winform.canvas.redraw();
}

// --- 游戏主循环 (更新逻辑) ---
function update(){
    if(isGameOver || !bullet) return;
    
    // 移动子弹
    bullet.x = bullet.x + bullet.dx;
    bullet.y = bullet.y + bullet.dy;
    
    // 墙壁反弹
    if(bullet.x <= RADIUS || bullet.x >= winform.canvas.width - RADIUS){
        bullet.dx = -bullet.dx;
        bullet.x = bullet.x + bullet.dx; // 推出墙壁
    }
    
    // 顶部碰撞
    if(bullet.y <= RADIUS){
        snapBubble();
        return;
    }
    
    // 泡泡碰撞检测
    var hit = false;
    for(r, row in grid){
        for(c, col in row){
            if(col){
                var px, py = getPixelPos(r, c);
                // 如果距离小于直径,说明碰撞
                if(getDistSq(bullet.x, bullet.y, px, py) < DIAMETER * DIAMETER * 0.8){ 
                    // *0.8 是为了让碰撞判定稍微宽松/紧凑一点
                    hit = true;
                    break; 
                }
            }
        }
        if(hit) break;
    }
    
    if(hit){
        snapBubble();
    }
    
    winform.canvas.redraw();
}

// --- 绘图 ---
winform.canvas.onDrawForegroundEnd  = function(graphics, rc){
    graphics.clear(0xFF333333);
    graphics.smoothingMode = 4;
    
    // 绘制网格中的泡泡
    for(r=0; ROWS){
        if(grid[r]){
            for(c, colorIdx in grid[r]){
                if(colorIdx){
                    var x, y = getPixelPos(r, c);
                    var brush = gdip.solidBrush(colors[colorIdx]);
                    graphics.fillEllipse(brush, x-RADIUS, y-RADIUS, DIAMETER, DIAMETER);
                    brush.delete();
                    
                    // 高光
                    var brushW = gdip.solidBrush(0x50FFFFFF);
                    graphics.fillEllipse(brushW, x-RADIUS+5, y-RADIUS+5, 10, 10);
                    brushW.delete();
                }
            }
        }
    }
    
    // 绘制发射台 (底部中间)
    var shooterX = rc.width / 2;
    var shooterY = rc.height - 50;
    
    // 绘制箭头
    var penArrow = gdip.pen(0xFFFFFFFF, 3);
    var endX = shooterX + math.cos(angle * math.pi / 180) * 60;
    var endY = shooterY + math.sin(angle * math.pi / 180) * 60;
    graphics.drawLine(penArrow, shooterX, shooterY, endX, endY);
    penArrow.delete();
    
    // 绘制当前待发射泡泡
    if(!bullet){
        var brushCurr = gdip.solidBrush(colors[currColor]);
        graphics.fillEllipse(brushCurr, shooterX-RADIUS, shooterY-RADIUS, DIAMETER, DIAMETER);
        brushCurr.delete();
    } else {
        // 绘制飞行中的泡泡
        var brushFly = gdip.solidBrush(colors[bullet.color]);
        graphics.fillEllipse(brushFly, bullet.x-RADIUS, bullet.y-RADIUS, DIAMETER, DIAMETER);
        brushFly.delete();
    }
    
    // 绘制下一个预览泡泡
    var brushNext = gdip.solidBrush(colors[nextColor]);
    graphics.fillEllipse(brushNext, shooterX + 80, shooterY, RADIUS, RADIUS); // 小一点
    brushNext.delete();

    var fontFamily = gdip.family("Arial");
    var font = fontFamily.createFont(10, 1); 
    var strformat = gdip.stringformat();
    graphics.drawString("NEXT", font, ::RECTF(shooterX + 80, shooterY + 20,1000,1000), strformat, gdip.solidBrush(0xFFFFFFFF));
    
    if(isGameOver){
    	var fontBig = fontFamily.createFont(30, 1); 
        graphics.drawString("GAME OVER", fontBig, ::RECTF(100, 300,1000,1000), strformat, gdip.solidBrush(0xFFFF0000));
        graphics.drawString("点击重启", font, ::RECTF(180, 350,1000,1000), strformat, gdip.solidBrush(0xFFFFFFFF));
        fontBig.delete();
    }
    font.delete();
}

// --- 鼠标移动 (瞄准) ---
winform.canvas.onMouseMove = function(wParam, lParam){
    if(isGameOver) return;
    var x, y = win.getMessagePos(lParam);
    var shooterX = winform.canvas.width / 2;
    var shooterY = winform.canvas.height - 50;
    
    // 计算角度 (atan2)
    var dx = x - shooterX;
    var dy = y - shooterY;
    
    // 限制角度,防止射向地板
    if(dy >= 0) dy = -1; 
    
    angle = math.atan2(dy, dx) * 180 / math.pi;
    winform.canvas.redraw();
}

// --- 鼠标点击 (发射) ---
winform.canvas.onMouseDown = function(wParam, lParam){
    if(isGameOver){
        initGame();
        return;
    }
    
    if(bullet) return; // 已有子弹在飞,不能发射
    
    var shooterX = winform.canvas.width / 2;
    var shooterY = winform.canvas.height - 50;
    var speed = 15;
    
    bullet = {
        x = shooterX;
        y = shooterY;
        dx = math.cos(angle * math.pi / 180) * speed;
        dy = math.sin(angle * math.pi / 180) * speed;
        color = currColor;
    };
}

// --- 定时器 (60FPS) ---
var tmr = winform.setInterval(
    16, 
    function(){
        update();
    }
)

// 调整窗口大小以适应网格宽度
var width = COLS * DIAMETER + RADIUS; // 加上左右边距
winform.width = width;
winform.canvas.right = width;

initGame();

winform.enableDpiScaling(false);
winform.show();
win.loopMessage();


最新回复 (0)
返回