下面是一个使用 **Python + Pygame** 实现的简易“动物连连看”小游戏(简化版),适合初学者理解核心逻辑。它具备以下功能:
- 6×6 的方格棋盘(共36个格子)
- 随机生成18对动物图标(如 🐶🐱🐰🦊🐼🐻 等 Unicode 动物符号,无需外部图片资源)
- 玩家点击两个格子,若图标相同且满足“可连接”规则(本简化版采用**最多一个拐角、路径无障碍+直线段无遮挡**的轻量判断),则消除;否则高亮提示后恢复
- 计时器 + 步数统计 + 成功通关提示
> ✅ 优势:纯 Python 标准库 + `pygame`(仅需 `pip install pygame`),无依赖图片/音频,开箱即用。
---
### ✅ 完整可运行代码(含详细注释)
```python
import pygame
import sys
import random
import math
# 初始化 Pygame
pygame.init()
# 常量定义
WIDTH, HEIGHT = 720, 800
GRID_SIZE = 6
CELL_SIZE = 80
MARGIN = 20
TOP_BAR_HEIGHT = 80
BOARD_X = (WIDTH - GRID_SIZE * CELL_SIZE) // 2
BOARD_Y = TOP_BAR_HEIGHT + 20
# 颜色
BG_COLOR = (240, 248, 255) # Alice Blue
GRID_COLOR = (173, 216, 230) # Light Blue
SELECTED_COLOR = (255, 215, 0) # Gold
MATCH_COLOR = (50, 205, 50) # Lime Green
TEXT_COLOR = (30, 30, 30)
HINT_COLOR = (220, 20, 60) # Crimson
# 动物 Unicode 符号(18种,每种2个 → 共36个)
ANIMALS = [
"🐶", "🐱", "🐰", "🦊", "🐼", "🐻", "🐨", "🐯", "🦁", "🐮",
"🐷", "🐸", "🐵", "🐔", "🐧", "🐦", "🦄", "🐴"
]
# 字体
font_large = pygame.font.SysFont("simhei", 28, bold=True)
font_medium = pygame.font.SysFont("simhei", 22)
font_small = pygame.font.SysFont("simhei", 18)
# 创建窗口
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("🐾 动物连连看(简易版)")
# 游戏状态类
class Game:
def __init__(self):
self.reset()
def reset(self):
# 生成配对动物列表并打乱
tiles = ANIMALS * 2
random.shuffle(tiles)
self.board = [tiles[i*GRID_SIZE:(i+1)*GRID_SIZE] for i in range(GRID_SIZE)]
self.selected = [] # 存储已选中的坐标,如 [(r1,c1), (r2,c2)]
self.matched = [[False]*GRID_SIZE for _ in range(GRID_SIZE)] # 标记已消除
self.moves = 0
self.start_time = pygame.time.get_ticks()
self.game_over = False
self.message = ""
self.message_timer = 0
# 判断两点是否「可连接」(简化:只允许 0 或 1 个直角转折,且路径上所有格子为空或为端点)
def can_connect(self, r1, c1, r2, c2):
if self.matched[r1][c1] or self.matched[r2][c2]:
return False
if (r1, c1) == (r2, c2):
return False
# 检查是否同行/同列(直线连接)
if r1 == r2:
step = 1 if c1 < c2 else -1
for c in range(c1 + step, c2, step):
if self.matched[r1][c]:
break
else:
return True # 直线通
if c1 == c2:
step = 1 if r1 < r2 else -1
for r in range(r1 + step, r2, step):
if self.matched[r][c1]:
break
else:
return True # 直线通
# 尝试一个拐点:(r1, c2) 或 (r2, c1)
for br, bc in [(r1, c2), (r2, c1)]:
if 0 <= br < GRID_SIZE and 0 <= bc < GRID_SIZE:
# 检查 (r1,c1) → (br,bc) → (r2,c2) 是否都通
seg1_ok = True
seg2_ok = True
# r1→br 垂直段(c1==bc)
if c1 == bc:
step = 1 if r1 < br else -1
for r in range(r1 + step, br, step):
if self.matched[r][c1]:
seg1_ok = False
break
else:
seg1_ok = False
# br→r2 垂直段(bc==c2)
if bc == c2:
step = 1 if br < r2 else -1
for r in range(br + step, r2, step):
if self.matched[r][c2]:
seg2_ok = False
break
else:
seg2_ok = False
if seg1_ok and seg2_ok:
return True
# 同理检查 (r1,c1) → (r2,c1) → (r2,c2)(水平+垂直)
seg1_ok = True
seg2_ok = True
if r1 == r2: # 已在上面处理过,跳过
pass
elif c1 == c1 and r1 != r2: # (r1,c1)->(r2,c1) 垂直
step = 1 if r1 < r2 else -1
for r in range(r1 + step, r2, step):
if self.matched[r][c1]:
seg1_ok = False
break
# (r2,c1)->(r2,c2) 水平
if seg1_ok and c1 != c2:
step = 1 if c1 < c2 else -1
for c in range(c1 + step, c2, step):
if self.matched[r2][c]:
seg2_ok = False
break
if seg1_ok and seg2_ok:
return True
return False
# 处理点击事件
def handle_click(self, pos):
x, y = pos
# 只响应棋盘区域点击
if not (BOARD_X <= x < BOARD_X + GRID_SIZE*CELL_SIZE and
BOARD_Y <= y < BOARD_Y + GRID_SIZE*CELL_SIZE):
return
col = (x - BOARD_X) // CELL_SIZE
row = (y - BOARD_Y) // CELL_SIZE
if not (0 <= row < GRID_SIZE and 0 <= col < GRID_SIZE):
return
if self.matched[row][col]:
return # 已匹配,忽略
if len(self.selected) == 0:
self.selected.append((row, col))
elif len(self.selected) == 1:
r1, c1 = self.selected[0]
r2, c2 = row, col
if (r1, c1) == (r2, c2):
self.selected = []
return
self.moves += 1
if self.board[r1][c1] == self.board[r2][c2] and self.can_connect(r1, c1, r2, c2):
self.matched[r1][c1] = True
self.matched[r2][c2] = True
self.selected = []
self.message = "✅ 匹配成功!"
self.message_timer = 60 # 显示1秒
# 检查是否全部匹配
if all(self.matched[r][c] for r in range(GRID_SIZE) for c in range(GRID_SIZE)):
self.game_over = True
self.message = "🎉 恭喜通关!"
self.message_timer = 180
else:
self.selected.append((row, col))
self.message = "❌ 不匹配或不可连!"
self.message_timer = 60
else: # 有2个已选,新点替换第一个
self.selected = [(row, col)]
# 绘制函数
def draw(self, screen):
# 背景
screen.fill(BG_COLOR)
# 顶部信息栏
pygame.draw.rect(screen, GRID_COLOR, (0, 0, WIDTH, TOP_BAR_HEIGHT))
time_sec = (pygame.time.get_ticks() - self.start_time) // 1000
moves_text = font_medium.render(f"步数: {self.moves}", True, TEXT_COLOR)
time_text = font_medium.render(f"时间: {time_sec}s", True, TEXT_COLOR)
screen.blit(moves_text, (20, 25))
screen.blit(time_text, (WIDTH - time_text.get_width() - 20, 25))
# 绘制棋盘背景
board_rect = pygame.Rect(BOARD_X, BOARD_Y, GRID_SIZE*CELL_SIZE, GRID_SIZE*CELL_SIZE)
pygame.draw.rect(screen, (200, 230, 255), board_rect)
pygame.draw.rect(screen, (100, 149, 237), board_rect, 3)
# 绘制每个格子
for r in range(GRID_SIZE):
for c in range(GRID_SIZE):
x = BOARD_X + c * CELL_SIZE
y = BOARD_Y + r * CELL_SIZE
rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
# 背景色:已匹配 → 浅绿;选中 → 金色;默认 → 白色
if self.matched[r][c]:
color = (220, 255, 220)
elif (r, c) in self.selected:
color = SELECTED_COLOR
else:
color = (255, 255, 255)
pygame.draw.rect(screen, color, rect)
pygame.draw.rect(screen, (180, 180, 180), rect, 1)
# 绘制动物文字(居中)
if not self.matched[r][c]:
text = font_large.render(self.board[r][c], True, TEXT_COLOR)
tx = x + (CELL_SIZE - text.get_width()) // 2
ty = y + (CELL_SIZE - text.get_height()) // 2
screen.blit(text, (tx, ty))
# 绘制提示消息
if self.message_timer > 0:
msg_surf = font_medium.render(self.message, True, HINT_COLOR)
screen.blit(msg_surf, (WIDTH//2 - msg_surf.get_width()//2, HEIGHT - 60))
self.message_timer -= 1
# 游戏结束弹窗
if self.game_over:
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
screen.blit(overlay, (0, 0))
win_text = font_large.render("🎊 游戏胜利! 🎊", True, (255, 215, 0))
restart_text = font_medium.render("按 R 键重新开始", True, (255, 255, 255))
screen.blit(win_text, (WIDTH//2 - win_text.get_width()//2, HEIGHT//2 - 50))
screen.blit(restart_text, (WIDTH//2 - restart_text.get_width()//2, HEIGHT//2 + 10))
# 主游戏循环
def main():
clock = pygame.time.Clock()
game = Game()
running = True
while running:
dt = clock.tick(60) # 60 FPS
# 事件处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # 左键
game.handle_click(event.pos)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r and game.game_over:
game.reset()
# 绘制
game.draw(screen)
# 更新屏幕
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
```
---
### 🔍 代码解释说明:
| 模块 | 说明 |
|------|------|
| **初始化与常量** | 设置窗口尺寸、网格参数、颜色、字体、动物符号池(Unicode 表情,跨平台兼容性好) |
| **`can_connect()` 方法** | 核心逻辑!实现「单折线连通」判断:<br>① 同行/同列直线通(中间无障碍)<br>② 尝试两个潜在拐点 `(r1,c2)` 和 `(r2,c1)`,分两段验证是否都通(每段必须是直线且无障碍)<br>⚠️ 注意:此为**简化版算法**,未实现 BFS 全路径搜索,但足够支撑小棋盘趣味性 |
| **状态管理** | `selected` 列表控制点击节奏(单击→选中,再点→配对/取消);`matched` 二维布尔数组标记已消除位置 |
| **UI 细节** | 顶部状态栏显示步数 & 时间;选中高亮金框;匹配成功绿色背景;失败红色提示;通关半透明弹窗 |
| **重玩机制** | 游戏结束后按 `R` 键调用 `game.reset()` 重新洗牌开局 |
> 💡 提示:该版本**不依赖任何图片资源**,全部使用系统支持的 Unicode Emoji 渲染,确保 Windows/macOS/Linux 均可运行(需系统字体支持 emoji,现代系统基本都支持)。
---
### 🧩 进阶优化方向(供你后续拓展):
- ✅ 添加音效(匹配成功/失败音)
- ✅ 支持撤销上一步(记录操作栈)
- ✅ 难度选择(4×4 / 6×6 / 8×8)
- ✅ 连通算法升级为「任意拐点 ≤ 2」或 BFS 最短路径
- ✅ 添加计分系统(时间奖励 + 步数惩罚)
- ✅ 导出为独立 `.exe`(用 `pyinstaller`)
---