|
|
import math, pygame, sys, copy, random
|
|
|
import pygame.gfxdraw
|
|
|
from pygame.locals import *
|
|
|
|
|
|
# 定义游戏的常量
|
|
|
FPS = 120 #帧率
|
|
|
WINDOWWIDTH = 640 #窗口宽度
|
|
|
WINDOWHEIGHT = 480 # 窗口高度
|
|
|
TEXTHEIGHT = 20 # 文本高度
|
|
|
BUBBLERADIUS = 20 # 泡泡半径
|
|
|
BUBBLEWIDTH = BUBBLERADIUS * 2 # 泡泡宽度
|
|
|
BUBBLELAYERS = 5 # 泡泡层数
|
|
|
BUBBLEYADJUST = 5 # 泡泡垂直调整
|
|
|
STARTX = WINDOWWIDTH / 2 # 开始 X 坐标
|
|
|
STARTY = WINDOWHEIGHT - 27 # 开始 Y 坐标
|
|
|
ARRAYWIDTH = 16 # 数组宽度
|
|
|
ARRAYHEIGHT = 14 # 数组高度
|
|
|
# 定义左右移动的常量
|
|
|
RIGHT = 'right'
|
|
|
LEFT = 'left'
|
|
|
BLANK = '.' # 空白符号
|
|
|
|
|
|
## COLORS ##
|
|
|
# 定义颜色常量
|
|
|
#RGB
|
|
|
# 每个元组代表一种颜色,格式为 (红, 绿, 蓝)
|
|
|
GRAY = (100, 100, 100)
|
|
|
NAVYBLUE = (60, 60, 100)
|
|
|
WHITE = (255, 255, 255)
|
|
|
RED = (255, 0, 0)
|
|
|
GREEN = (0, 255, 0)
|
|
|
BLUE = (0, 0, 255)
|
|
|
YELLOW = (255, 255, 0)
|
|
|
ORANGE = (255, 128, 0)
|
|
|
PURPLE = (255, 0, 255)
|
|
|
CYAN = (0, 255, 255)
|
|
|
BLACK = (0, 0, 0)
|
|
|
COMBLUE = (233, 232, 255)
|
|
|
|
|
|
BGCOLOR = WHITE #背景颜色
|
|
|
COLORLIST = [RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN] #泡泡颜色列表
|
|
|
|
|
|
|
|
|
# 定义泡泡类,继承自pygame.sprite.Sprite
|
|
|
class Bubble(pygame.sprite.Sprite):
|
|
|
def __init__(self, color, row=0, column=0):
|
|
|
pygame.sprite.Sprite.__init__(self)
|
|
|
# 初始化泡泡属性
|
|
|
self.rect = pygame.Rect(0, 0, 30, 30) #创建了一个表示气泡的矩形区域,初始位置在(0, 0),宽和高都是30像素。
|
|
|
self.rect.centerx = STARTX
|
|
|
self.rect.centery = STARTY
|
|
|
self.speed = 10
|
|
|
self.color = color
|
|
|
self.radius = BUBBLERADIUS
|
|
|
self.angle = 0
|
|
|
self.row = row
|
|
|
self.column = column
|
|
|
|
|
|
# 更新泡泡位置的方法
|
|
|
def update(self):
|
|
|
|
|
|
global xmove, ymove
|
|
|
if self.angle == 90:
|
|
|
xmove = 0
|
|
|
ymove = self.speed * -1
|
|
|
elif self.angle < 90:
|
|
|
xmove = self.xcalculate(self.angle)
|
|
|
ymove = self.ycalculate(self.angle)
|
|
|
elif self.angle > 90:
|
|
|
xmove = self.xcalculate(180 - self.angle) * -1
|
|
|
ymove = self.ycalculate(180 - self.angle)
|
|
|
|
|
|
self.rect.x += xmove
|
|
|
self.rect.y += ymove
|
|
|
|
|
|
# 绘制泡泡的方法
|
|
|
def draw(self):
|
|
|
pygame.gfxdraw.filled_circle(DISPLAYSURF, self.rect.centerx, self.rect.centery, self.radius, self.color)
|
|
|
pygame.gfxdraw.aacircle(DISPLAYSURF, self.rect.centerx, self.rect.centery, self.radius, GRAY)
|
|
|
|
|
|
# 计算X轴移动距离的方法
|
|
|
def xcalculate(self, angle):
|
|
|
radians = math.radians(angle)
|
|
|
xmove = math.cos(radians) * (self.speed)
|
|
|
return xmove
|
|
|
|
|
|
# 计算Y轴移动距离的方法
|
|
|
def ycalculate(self, angle):
|
|
|
radians = math.radians(angle)
|
|
|
ymove = math.sin(radians) * (self.speed) * -1
|
|
|
return ymove
|
|
|
|
|
|
|
|
|
# 定义箭头类,继承自pygame.sprite.Sprite
|
|
|
class Arrow(pygame.sprite.Sprite):
|
|
|
# 初始化箭头属性
|
|
|
def __init__(self):
|
|
|
"""
|
|
|
初始化箭头对象。
|
|
|
|
|
|
该方法对箭头对象进行初始化,包括加载箭头图像、设置初始角度、确定箭头在屏幕上的初始位置。
|
|
|
"""
|
|
|
# 调用父类pygame.sprite.Sprite的初始化方法
|
|
|
pygame.sprite.Sprite.__init__(self)
|
|
|
|
|
|
self.angle = 90 # 将箭头的初始角度设置为90度,指向正上方
|
|
|
# 加载箭头图像并进行透明处理
|
|
|
arrowImage = pygame.image.load('Arrow.png')
|
|
|
arrowImage.convert_alpha()
|
|
|
# 获取箭头图像的矩形区域
|
|
|
arrowRect = arrowImage.get_rect()
|
|
|
self.image = arrowImage # 将处理后的图像赋值给箭头对象的image属性
|
|
|
self.transformImage = self.image # 初始化transformImage为箭头的初始图像
|
|
|
self.rect = arrowRect # 将图像的矩形区域赋值给箭头对象的rect属性
|
|
|
# 设置箭头在屏幕上的初始位置
|
|
|
self.rect.centerx = STARTX
|
|
|
self.rect.centery = STARTY
|
|
|
|
|
|
# 更新箭头方向的方法
|
|
|
def update(self, direction):
|
|
|
|
|
|
if direction == LEFT and self.angle < 176:
|
|
|
self.angle += 2 # 如果按下左箭头键,且当前角度小于180度,则增加角度
|
|
|
elif direction == RIGHT and self.angle > 4:
|
|
|
self.angle -= 2 # 如果按下右箭头键,且当前角度大于0度,则减少角度
|
|
|
|
|
|
self.transformImage = pygame.transform.rotate(self.image, self.angle)
|
|
|
self.rect = self.transformImage.get_rect()
|
|
|
self.rect.centerx = STARTX # 保持箭头在水平中心位置
|
|
|
self.rect.centery = STARTY # 保持箭头在垂直起始位置
|
|
|
|
|
|
# 绘制箭头的方法
|
|
|
def draw(self):
|
|
|
DISPLAYSURF.blit(self.transformImage, self.rect)
|
|
|
|
|
|
|
|
|
# 定义分数类
|
|
|
class Score(object):
|
|
|
def __init__(self):
|
|
|
self.total = 0
|
|
|
self.font = pygame.font.SysFont('Helvetica', 15)
|
|
|
self.render = self.font.render('Score: ' + str(self.total), True, BLACK, WHITE)
|
|
|
self.rect = self.render.get_rect()
|
|
|
self.rect.left = 5
|
|
|
self.rect.bottom = WINDOWHEIGHT - 5
|
|
|
|
|
|
# 更新分数的方法
|
|
|
def update(self, deleteList):
|
|
|
self.total += ((len(deleteList)) * 10)
|
|
|
self.render = self.font.render('Score: ' + str(self.total), True, BLACK, WHITE)
|
|
|
|
|
|
# 绘制分数的方法
|
|
|
def draw(self):
|
|
|
DISPLAYSURF.blit(self.render, self.rect)
|
|
|
|
|
|
|
|
|
# 主函数
|
|
|
def main():
|
|
|
|
|
|
global FPSCLOCK, DISPLAYSURF, DISPLAYRECT, MAINFONT # 引用全局变量
|
|
|
|
|
|
pygame.init() # 初始化Pygame
|
|
|
FPSCLOCK = pygame.time.Clock() # 创建一个时钟对象,用于控制游戏帧率
|
|
|
pygame.display.set_caption('泡泡天堂小游戏') # 设置游戏窗口标题
|
|
|
MAINFONT = pygame.font.SysFont('Helvetica', TEXTHEIGHT) # 设置游戏使用的默认字体
|
|
|
DISPLAYSURF, DISPLAYRECT = makeDisplay() # 创建游戏显示的表面和矩形
|
|
|
|
|
|
while True: # 进入游戏主循环
|
|
|
score, winorlose = runGame() # 运行游戏,返回得分和游戏结果
|
|
|
endScreen(score, winorlose) # 显示游戏结束画面
|
|
|
|
|
|
# 运行游戏的函数
|
|
|
def runGame():
|
|
|
|
|
|
# 初始化音乐列表
|
|
|
musicList = ['知更鸟 _ HOYO-MiX _ Chevy - 使一颗心免于哀伤.ogg', '知更鸟 _ HOYO-MiX _ Chevy - 希望有羽毛和翅膀.ogg',
|
|
|
'知更鸟 _ HOYO-MiX _ Chevy - 在银河中孤独摇摆.ogg']
|
|
|
pygame.mixer.music.load(musicList[0])
|
|
|
pygame.mixer.music.play()
|
|
|
track = 0 # 音乐曲目索引
|
|
|
|
|
|
# 初始化游戏颜色列表和游戏颜色数组
|
|
|
gameColorList = copy.deepcopy(COLORLIST)
|
|
|
direction = None # 玩家控制的方向
|
|
|
launchBubble = False # 是否发射气泡
|
|
|
newBubble = None # 将要发射或正在移动的气泡
|
|
|
|
|
|
# 初始化箭头、游戏面板(气泡数组)和得分
|
|
|
arrow = Arrow()
|
|
|
bubbleArray = makeBlankBoard()
|
|
|
setBubbles(bubbleArray, gameColorList)
|
|
|
nextBubble = Bubble(gameColorList[0]) # 下一个要发射的气泡
|
|
|
nextBubble.rect.right = WINDOWWIDTH - 5
|
|
|
nextBubble.rect.bottom = WINDOWHEIGHT - 5
|
|
|
score = Score()
|
|
|
|
|
|
while True:
|
|
|
DISPLAYSURF.fill(BGCOLOR) # 填充游戏背景
|
|
|
|
|
|
# 处理事件:退出、按键等
|
|
|
for event in pygame.event.get():
|
|
|
if event.type == QUIT:
|
|
|
terminate()
|
|
|
|
|
|
elif event.type == KEYDOWN:
|
|
|
if (event.key == K_LEFT):
|
|
|
direction = LEFT
|
|
|
elif (event.key == K_RIGHT):
|
|
|
direction = RIGHT
|
|
|
|
|
|
elif event.type == KEYUP:
|
|
|
direction = None
|
|
|
if event.key == K_SPACE:
|
|
|
launchBubble = True
|
|
|
elif event.key == K_ESCAPE:
|
|
|
terminate()
|
|
|
|
|
|
# 如果按下发射气泡的按键
|
|
|
if launchBubble == True:
|
|
|
if newBubble == None:
|
|
|
newBubble = Bubble(nextBubble.color)
|
|
|
newBubble.angle = arrow.angle
|
|
|
|
|
|
newBubble.update()
|
|
|
newBubble.draw()
|
|
|
|
|
|
# 反转气泡下落方向
|
|
|
if newBubble.rect.right >= WINDOWWIDTH - 5:
|
|
|
newBubble.angle = 180 - newBubble.angle
|
|
|
elif newBubble.rect.left <= 5:
|
|
|
newBubble.angle = 180 - newBubble.angle
|
|
|
|
|
|
# 处理气泡发射和消除逻辑
|
|
|
launchBubble, newBubble, score = stopBubble(bubbleArray, newBubble, launchBubble, score)
|
|
|
|
|
|
# 检查游戏是否结束
|
|
|
finalBubbleList = []
|
|
|
for row in range(len(bubbleArray)):
|
|
|
for column in range(len(bubbleArray[0])):
|
|
|
if bubbleArray[row][column] != BLANK:
|
|
|
finalBubbleList.append(bubbleArray[row][column])
|
|
|
if bubbleArray[row][column].rect.bottom > (WINDOWHEIGHT - arrow.rect.height - 10):
|
|
|
return score.total, 'lose'
|
|
|
|
|
|
# 游戏胜利条件检查
|
|
|
if len(finalBubbleList) < 1:
|
|
|
return score.total, 'win'
|
|
|
|
|
|
# 更新游戏颜色列表并随机排序
|
|
|
gameColorList = updateColorList(bubbleArray)
|
|
|
random.shuffle(gameColorList)
|
|
|
|
|
|
# 重置下一个要发射的气泡
|
|
|
if launchBubble == False:
|
|
|
nextBubble = Bubble(gameColorList[0])
|
|
|
nextBubble.rect.right = WINDOWWIDTH - 5
|
|
|
nextBubble.rect.bottom = WINDOWHEIGHT - 5
|
|
|
|
|
|
# 绘制游戏元素
|
|
|
nextBubble.draw()
|
|
|
if launchBubble == True:
|
|
|
coverNextBubble() # 遮挡下一个气泡
|
|
|
|
|
|
arrow.update(direction) # 更新箭头方向
|
|
|
arrow.draw() # 绘制箭头
|
|
|
|
|
|
setArrayPos(bubbleArray) # 更新气泡数组位置
|
|
|
drawBubbleArray(bubbleArray) # 绘制气泡数组
|
|
|
|
|
|
score.draw() # 绘制得分
|
|
|
|
|
|
# 切换音乐
|
|
|
if pygame.mixer.music.get_busy() == False:
|
|
|
if track == len(musicList) - 1:
|
|
|
track = 0
|
|
|
else:
|
|
|
track += 1
|
|
|
|
|
|
pygame.mixer.music.load(musicList[track])
|
|
|
pygame.mixer.music.play()
|
|
|
|
|
|
pygame.display.update()
|
|
|
FPSCLOCK.tick(FPS) # 控制游戏帧率
|
|
|
|
|
|
# 创建空白游戏板的函数
|
|
|
def makeBlankBoard():
|
|
|
|
|
|
array = [] # 初始化一个空的一维列表,用于存放棋盘的每一行
|
|
|
|
|
|
# 循环创建ARRAYHEIGHT行
|
|
|
for row in range(ARRAYHEIGHT):
|
|
|
column = [] # 初始化一个空的一维列表,用于存放当前行的列
|
|
|
|
|
|
# 循环创建ARRAYWIDTH列,每列都初始化为BLANK
|
|
|
for i in range(ARRAYWIDTH):
|
|
|
column.append(BLANK)
|
|
|
|
|
|
array.append(column) # 将当前行的列添加到棋盘数组中
|
|
|
|
|
|
return array # 返回创建好的空棋盘
|
|
|
|
|
|
|
|
|
# 在游戏板上设置泡泡的函数
|
|
|
def setBubbles(array, gameColorList):
|
|
|
|
|
|
for row in range(BUBBLELAYERS): # 遍历气泡层数
|
|
|
for column in range(len(array[row])): # 遍历每一层的气泡列数
|
|
|
random.shuffle(gameColorList) # 随机重洗颜色列表
|
|
|
newBubble = Bubble(gameColorList[0], row, column) # 创建新的气泡对象
|
|
|
array[row][column] = newBubble # 将新的气泡对象设置到对应的位置
|
|
|
|
|
|
setArrayPos(array) # 更新气泡数组的位置信息
|
|
|
|
|
|
# 设置游戏板上泡泡位置的函数def setArrayPos(array):
|
|
|
|
|
|
# 初始化元素位置
|
|
|
for row in range(ARRAYHEIGHT):
|
|
|
for column in range(len(array[row])):
|
|
|
if array[row][column] != BLANK:
|
|
|
array[row][column].rect.x = (BUBBLEWIDTH * column) + 5
|
|
|
array[row][column].rect.y = (BUBBLEWIDTH * row) + 5
|
|
|
|
|
|
# 调整奇数行元素的 x 坐标
|
|
|
for row in range(1, ARRAYHEIGHT, 2):
|
|
|
for column in range(len(array[row])):
|
|
|
if array[row][column] != BLANK:
|
|
|
array[row][column].rect.x += BUBBLERADIUS
|
|
|
|
|
|
# 调整所有行元素的 y 坐标
|
|
|
for row in range(1, ARRAYHEIGHT):
|
|
|
for column in range(len(array[row])):
|
|
|
if array[row][column] != BLANK:
|
|
|
array[row][column].rect.y -= (BUBBLEYADJUST * row)
|
|
|
|
|
|
# 删除多余气泡
|
|
|
deleteExtraBubbles(array)
|
|
|
|
|
|
#设置游戏板上泡泡位置的函数
|
|
|
def setArrayPos(array):
|
|
|
|
|
|
# 初始化元素位置
|
|
|
for row in range(ARRAYHEIGHT):
|
|
|
for column in range(len(array[row])):
|
|
|
if array[row][column] != BLANK:
|
|
|
array[row][column].rect.x = (BUBBLEWIDTH * column) + 5
|
|
|
array[row][column].rect.y = (BUBBLEWIDTH * row) + 5
|
|
|
|
|
|
# 调整奇数行元素的 x 坐标
|
|
|
for row in range(1, ARRAYHEIGHT, 2):
|
|
|
for column in range(len(array[row])):
|
|
|
if array[row][column] != BLANK:
|
|
|
array[row][column].rect.x += BUBBLERADIUS
|
|
|
|
|
|
# 调整所有行元素的 y 坐标
|
|
|
for row in range(1, ARRAYHEIGHT):
|
|
|
for column in range(len(array[row])):
|
|
|
if array[row][column] != BLANK:
|
|
|
array[row][column].rect.y -= (BUBBLEYADJUST * row)
|
|
|
|
|
|
# 删除多余气泡
|
|
|
deleteExtraBubbles(array)
|
|
|
|
|
|
#(删除超出游戏区域的泡泡)
|
|
|
def deleteExtraBubbles(array):
|
|
|
|
|
|
for row in range(ARRAYHEIGHT): # 遍历每一行
|
|
|
for column in range(len(array[row])): # 遍历当前行的每个元素
|
|
|
# 如果当前元素不为空
|
|
|
if array[row][column] != BLANK:
|
|
|
# 如果气泡的右边界超出窗口宽度
|
|
|
if array[row][column].rect.right > WINDOWWIDTH:
|
|
|
# 将该气泡设置为空
|
|
|
array[row][column] = BLANK
|
|
|
|
|
|
# 更新颜色列表的函数
|
|
|
def updateColorList(bubbleArray):
|
|
|
newColorList = []
|
|
|
|
|
|
# 遍历气泡数组,将非空气泡的颜色添加到新颜色列表中
|
|
|
for row in range(len(bubbleArray)):
|
|
|
for column in range(len(bubbleArray[0])):
|
|
|
if bubbleArray[row][column] != BLANK:
|
|
|
newColorList.append(bubbleArray[row][column].color)
|
|
|
|
|
|
colorSet = set(newColorList) # 使用集合去重
|
|
|
|
|
|
# 如果集合为空,说明气泡数组中没有非空气泡,返回包含白色的列表
|
|
|
if len(colorSet) < 1:
|
|
|
colorList = []
|
|
|
colorList.append(WHITE)
|
|
|
return colorList
|
|
|
|
|
|
else:
|
|
|
return list(colorSet) # 返回去重后的颜色列表
|
|
|
|
|
|
|
|
|
|
|
|
#(检查孤立泡泡)
|
|
|
def checkForFloaters(bubbleArray):
|
|
|
|
|
|
# 遍历数组第一行,找出非空列的索引,存储到bubbleList中
|
|
|
bubbleList = [column for column in range(len(bubbleArray[0]))
|
|
|
if bubbleArray[0][column] != BLANK]
|
|
|
|
|
|
newBubbleList = []
|
|
|
|
|
|
# 筛选出bubbleList中需要处理的列,即数值间隔大于1的列
|
|
|
for i in range(len(bubbleList)):
|
|
|
if i == 0:
|
|
|
newBubbleList.append(bubbleList[i])
|
|
|
elif bubbleList[i] > bubbleList[i - 1] + 1:
|
|
|
newBubbleList.append(bubbleList[i])
|
|
|
|
|
|
# 深拷贝bubbleArray,以用于后续操作,避免修改原数组
|
|
|
copyOfBoard = copy.deepcopy(bubbleArray)
|
|
|
|
|
|
# 清空原数组中的所有元素,准备进行浮选物处理
|
|
|
for row in range(len(bubbleArray)):
|
|
|
for column in range(len(bubbleArray[0])):
|
|
|
bubbleArray[row][column] = BLANK
|
|
|
|
|
|
# 对需要处理的列,调用popFloaters函数进行浮选物处理
|
|
|
for column in newBubbleList:
|
|
|
popFloaters(bubbleArray, copyOfBoard, column)
|
|
|
|
|
|
def popFloaters(bubbleArray, copyOfBoard, column, row=0):
|
|
|
if (row < 0 or row > (len(bubbleArray) - 1)
|
|
|
or column < 0 or column > (len(bubbleArray[0]) - 1)):
|
|
|
return
|
|
|
|
|
|
elif copyOfBoard[row][column] == BLANK:
|
|
|
return
|
|
|
|
|
|
elif bubbleArray[row][column] == copyOfBoard[row][column]:
|
|
|
return
|
|
|
|
|
|
bubbleArray[row][column] = copyOfBoard[row][column]
|
|
|
|
|
|
if row == 0:
|
|
|
popFloaters(bubbleArray, copyOfBoard, column + 1, row)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column - 1, row)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column, row + 1)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column - 1, row + 1)
|
|
|
|
|
|
elif row % 2 == 0:
|
|
|
popFloaters(bubbleArray, copyOfBoard, column + 1, row)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column - 1, row)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column, row + 1)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column - 1, row + 1)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column, row - 1)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column - 1, row - 1)
|
|
|
|
|
|
else:
|
|
|
popFloaters(bubbleArray, copyOfBoard, column + 1, row)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column - 1, row)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column, row + 1)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column + 1, row + 1)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column, row - 1)
|
|
|
popFloaters(bubbleArray, copyOfBoard, column + 1, row - 1)
|
|
|
|
|
|
|
|
|
# 这个函数处理泡泡停止时的逻辑,包括判断泡泡是否到达了发射的终点,以及是否与其它泡泡发生碰撞。
|
|
|
def stopBubble(bubbleArray, newBubble, launchBubble, score):
|
|
|
|
|
|
global newRow, newColumn
|
|
|
deleteList = [] # 用于存储需要消除的气泡位置
|
|
|
popSound = pygame.mixer.Sound('popcork.ogg') # 播放气泡消除音效
|
|
|
|
|
|
# 遍历气泡数组,检查新气泡与现有气泡的碰撞
|
|
|
for row in range(len(bubbleArray)):
|
|
|
for column in range(len(bubbleArray[row])):
|
|
|
|
|
|
# 如果当前位置有气泡,并且有新气泡即将落下
|
|
|
if (bubbleArray[row][column] != BLANK and newBubble != None):
|
|
|
# 检查新气泡是否与当前气泡碰撞,或者是否已经越界
|
|
|
if (pygame.sprite.collide_rect(newBubble, bubbleArray[row][column])) or newBubble.rect.top < 0:
|
|
|
# 如果新气泡越界,则把它添加到上方
|
|
|
if newBubble.rect.top < 0:
|
|
|
newRow, newColumn = addBubbleToTop(bubbleArray, newBubble)
|
|
|
|
|
|
# 检查新气泡与当前气泡的中心是否在同一垂直线上
|
|
|
elif newBubble.rect.centery >= bubbleArray[row][column].rect.centery:
|
|
|
# 向上或向下移动新气泡,并考虑奇偶行的规则
|
|
|
if newBubble.rect.centerx >= bubbleArray[row][column].rect.centerx:
|
|
|
if row == 0 or (row) % 2 == 0:
|
|
|
newRow = row + 1
|
|
|
newColumn = column
|
|
|
# 如果目标位置已有气泡,则向上移动一行
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow - 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
|
|
|
else:
|
|
|
newRow = row + 1
|
|
|
newColumn = column + 1
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow - 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
|
|
|
# 向左或向右移动新气泡,并考虑奇偶行的规则
|
|
|
elif newBubble.rect.centerx < bubbleArray[row][column].rect.centerx:
|
|
|
if row == 0 or row % 2 == 0:
|
|
|
newRow = row + 1
|
|
|
newColumn = column - 1
|
|
|
if newColumn < 0:
|
|
|
newColumn = 0
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow - 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
else:
|
|
|
newRow = row + 1
|
|
|
newColumn = column
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow - 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
|
|
|
# 检查新气泡与当前气泡的中心是否在同一水平线上
|
|
|
elif newBubble.rect.centery < bubbleArray[row][column].rect.centery:
|
|
|
# 向上或向下移动新气泡,并考虑奇偶行的规则
|
|
|
if newBubble.rect.centerx >= bubbleArray[row][column].rect.centerx:
|
|
|
if row == 0 or row % 2 == 0:
|
|
|
newRow = row - 1
|
|
|
newColumn = column
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow + 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
else:
|
|
|
newRow = row - 1
|
|
|
newColumn = column + 1
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow + 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
|
|
|
# 向左或向右移动新气泡,并考虑奇偶行的规则
|
|
|
elif newBubble.rect.centerx <= bubbleArray[row][column].rect.centerx:
|
|
|
if row == 0 or row % 2 == 0:
|
|
|
newRow = row - 1
|
|
|
newColumn = column - 1
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow + 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
|
|
|
else:
|
|
|
newRow = row - 1
|
|
|
newColumn = column
|
|
|
if bubbleArray[newRow][newColumn] != BLANK:
|
|
|
newRow = newRow + 1
|
|
|
bubbleArray[newRow][newColumn] = copy.copy(newBubble)
|
|
|
bubbleArray[newRow][newColumn].row = newRow
|
|
|
bubbleArray[newRow][newColumn].column = newColumn
|
|
|
|
|
|
# 消除选中的气泡,并处理连消和得分
|
|
|
popBubbles(bubbleArray, newRow, newColumn, newBubble.color, deleteList)
|
|
|
|
|
|
# 如果消除的气泡数量达到3个或以上,播放音效、更新布局和得分
|
|
|
if len(deleteList) >= 3:
|
|
|
for pos in deleteList:
|
|
|
popSound.play()
|
|
|
row = pos[0]
|
|
|
column = pos[1]
|
|
|
bubbleArray[row][column] = BLANK
|
|
|
checkForFloaters(bubbleArray)
|
|
|
|
|
|
score.update(deleteList)
|
|
|
|
|
|
# 更新发射状态和新气泡为None
|
|
|
launchBubble = False
|
|
|
newBubble = None
|
|
|
|
|
|
return launchBubble, newBubble, score
|
|
|
|
|
|
|
|
|
|
|
|
def addBubbleToTop(bubbleArray, bubble):
|
|
|
|
|
|
# 获取气泡中心的x坐标,并计算气泡左侧边界的x坐标
|
|
|
posx = bubble.rect.centerx
|
|
|
leftSidex = posx - BUBBLERADIUS
|
|
|
|
|
|
# 根据气泡左侧边界的位置计算其所在的列
|
|
|
columnDivision = math.modf(float(leftSidex) / float(BUBBLEWIDTH))
|
|
|
column = int(columnDivision[1])
|
|
|
|
|
|
# 判断气泡应该添加到当前列还是下一列
|
|
|
if columnDivision[0] < 0.5:
|
|
|
bubbleArray[0][column] = copy.copy(bubble)
|
|
|
else:
|
|
|
column += 1
|
|
|
bubbleArray[0][column] = copy.copy(bubble)
|
|
|
|
|
|
# 设置行索引为0,因为气泡被添加到数组顶部
|
|
|
row = 0
|
|
|
|
|
|
return row, column
|
|
|
|
|
|
|
|
|
|
|
|
#这个函数递归地查找并消除连接的同色泡泡。
|
|
|
def popBubbles(bubbleArray, row, column, color, deleteList):
|
|
|
# 检查索引是否越界
|
|
|
if row < 0 or column < 0 or row > (len(bubbleArray) - 1) or column > (len(bubbleArray[0]) - 1):
|
|
|
return
|
|
|
|
|
|
# 检查当前位置是否为空或颜色不匹配
|
|
|
elif bubbleArray[row][column] == BLANK:
|
|
|
return
|
|
|
elif bubbleArray[row][column].color != color:
|
|
|
return
|
|
|
|
|
|
# 如果气泡颜色匹配,但已在删除列表中,则不处理
|
|
|
for bubble in deleteList:
|
|
|
if bubbleArray[bubble[0]][bubble[1]] == bubbleArray[row][column]:
|
|
|
return
|
|
|
|
|
|
# 将当前气泡加入删除列表
|
|
|
deleteList.append((row, column))
|
|
|
|
|
|
# 递归消除相邻的气泡
|
|
|
# 处理顶部行或底部行的逻辑
|
|
|
if row == 0:
|
|
|
popBubbles(bubbleArray, row, column - 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row, column + 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row + 1, column, color, deleteList)
|
|
|
popBubbles(bubbleArray, row + 1, column - 1, color, deleteList)
|
|
|
|
|
|
# 处理中间行的逻辑,奇数行和偶数行处理方式略有不同
|
|
|
elif row % 2 == 0:
|
|
|
popBubbles(bubbleArray, row + 1, column, color, deleteList)
|
|
|
popBubbles(bubbleArray, row + 1, column - 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row - 1, column, color, deleteList)
|
|
|
popBubbles(bubbleArray, row - 1, column - 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row, column + 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row, column - 1, color, deleteList)
|
|
|
|
|
|
else:
|
|
|
popBubbles(bubbleArray, row - 1, column, color, deleteList)
|
|
|
popBubbles(bubbleArray, row - 1, column + 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row + 1, column, color, deleteList)
|
|
|
popBubbles(bubbleArray, row + 1, column + 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row, column + 1, color, deleteList)
|
|
|
popBubbles(bubbleArray, row, column - 1, color, deleteList)
|
|
|
|
|
|
|
|
|
|
|
|
def drawBubbleArray(array):
|
|
|
for row in range(ARRAYHEIGHT):
|
|
|
for column in range(len(array[row])):
|
|
|
if array[row][column] != BLANK:
|
|
|
array[row][column].draw()
|
|
|
|
|
|
|
|
|
def makeDisplay():
|
|
|
|
|
|
# 创建显示表面并获取其矩形区域
|
|
|
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
|
|
|
DISPLAYRECT = DISPLAYSURF.get_rect()
|
|
|
|
|
|
# 填充显示表面的背景色
|
|
|
DISPLAYSURF.fill(BGCOLOR)
|
|
|
|
|
|
# 转换显示表面的格式以优化性能
|
|
|
DISPLAYSURF.convert()
|
|
|
|
|
|
# 更新显示内容
|
|
|
pygame.display.update()
|
|
|
|
|
|
return DISPLAYSURF, DISPLAYRECT
|
|
|
|
|
|
# 退出游戏的函数
|
|
|
def terminate():
|
|
|
pygame.quit()
|
|
|
sys.exit()
|
|
|
|
|
|
# 遮盖下一个泡泡的函数
|
|
|
def coverNextBubble():
|
|
|
|
|
|
# 初始化一个白色矩形,位置在窗口的右下角
|
|
|
whiteRect = pygame.Rect(0, 0, BUBBLEWIDTH, BUBBLEWIDTH)
|
|
|
whiteRect.bottom = WINDOWHEIGHT
|
|
|
whiteRect.right = WINDOWWIDTH
|
|
|
# 在显示表面绘制矩形
|
|
|
pygame.draw.rect(DISPLAYSURF, BGCOLOR, whiteRect)
|
|
|
|
|
|
# 游戏结束屏幕的函数
|
|
|
def endScreen(score, winorlose):
|
|
|
|
|
|
# 初始化游戏结束信息的字体和内容
|
|
|
endFont = pygame.font.SysFont('Helvetica', 20)
|
|
|
endMessage1 = endFont.render('You ' + winorlose + '! Your Score is ' + str(score) + '. Press Enter to Play Again.',
|
|
|
True, BLACK, BGCOLOR)
|
|
|
endMessage1Rect = endMessage1.get_rect()
|
|
|
endMessage1Rect.center = DISPLAYRECT.center # 将结束信息居中显示
|
|
|
|
|
|
# 清空显示屏幕并绘制结束信息
|
|
|
DISPLAYSURF.fill(BGCOLOR)
|
|
|
DISPLAYSURF.blit(endMessage1, endMessage1Rect)
|
|
|
pygame.display.update()
|
|
|
|
|
|
# 等待玩家按键选择重新开始或退出游戏
|
|
|
while True:
|
|
|
for event in pygame.event.get():
|
|
|
if event.type == QUIT: # 如果检测到退出事件,则终止游戏
|
|
|
terminate()
|
|
|
elif event.type == KEYUP: # 如果检测到按键事件
|
|
|
if event.key == K_RETURN: # 如果玩家按下回车键,则返回主游戏循环
|
|
|
return
|
|
|
elif event.key == K_ESCAPE: # 如果玩家按下Esc键,则终止游戏
|
|
|
terminate()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
main()
|