1. 石头剪刀布游戏 Python 实现详解
作为一名Python开发者,我经常用这个小游戏作为教学案例。它不仅涵盖了Python基础语法的核心要素,还能让初学者快速获得成就感。今天我就来分享一个功能完整、可扩展的石头剪刀布实现方案,包含对战逻辑、胜负判断和战绩统计等实用功能。
这个实现特别适合刚学完Python基础语法(变量、条件判断、循环)的开发者练手。通过200行左右的代码,你能掌握random模块的使用、函数封装技巧以及面向对象编程的初步应用。我会从最基础的命令行版本开始,逐步添加AI对战、历史记录等进阶功能。
1.1 游戏规则与程序设计思路
石头剪刀布的基本规则大家都很熟悉:石头砸剪刀、剪刀剪布、布包石头。在程序实现上,我们需要解决三个核心问题:
- 如何获取玩家输入并验证有效性
- 如何生成电脑的随机选择
- 如何判断胜负关系
我设计的程序流程图如下:
- 初始化游戏(设置回合数、胜负记录)
- 进入游戏循环:
- 玩家输入选择
- 电脑随机生成选择
- 比较双方选择判断胜负
- 记录本局结果
- 游戏结束后显示统计信息
提示:在验证玩家输入时,要考虑大小写兼容、拼写错误等情况,提升用户体验。
1.2 基础版本实现
我们先实现最基础的命令行版本。创建一个新的Python文件(如rps.py),开始编写核心代码:
python复制import random
# 定义游戏选项和胜负规则
CHOICES = ['rock', 'paper', 'scissors']
WIN_RULES = {
'rock': {'scissors': 'crushes', 'paper': 'is covered by'},
'paper': {'rock': 'covers', 'scissors': 'is cut by'},
'scissors': {'paper': 'cut', 'rock': 'are crushed by'}
}
def get_player_choice():
"""获取并验证玩家输入"""
while True:
player_choice = input("\nEnter your choice (rock/paper/scissors): ").lower()
if player_choice in CHOICES:
return player_choice
print("Invalid choice! Please try again.")
def get_computer_choice():
"""随机生成电脑选择"""
return random.choice(CHOICES)
def determine_winner(player, computer):
"""判断胜负关系"""
if player == computer:
return 'tie'
if computer in WIN_RULES[player]:
return 'player'
return 'computer'
def play_round():
"""执行单局游戏"""
player = get_player_choice()
computer = get_computer_choice()
print(f"\nYou chose: {player}")
print(f"Computer chose: {computer}")
result = determine_winner(player, computer)
if result == 'tie':
print("It's a tie!")
else:
verb = WIN_RULES[player].get(computer, '') or WIN_RULES[computer][player]
winner = 'You' if result == 'player' else 'Computer'
print(f"{winner} win! {player.capitalize()} {verb} {computer}.")
return result
def main():
"""游戏主循环"""
scores = {'player': 0, 'computer': 0, 'tie': 0}
while True:
result = play_round()
scores[result] += 1
play_again = input("\nPlay again? (y/n): ").lower()
if play_again != 'y':
break
print("\nFinal Scores:")
print(f"You: {scores['player']} | Computer: {scores['computer']} | Ties: {scores['tie']}")
if __name__ == "__main__":
print("Welcome to Rock Paper Scissors!")
main()
这个基础版本已经实现了核心游戏逻辑。几个关键点说明:
- 使用字典WIN_RULES清晰定义了胜负关系,便于维护和扩展
- 输入验证确保游戏不会因错误输入而崩溃
- 胜负判断函数返回明确的结果状态,便于后续统计
- 主循环结构清晰,容易添加新功能
注意:WIN_RULES字典的设计是胜负判断的关键,它同时存储了动作描述,使得结果输出更加自然。
1.3 添加游戏统计与历史记录
让我们增强这个游戏,添加对战统计和历史记录功能。修改后的代码如下:
python复制import random
from collections import defaultdict
import json
import os
# 常量定义
GAME_HISTORY_FILE = 'rps_history.json'
class RPSGame:
def __init__(self):
self.scores = defaultdict(int)
self.history = []
self.load_history()
def load_history(self):
"""加载历史记录"""
if os.path.exists(GAME_HISTORY_FILE):
with open(GAME_HISTORY_FILE, 'r') as f:
data = json.load(f)
self.scores = defaultdict(int, data['scores'])
self.history = data['history']
def save_history(self):
"""保存历史记录"""
with open(GAME_HISTORY_FILE, 'w') as f:
json.dump({
'scores': dict(self.scores),
'history': self.history
}, f)
def record_result(self, result, player_choice, computer_choice):
"""记录单局结果"""
self.scores[result] += 1
self.history.append({
'player': player_choice,
'computer': computer_choice,
'result': result,
'timestamp': datetime.now().isoformat()
})
# 限制历史记录条数
if len(self.history) > 50:
self.history = self.history[-50:]
def show_stats(self):
"""显示统计数据"""
total = sum(self.scores.values())
if total == 0:
print("No games played yet.")
return
print("\n=== Game Statistics ===")
print(f"Total games: {total}")
print(f"Your wins: {self.scores['player']} ({self.scores['player']/total:.1%})")
print(f"Computer wins: {self.scores['computer']} ({self.scores['computer']/total:.1%})")
print(f"Ties: {self.scores['tie']} ({self.scores['tie']/total:.1%})")
if self.history:
print("\nLast 5 games:")
for game in self.history[-5:]:
outcome = "Tie" if game['result'] == 'tie' else \
"You won" if game['result'] == 'player' else "Computer won"
print(f"{game['timestamp']}: {game['player']} vs {game['computer']} - {outcome}")
# 修改后的play_round函数
def play_round(game):
player = get_player_choice()
computer = get_computer_choice()
print(f"\nYou chose: {player}")
print(f"Computer chose: {computer}")
result = determine_winner(player, computer)
game.record_result(result, player, computer)
# 剩余代码与之前相同...
# 修改后的main函数
def main():
game = RPSGame()
print("Welcome to Rock Paper Scissors!")
while True:
result = play_round(game)
play_again = input("\nPlay again? (y/n/stats): ").lower()
if play_again == 'stats':
game.show_stats()
continue
if play_again != 'y':
break
game.save_history()
game.show_stats()
新增功能包括:
- 使用类封装游戏逻辑,提高代码组织性
- 将比赛记录保存到JSON文件,实现持久化存储
- 添加详细的统计信息,包括胜率和最近比赛记录
- 通过输入"stats"可以随时查看当前统计数据
实操技巧:使用defaultdict可以避免键不存在的判断,简化代码。保存历史记录时限制条数可以防止文件过大。
1.4 实现AI对手策略
基础版本中电脑是纯随机选择,我们可以实现一些简单的AI策略让游戏更有挑战性:
python复制class AIPlayer:
def __init__(self):
self.player_patterns = []
self.last_player_choice = None
def predict(self):
"""根据玩家历史模式预测下一步"""
if not self.player_patterns:
return random.choice(CHOICES)
# 简单模式识别:如果玩家连续两次出同样的,预测第三次还会出
if len(self.player_patterns) >= 2 and \
self.player_patterns[-1] == self.player_patterns[-2]:
return BEATS[self.player_patterns[-1]]
# 统计玩家最常出的选择
counts = {choice: self.player_patterns.count(choice) for choice in CHOICES}
most_common = max(counts.items(), key=lambda x: x[1])[0]
return BEATS[most_common]
def record_player_choice(self, choice):
"""记录玩家选择用于分析"""
self.player_patterns.append(choice)
self.last_player_choice = choice
# 限制记录长度
if len(self.player_patterns) > 20:
self.player_patterns = self.player_patterns[-20:]
# 在RPSGame类中添加AI玩家
class RPSGame:
def __init__(self):
# ...原有代码...
self.ai = AIPlayer()
def get_computer_choice(self):
"""获取电脑选择,可以是随机或AI策略"""
if random.random() < 0.7: # 70%概率使用AI策略
return self.ai.predict()
return random.choice(CHOICES)
def record_result(self, result, player_choice, computer_choice):
# ...原有代码...
self.ai.record_player_choice(player_choice)
这个AI实现了几种简单策略:
- 检测玩家是否重复出同样的选择(人类常见模式)
- 统计玩家最常出的选择并针对性克制
- 保留一定随机性防止被玩家反制
避坑指南:AI策略不宜太强,否则玩家会感到沮丧。保持适当的随机性和可预测性之间的平衡很重要。
1.5 添加图形界面(Tkinter版)
为了让游戏更友好,我们可以使用Tkinter添加简单的图形界面:
python复制import tkinter as tk
from tkinter import messagebox, ttk
class RPSGUI:
def __init__(self, game):
self.game = game
self.root = tk.Tk()
self.root.title("Rock Paper Scissors")
self.create_widgets()
self.root.mainloop()
def create_widgets(self):
"""创建界面组件"""
# 顶部标签
ttk.Label(self.root, text="Make your choice:",
font=('Helvetica', 14)).pack(pady=10)
# 三个选择按钮
button_frame = ttk.Frame(self.root)
button_frame.pack(pady=5)
self.player_choice = None
for choice in CHOICES:
btn = ttk.Button(
button_frame,
text=choice.capitalize(),
command=lambda c=choice: self.play_round(c)
)
btn.pack(side=tk.LEFT, padx=5)
# 结果显示区域
self.result_var = tk.StringVar()
ttk.Label(self.root, textvariable=self.result_var,
font=('Helvetica', 12)).pack(pady=10)
# 统计按钮
ttk.Button(self.root, text="View Stats",
command=self.show_stats).pack(pady=5)
def play_round(self, player_choice):
"""处理一局游戏"""
computer_choice = self.game.get_computer_choice()
result = determine_winner(player_choice, computer_choice)
self.game.record_result(result, player_choice, computer_choice)
# 显示结果
if result == 'tie':
msg = f"Both chose {player_choice}. It's a tie!"
else:
verb = WIN_RULES[player_choice].get(computer_choice, '') or \
WIN_RULES[computer_choice][player_choice]
winner = 'You' if result == 'player' else 'Computer'
msg = f"{winner} win! {player_choice.capitalize()} {verb} {computer_choice}."
self.result_var.set(f"You: {player_choice}\nComputer: {computer_choice}\n\n{msg}")
def show_stats(self):
"""显示统计信息"""
stats_window = tk.Toplevel(self.root)
stats_window.title("Game Statistics")
total = sum(self.game.scores.values())
if total == 0:
ttk.Label(stats_window, text="No games played yet.").pack(pady=10)
return
# 创建统计标签
ttk.Label(stats_window, text="=== Game Statistics ===",
font=('Helvetica', 12, 'bold')).pack(pady=5)
stats_text = f"""
Total games: {total}
Your wins: {self.game.scores['player']} ({self.game.scores['player']/total:.1%})
Computer wins: {self.game.scores['computer']} ({self.game.scores['computer']/total:.1%})
Ties: {self.game.scores['tie']} ({self.game.scores['tie']/total:.1%})
"""
ttk.Label(stats_window, text=stats_text).pack(pady=5)
# 历史记录表格
if self.game.history:
ttk.Label(stats_window, text="Last 5 games:",
font=('Helvetica', 10, 'bold')).pack(pady=5)
columns = ('#', 'Player', 'Computer', 'Result')
tree = ttk.Treeview(stats_window, columns=columns, show='headings')
for col in columns:
tree.heading(col, text=col)
tree.column(col, width=80)
for i, game in enumerate(self.game.history[-5:], 1):
result = "Tie" if game['result'] == 'tie' else \
"Win" if game['result'] == 'player' else "Lose"
tree.insert('', 'end', values=(
i, game['player'], game['computer'], result
))
tree.pack(pady=5)
ttk.Button(stats_window, text="Close",
command=stats_window.destroy).pack(pady=10)
# 修改启动方式
if __name__ == "__main__":
game = RPSGame()
if input("Console (c) or GUI (g)? ").lower() == 'g':
RPSGUI(game)
else:
print("Welcome to Rock Paper Scissors!")
main()
图形界面版特点:
- 使用按钮代替文本输入,操作更直观
- 实时显示对战结果
- 弹出式统计窗口展示详细数据
- 保留所有后台逻辑,只是换了交互方式
开发心得:Tkinter适合快速开发简单GUI,但要注意组件布局和事件处理。将游戏逻辑与界面分离(MVC模式)使代码更易维护。
1.6 测试与调试技巧
在开发过程中,我总结了以下测试方法和常见问题:
单元测试示例:
python复制import unittest
class TestRPSGame(unittest.TestCase):
def test_determine_winner(self):
self.assertEqual(determine_winner('rock', 'scissors'), 'player')
self.assertEqual(determine_winner('paper', 'rock'), 'player')
self.assertEqual(determine_winner('scissors', 'paper'), 'player')
self.assertEqual(determine_winner('rock', 'paper'), 'computer')
self.assertEqual(determine_winner('rock', 'rock'), 'tie')
def test_ai_prediction(self):
ai = AIPlayer()
ai.record_player_choice('rock')
ai.record_player_choice('rock') # 连续两次出石头
self.assertEqual(ai.predict(), 'paper') # 应该出布
ai.record_player_choice('scissors')
ai.record_player_choice('scissors')
ai.record_player_choice('scissors') # 多次出剪刀
self.assertEqual(ai.predict(), 'rock') # 应该出石头
if __name__ == '__main__':
unittest.main()
常见问题排查:
-
输入验证不工作
- 检查
.lower()是否正确处理了大小写 - 确认比较的是完整字符串而非首字母
- 检查
-
胜负判断错误
- 打印出双方选择确认输入正确
- 检查WIN_RULES字典结构是否完整
-
历史记录未保存
- 检查文件写入权限
- 确认保存路径是否正确
- 添加打印语句调试保存过程
-
GUI无响应
- 确保在主线程中运行Tkinter
- 避免在回调函数中执行长时间操作
调试技巧:在关键函数添加print语句输出中间结果,这是调试Python程序最简单有效的方法。对于更复杂的项目,可以使用pdb调试器。
1.7 项目扩展思路
这个基础项目可以进一步扩展:
-
网络对战版
- 使用socket实现双人对战
- 添加房间创建和加入功能
-
机器学习AI
- 收集大量对战数据
- 使用决策树或简单神经网络训练AI模型
-
锦标赛模式
- 实现多轮淘汰赛
- 添加排行榜功能
-
更多手势
- 扩展为"石头剪刀布蜥蜴史波克"等变体
- 需要更新WIN_RULES和选择列表
-
移动端适配
- 使用Kivy框架开发跨平台应用
- 添加触摸手势支持
实现网络对战版的简单示例:
python复制import socket
import threading
class RPSServer:
def __init__(self, port=5000):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.bind(('0.0.0.0', port))
self.server.listen(2)
self.players = []
self.choices = {}
print(f"Server started on port {port}")
self.accept_connections()
def accept_connections(self):
while len(self.players) < 2:
conn, addr = self.server.accept()
self.players.append(conn)
threading.Thread(target=self.handle_client, args=(conn,)).start()
print(f"Player {len(self.players)} connected from {addr}")
self.notify_players("Game is starting! Make your choice.")
def handle_client(self, conn):
while True:
try:
data = conn.recv(1024).decode()
if not data:
break
player_id = self.players.index(conn) + 1
if data in CHOICES:
self.choices[player_id] = data
self.check_round()
except:
break
conn.close()
def check_round(self):
if len(self.choices) == 2:
p1_choice = self.choices[1]
p2_choice = self.choices[2]
result = determine_winner(p1_choice, p2_choice)
if result == 'tie':
msg = "It's a tie!"
else:
winner = 1 if result == 'player' else 2
verb = WIN_RULES[p1_choice].get(p2_choice, '') or WIN_RULES[p2_choice][p1_choice]
msg = f"Player {winner} wins! {p1_choice} {verb} {p2_choice}"
self.notify_players(f"Player 1: {p1_choice}\nPlayer 2: {p2_choice}\n{msg}")
self.choices.clear()
def notify_players(self, msg):
for player in self.players:
try:
player.send(msg.encode())
except:
continue
# 客户端代码类似,需要实现连接和选择发送逻辑
这个网络版实现了基本的双人对战功能,展示了如何将单机游戏扩展为网络应用。实际开发中还需要考虑更多错误处理和超时控制。
通过这个项目,我们不仅实现了一个经典游戏,还实践了Python的核心编程概念。从基础版本到功能增强,再到图形界面和网络扩展,展示了Python语言的灵活性和强大功能。
