在这篇文章中,我们将学习如何使用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()
脚本下载链接如下:

298

被折叠的 条评论
为什么被折叠?



