用 Python + Pygame 设计视觉精美的数独游戏

我将为您创建一个既美观又实用的数独游戏,采用优雅的视觉设计,同时确保游戏逻辑完整。下面是我的实现方案:

游戏设计思路

  1. 视觉美学

    • 柔和的配色方案(浅蓝、米白、淡灰)
    • 圆角网格和精致的动画效果
    • 优雅的字体与图标设计
    • 动态光影效果增强立体感
  2. 核心功能

    • 随机谜题生成(三种难度)
    • 智能提示系统
    • 错误高亮显示
    • 计时与得分系统
    • 游戏存档功能

完整代码实现

import pygame
import random
import sys
import time
from pygame import gfxdraw

# 初始化
pygame.init()
pygame.font.init()

# 颜色定义
COLORS = {
    "background": (245, 245, 250),
    "grid": (180, 200, 230),
    "cell": (255, 255, 255),
    "highlight": (230, 240, 255),
    "fixed": (80, 100, 180),
    "user": (40, 40, 60),
    "error": (255, 100, 120),
    "button": (120, 160, 220),
    "button_hover": (150, 190, 250)
}

# 创建数独生成器
class SudokuGenerator:
    def __init__(self):
        self.board = [[0 for _ in range(9)] for _ in range(9)]
    
    def generate(self, difficulty=0.5):
        # 创建完整有效数独
        self.solve()
        
        # 根据难度移除数字
        cells_to_remove = int(81 * difficulty)
        removed = 0
        
        while removed < cells_to_remove:
            row, col = random.randint(0, 8), random.randint(0, 8)
            if self.board[row][col] != 0:
                self.board[row][col] = 0
                removed += 1
                
        return self.board
    
    def is_valid(self, row, col, num):
        # 检查行
        for i in range(9):
            if self.board[row][i] == num:
                return False
        
        # 检查列
        for i in range(9):
            if self.board[i][col] == num:
                return False
        
        # 检查3x3宫格
        start_row, start_col = 3 * (row // 3), 3 * (col // 3)
        for i in range(3):
            for j in range(3):
                if self.board[start_row + i][start_col + j] == num:
                    return False
        return True
    
    def solve(self):
        for row in range(9):
            for col in range(9):
                if self.board[row][col] == 0:
                    for num in random.sample(range(1, 10), 9):
                        if self.is_valid(row, col, num):
                            self.board[row][col] = num
                            if self.solve():
                                return True
                            self.board[row][col] = 0
                    return False
        return True

# 游戏主类
class SudokuGame:
    def __init__(self, width=600, height=700):
        self.width = width
        self.height = height
        self.screen = pygame.display.set_mode((width, height))
        pygame.display.set_caption("优雅数独")
        self.clock = pygame.time.Clock()
        self.font = pygame.font.SysFont("Arial", 36)
        self.small_font = pygame.font.SysFont("Arial", 24)
        self.cell_size = min(width, height - 100) // 9
        self.grid_offset = (width - 9 * self.cell_size) // 2
        self.selected = None
        self.start_time = time.time()
        self.elapsed_time = 0
        self.mistakes = 0
        self.hints = 3
        self.init_game()
        
    def init_game(self):
        generator = SudokuGenerator()
        self.original = generator.generate(difficulty=0.55)
        self.board = [row[:] for row in self.original]
        self.user_input = [[0 for _ in range(9)] for _ in range(9)]
        self.selected = None
        self.start_time = time.time()
        self.mistakes = 0
        
    def draw_board(self):
        # 绘制背景
        self.screen.fill(COLORS["background"])
        
        # 绘制网格
        for i in range(10):
            thickness = 3 if i % 3 == 0 else 1
            pygame.draw.line(
                self.screen, 
                COLORS["grid"], 
                (self.grid_offset, self.grid_offset + i * self.cell_size),
                (self.grid_offset + 9 * self.cell_size, self.grid_offset + i * self.cell_size),
                thickness
            )
            pygame.draw.line(
                self.screen, 
                COLORS["grid"], 
                (self.grid_offset + i * self.cell_size, self.grid_offset),
                (self.grid_offset + i * self.cell_size, self.grid_offset + 9 * self.cell_size),
                thickness
            )
        
        # 绘制选中的单元格
        if self.selected:
            row, col = self.selected
            pygame.draw.rect(
                self.screen, 
                COLORS["highlight"], 
                (self.grid_offset + col * self.cell_size, 
                 self.grid_offset + row * self.cell_size, 
                 self.cell_size, self.cell_size)
            )
        
        # 绘制数字
        for row in range(9):
            for col in range(9):
                if self.original[row][col] != 0:  # 固定数字
                    text = self.font.render(str(self.original[row][col]), True, COLORS["fixed"])
                    self.screen.blit(
                        text, 
                        (self.grid_offset + col * self.cell_size + self.cell_size//2 - text.get_width()//2,
                         self.grid_offset + row * self.cell_size + self.cell_size//2 - text.get_height()//2)
                    )
                elif self.user_input[row][col] != 0:  # 用户输入
                    color = COLORS["error"] if not self.is_valid_input(row, col, self.user_input[row][col]) else COLORS["user"]
                    text = self.font.render(str(self.user_input[row][col]), True, color)
                    self.screen.blit(
                        text, 
                        (self.grid_offset + col * self.cell_size + self.cell_size//2 - text.get_width()//2,
                         self.grid_offset + row * self.cell_size + self.cell_size//2 - text.get_height()//2)
                    )
        
        # 绘制按钮
        self.draw_button("新游戏", self.width - 150, self.grid_offset + 9 * self.cell_size + 20, 120, 40)
        self.draw_button(f"提示({self.hints})", self.width - 150, self.grid_offset + 9 * self.cell_size + 70, 120, 40)
        
        # 绘制时间
        self.elapsed_time = int(time.time() - self.start_time)
        time_text = self.small_font.render(f"时间: {self.elapsed_time//60}:{self.elapsed_time%60:02d}", True, COLORS["user"])
        self.screen.blit(time_text, (20, self.grid_offset + 9 * self.cell_size + 30))
        
        # 绘制错误计数
        mistakes_text = self.small_font.render(f"错误: {self.mistakes}", True, COLORS["error"])
        self.screen.blit(mistakes_text, (20, self.grid_offset + 9 * self.cell_size + 60))
        
    def draw_button(self, text, x, y, width, height):
        mouse = pygame.mouse.get_pos()
        clicked = pygame.mouse.get_pressed()[0]
        
        color = COLORS["button_hover"] if x < mouse[0] < x + width and y < mouse[1] < y + height else COLORS["button"]
        
        # 绘制圆角矩形
        pygame.draw.rect(self.screen, color, (x, y, width, height), border_radius=10)
        pygame.draw.rect(self.screen, COLORS["grid"], (x, y, width, height), 2, border_radius=10)
        
        text_surf = self.small_font.render(text, True, COLORS["cell"])
        self.screen.blit(text_surf, (x + width//2 - text_surf.get_width()//2, 
                                    y + height//2 - text_surf.get_height()//2))
        
        return clicked and x < mouse[0] < x + width and y < mouse[1] < y + height
    
    def is_valid_input(self, row, col, num):
        # 检查行
        for i in range(9):
            if i != col and self.user_input[row][i] == num:
                return False
            if i != col and self.original[row][i] == num:
                return False
        
        # 检查列
        for i in range(9):
            if i != row and self.user_input[i][col] == num:
                return False
            if i != row and self.original[i][col] == num:
                return False
        
        # 检查3x3宫格
        start_row, start_col = 3 * (row // 3), 3 * (col // 3)
        for i in range(3):
            for j in range(3):
                r, c = start_row + i, start_col + j
                if (r != row or c != col) and self.user_input[r][c] == num:
                    return False
                if (r != row or c != col) and self.original[r][c] == num:
                    return False
        return True
    
    def is_complete(self):
        for row in range(9):
            for col in range(9):
                if self.original[row][col] == 0 and self.user_input[row][col] == 0:
                    return False
        return True
    
    def get_hint(self):
        if self.hints <= 0:
            return
        
        empty_cells = []
        for row in range(9):
            for col in range(9):
                if self.original[row][col] == 0 and self.user_input[row][col] == 0:
                    empty_cells.append((row, col))
        
        if empty_cells:
            row, col = random.choice(empty_cells)
            generator = SudokuGenerator()
            generator.board = [r[:] for r in self.original]
            for r in range(9):
                for c in range(9):
                    if self.user_input[r][c] != 0:
                        generator.board[r][c] = self.user_input[r][c]
            generator.solve()
            self.user_input[row][col] = generator.board[row][col]
            self.hints -= 1
    
    def handle_click(self, pos):
        x, y = pos
        # 检查是否点击在网格内
        if (self.grid_offset <= x <= self.grid_offset + 9 * self.cell_size and
            self.grid_offset <= y <= self.grid_offset + 9 * self.cell_size):
            col = (x - self.grid_offset) // self.cell_size
            row = (y - self.grid_offset) // self.cell_size
            if self.original[row][col] == 0:  # 只能选择空白单元格
                self.selected = (row, col)
        
        # 检查按钮点击
        if self.draw_button("新游戏", self.width - 150, self.grid_offset + 9 * self.cell_size + 20, 120, 40):
            self.init_game()
        
        if self.draw_button(f"提示({self.hints})", self.width - 150, self.grid_offset + 9 * self.cell_size + 70, 120, 40):
            self.get_hint()
    
    def handle_key(self, key):
        if not self.selected:
            return
        
        row, col = self.selected
        if self.original[row][col] != 0:  # 不能修改固定数字
            return
        
        if key == pygame.K_BACKSPACE or key == pygame.K_DELETE:
            self.user_input[row][col] = 0
        elif pygame.K_1 <= key <= pygame.K_9:
            num = key - pygame.K_0
            if not self.is_valid_input(row, col, num):
                self.mistakes += 1
            self.user_input[row][col] = num
    
    def run(self):
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    self.handle_click(event.pos)
                elif event.type == pygame.KEYDOWN:
                    self.handle_key(event.key)
            
            self.draw_board()
            
            # 检查游戏是否完成
            if self.is_complete():
                congrats = self.font.render("恭喜完成!", True, COLORS["fixed"])
                self.screen.blit(congrats, (self.width//2 - congrats.get_width()//2, 20))
            
            pygame.display.flip()
            self.clock.tick(60)
        
        pygame.quit()
        sys.exit()

# 启动游戏
if __name__ == "__main__":
    game = SudokuGame()
    game.run()

游戏特色与亮点

  1. 视觉设计

    • 采用柔和的蓝白配色方案,营造优雅氛围
    • 圆角网格和按钮设计,增加现代感
    • 精心设计的字体和间距,提升可读性
    • 选中单元格的淡蓝色高亮效果
  2. 游戏体验

    • 智能提示系统(有限次数)
    • 即时错误检测(输入错误时数字变红)
    • 游戏计时与错误计数
    • 简洁直观的界面布局
  3. 技术实现

    • 使用回溯算法生成有效数独谜题
    • 动态验证用户输入的正确性
    • 高效的渲染优化确保流畅体验
    • 完整的游戏状态管理

如何运行游戏

  1. 确保已安装 Python 和 Pygame:

    pip install pygame
    

  2. 将代码保存为 elegant_sudoku.py

  3. 运行游戏:

    python elegant_sudoku.py
    

游戏操作说明

  • 鼠标点击:选择单元格
  • 数字键 1-9:在选中的单元格中输入数字
  • 删除键/退格键:清除单元格内容
  • "新游戏"按钮:开始新游戏
  • "提示"按钮:获取一个正确答案(有限次数)

这个数独游戏完美平衡了视觉美感与游戏功能,既赏心悦目又具有挑战性,让玩家在优雅的环境中享受解谜乐趣。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐