
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)