大家好,欢迎来到 Crossin 的编程教室。
之前我用 Python 写过一个罚点球小游戏:
Python写个“点球大战”小游戏
这个游戏是在终端里通过输入数字来选择射门/扑救的方向(可选左/中/右3个方向),玩家和程序轮换进行射门,如果射门和扑救方向不同就算进球,仿照世界杯点球大战的规则进行多轮。
程序代码比较简单,可以看作是对 Python 中列表、循环、条件判断和 random 模块的练习。
今天我们再进一步,把它做成一个可视化的窗口游戏:通过点击选择方向,并通过图形化的展示让射门和扑救的方向一目了然。
1. tkinter 创建窗口应用
要实现我们想要的可视化效果,需要用到绘制界面的 GUI 库。
在 Python 中做简单的带界面程序,最省心的就是用内置的 Tkinter 库,无需安装,开箱即用。创建一个基础窗口只需要三步:
import tkinter as tk
root = tk.Tk() root.title("点球大战") root.geometry("300x200")
root.mainloop()
2. Canvas 绘制游戏元素
有了窗口还不够,我们需要能“画”出球场、球门、足球、守门员等游戏元素。这时候就要用到 tkinter 的 Canvas(画布)组件。
通过指定坐标,可以在 Canvas 上面画出各种几何图形:
import tkinter as tk
root = tk.Tk()canvas = tk.Canvas(root, width=500, height=220, bg="#228B22")canvas.pack()
canvas.create_rectangle(100, 30, 400, 180, outline="white", width=5)gk = canvas.create_rectangle(235, 100, 265, 150, fill="#1E90FF", outline="white")ball = canvas.create_oval(240, 190, 260, 210, fill="#FFD700", outline="black")
root.mainloop()
3. 组件布局管理
游戏窗口上除了绘制游戏元素,还需要有选择方向的按钮和提示文本。
我打算把提示文字放在上方,把6个方向按钮放在下方排成2行3列。
Tkinter 提供了 pack()(上下堆叠)和 grid()(网格矩阵)两种经典的布局管理器来控制界面上的组件摆放:
import tkinter as tk
root = tk.Tk()root.geometry()
status_label = tk.Label(root, text="请选择你的射门方向:", font=("Arial", 12))status_label.pack(pady=10)
canvas = tk.Canvas(root, width=500, height=220, bg="#228B22")canvas.pack()
canvas.create_rectangle(100, 30, 400, 180, outline="white", width=5)gk = canvas.create_rectangle(235, 100, 265, 150, fill="#1E90FF", outline="white")ball
= canvas.create_oval(240, 190, 260, 210, fill="#FFD700", outline="black")
btn_frame = tk.Frame(root)btn_frame.pack()
directions = ["左上", "上", "右上", "左", "中", "右"]for i, name in enumerate(directions): btn = tk.Button(btn_frame, text=name, width=8, height=2) btn.grid(row=i // 3, column=i % 3, padx=5, pady=5)
root.mainloop()
4. 按钮交互:让画面动起来
按钮排好后,还要增加点击响应。我们可以通过 command 参数为按钮绑定点击事件,并利用 canvas.coords() 动态改变画布组件的坐标,让守门员向指定方向“扑”过去:
import tkinter as tk
GK_POS = { "左上": (150, 50), "上": (250, 50), "右上": (350, 50), "左": (150, 130), "中": (250, 130), "右": (350, 130),}
def move_gk(direction): gx, gy = GK_POS[direction] canvas.coords(gk_marker, gx - 15, gy - 25, gx + 15, gy + 25)
root = tk.Tk()canvas = tk.Canvas(root, width=500, height=200, bg="#228B22")canvas.pack()canvas.create_rectangle(100, 30, 400, 180, outline="white", width=4)gk_marker = canvas.create_rectangle( 235, 100, 265, 150, fill="#FFD700", outline="white")
btn_frame = tk.Frame(root)btn_frame.pack()for i, d in enumerate(GK_POS.keys()): btn = tk.Button( btn_frame, text=d, command=lambda direction=d: move_gk(direction) ) btn.grid(row=i // 3, column=i % 3, padx=5, pady=5)
root.mainloop()
完整示例代码
把以上四个核心部分组合起来,再加上比赛计分、轮流攻防切换、10%踢飞概率以及配套的文字提示,就完成了这个可视化的点球大战游戏。
你可以直接复制到编辑器中运行体验:
import randomimport tkinter as tk
from tkinter import messagebox
DIRECTIONS = { "左上": (150, 50), "上": (250, 50), "右上": (350, 50), "左": (150, 130), "中": (250, 130), "右": (350, 130),}
class PenaltyGameSmooth:
def __init__(self, root): self.root = root self.root.title("点球大战(内嵌提示版) ⚽") self.root.geometry("550x530")
self.is_player_shooting = True self.round_num = 1 self.is_waiting_next = ( False )
self.PLAYER_COLOR = "#1E90FF" self.COM_COLOR = "#FFD700"
self.player_history = [] self.com_history = []
self.status_label = tk.Label( root, text="【 第 1 轮:轮到你射门了! 】", font=("Microsoft YaHei", 13, "bold"), fg="#228B22", ) self.status_label.pack(pady=(10, 0))
self.result_label = tk.Label( root, text="", font=("Microsoft YaHei", 14, "bold"), fg="#FF4500" ) self.result_label.pack(pady=(5, 5))
score_frame = tk.Frame(root, bg="#F0F0F0", bd=2, relief="groove") score_frame.pack(fill="x", padx=40, pady=5)
self.p_color_lbl = tk.Label( score_frame, text="■", fg=self.PLAYER_COLOR, bg="#F0F0F0", font=("Arial", 14), ) self.p_color_lbl.grid(row=0, column=0, padx=(10, 2), sticky="w") self.p_score_lbl = tk.Label( score_frame, text="玩家 (得分: 0) : 开始游戏...", font=("Microsoft YaHei", 11), bg="#F0F0F0", ) self.p_score_lbl.grid(row=0, column=1, sticky="w")
self.c_color_lbl = tk.Label( score_frame, text="■", fg=self.COM_COLOR, bg="#F0F0F0", font=("Arial", 14), ) self.c_color_lbl.grid(row=1, column=0, padx=(10, 2), sticky="w") self.c_score_lbl = tk.Label( score_frame,
text="电脑 (得分: 0) : 开始游戏...", font=("Microsoft YaHei", 11), bg="#F0F0F0", ) self.c_score_lbl.grid(row=1, column=1, sticky="w")
self.canvas = tk.Canvas(root, width=500, height=220, bg="#228B22") self.canvas.pack(pady=10)
self.canvas.create_rectangle( 100, 30, 400, 180, outline="white", width=5 ) self.canvas.create_line(0, 180, 500, 180, fill="white", width=3)
self.gk_marker = self.canvas.create_rectangle( 0, 0, 0, 0, outline="white", width=2 ) self.ball_marker = self.canvas.create_oval( 0, 0, 0, 0, outline="black", width=2 )
btn_frame = tk.Frame(root) btn_frame.pack(pady=5)
for i, direction in enumerate(DIRECTIONS.keys()): btn = tk.Button( btn_frame, text=direction, width=8, height=2, font=("Microsoft YaHei", 10), command=lambda d=direction: self.play_turn(d), ) btn.grid(row=i // 3, column=i % 3, padx=8, pady=5)
self.update_score_board() self.reset_visuals()
def reset_visuals(self): """重置画布,清空单发文字提示""" if self.is_player_shooting: ball_color = self.PLAYER_COLOR gk_color = self.COM_COLOR else: ball_color = self.COM_COLOR gk_color = self.PLAYER_COLOR
self.canvas.itemconfig(self.ball_marker, fill=ball_color) self.canvas.itemconfig(self.gk_marker, fill=gk_color)
self.canvas.coords(self.ball_marker, 240, 190, 260, 210) self.canvas.coords(self.gk_marker, 235, 100, 265, 150)
self.result_label.config(text="") self.is_waiting_next = False
def update_score_board(self): """更新记分牌文字和顶部状态提示""" p_total = self.player_history.count("⚽") c_total = self.com_history.count("⚽") p_bar = " ".join(self.player_history) if self.player_history else "无" c_bar = " ".join(self.com_history) if self.com_history else "无"
if self.is_player_shooting: self.status_label.config( text=f"【 第 {self.round_num}
轮:轮到你射门了! 】", fg="#228B22" ) else: self.status_label.config( text=f"【 第 {self.round_num} 轮:请注意防守! 】", fg="#D2691E" )
self.p_score_lbl.config(text=f"玩家 (总分: {p_total}) 进度: {p_bar}") self.c_score_lbl.config(text=f"电脑 (总分: {c_total}) 进度: {c_bar}")
def play_turn(self, player_choice): if self.is_waiting_next: return self.is_waiting_next = True
com_choice = random.choice(list(DIRECTIONS.keys()))
shoot_dir = player_choice if self.is_player_shooting else com_choice save_dir = com_choice if self.is_player_shooting else player_choice
bx, by = DIRECTIONS[shoot_dir] gx, gy = DIRECTIONS[save_dir] self.canvas.coords(self.ball_marker, bx - 10, by - 10, bx + 10, by + 10) self.canvas.coords(self.gk_marker, gx - 15, gy - 25, gx + 15, gy + 25)
is_missed = random.random() 0.10 goal = False
if is_missed: result_text = f"你选【{player_choice}】· 电脑选【{com_choice}】 ❌ 踢飞了!高出横梁!" text_color = "#FF0000" self.canvas.coords( self.ball_marker, bx - 10, by - 45, bx + 10, by - 25 ) elif shoot_dir == save_dir: result_text = f"你选【{player_choice}】· 电脑选【{com_choice}】 🧤 被扑住了!" text_color = "#FF4500" else: result_text = f"你选【{player_choice}】· 电脑选【{com_choice}】 ⚽ 球进了!!" text_color = "#008000" goal = True
self.result_label.config(text=result_text, fg=text_color)
mark = "⚽" if goal else "❌" if self.is_player_shooting: self.player_history.append(mark) else: self.com_history.append(mark)
if not self.is_player_shooting: self.round_num += 1
self.is_player_shooting = not self.is_player_shooting
self.root.after(1500, self.next_turn_check)
def next_turn_check(self): """1.5秒延迟后执行:刷新记分牌,并检查是否进入结局""" self.update_score_board()
if self.round_num > 5 and
self.is_player_shooting: self.end_game() else: self.reset_visuals()
def end_game(self): p_total = self.player_history.count("⚽") c_total = self.com_history.count("⚽")
if p_total > c_total: final_msg = "🏆 最终胜利属于你!太棒了!" elif p_total < c_total: final_msg = "💀 电脑赢得了比赛,洗洗手再来一把吧!" else: final_msg = "🤝 双方难解难分,最终战平!"
messagebox.showinfo( "比赛结束", f"点球大战结束!\n\n最终比分 -> 玩家 {p_total} : {c_total} 电脑\n{final_msg}", )
self.player_history.clear() self.com_history.clear() self.round_num = 1 self.is_player_shooting = True self.update_score_board() self.reset_visuals()
if __name__ == "__main__": root = tk.Tk() game = PenaltyGameSmooth(root) root.mainloop()

利用 Tkinter 制作基础窗口交互非常方便,但如果你想追求更高级的效果——比如增加足球的运行轨迹、守门员的扑救动画、射门的音效等,Tkinter 就会显得有些吃力了。
这时候,你可以去了解 Python 的另一个游戏开发利器:Pygame。
Pygame 是专为 2D 游戏开发的第三方库,拥有完整的逐帧渲染机制、碰撞检测和音频混合器,能做出更加商业化的游戏作品。之前我们也做过不少 Pygame 的开发案例:
手把手教你用Python写一个俄罗斯方块
手把手带你用 Python 写《2048》
用Python写一个FlappyBird游戏
用python写一个简单的贪吃蛇游戏
如何开发一个打飞机游戏
感兴趣的同学,不妨试着把今天的逻辑用 Pygame 的框架改写一下吧。
如果本文对你有帮助,欢迎点赞、评论、转发。你们的支持是我更新的动力~
添加微信 crossin123 ,加入编程教室共同学习~