把 4×4 共 16 张卡片随机打乱,点击翻开两张,图案相同就消掉,不同就翻回去。考验记忆力的同时,练会"状态机":翻牌前、翻一张、翻两张、配对成功、配对失败。
1. 卡片数据与洗牌
16 张卡片 = 8 对图案。用数字 0~7 各出现两次表示图案,洗牌用 random.shuffle。每张卡片记录:图案、是否已翻开、是否已配对:
import random, pygame
COLS = ROWS = 4
N = COLS * ROWS
pairs = list(range(N // 2)) * 2 # 0~7 各两次
random.shuffle(pairs) # 洗牌
state = ['closed'] * N # closed / open / matched
2. 点击与翻开
鼠标点击换算成卡片下标,只有"关着的卡片"能翻开。翻开第一张记录位置,翻开第二张做判断:
flipped = -1 # 当前翻开的第一张下标
def on_click(card):
global flipped
if state[card] != 'closed':
return
if flipped == -1:
state[card] = 'open'
flipped = card
else:
state[card] = 'open'
if pairs[flipped] == pairs[card]: # 配对成功
state[flipped] = state[card] = 'matched'
else: # 失败:短暂展示后翻回
state[flipped] = state[card] = 'closed'
flipped = -1
3. 绘制卡片
用 pygame.Rect 画卡片:关着的画背面(纯色+问号),翻开的画图案(不同颜色方块)。配对成功的画半透明,表示已消除:
CELL = 100
GAP = 12
W = H = COLS * CELL + (COLS + 1) * GAP
screen = pygame.display.set_mode((W, H))
def draw():
screen.fill((30, 30, 45))
for i in range(N):
x = GAP + (i % COLS) * (CELL + GAP)
y = GAP + (i // COLS) * (CELL + GAP)
rect = (x, y, CELL, CELL)
if state[i] == 'closed':
pygame.draw.rect(screen, (70, 90, 140), rect, border_radius=10)
elif state[i] == 'open':
pygame.draw.rect(screen, (220, 180, 90), rect, border_radius=10)
color = [(220,60,60),(60,160,220),(90,200,90),(220,150,50),
(180,90,220),(60,200,200),(230,90,120),(160,160,60)][pairs[i]]
pygame.draw.circle(screen, color, (x+CELL//2, y+CELL//2), 22)
else:
pygame.draw.rect(screen, (60, 200, 120), rect, border_radius=10)
pygame.display.flip()
4. 完整代码
import random, pygame
COLS = ROWS = 4
N = COLS * ROWS
CELL = 100
GAP = 12
W = H = COLS * CELL + (COLS + 1) * GAP
pygame.init()
screen = pygame.display.set_mode((W, H))
pygame.display.set_caption('记忆翻牌')
clock = pygame.time.Clock()
pairs = list(range(N // 2)) * 2
random.shuffle(pairs)
state = ['closed'] * N
flipped = -1
moves = 0
won = False
def draw():
screen.fill((30, 30, 45))
for i in range(N):
x = GAP + (i % COLS) * (CELL + GAP)
y = GAP + (i // COLS) * (CELL + GAP)
rect = (x, y, CELL, CELL)
if state[i] == 'closed':
pygame.draw.rect(screen, (70, 90, 140), rect, border_radius=10)
elif state[i] == 'open':
pygame.draw.rect(screen, (220, 180, 90), rect, border_radius=10)
color = [(220,60,60),(60,160,220),(90,200,90),(220,150,50),
(180,90,220),(60,200,200),(230,90,120),(160,160,60)][pairs[i]]
pygame.draw.circle(screen, color, (x+CELL//2, y+CELL//2), 22)
else:
pygame.draw.rect(screen, (60, 200, 120), rect, border_radius=10)
pygame.display.set_caption('记忆翻牌 · 步数 %d' % moves)
pygame.display.flip()
while not won:
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit(); raise SystemExit
if e.type == pygame.MOUSEBUTTONDOWN and e.button == 1:
mx, my = pygame.mouse.get_pos()
c = (my - GAP) // (CELL + GAP)
r = (mx - GAP) // (CELL + GAP)
if 0 <= c < COLS and 0 <= r < ROWS:
card = c * COLS + r
if state[card] == 'closed':
moves += 1
if flipped == -1:
state[card] = 'open'
flipped = card
else:
state[card] = 'open'
if pairs[flipped] == pairs[card]:
state[flipped] = state[card] = 'matched'
else:
state[flipped] = state[card] = 'closed'
flipped = -1
won = all(s == 'matched' for s in state)
draw()
clock.tick(30)
print('通关!用了 %d 步' % moves)
5. 运行与常见问题
保存为 memory.py,python3 memory.py 运行。常见问题:①点同一张卡算一次——翻第一张后,判断里要先排除当前卡片本身;②图案太少/太多——pairs = list(range(N//2)) * 2 决定了图案数,改成 6×6 就是 18 对;③想加难度——限定步数或加计时。
💡 改进方向:①翻牌动画(缩放/翻转);②计时与最佳纪录;③图案换成 emoji 或图片;④配对成功音效;⑤多难度棋盘。