用Python编写数独游戏:一步步教学

       在这篇文章中,我们将学习如何使用Python编写一个简单的数独游戏。我们将通过创建一个图形用户界面(GUI)应用程序,使得玩家可以在计算机上玩数独游戏。我们将使用tkinter库来构建界面,并使用一些基本的编程概念来实现游戏逻辑。

准备工作

       在开始编写代码之前,请确保你的计算机上已经安装了Python。tkinter是Python的标准GUI库,因此你不需要额外安装任何软件。

步骤1:导入必要的库

首先,我们需要导入编写程序所需的库。

python Copy Code

import tkinter as tk
from tkinter import messagebox, ttk
import random
import time
import json
import os

步骤2:创建SudokuGame类

接下来,我们将创建一个名为SudokuGame的类,它将包含所有游戏逻辑和界面设置。

python Copy Codeclass SudokuGame:
    def __init__(self):
        # 初始化窗口和变量
        self.window = tk.Tk()
        self.window.title("数独游戏")
        self.window.geometry("600x800")
        self.window.configure(bg='#FFE5E5')
        
        # 设置样式
        style = ttk.Style()
        style.configure('TButton', padding=5, font=('Comic Sans MS', 10))
        style.configure('TRadiobutton', font=('Comic Sans MS', 10))
        style.configure('TLabel', font=('Comic Sans MS', 12), background='#FFE5E5')
        
        # 游戏变量
        self.difficulty = tk.StringVar(value="简单")
        self.timer_running = False
        self.start_time = 0
        self.current_time = 0
        self.solution = None
        self.puzzle = None
        self.selected_cell = None
        
        # 分数系统
        self.scores_file = "sudoku_scores.json"
        self.scores = self.load_scores()
        
        # 评价系统
        self.ratings_file = "sudoku_ratings.json"
        self.ratings = self.load_ratings()              

步骤3:设置用户界面

       在这个步骤中,我们将创建游戏的用户界面,包括标题、难度选择、计时器、游戏网格、数字按钮和控制按钮。

pythonCopy Code  self.setup_ui()
        self.generate_puzzle()
        
    def setup_ui(self):
        # 标题
        title_label = tk.Label(self.window, 
                             text="可爱数独", 
                             font=("Comic Sans MS", 24, "bold"),
                             bg='#FFE5E5', 
                             fg='#FF69B4')
        title_label.pack(pady=10)
        
        # 难度选择
        difficulty_frame = tk.Frame(self.window, bg='#FFE5E5')
        difficulty_frame.pack(pady=10)
        
        tk.Label(difficulty_frame, 
                text="难度:", 
                bg='#FFE5E5', 
                font=('Comic Sans MS', 12),
                fg='#FF69B4').pack(side=tk.LEFT)
        
        difficulties = ["简单", "中等", "困难"]
        for diff in difficulties:
            tk.Radiobutton(difficulty_frame, 
                          text=diff, 
                          variable=self.difficulty, 
                          value=diff, 
                          command=self.new_game,
                          bg='#FFE5E5', 
                          font=('Comic Sans MS', 10),
                          fg='#FF69B4',
                          selectcolor='#FFB6C1').pack(side=tk.LEFT, padx=5)
        
        # 计时器
        self.timer_label = tk.Label(self.window, 
                                  text="时间: 0:00", 
                                  font=("Comic Sans MS", 14, "bold"),
                                  bg='#FFE5E5', 
                                  fg='#FF69B4')
        self.timer_label.pack(pady=5)
        
        # 最佳成绩显示
        self.best_score_label = tk.Label(self.window, 
                                       text="最佳成绩: 无", 
                                       font=("Comic Sans MS", 12),
                                       bg='#FFE5E5', 
                                       fg='#FF69B4')
        self.best_score_label.pack(pady=5)
        self.update_best_score_display()
        
        # 游戏网格
        self.cells = {}
        self.game_frame = tk.Frame(self.window, bg='#FFE5E5')
        self.game_frame.pack(pady=10)
        
        for i in range(9):
            for j in range(9):
                cell = tk.Label(self.game_frame, 
                              width=3, 
                              font=("Comic Sans MS", 16, "bold"),
                              relief="solid",
                              bd=1,
                              bg='white')
                cell.grid(row=i, column=j, padx=1, pady=1)
                cell.bind('<Button-1>', lambda e, row=i, col=j: self.select_cell(row, col))
                self.cells[(i, j)] = cell
                
                # 添加粗线分隔3x3区域
                if i % 3 == 0 and i != 0:
                    cell.grid(pady=(3, 0))
                if j % 3 == 0 and j != 0:
                    cell.grid(padx=(3, 0))
        
        # 数字按钮
        number_frame = tk.Frame(self.window, bg='#FFE5E5')
        number_frame.pack(pady=10)
        
        # 定义数字按钮的颜色
        number_colors = {
            1: '#FFB6C1',  # 浅粉红
            2: '#FFC0CB',  # 粉红
            3: '#FFD700',  # 金色
            4: '#98FB98',  # 浅绿色
            5: '#87CEEB',  # 天蓝色
            6: '#DDA0DD',  # 梅红色
            7: '#F0E68C',  # 卡其色
            8: '#FFA07A',  # 浅鲑鱼色
            9: '#B0E0E6'   # 粉蓝色
        }
        
        for i in range(1, 10):
            btn = tk.Button(number_frame, 
                          text=str(i),
                          font=('Comic Sans MS', 14, "bold"),
                          bg=number_colors[i],
                          fg='white',
                          width=3,
                          relief='raised',
                          command=lambda x=i: self.enter_number(x))
            btn.grid(row=0, column=i-1, padx=2, pady=2)
        
        # 控制按钮
        button_frame = tk.Frame(self.window, bg='#FFE5E5')
        button_frame.pack(pady=10)
        
        button_style = {'font': ('Comic Sans MS', 10, "bold"),
                       'bg': '#FF69B4',  # 热粉红
                       'fg': 'white',
                       'relief': 'raised',
                       'padx': 10,
                       'pady': 5}
        
        tk.Button(button_frame, text="新游戏", command=self.new_game, **button_style).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="检查答案", command=self.check_solution, **button_style).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="显示答案", command=self.show_solution, **button_style).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="查看所有成绩", command=self.show_all_scores, **button_style).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="评价游戏", command=self.show_rating_dialog, **button_style).pack(side=tk.LEFT, padx=5)
        

步骤4:生成数独谜题

在这个步骤中,我们将编写代码来生成一个完整的数独解决方案,并根据难度级别移除一些数字以创建谜题。

pythonCopy Codedef generate_puzzle(self):
        # 生成完整的数独解决方案
        self.solution = self.generate_solution()
        
        # 根据难度移除数字
        difficulty_levels = {
            "简单": 40,
            "中等": 50,
            "困难": 60
        }
        cells_to_remove = difficulty_levels[self.difficulty.get()]
        
        # 创建谜题副本
        self.puzzle = [row[:] for row in self.solution]
        
        # 随机移除数字
        positions = [(i, j) for i in range(9) for j in range(9)]
        random.shuffle(positions)
        
        for i, j in positions[:cells_to_remove]:
            self.puzzle[i][j] = 0
            
        self.update_display()
        
    def generate_solution(self):
        # 生成有效的数独解决方案
        def is_valid(board, row, col, num):
            # 检查行
            for x in range(9):
                if board[row][x] == num:
                    return False
                    
            # 检查列
            for x in range(9):
                if board[x][col] == num:
                    return False
                    
            # 检查3x3方格
            start_row = row - row % 3
            start_col = col - col % 3
            for i in range(3):
                for j in range(3):
                    if board[i + start_row][j + start_col] == num:
                        return False
                        
            return True
            
        def solve(board):
            empty = find_empty(board)
            if not empty:
                return True
                
            row, col = empty
            for num in range(1, 10):
                if is_valid(board, row, col, num):
                    board[row][col] = num
                    if solve(board):
                        return True
                    board[row][col] = 0
            return False
            
        def find_empty(board):
            for i in range(9):
                for j in range(9):
                    if board[i][j] == 0:
                        return (i, j)
            return None
            
        # 创建空棋盘
        board = [[0 for _ in range(9)] for _ in range(9)]
        
        # 填充一些初始数字
        for i in range(17):
            row = random.randint(0, 8)
            col = random.randint(0, 8)
            num = random.randint(1, 9)
            if is_valid(board, row, col, num):
                board[row][col] = num
                
        # 解决剩余部分
        solve(board)
        return board

步骤5:实现游戏逻辑

接下来,我们将实现游戏的逻辑,包括选择单元格、输入数字、检查答案、显示答案、开始新游戏等。

pythonCopy Code  def update_display(self):
        for i in range(9):
            for j in range(9):
                value = self.puzzle[i][j]
                cell = self.cells[(i, j)]
                if value != 0:
                    cell.config(text=str(value), bg='white')
                else:
                    cell.config(text='', bg='white')
                    
    def select_cell(self, row, col):
        # 取消之前选中单元格的高亮
        if self.selected_cell:
            prev_row, prev_col = self.selected_cell
            self.cells[self.selected_cell].config(bg='white')
        
        # 高亮新选中的单元格
        self.selected_cell = (row, col)
        self.cells[self.selected_cell].config(bg='#FFE4E1')  # 浅粉红色高亮
    
    def enter_number(self, number):
        if self.selected_cell:
            row, col = self.selected_cell
            if self.puzzle[row][col] == 0:  # 只在空格处允许输入
                self.cells[self.selected_cell].config(text=str(number))
    
    def check_solution(self):
        for i in range(9):
            for j in range(9):
                cell = self.cells[(i, j)]
                value = cell.cget("text")
                if not value or int(value) != self.solution[i][j]:
                    messagebox.showinfo("检查结果", "答案不正确,请继续尝试!")
                    return
        
        # 保存成绩
        current_diff = self.difficulty.get()
        self.scores[current_diff].append(self.current_time)
        self.save_scores()
        self.update_best_score_display()
        
        messagebox.showinfo("检查结果", f"恭喜!答案正确!\n用时:{self.current_time // 60}:{self.current_time % 60:02d}")
        self.stop_timer()
    
    def show_solution(self):
        for i in range(9):
            for j in range(9):
                cell = self.cells[(i, j)]
                cell.config(text=str(self.solution[i][j]), bg='white')
        self.stop_timer()
        
    def new_game(self):
        self.stop_timer()
        self.generate_puzzle()
        self.start_timer()
        
    def start_timer(self):
        self.start_time = time.time()
        self.timer_running = True
        self.update_timer()
        
    def stop_timer(self):
        self.timer_running = False
        
    def update_timer(self):
        if self.timer_running:
            self.current_time = int(time.time() - self.start_time)
            minutes = self.current_time // 60
            seconds = self.current_time % 60
            self.timer_label.config(text=f"时间: {minutes}:{seconds:02d}")
            self.window.after(1000, self.update_timer)
            
    def run(self):
        self.start_timer()
        self.window.mainloop()

    def load_scores(self):
        if os.path.exists(self.scores_file):
            try:
                with open(self.scores_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except:
                return {"简单": [], "中等": [], "困难": []}
        return {"简单": [], "中等": [], "困难": []}

    def save_scores(self):
        with open(self.scores_file, 'w', encoding='utf-8') as f:
            json.dump(self.scores, f, ensure_ascii=False, indent=4)

    def update_best_score_display(self):
        current_diff = self.difficulty.get()
        if self.scores[current_diff]:
            best_time = min(self.scores[current_diff])
            minutes = best_time // 60
            seconds = best_time % 60
            self.best_score_label.config(text=f"最佳成绩: {minutes}:{seconds:02d}")
        else:
            self.best_score_label.config(text="最佳成绩: 无")

    def show_all_scores(self):
        scores_window = tk.Toplevel(self.window)
        scores_window.title("所有成绩")
        scores_window.geometry("500x600")  # 增加宽度以显示更多信息
        scores_window.configure(bg='#FFE5E5')

        # 添加标题
        tk.Label(scores_window, 
                text="成绩记录", 
                font=("Comic Sans MS", 16, "bold"),
                bg='#FFE5E5', 
                fg='#FF69B4').pack(pady=10)

        # 成绩显示
        for diff in ["简单", "中等", "困难"]:
            frame = tk.Frame(scores_window, bg='#FFE5E5')
            frame.pack(pady=5)
            
            tk.Label(frame, 
                    text=f"{diff}难度:", 
                    bg='#FFE5E5', 
                    font=('Comic Sans MS', 12),
                    fg='#FF69B4').pack(side=tk.LEFT)
            
            if self.scores[diff]:
                best_time = min(self.scores[diff])
                minutes = best_time // 60
                seconds = best_time % 60
                tk.Label(frame, 
                        text=f"最佳: {minutes}:{seconds:02d}", 
                        bg='#FFE5E5', 
                        font=('Comic Sans MS', 12),
                        fg='#FF69B4').pack(side=tk.LEFT, padx=10)
            else:
                tk.Label(frame, 
                        text="暂无成绩", 
                        bg='#FFE5E5', 
                        font=('Comic Sans MS', 12),
                        fg='#FF69B4').pack(side=tk.LEFT, padx=10)

        # 显示评价统计
        if self.ratings["ratings"]:
            tk.Label(scores_window,
                    text="\n游戏评价",
                    font=('Comic Sans MS', 14, "bold"),
                    bg='#FFE5E5',
                    fg='#FF69B4').pack(pady=10)

            # 统计表情数量
            emoji_counts = {}
            for emoji in self.ratings["ratings"]:
                emoji_counts[emoji] = emoji_counts.get(emoji, 0) + 1

            # 显示表情统计
            for emoji, count in emoji_counts.items():
                tk.Label(scores_window,
                        text=f"{emoji}: {count}次",
                        font=('Comic Sans MS', 12),
                        bg='#FFE5E5',
                        fg='#FF69B4').pack(pady=2)

            # 显示最近评价
            tk.Label(scores_window,
                    text="\n最近评价",
                    font=('Comic Sans MS', 14, "bold"),
                    bg='#FFE5E5',
                    fg='#FF69B4').pack(pady=10)

            # 创建滚动框架
            scroll_frame = tk.Frame(scores_window, bg='#FFE5E5')
            scroll_frame.pack(fill=tk.BOTH, expand=True, padx=10)

            # 创建画布和滚动条
            canvas = tk.Canvas(scroll_frame, bg='#FFE5E5')
            scrollbar = tk.Scrollbar(scroll_frame, orient="vertical", command=canvas.yview)
            scrollable_frame = tk.Frame(canvas, bg='#FFE5E5')

            scrollable_frame.bind(
                "<Configure>",
                lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
            )

            canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
            canvas.configure(yscrollcommand=scrollbar.set)

            # 显示最近5条评价
            for i in range(min(5, len(self.ratings["ratings"]))):
                idx = -(i+1)  # 从最新的开始
                frame = tk.Frame(scrollable_frame, bg='#FFE5E5')
                frame.pack(pady=5, fill=tk.X)
                
                # 表情和时间
                tk.Label(frame,
                        text=f"{self.ratings['ratings'][idx]} {self.ratings['timestamps'][idx]}",
                        font=('Comic Sans MS', 10),
                        bg='#FFE5E5',
                        fg='#FF69B4').pack(anchor="w")
                
                # 位置信息
                location = self.ratings['locations'][idx]
                if location:
                    tk.Label(frame,
                            text=f"📍 {location['city']}, {location['country']}",
                            font=('Comic Sans MS', 10),
                            bg='#FFE5E5',
                            fg='#FF69B4').pack(anchor="w")
                
                # 评价内容
                if self.ratings['comments'][idx]:
                    tk.Label(frame,
                            text=self.ratings['comments'][idx],
                            font=('Comic Sans MS', 10),
                            bg='#FFE5E5',
                            fg='#FF69B4',
                            wraplength=400).pack(anchor="w")

            # 配置滚动区域
            canvas.pack(side="left", fill="both", expand=True)
            scrollbar.pack(side="right", fill="y")
 

步骤6:保存和加载分数与评价

最后,我们将编写代码来保存和加载玩家的分数和游戏评价。

pythonCopy Code def load_ratings(self):
        if os.path.exists(self.ratings_file):
            try:
                with open(self.ratings_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except:
                return {"ratings": [], "comments": [], "locations": [], "timestamps": []}
        return {"ratings": [], "comments": [], "locations": [], "timestamps": []}

    def save_ratings(self):
        with open(self.ratings_file, 'w', encoding='utf-8') as f:
            json.dump(self.ratings, f, ensure_ascii=False, indent=4)

    def get_location(self):
        try:
            g = geocoder.ip('me')
            if g.ok:
                return {
                    "city": g.city,
                    "state": g.state,
                    "country": g.country,
                    "lat": g.lat,
                    "lng": g.lng
                }
        except:
            pass
        return None

    def show_rating_dialog(self):
        rating_window = tk.Toplevel(self.window)
        rating_window.title("游戏评价")
        rating_window.geometry("400x600")  # 增加高度以显示位置信息
        rating_window.configure(bg='#FFE5E5')

        # 标题
        tk.Label(rating_window, 
                text="给游戏一个评价吧!", 
                font=("Comic Sans MS", 16, "bold"),
                bg='#FFE5E5', 
                fg='#FF69B4').pack(pady=10)

        # 表情选择
        emoji_frame = tk.Frame(rating_window, bg='#FFE5E5')
        emoji_frame.pack(pady=10)

        emojis = {
            "😊": "非常喜欢",
            "😄": "很喜欢",
            "😐": "一般般",
            "😕": "不太喜欢",
            "😢": "不喜欢"
        }

        selected_emoji = tk.StringVar()
        
        for emoji, text in emojis.items():
            frame = tk.Frame(emoji_frame, bg='#FFE5E5')
            frame.pack(pady=5)
            
            tk.Radiobutton(frame, 
                          text=f"{emoji} {text}",
                          variable=selected_emoji,
                          value=emoji,
                          bg='#FFE5E5',
                          font=('Comic Sans MS', 12),
                          fg='#FF69B4',
                          selectcolor='#FFB6C1').pack(side=tk.LEFT)

        # 评价文本框
        tk.Label(rating_window,
                text="写下你的评价吧:",
                font=('Comic Sans MS', 12),
                bg='#FFE5E5',
                fg='#FF69B4').pack(pady=5)

        comment_text = tk.Text(rating_window,
                             height=5,
                             width=40,
                             font=('Comic Sans MS', 10),
                             bg='white')
        comment_text.pack(pady=5)

        # 位置信息显示
        location_frame = tk.Frame(rating_window, bg='#FFE5E5')
        location_frame.pack(pady=10)

        location_label = tk.Label(location_frame,
                                text="正在获取位置信息...",
                                font=('Comic Sans MS', 10),
                                bg='#FFE5E5',
                                fg='#FF69B4')
        location_label.pack()

        def update_location():
            location = self.get_location()
            if location:
                location_text = f"📍 {location['city']}, {location['country']}"
            else:
                location_text = "📍 无法获取位置信息"
            location_label.config(text=location_text)

        # 获取位置信息
        update_location()

        def submit_rating():
            if not selected_emoji.get():
                messagebox.showwarning("提示", "请选择一个表情!")
                return

            emoji = selected_emoji.get()
            comment = comment_text.get("1.0", tk.END).strip()
            location = self.get_location()
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            
            # 保存评价
            self.ratings["ratings"].append(emoji)
            self.ratings["comments"].append(comment)
            self.ratings["locations"].append(location)
            self.ratings["timestamps"].append(timestamp)
            self.save_ratings()
            
            messagebox.showinfo("感谢", "感谢你的评价!")
            rating_window.destroy()

        # 提交按钮
        tk.Button(rating_window,
                 text="提交评价",
                 font=('Comic Sans MS', 10, "bold"),
                 bg='#FF69B4',
                 fg='white',
                 command=submit_rating).pack(pady=10)

#运行
if __name__ == "__main__":
    game = SudokuGame()
    game.run() 

脚本下载链接如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值