Tkinter记忆游戏:第二次点击的Tile无法翻转显示求助
Tkinter记忆游戏第二次翻牌显示异常修复
问题核心
第一次点击牌可正常翻出图片,但第二次点击时,覆盖的白色矩形无法下移显示图片;程序能识别第二次点击(不匹配时会自动翻回所有牌),仅存在显示层面的异常。
问题根源
- 全局变量引发状态混乱:使用全局
list存储选中牌,在事件驱动的Tkinter环境中,全局变量容易导致不可预期的状态冲突。 - 阻塞主事件循环:
time.sleep(1.0)会卡住Tkinter主循环,导致第二次翻牌的界面更新被延迟,直到sleep结束才执行,此时已触发翻回逻辑,用户看不到第二次翻牌效果。 - 牌状态存储错误:循环创建牌时,
self.id被反复覆盖,导致self.imageCollection仅记录最后一个牌的id,无法正确跟踪每个牌的翻牌状态。 - 点击逻辑不严谨:未判断当前点击的牌是否已翻开,可能出现重复点击同一牌的无效操作;
find_overlapping返回的item顺序不稳定,可能导致层级操作对象错误。
修复方案
1. 替换全局变量为实例变量
将存储选中牌的list改为类的实例变量self.selected_tiles,避免全局状态干扰。
2. 用after()替代time.sleep()
Tkinter的after()方法可在指定时间后执行回调,不会阻塞主事件循环,保证界面实时更新。
3. 正确存储每个牌的状态
创建牌时,为每个图片和对应的矩形建立关联,将两者的id都存入self.imageCollection,同时记录翻牌状态。
4. 完善点击逻辑
- 点击时先判断当前牌是否已翻开,避免无效操作。
- 从
find_overlapping结果中准确区分图片和矩形对象,确保层级操作正确。
修改后的完整代码
import tkinter as tk import random import PIL.Image as Image import PIL.ImageTk as ImageTk class MemoryGame: def __init__(self): # 初始化窗口 self.window = tk.Tk() self.window.title("Sea Life Memory Game") self.window.minsize(590, 600) self.window.maxsize(590, 600) # 设置主画布 self.canvas = tk.Canvas(self.window, bg="lightblue", bd=0, highlightthickness=0, width=590, height=600) self.canvas.grid(row=0, column=0) self.canvas.bind("<Button-1>", self.chooseTile) # 牌坐标与图片选择,打乱坐标顺序 coordinates = [(5,30,105,130), (5,160,105,260), (5,290,105,390), (5,420,105,520), (125,30,225,130), (125,160,225,260), (125,290,225,390), (125,420,225,520), (245,30,345,130), (245,160,345,260), (245,290,345,390), (245,420,345,520), (365,30,465,130), (365,160,465,260), (365,290,465,390), (365,420,465,520), (485,30,585,130), (485,160,585,260), (485,290,585,390), (485,420,585,520)] imageChoices = ['cropped images/001-turtle.png','cropped images/007-blowfish.png', 'cropped images/010-jellyfish.png','cropped images/011-starfish.png', 'cropped images/018-lobster.png','cropped images/028-fish.png', 'cropped images/033-walrus.png','cropped images/042-goldfish.png', 'cropped images/045-seal.png','cropped images/046-penguin.png'] random.shuffle(coordinates) # 绘制标题 self.canvas.create_text(295, 15, text="Sea Life Memory Game!", anchor="center", fill="white", font="Times 24 bold") # 初始化选中牌列表与牌状态字典 self.selected_tiles = [] self.imageCollection = {} # 循环创建所有牌(图片+覆盖矩形) coordinateCount = 0 for img_path in imageChoices: # 加载图片 img = ImageTk.PhotoImage(Image.open(img_path)) # 创建第一张牌 x1, y1, x2, y2 = coordinates[coordinateCount] img_id = self.canvas.create_image(x1, y1, anchor="nw", image=img) rect_id = self.canvas.create_rectangle(x1, y1, x2, y2, fill="white", outline="white") # 存储牌状态:图片id、矩形id、是否翻面 self.imageCollection[img_id] = {"rect_id": rect_id, "faceDown": True, "img": img} coordinateCount += 1 # 创建第二张相同的牌 x1, y1, x2, y2 = coordinates[coordinateCount] img_id2 = self.canvas.create_image(x1, y1, anchor="nw", image=img) rect_id2 = self.canvas.create_rectangle(x1, y1, x2, y2, fill="white", outline="white") self.imageCollection[img_id2] = {"rect_id": rect_id2, "faceDown": True, "img": img} coordinateCount += 1 # 绘制说明文字 self.canvas.create_text(295, 550, text="Find all the pairs as fast as possible.", fill="white", font="Times 18", anchor="center") self.canvas.create_text(295, 570, text="Click on a card to turn it over and find the same matching card.", fill="white", font="Times 18", anchor="center") def run(self): self.window.mainloop() def chooseTile(self, event): # 获取点击位置的所有对象 items = self.canvas.find_overlapping(event.x-5, event.y-5, event.x+5, event.y+5) # 找到对应的图片id(排除文字、矩形等其他对象) img_id = None for item in items: if item in self.imageCollection: img_id = item break if not img_id: return # 判断当前牌是否已翻开,或是否已选了两张牌 tile_info = self.imageCollection[img_id] if not tile_info["faceDown"] or len(self.selected_tiles) >= 2: return # 翻开当前牌:将矩形移到图片下方 self.canvas.tag_lower(tile_info["rect_id"]) tile_info["faceDown"] = False self.selected_tiles.append(img_id) # 选满两张牌时判断是否匹配 if len(self.selected_tiles) == 2: tile1 = self.imageCollection[self.selected_tiles[0]] tile2 = self.imageCollection[self.selected_tiles[1]] # 图片对象相同则匹配成功,清空选中列表 if tile1["img"] == tile2["img"]: self.selected_tiles.clear() else: # 1秒后翻回两张牌,用after避免阻塞主循环 def flip_back(): self.canvas.tag_raise(tile1["rect_id"]) self.canvas.tag_raise(tile2["rect_id"]) tile1["faceDown"] = True tile2["faceDown"] = True self.selected_tiles.clear() self.window.after(1000, flip_back) # 启动游戏 game = MemoryGame() game.run()
关键修改说明
- 实例变量替代全局变量:用
self.selected_tiles存储选中的牌,每个游戏实例独立维护状态。 after()实现延迟翻回:避免time.sleep()阻塞界面更新,保证第二次翻牌的视觉效果正常显示。- 完善牌状态存储:每个牌的图片id、矩形id、翻面状态都被正确记录,不会因循环覆盖丢失数据。
- 严谨的点击判断:过滤无效点击(已翻开的牌、非牌区域),确保每次操作只处理有效牌。
内容的提问来源于stack exchange,提问作者Stephanie Miles
相关产品推荐
相关产品推荐

