import pygame
import sys
import random
import time
import math
from pygame.locals import *
# 初始化pygame
pygame.init()
# 游戏常量
SCREEN_WIDTH = 900
SCREEN_HEIGHT = 700
GRID_SIZE = 8
CELL_SIZE = 60
MARGIN = 50
FPS = 60
# 颜色定义
BACKGROUND = (40, 44, 52)
GRID_BACKGROUND = (30, 34, 42)
GRID_LINE = (56, 60, 74)
COLORS = [
(241, 76, 76), # 红色
(76, 173, 241), # 蓝色
(76, 241, 157), # 绿色
(241, 214, 76), # 黄色
(185, 76, 241), # 紫色
(241, 147, 76) # 橙色
]
TEXT_COLOR = (220, 220, 220)
HIGHLIGHT = (255, 255, 255, 100)
MATCH_HIGHLIGHT = (255, 255, 255, 150)
# 创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Match-3 游戏(按 A 切换AI模式,R 重置)")
clock = pygame.time.Clock()
# 字体 - 使用系统默认字体,确保兼容性
try:
# 尝试加载中文字体
font = pygame.font.SysFont("simhei,microsoftyahei,arial", 24)
small_font = pygame.font.SysFont("simhei,microsoftyahei,arial", 20)
except:
# 如果失败则使用默认字体
font = pygame.font.Font(None, 36)
small_font = pygame.font.Font(None, 24)
class Animation:
def __init__(self):
self.animations = []
def add_swap(self, cell1, cell2, duration=0.3):
i1, j1 = cell1
i2, j2 = cell2
# 计算屏幕坐标
x1 = MARGIN + j1 * CELL_SIZE + CELL_SIZE // 2
y1 = MARGIN + i1 * CELL_SIZE + CELL_SIZE // 2
x2 = MARGIN + j2 * CELL_SIZE + CELL_SIZE // 2
y2 = MARGIN + i2 * CELL_SIZE + CELL_SIZE // 2
self.animations.append({
'type': 'swap',
'cells': [cell1, cell2],
'start_pos': [(x1, y1), (x2, y2)],
'end_pos': [(x2, y2), (x1, y1)],
'progress': 0,
'duration': duration,
'start_time': time.time()
})
def add_fall(self, from_cell, to_cell, duration=0.4):
i1, j1 = from_cell
i2, j2 = to_cell
# 计算屏幕坐标
x1 = MARGIN + j1 * CELL_SIZE + CELL_SIZE // 2
y1 = MARGIN + i1 * CELL_SIZE + CELL_SIZE // 2
x2 = MARGIN + j2 * CELL_SIZE + CELL_SIZE // 2
y2 = MARGIN + i2 * CELL_SIZE + CELL_SIZE // 2
self.animations.append({
'type': 'fall',
'cell': to_cell, # 最终位置
'from_pos': (x1, y1),
'to_pos': (x2, y2),
'progress': 0,
'duration': duration,
'start_time': time.time()
})
def add_match_highlight(self, cells, duration=0.5):
screen_cells = []
for cell in cells:
i, j = cell
x = MARGIN + j * CELL_SIZE + CELL_SIZE // 2
y = MARGIN + i * CELL_SIZE + CELL_SIZE // 2
screen_cells.append((x, y, CELL_SIZE // 2 - 5))
self.animations.append({
'type': 'match',
'cells': screen_cells,
'progress': 0,
'duration': duration,
'start_time': time.time(),
'alpha': 255
})
def add_new_gem(self, cell, duration=0.3):
i, j = cell
x = MARGIN + j * CELL_SIZE + CELL_SIZE // 2
y = MARGIN - CELL_SIZE // 2 # 从上方开始
final_y = MARGIN + i * CELL_SIZE + CELL_SIZE // 2
self.animations.append({
'type': 'new_gem',
'cell': cell,
'start_pos': (x, y),
'end_pos': (x, final_y),
'progress': 0,
'duration': duration,
'start_time': time.time()
})
def update(self):
current_time = time.time()
completed_animations = []
for i, anim in enumerate(self.animations):
elapsed = current_time - anim['start_time']
anim['progress'] = min(elapsed / anim['duration'], 1.0)
# 特殊处理匹配高亮动画
if anim['type'] == 'match':
# 脉冲效果
anim['alpha'] = int(128 + 127 * math.sin(anim['progress'] * math.pi * 4))
if anim['progress'] >= 1.0:
completed_animations.append(i)
# 移除完成的动画(从后往前避免索引问题)
for i in reversed(completed_animations):
self.animations.pop(i)
return len(self.animations) > 0
def draw(self, grid):
for anim in self.animations:
if anim['type'] == 'swap':
# 使用缓动函数使动画更自然
progress = self.ease_in_out(anim['progress'])
for idx, cell in enumerate(anim['cells']):
start_x, start_y = anim['start_pos'][idx]
end_x, end_y = anim['end_pos'][idx]
x = start_x + (end_x - start_x) * progress
y = start_y + (end_y - start_y) * progress
color_idx = grid[cell[0]][cell[1]]
radius = CELL_SIZE // 2 - 5
# 绘制移动中的宝石
pygame.draw.circle(screen, COLORS[color_idx], (int(x), int(y)), radius)
pygame.draw.circle(screen, HIGHLIGHT, (int(x - radius // 3), int(y - radius // 3)), radius // 2)
elif anim['type'] == 'fall':
progress = self.ease_out(anim['progress'])
start_x, start_y = anim['from_pos']
end_x, end_y = anim['to_pos']
x = start_x + (end_x - start_x) * progress
y = start_y + (end_y - start_y) * progress
color_idx = grid[anim['cell'][0]][anim['cell'][1]]
radius = CELL_SIZE // 2 - 5
pygame.draw.circle(screen, COLORS[color_idx], (int(x), int(y)), radius)
pygame.draw.circle(screen, HIGHLIGHT, (int(x - radius // 3), int(y - radius // 3)), radius // 2)
elif anim['type'] == 'match':
for x, y, radius in anim['cells']:
# 绘制高亮环
highlight_surface = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
pygame.draw.circle(highlight_surface, (*MATCH_HIGHLIGHT[:3], anim['alpha']),
(radius, radius), radius, 4)
screen.blit(highlight_surface, (x - radius, y - radius))
elif anim['type'] == 'new_gem':
progress = self.ease_out(anim['progress'])
start_x, start_y = anim['start_pos']
end_x, end_y = anim['end_pos']
x = start_x + (end_x - start_x) * progress
y = start_y + (end_y - start_y) * progress
color_idx = grid[anim['cell'][0]][anim['cell'][1]]
radius = CELL_SIZE // 2 - 5
pygame.draw.circle(screen, COLORS[color_idx], (int(x), int(y)), radius)
pygame.draw.circle(screen, HIGHLIGHT, (int(x - radius // 3), int(y - radius // 3)), radius // 2)
def ease_in_out(self, t):
return 0.5 * (1 - math.cos(t * math.pi))
def ease_out(self, t):
return 1 - (1 - t) * (1 - t)
class Game:
def __init__(self):
self.grid = []
self.selected = None
self.score = 0
self.ai_mode = False
self.ai_thinking = False
self.animations = Animation()
self.state = "playing" # playing, animating, game_over
self.initialize_grid()
def initialize_grid(self):
# 创建初始网格
self.grid = []
for i in range(GRID_SIZE):
row = []
for j in range(GRID_SIZE):
row.append(random.randint(0, len(COLORS) - 1))
self.grid.append(row)
# 确保初始网格没有可消除的组合
while self.find_matches():
self.remove_matches()
self.fill_grid()
def draw(self):
# 绘制背景
screen.fill(BACKGROUND)
# 绘制网格背景
grid_rect = pygame.Rect(
MARGIN, MARGIN,
GRID_SIZE * CELL_SIZE,
GRID_SIZE * CELL_SIZE
)
pygame.draw.rect(screen, GRID_BACKGROUND, grid_rect)
# 绘制网格线
for i in range(GRID_SIZE + 1):
# 垂直线
pygame.draw.line(
screen, GRID_LINE,
(MARGIN + i * CELL_SIZE, MARGIN),
(MARGIN + i * CELL_SIZE, MARGIN + GRID_SIZE * CELL_SIZE),
2
)
# 水平线
pygame.draw.line(
screen, GRID_LINE,
(MARGIN, MARGIN + i * CELL_SIZE),
(MARGIN + GRID_SIZE * CELL_SIZE, MARGIN + i * CELL_SIZE),
2
)
# 绘制静态宝石(不在动画中的)
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
color_idx = self.grid[i][j]
if color_idx >= 0: # 确保是有效颜色索引
# 检查这个宝石是否在动画中
in_animation = False
for anim in self.animations.animations:
if anim['type'] in ['swap', 'fall', 'new_gem']:
if 'cell' in anim and anim['cell'] == (i, j):
in_animation = True
break
elif 'cells' in anim and (i, j) in anim['cells']:
in_animation = True
break
if not in_animation:
x = MARGIN + j * CELL_SIZE + CELL_SIZE // 2
y = MARGIN + i * CELL_SIZE + CELL_SIZE // 2
radius = CELL_SIZE // 2 - 5
# 绘制宝石主体
pygame.draw.circle(screen, COLORS[color_idx], (x, y), radius)
# 绘制高光效果
highlight_radius = radius // 2
pygame.draw.circle(screen, HIGHLIGHT, (x - radius // 3, y - radius // 3), highlight_radius)
# 绘制动画
self.animations.draw(self.grid)
# 绘制选中的宝石
if self.selected and self.state == "playing":
i, j = self.selected
rect = pygame.Rect(
MARGIN + j * CELL_SIZE + 2,
MARGIN + i * CELL_SIZE + 2,
CELL_SIZE - 4,
CELL_SIZE - 4
)
pygame.draw.rect(screen, HIGHLIGHT, rect, 4)
# 绘制分数
score_text = font.render(f"分数: {self.score}", True, TEXT_COLOR)
screen.blit(score_text, (SCREEN_WIDTH - 250, 20))
# 绘制模式指示器
mode_text = font.render(
"模式: AI自动" if self.ai_mode else "模式: 手动",
True, TEXT_COLOR
)
screen.blit(mode_text, (SCREEN_WIDTH - 250, 60))
# 绘制提示
hints = [
"按 A 切换AI模式",
"按 R 重置游戏",
"手动模式下点击宝石进行交换"
]
for i, hint in enumerate(hints):
hint_text = small_font.render(hint, True, TEXT_COLOR)
screen.blit(hint_text, (SCREEN_WIDTH - 350, SCREEN_HEIGHT - 120 + i * 25))
# 如果AI正在思考,显示提示
if self.ai_thinking:
thinking_text = font.render("AI正在思考...", True, TEXT_COLOR)
screen.blit(thinking_text, (SCREEN_WIDTH // 2 - 80, SCREEN_HEIGHT - 40))
# 如果正在播放动画,显示状态
if self.state == "animating":
anim_text = small_font.render("动画播放中...", True, TEXT_COLOR)
screen.blit(anim_text, (SCREEN_WIDTH // 2 - 60, 20))
def get_cell_at_pos(self, pos):
x, y = pos
# 检查是否在网格内
if (x < MARGIN or y < MARGIN or
x >= MARGIN + GRID_SIZE * CELL_SIZE or
y >= MARGIN + GRID_SIZE * CELL_SIZE):
return None
# 计算网格索引
j = (x - MARGIN) // CELL_SIZE
i = (y - MARGIN) // CELL_SIZE
return (i, j)
def select(self, cell):
if not cell or self.state != "playing":
self.selected = None
return
i, j = cell
if not self.selected:
self.selected = (i, j)
else:
# 检查是否相邻
si, sj = self.selected
if ((abs(i - si) == 1 and j == sj) or
(abs(j - sj) == 1 and i == si)):
# 添加交换动画
self.animations.add_swap(self.selected, (i, j))
self.state = "animating"
# 交换宝石(实际交换会在动画完成后进行)
self.swap((si, sj), (i, j))
# 检查是否有匹配
matches = self.find_matches()
if matches:
# 添加匹配高亮动画
match_cells = []
for match in matches:
i1, j1, i2, j2 = match
if i1 == i2: # 水平匹配
for j in range(j1, j2 + 1):
match_cells.append((i1, j))
else: # 垂直匹配
for i in range(i1, i2 + 1):
match_cells.append((i, j1))
self.animations.add_match_highlight(match_cells)
else:
# 如果没有匹配,稍后交换回来
pass
self.selected = None
else:
# 选择新的宝石
self.selected = (i, j)
def swap(self, cell1, cell2):
i1, j1 = cell1
i2, j2 = cell2
self.grid[i1][j1], self.grid[i2][j2] = self.grid[i2][j2], self.grid[i1][j1]
def find_matches(self):
matches = []
# 检查水平匹配
for i in range(GRID_SIZE):
for j in range(GRID_SIZE - 2):
if (self.grid[i][j] == self.grid[i][j + 1] == self.grid[i][j + 2] and
self.grid[i][j] != -1):
# 找到匹配,扩展到所有连续相同宝石
k = j + 3
while k < GRID_SIZE and self.grid[i][k] == self.grid[i][j]:
k += 1
matches.append((i, j, i, k - 1)) # 水平匹配
# 检查垂直匹配
for i in range(GRID_SIZE - 2):
for j in range(GRID_SIZE):
if (self.grid[i][j] == self.grid[i + 1][j] == self.grid[i + 2][j] and
self.grid[i][j] != -1):
# 找到匹配,扩展到所有连续相同宝石
k = i + 3
while k < GRID_SIZE and self.grid[k][j] == self.grid[i][j]:
k += 1
matches.append((i, j, k - 1, j)) # 垂直匹配
return matches
def remove_matches(self):
matches = self.find_matches()
if not matches:
return False
# 标记要移除的宝石
to_remove = [[False for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
for match in matches:
i1, j1, i2, j2 = match
# 水平匹配
if i1 == i2:
for j in range(j1, j2 + 1):
to_remove[i1][j] = True
# 垂直匹配
elif j1 == j2:
for i in range(i1, i2 + 1):
to_remove[i][j1] = True
# 移除标记的宝石并增加分数
removed_count = 0
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
if to_remove[i][j]:
self.grid[i][j] = -1
removed_count += 1
# 每移除一个宝石得10分
self.score += removed_count * 10
return True
def fill_grid(self):
# 宝石下落
falling_gems = []
for j in range(GRID_SIZE):
# 从底部向上移动宝石
empty_spaces = 0
for i in range(GRID_SIZE - 1, -1, -1):
if self.grid[i][j] == -1:
empty_spaces += 1
elif empty_spaces > 0:
# 记录下落动画
falling_gems.append(((i, j), (i + empty_spaces, j)))
self.grid[i + empty_spaces][j] = self.grid[i][j]
self.grid[i][j] = -1
# 添加下落动画
for from_cell, to_cell in falling_gems:
self.animations.add_fall(from_cell, to_cell)
# 填充顶部空位
new_gems = []
for j in range(GRID_SIZE):
for i in range(GRID_SIZE):
if self.grid[i][j] == -1:
self.grid[i][j] = random.randint(0, len(COLORS) - 1)
new_gems.append((i, j))
# 添加新宝石动画
for cell in new_gems:
self.animations.add_new_gem(cell)
# 检查是否有新的匹配
if self.find_matches():
# 添加匹配高亮动画
matches = self.find_matches()
match_cells = []
for match in matches:
i1, j1, i2, j2 = match
if i1 == i2: # 水平匹配
for j in range(j1, j2 + 1):
match_cells.append((i1, j))
else: # 垂直匹配
for i in range(i1, i2 + 1):
match_cells.append((i, j1))
self.animations.add_match_highlight(match_cells)
def find_best_move(self):
"""AI: 寻找最佳移动"""
best_score = 0
best_move = None
# 检查所有可能的交换
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
# 检查右侧交换
if j < GRID_SIZE - 1:
# 尝试交换
self.swap((i, j), (i, j + 1))
# 评估这个交换
matches = self.find_matches()
if matches:
# 计算这个交换的得分潜力
score_potential = 0
for match in matches:
i1, j1, i2, j2 = match
if i1 == i2: # 水平匹配
score_potential += (j2 - j1 + 1) * 10
else: # 垂直匹配
score_potential += (i2 - i1 + 1) * 10
# 如果这个交换更好,更新最佳移动
if score_potential > best_score:
best_score = score_potential
best_move = ((i, j), (i, j + 1))
# 交换回来
self.swap((i, j), (i, j + 1))
# 检查下方交换
if i < GRID_SIZE - 1:
# 尝试交换
self.swap((i, j), (i + 1, j))
# 评估这个交换
matches = self.find_matches()
if matches:
# 计算这个交换的得分潜力
score_potential = 0
for match in matches:
i1, j1, i2, j2 = match
if i1 == i2: # 水平匹配
score_potential += (j2 - j1 + 1) * 10
else: # 垂直匹配
score_potential += (i2 - i1 + 1) * 10
# 如果这个交换更好,更新最佳移动
if score_potential > best_score:
best_score = score_potential
best_move = ((i, j), (i + 1, j))
# 交换回来
self.swap((i, j), (i + 1, j))
return best_move
def ai_play(self):
"""AI: 执行一步移动"""
if self.ai_thinking or self.state != "playing":
return
self.ai_thinking = True
# 寻找最佳移动
move = self.find_best_move()
if move:
# 添加交换动画
self.animations.add_swap(move[0], move[1])
self.state = "animating"
# 执行移动
self.swap(move[0], move[1])
# 检查是否有匹配
matches = self.find_matches()
if matches:
# 添加匹配高亮动画
match_cells = []
for match in matches:
i1, j1, i2, j2 = match
if i1 == i2: # 水平匹配
for j in range(j1, j2 + 1):
match_cells.append((i1, j))
else: # 垂直匹配
for i in range(i1, i2 + 1):
match_cells.append((i, j1))
self.animations.add_match_highlight(match_cells)
self.ai_thinking = False
def update(self):
# 更新动画
has_animations = self.animations.update()
if has_animations:
self.state = "animating"
else:
if self.state == "animating":
# 动画结束,检查是否需要进一步处理
self.state = "playing"
# 检查是否有匹配需要消除
if self.remove_matches():
self.fill_grid()
self.state = "animating" # 继续播放填充动画
else:
# 检查交换后是否有匹配,如果没有匹配则交换回来
if not self.find_matches():
# 查找最近的一次交换
for anim in reversed(self.animations.animations):
if anim['type'] == 'swap' and anim['progress'] >= 1.0:
# 交换回来
self.swap(anim['cells'][0], anim['cells'][1])
break
def main():
game = Game()
last_ai_move_time = 0
ai_move_delay = 2.0 # AI每次移动的延迟(秒)
# 游戏主循环
while True:
current_time = time.time()
# 处理事件
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_a:
# 切换AI模式
game.ai_mode = not game.ai_mode
game.selected = None
elif event.key == K_r:
# 重置游戏
game = Game()
elif event.type == MOUSEBUTTONDOWN and not game.ai_mode and game.state == "playing":
# 手动模式下的鼠标点击
if event.button == 1: # 左键
cell = game.get_cell_at_pos(event.pos)
game.select(cell)
# 更新游戏状态
game.update()
# AI模式下的自动移动
if (game.ai_mode and
current_time - last_ai_move_time > ai_move_delay and
game.state == "playing" and
not game.ai_thinking):
game.ai_play()
last_ai_move_time = current_time
# 绘制游戏
game.draw()
# 更新显示
pygame.display.flip()
clock.tick(FPS)
if __name__ == "__main__":
main()