SQLite RTree空间索引逻辑漏洞剖析与防御性编码实践
2026/10/10 12:47:03
""" 开心消消乐 - Python 重制版(无限全屏消除) 运行: python xiaoxiaole.py 依赖: pip install pygame 操作: 鼠标点击交换相邻方块 | R 重开 | 空格 全屏消除 | ESC 退出 """ import os import sys import random import math import pygame # ==================== 配置 ==================== COLS = 8 ROWS = 8 CELL = 64 PAD = 24 TOP = 100 BOTTOM = 70 BOARD_W = COLS * CELL BOARD_H = ROWS * CELL W = BOARD_W + PAD * 2 H = TOP + BOARD_H + BOTTOM FPS = 60 # 动画速度 SWAP_FRAMES = 6 CLEAR_FRAMES = 7 FALL_FRAMES = 9 INTERP = 0.55 FALL_INTERP = 0.65 # 全屏消除相关 SHAKE_DURATION = 30 # 动物: (名字, 主色, 深色, 浅色, emoji) ANIMALS = [ ("熊猫", (245, 245, 245), (200, 200, 200), (255, 255, 255), "🐼"), ("狐狸", (255, 150, 70), (210, 110, 40), (255, 190, 120), "🦊"), ("青蛙", (110, 220, 110), (70, 170, 70), (160, 240, 160), "🐸"), ("小鸡", (255, 220, 90), (210, 170, 50), (255, 240, 150), "🐤"), ("兔子", (255, 180, 220), (210, 130, 180), (255, 210, 240), "🐰"), ("猫咪", (180, 150, 255), (130, 100, 220), (210, 190, 255), "🐱"), ] TYPE_COUNT = len(ANIMALS) # ==================== 字体 ==================== def find_font(candidates): for p in candidates: if os.path.exists(p): return p return None def get_cn_font(size): path = find_font([ "C:/Windows/Fonts/msyh.ttc", "C:/Windows/Fonts/simhei.ttf", "C:/Windows/Fonts/simsun.ttc", "/System/Library/Fonts/PingFang.ttc", "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", ]) if path: return pygame.font.Font(path, size) return pygame.font.Font(None, size) def get_emoji_font(size): path = find_font([ "C:/Windows/Fonts/seguiemj.ttf", "/System/Library/Fonts/Apple Color Emoji.ttc", "/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", ]) if path: try: return pygame.font.Font(path, size) except Exception: return None return None # ==================== 方块 ==================== class Tile: __slots__ = ("type", "col", "row", "x", "y", "sx", "sy", "scale", "target_scale", "alpha", "target_alpha", "selected", "falling") def __init__(self, type_idx, col, row, start_y=None): self.type = type_idx self.col = col self.row = row self.x = col * CELL self.y = row * CELL if start_y is None else start_y self.sx = col * CELL self.sy = row * CELL self.scale = 1.0 self.target_scale = 1.0 self.alpha = 255.0 self.target_alpha = 255.0 self.selected = False self.falling = False def update(self): interp = FALL_INTERP if self.falling else INTERP self.x += (self.sx - self.x) * interp self.y += (self.sy - self.y) * interp if abs(self.sx - self.x) < 0.6: self.x = self.sx if abs(self.sy - self.y) < 0.6: self.y = self.sy self.scale += (self.target_scale - self.scale) * interp if abs(self.target_scale - self.scale) < 0.02: self.scale = self.target_scale self.alpha += (self.target_alpha - self.alpha) * interp if abs(self.target_alpha - self.alpha) < 3: self.alpha = self.target_alpha if self.falling and abs(self.sy - self.y) < 1.5: self.falling = False # ==================== 粒子 ==================== class Particle: __slots__ = ("x", "y", "vx", "vy", "life", "color", "size", "gravity") def __init__(self, x, y, color, speed_mult=1.0, size_mult=1.0): angle = random.random() * math.pi * 2 speed = (random.random() * 5 + 1.5) * speed_mult self.x = x self.y = y self.vx = math.cos(angle) * speed self.vy = math.sin(angle) * speed - 1.5 self.life = 1.0 self.color = color self.size = (random.random() * 4 + 2) * size_mult self.gravity = 0.22 def update(self): self.x += self.vx self.y += self.vy self.vy += self.gravity self.vx *= 0.95 self.life -= 0.045 def draw(self, surf): if self.life <= 0: return alpha = int(self.life * 255) r = int(self.size * self.life) if r < 1: return s = pygame.Surface((r * 2, r * 2), pygame.SRCALPHA) pygame.draw.circle(s, (*self.color, alpha), (r, r), r) surf.blit(s, (self.x - r, self.y - r)) # ==================== 冲击波 ==================== class Shockwave: __slots__ = ("x", "y", "radius", "max_radius", "life", "color") def __init__(self, x, y, max_radius, color): self.x = x self.y = y self.radius = 5 self.max_radius = max_radius self.life = 1.0 self.color = color def update(self): self.radius += (self.max_radius - self.radius) * 0.18 self.life -= 0.035 def draw(self, surf): if self.life <= 0: return alpha = int(self.life * 180) r = int(self.radius) if r < 2: return s = pygame.Surface((r * 2, r * 2), pygame.SRCALPHA) w = max(2, int(6 * self.life)) pygame.draw.circle(s, (*self.color, alpha), (r, r), r, w) surf.blit(s, (self.x - r, self.y - r)) # ==================== 飘分 ==================== class FloatText: __slots__ = ("text", "x", "y", "vy", "life", "color", "font") def __init__(self, text, x, y, color, font): self.text = text self.x = x self.y = y self.vy = -2.0 self.life = 1.0 self.color = color self.font = font def update(self): self.y += self.vy self.vy *= 0.94 self.life -= 0.04 def draw(self, surf): if self.life <= 0: return alpha = int(self.life * 255) s = self.font.render(self.text, True, self.color) s.set_alpha(alpha) rect = s.get_rect(center=(self.x, self.y)) surf.blit(s, rect) # ==================== 按钮 ==================== class Button: def __init__(self, x, y, w, h, text, color, hover_color, text_color=(255, 255, 255)): self.rect = pygame.Rect(x, y, w, h) self.text = text self.color = color self.hover_color = hover_color self.text_color = text_color self.hovered = False self.pulse = 0.0 def update(self, mx, my): self.hovered = self.rect.collidepoint(mx, my) self.pulse += 0.08 def draw(self, surf, font): # 永远脉冲浮动 pulse_offset = int(3 * math.sin(self.pulse)) draw_rect = self.rect.move(0, pulse_offset) if self.hovered: color = self.hover_color glow = pygame.Surface((draw_rect.w + 20, draw_rect.h + 20), pygame.SRCALPHA) glow_color = (*self.hover_color, 100) pygame.draw.rect(glow, glow_color, (0, 0, draw_rect.w + 20, draw_rect.h + 20), border_radius=16) surf.blit(glow, (draw_rect.x - 10, draw_rect.y - 10)) else: color = self.color # 主体 pygame.draw.rect(surf, color, draw_rect, border_radius=12) # 高光 hl_rect = pygame.Rect(draw_rect.x + 4, draw_rect.y + 3, draw_rect.w - 8, draw_rect.h // 3) hl = pygame.Surface((hl_rect.w, hl_rect.h), pygame.SRCALPHA) pygame.draw.rect(hl, (255, 255, 255, 80), (0, 0, hl_rect.w, hl_rect.h), border_radius=8) surf.blit(hl, hl_rect) # 边框 pygame.draw.rect(surf, (255, 255, 255, 200), draw_rect, 2, border_radius=12) # 文字 s = font.render(self.text, True, self.text_color) surf.blit(s, (draw_rect.centerx - s.get_width() // 2, draw_rect.centery - s.get_height() // 2 + 1)) # ==================== 游戏 ==================== class Game: def __init__(self): pygame.init() self.screen = pygame.display.set_mode((W, H)) pygame.display.set_caption("开心消消乐") self.clock = pygame.time.Clock() self.font_sm = get_cn_font(15) self.font_md = get_cn_font(20) self.font_lg = get_cn_font(28) self.font_xl = get_cn_font(38) self.font_score = get_cn_font(22) self.font_btn = get_cn_font(18) self.emoji_font = get_emoji_font(40) self._emoji_cache = {} self._build_emoji_cache() self._static_bg = None self._build_static_bg() # 全屏消除按钮(永远可用) btn_w, btn_h = 180, 44 self.bomb_button = Button( (W - btn_w) // 2, H - BOTTOM + 10, btn_w, btn_h, "💥 全屏消除", (255, 100, 130), (255, 140, 160), ) self.reset() def _build_emoji_cache(self): if not self.emoji_font: return target = int(CELL * 0.62) for i in range(len(ANIMALS)): emoji = ANIMALS[i][4] s = self.emoji_font.render(emoji, True, (0, 0, 0)) sw, sh = s.get_size() if sw > target or sh > target: ratio = target / max(sw, sh) s = pygame.transform.smoothscale(s, (int(sw * ratio), int(sh * ratio))) self._emoji_cache[i] = s def _build_static_bg(self): surf = pygame.Surface((W, H)) for y in range(H): ratio = y / H r = int(255 - 20 * ratio) g = int(240 - 30 * ratio) b = int(225 - 20 * ratio) pygame.draw.line(surf, (r, g, b), (0, y), (W, y)) board_rect = pygame.Rect(PAD - 6, TOP - 6, BOARD_W + 12, BOARD_H + 12) pygame.draw.rect(surf, (245, 235, 225), board_rect, border_radius=18) pygame.draw.rect(surf, (230, 210, 190), board_rect, 3, border_radius=18) for r in range(ROWS): for c in range(COLS): gx = c * CELL + PAD gy = r * CELL + TOP color = (252, 248, 240) if (c + r) % 2 == 0 else (245, 238, 225) pygame.draw.rect(surf, color, (gx, gy, CELL, CELL)) self._static_bg = surf # ---------- 初始化 ---------- def reset(self): self.grid = [[None] * COLS for _ in range(ROWS)] self.score = 0 self.combo = 0 self.selected = None self.state = "idle" self.timer = 0 self.swap_pair = None self.clearing = [] self.particles = [] self.floats = [] self.shockwaves = [] self.shake = 0 self.bomb_flash = 0 self.bomb_count = 0 # ⚡ 记录用了多少次,纯统计 self.hint_timer = 0 for r in range(ROWS): for c in range(COLS): self.grid[r][c] = Tile(self._safe_type(c, r), c, r) guard = 0 while not self._has_move() and guard < 50: self._shuffle() guard += 1 def _safe_type(self, c, r): avoid = set() if c >= 2 and self.grid[r][c-1] and self.grid[r][c-2]: if self.grid[r][c-1].type == self.grid[r][c-2].type: avoid.add(self.grid[r][c-1].type) if r >= 2 and self.grid[r-1][c] and self.grid[r-2][c]: if self.grid[r-1][c].type == self.grid[r-2][c].type: avoid.add(self.grid[r-1][c].type) choices = [i for i in range(TYPE_COUNT) if i not in avoid] return random.choice(choices) # ---------- 匹配检测 ---------- def _find_matches(self): matched = set() for r in range(ROWS): c = 0 while c < COLS: if self.grid[r][c] is None: c += 1 continue t = self.grid[r][c].type end = c + 1 while end < COLS and self.grid[r][end] and self.grid[r][end].type == t: end += 1 if end - c >= 3: for k in range(c, end): matched.add((k, r)) c = end for c in range(COLS): r = 0 while r < ROWS: if self.grid[r][c] is None: r += 1 continue t = self.grid[r][c].type end = r + 1 while end < ROWS and self.grid[end][c] and self.grid[end][c].type == t: end += 1 if end - r >= 3: for k in range(r, end): matched.add((c, k)) r = end return matched def _has_move(self): for r in range(ROWS): for c in range(COLS): if c + 1 < COLS: self.grid[r][c].type, self.grid[r][c+1].type = \ self.grid[r][c+1].type, self.grid[r][c].type has = bool(self._find_matches()) self.grid[r][c].type, self.grid[r][c+1].type = \ self.grid[r][c+1].type, self.grid[r][c].type if has: return True if r + 1 < ROWS: self.grid[r][c].type, self.grid[r+1][c].type = \ self.grid[r+1][c].type, self.grid[r][c].type has = bool(self._find_matches()) self.grid[r][c].type, self.grid[r+1][c].type = \ self.grid[r+1][c].type, self.grid[r][c].type if has: return True return False def _shuffle(self): types = [] for r in range(ROWS): for c in range(COLS): if self.grid[r][c]: types.append(self.grid[r][c].type) random.shuffle(types) idx = 0 for r in range(ROWS): for c in range(COLS): if self.grid[r][c]: self.grid[r][c].type = types[idx] idx += 1 for r in range(ROWS): for c in range(COLS): t = self.grid[r][c] if t: t.x = t.sx = c * CELL t.y = t.sy = r * CELL # ---------- 全屏消除(无限次) ---------- def use_bomb(self): """全屏消除:清除所有方块,触发爆炸效果,无使用次数限制""" if self.state != "idle": return self.bomb_count += 1 self.bomb_flash = 20 self.shake = SHAKE_DURATION all_tiles = [] for r in range(ROWS): for c in range(COLS): t = self.grid[r][c] if t: all_tiles.append((c, r, t)) if not all_tiles: return total = len(all_tiles) * 15 + 500 self.score += total self.floats.append(FloatText( f"+{total}", W // 2, H // 2 - 60, (255, 230, 100), self.font_xl )) # 冲击波 self.shockwaves.append(Shockwave(W // 2, H // 2, 500, (255, 240, 120))) self.shockwaves.append(Shockwave(W // 2, H // 2, 350, (255, 180, 80))) # 每个方块爆炸粒子 for c, r, t in all_tiles: cx = c * CELL + CELL // 2 + PAD cy = r * CELL + CELL // 2 + TOP color = ANIMALS[t.type][1] for _ in range(8): self.particles.append(Particle(cx, cy, color, speed_mult=1.5, size_mult=1.3)) for _ in range(3): self.particles.append(Particle(cx, cy, (255, 255, 255), speed_mult=1.2, size_mult=1.0)) self.clearing = [(c, r) for c, r, _ in all_tiles] self.state = "clear" self.timer = 0 self.combo = 0 for c, r, t in all_tiles: t.target_scale = 0.0 t.target_alpha = 0 # ---------- 交互 ---------- def click(self, mx, my): # 按钮永远可点 if self.bomb_button.rect.collidepoint(mx, my): self.use_bomb() return if self.state != "idle": return bx = mx - PAD by = my - TOP if bx < 0 or by < 0 or bx >= BOARD_W or by >= BOARD_H: return c = bx // CELL r = by // CELL if c < 0 or c >= COLS or r < 0 or r >= ROWS: return if self.selected is None: self.selected = (c, r) t = self.grid[r][c] if t: t.selected = True t.target_scale = 0.82 else: sc, sr = self.selected if (sc, sr) == (c, r): t = self.grid[sr][sc] if t: t.selected = False t.target_scale = 1.0 self.selected = None return if abs(sc - c) + abs(sr - r) == 1: t1 = self.grid[sr][sc] if t1: t1.selected = False t1.target_scale = 1.0 self.selected = None self.swap_pair = ((sc, sr), (c, r)) self.state = "swap" self.timer = 0 else: old = self.grid[sr][sc] if old: old.selected = False old.target_scale = 1.0 self.selected = (c, r) t = self.grid[r][c] if t: t.selected = True t.target_scale = 0.82 # ---------- 状态更新 ---------- def update(self): mx, my = pygame.mouse.get_pos() self.bomb_button.update(mx, my) # 无解提示(仅提示,不影响按钮使用) if self.state == "idle" and not self._has_move(): self.hint_timer += 1 else: self.hint_timer = 0 for r in range(ROWS): for c in range(COLS): t = self.grid[r][c] if t: t.update() for p in self.particles[:]: p.update() if p.life <= 0: self.particles.remove(p) for f in self.floats[:]: f.update() if f.life <= 0: self.floats.remove(f) for sw in self.shockwaves[:]: sw.update() if sw.life <= 0: self.shockwaves.remove(sw) if self.shake > 0: self.shake -= 1 if self.bomb_flash > 0: self.bomb_flash -= 1 if self.state == "swap": self.timer += 1 if self.timer == 1: self._start_swap_anim() if self.timer >= SWAP_FRAMES: self._finish_swap() elif self.state == "clear": self.timer += 1 if self.timer >= CLEAR_FRAMES: self._finish_clear() elif self.state == "fall": self.timer += 1 if self.timer >= FALL_FRAMES and self._all_landed(): m = self._find_matches() if m: self.combo += 1 self._do_clear(m) else: self.state = "idle" self.combo = 0 if not self._has_move(): self._shuffle() def _start_swap_anim(self): (c1, r1), (c2, r2) = self.swap_pair t1 = self.grid[r1][c1] t2 = self.grid[r2][c2] if t1: t1.sx = c2 * CELL t1.sy = r2 * CELL if t2: t2.sx = c1 * CELL t2.sy = r1 * CELL def _finish_swap(self): (c1, r1), (c2, r2) = self.swap_pair self.grid[r1][c1], self.grid[r2][c2] = self.grid[r2][c2], self.grid[r1][c1] t1 = self.grid[r1][c1] t2 = self.grid[r2][c2] if t1: t1.col, t1.row = c1, r1 t1.x = t1.sx = c1 * CELL t1.y = t1.sy = r1 * CELL if t2: t2.col, t2.row = c2, r2 t2.x = t2.sx = c2 * CELL t2.y = t2.sy = r2 * CELL m = self._find_matches() if m: self.combo = 1 self._do_clear(m) else: self.grid[r1][c1], self.grid[r2][c2] = self.grid[r2][c2], self.grid[r1][c1] t1 = self.grid[r1][c1] t2 = self.grid[r2][c2] if t1: t1.col, t1.row = c1, r1 t1.x = t1.sx = c1 * CELL t1.y = t1.sy = r1 * CELL if t2: t2.col, t2.row = c2, r2 t2.x = t2.sx = c2 * CELL t2.y = t2.sy = r2 * CELL self.state = "idle" self.swap_pair = None def _do_clear(self, matches): self.clearing = list(matches) self.state = "clear" self.timer = 0 for (c, r) in matches: t = self.grid[r][c] if t: t.target_scale = 0.0 t.target_alpha = 0 base = len(matches) * 10 bonus = self.combo * 20 total = base + bonus self.score += total self.shake = 4 if matches: ax = sum(c for c, _ in matches) / len(matches) * CELL + CELL // 2 + PAD ay = sum(r for _, r in matches) / len(matches) * CELL + CELL // 2 + TOP color = (255, 220, 80) if self.combo < 3 else (255, 130, 130) self.floats.append(FloatText(f"+{total}", ax, ay, color, self.font_score)) for (c, r) in matches: t = self.grid[r][c] if t: cx = c * CELL + CELL // 2 + PAD cy = r * CELL + CELL // 2 + TOP color = ANIMALS[t.type][1] for _ in range(5): self.particles.append(Particle(cx, cy, color)) def _finish_clear(self): for (c, r) in self.clearing: self.grid[r][c] = None self.clearing = [] for c in range(COLS): stack = [] for r in range(ROWS - 1, -1, -1): if self.grid[r][c]: stack.append(self.grid[r][c]) for i, t in enumerate(stack): nr = ROWS - 1 - i if t.row != nr: t.row = nr t.col = c t.sx = c * CELL t.sy = nr * CELL t.falling = True self.grid[nr][c] = t for r in range(ROWS - len(stack)): t = Tile(random.randrange(TYPE_COUNT), c, r, start_y=-CELL * (ROWS - len(stack) - r) - CELL) t.sy = r * CELL t.falling = True self.grid[r][c] = t self.state = "fall" self.timer = 0 def _all_landed(self): for r in range(ROWS): for c in range(COLS): t = self.grid[r][c] if t and t.falling: return False return True # ---------- 绘制 ---------- def draw(self): self.screen.blit(self._static_bg, (0, 0)) shake_x = random.randint(-3, 3) if self.shake > 0 else 0 shake_y = random.randint(-3, 3) if self.shake > 0 else 0 for r in range(ROWS): for c in range(COLS): t = self.grid[r][c] if t and t.alpha > 3: self._draw_tile(t, shake_x, shake_y) for p in self.particles: p.draw(self.screen) for sw in self.shockwaves: sw.draw(self.screen) for f in self.floats: f.draw(self.screen) self._draw_header() # 底部按钮(永远可用) self.bomb_button.draw(self.screen, self.font_btn) # 无解提示 if self.state == "idle" and not self._has_move(): if (self.hint_timer // 20) % 2 == 0: hint = self.font_sm.render( "没有可消除的组合了!点击「全屏消除」清场", True, (255, 80, 120) ) self.screen.blit(hint, (W // 2 - hint.get_width() // 2, TOP - 22)) # 底部快捷键提示 hint = self.font_sm.render( "点击相邻方块交换 | 空格 全屏消除(无限次) | R 重开 | ESC 退出", True, (160, 140, 120) ) self.screen.blit(hint, (W // 2 - hint.get_width() // 2, H - 18)) # 全屏白闪 if self.bomb_flash > 0: alpha = int(255 * (self.bomb_flash / 20.0)) flash = pygame.Surface((W, H), pygame.SRCALPHA) flash.fill((255, 250, 230, alpha)) self.screen.blit(flash, (0, 0)) def _draw_tile(self, t, sx, sy): size = CELL * t.scale if size < 2: return cx = t.x + CELL / 2 + PAD + sx cy = t.y + CELL / 2 + TOP + sy half = size / 2 name, color, dark, light, emoji = ANIMALS[t.type] rect = pygame.Rect(cx - half, cy - half, size, size) radius = int(size * 0.22) shadow_surf = pygame.Surface((int(size) + 8, int(size) + 8), pygame.SRCALPHA) pygame.draw.rect(shadow_surf, (0, 0, 0, 60), (4, 4, int(size), int(size)), border_radius=radius) self.screen.blit(shadow_surf, (rect.x - 4, rect.y - 4)) base_color = ( int(color[0] * 0.88), int(color[1] * 0.88), int(color[2] * 0.88), ) pygame.draw.rect(self.screen, base_color, rect, border_radius=radius) if size > 12: hl_rect = pygame.Rect(rect.x + size * 0.12, rect.y + size * 0.08, size * 0.76, size * 0.32) hl = pygame.Surface((int(hl_rect.w), int(hl_rect.h)), pygame.SRCALPHA) pygame.draw.rect(hl, (255, 255, 255, 75), (0, 0, int(hl_rect.w), int(hl_rect.h)), border_radius=max(2, int(radius * 0.6))) self.screen.blit(hl, hl_rect) border_color = (255, 215, 0) if t.selected else dark border_w = max(2, int(3 * t.scale)) if t.selected else max(1, int(2 * t.scale)) pygame.draw.rect(self.screen, border_color, rect, border_w, border_radius=radius) if t.alpha > 30 and size > 20: if self.emoji_font and t.type in self._emoji_cache: s = self._emoji_cache[t.type].copy() if t.scale < 0.98: sw, sh = s.get_size() new_w = max(1, int(sw * t.scale)) new_h = max(1, int(sh * t.scale)) s = pygame.transform.scale(s, (new_w, new_h)) s.set_alpha(int(t.alpha)) self.screen.blit(s, (cx - s.get_width() // 2, cy - s.get_height() // 2)) else: letter = name[0] s = self.font_lg.render(letter, True, dark) s.set_alpha(int(t.alpha)) self.screen.blit(s, (cx - s.get_width() // 2, cy - s.get_height() // 2)) def _draw_header(self): title = self.font_lg.render("开心消消乐", True, (240, 90, 120)) self.screen.blit(title, (W // 2 - title.get_width() // 2, 18)) score_str = f"分数 {self.score}" s = self.font_score.render(score_str, True, (90, 70, 50)) self.screen.blit(s, (PAD, 66)) if self.combo > 1: c = self.font_score.render(f"连击 x{self.combo}", True, (255, 100, 100)) self.screen.blit(c, (W - PAD - c.get_width(), 66)) # 右上角显示已用全屏消除次数 if self.bomb_count > 0: bc = self.font_sm.render(f"💥 x{self.bomb_count}", True, (255, 120, 150)) self.screen.blit(bc, (W - PAD - bc.get_width(), 44)) # ---------- 主循环 ---------- def run(self): running = True while running: for e in pygame.event.get(): if e.type == pygame.QUIT: running = False elif e.type == pygame.KEYDOWN: if e.key == pygame.K_ESCAPE: running = False elif e.key == pygame.K_r: self.reset() elif e.key == pygame.K_SPACE: self.use_bomb() elif e.type == pygame.MOUSEBUTTONDOWN and e.button == 1: self.click(*e.pos) self.update() self.draw() pygame.display.flip() self.clock.tick(FPS) pygame.quit() sys.exit() if __name__ == "__main__": print("=" * 50) print(" 开心消消乐") print("=" * 50) print(" 鼠标点击交换相邻方块") print(" 空格 / 点击按钮 → 全屏消除(无限次!)") print(" R 重开 | ESC 退出") print("=" * 50) Game().run()