1. 项目概述与需求分析
这个石头剪刀布小游戏项目是一个经典的回合制对战游戏实现,采用三局两胜制规则。游戏的核心机制是:玩家与电脑各自出拳(石头、剪刀或布),根据胜负规则判断每回合结果,当任意一方累计胜利达到两次时游戏结束。
核心规则解析:
- 出拳对应数字:石头=0,剪刀=1,布=2
- 胜负判定逻辑:
- 石头(0) 胜 剪刀(1)
- 剪刀(1) 胜 布(2)
- 布(2) 胜 石头(0)
- 相同则为平局
- 胜利条件:先赢得两局的玩家获胜
2. 技术实现方案设计
2.1 基础数据结构设计
首先我们需要定义游戏的基本数据结构。采用面向对象的方式,可以设计以下核心类:
python复制class Game:
def __init__(self):
self.player_score = 0
self.computer_score = 0
self.round = 1
self.choices = {0: "石头", 1: "剪刀", 2: "布"}
2.2 胜负判定算法
胜负判定是游戏的核心逻辑,可以采用数学模运算实现简洁的判断:
python复制def determine_winner(player_choice, computer_choice):
if player_choice == computer_choice:
return "平局"
elif (player_choice - computer_choice) % 3 == 1:
return "电脑获胜"
else:
return "玩家获胜"
这个算法利用了模运算的特性:(玩家选择-电脑选择) mod 3的结果为1时电脑获胜,否则玩家获胜。这种实现比传统的多重if-else更简洁。
2.3 电脑AI设计
为了让游戏更有趣,可以设计不同难度级别的电脑AI:
- 初级AI:完全随机出拳
python复制def computer_choice_easy():
return random.randint(0, 2)
- 中级AI:会记录玩家出拳模式
python复制def computer_choice_medium(player_history):
if len(player_history) < 3:
return random.randint(0, 2)
# 分析玩家最近三次出拳模式
last_three = player_history[-3:]
# 简单预测玩家可能出的拳
predicted = max(set(last_three), key=last_three.count)
# 出能打败预测结果的拳
return (predicted - 1) % 3
- 高级AI:使用马尔可夫链预测
python复制def computer_choice_hard(player_history):
# 构建转移矩阵
transition_matrix = [[0]*3 for _ in range(3)]
for i in range(1, len(player_history)):
prev = player_history[i-1]
curr = player_history[i]
transition_matrix[prev][curr] += 1
if len(player_history) == 0:
return random.randint(0, 2)
last_move = player_history[-1]
# 根据历史数据预测下一步最可能的出拳
predicted = transition_matrix[last_move].index(max(transition_matrix[last_move]))
# 出能打败预测结果的拳
return (predicted - 1) % 3
3. 完整游戏流程实现
3.1 游戏主循环
python复制def game_loop():
game = Game()
player_history = []
while game.player_score < 2 and game.computer_score < 2:
print(f"\n=== 第 {game.round} 局 ===")
# 玩家出拳
while True:
try:
player_input = input("请出拳 (0-石头, 1-剪刀, 2-布): ")
player_choice = int(player_input)
if player_choice in game.choices:
break
print("输入无效,请重新输入!")
except ValueError:
print("请输入数字!")
# 电脑出拳
computer_choice = computer_choice_medium(player_history)
player_history.append(player_choice)
print(f"玩家出: {game.choices[player_choice]}")
print(f"电脑出: {game.choices[computer_choice]}")
# 判断胜负
result = determine_winner(player_choice, computer_choice)
if result == "玩家获胜":
game.player_score += 1
elif result == "电脑获胜":
game.computer_score += 1
print(f"结果: {result}")
print(f"比分: 玩家 {game.player_score} - {game.computer_score} 电脑")
game.round += 1
# 游戏结束
print("\n=== 游戏结束 ===")
if game.player_score > game.computer_score:
print("恭喜玩家获胜!")
else:
print("电脑获胜!")
3.2 用户界面优化
为了让游戏体验更好,可以添加以下改进:
- 输入验证增强:
python复制def get_player_choice():
while True:
try:
player_input = input("请出拳 (0-石头, 1-剪刀, 2-布): ").strip().lower()
if player_input in ["0", "石头", "rock"]:
return 0
elif player_input in ["1", "剪刀", "scissors"]:
return 1
elif player_input in ["2", "布", "paper"]:
return 2
print("输入无效,请重新输入!")
except:
print("请输入有效选项!")
- 彩色输出(使用ANSI颜色代码):
python复制def colored_text(text, color):
colors = {
"red": "\033[91m",
"green": "\033[92m",
"yellow": "\033[93m",
"blue": "\033[94m",
"end": "\033[0m"
}
return f"{colors[color]}{text}{colors['end']}"
- 游戏统计功能:
python复制class GameStats:
def __init__(self):
self.total_games = 0
self.player_wins = 0
self.computer_wins = 0
self.draws = 0
self.player_choices = [0, 0, 0] # 石头,剪刀,布
def update(self, result, player_choice):
self.total_games += 1
if result == "玩家获胜":
self.player_wins += 1
elif result == "电脑获胜":
self.computer_wins += 1
else:
self.draws += 1
self.player_choices[player_choice] += 1
def show_stats(self):
print("\n=== 游戏统计 ===")
print(f"总局数: {self.total_games}")
print(f"玩家胜率: {self.player_wins/self.total_games*100:.1f}%")
print(f"电脑胜率: {self.computer_wins/self.total_games*100:.1f}%")
print(f"平局率: {self.draws/self.total_games*100:.1f}%")
print("玩家出拳偏好:")
print(f" 石头: {self.player_choices[0]}次")
print(f" 剪刀: {self.player_choices[1]}次")
print(f" 布: {self.player_choices[2]}次")
4. 高级功能扩展
4.1 游戏回放系统
实现游戏记录和回放功能:
python复制class GameReplay:
def __init__(self):
self.history = []
def record_round(self, round_data):
self.history.append(round_data)
def save_to_file(self, filename):
with open(filename, 'w') as f:
json.dump(self.history, f)
def load_from_file(self, filename):
with open(filename, 'r') as f:
self.history = json.load(f)
def replay(self):
for i, round_data in enumerate(self.history, 1):
print(f"\n=== 回放第 {i} 局 ===")
print(f"玩家出: {round_data['player_choice']}")
print(f"电脑出: {round_data['computer_choice']}")
print(f"结果: {round_data['result']}")
input("按Enter继续...")
4.2 网络对战功能
使用socket实现简单的网络对战:
python复制import socket
class NetworkGame:
def __init__(self, host=False, port=12345):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if host:
self.sock.bind(('0.0.0.0', port))
self.sock.listen(1)
print("等待对手连接...")
self.conn, _ = self.sock.accept()
else:
self.sock.connect(('localhost', port))
self.conn = self.sock
def send_choice(self, choice):
self.conn.send(str(choice).encode())
def receive_choice(self):
return int(self.conn.recv(1024).decode())
def close(self):
self.conn.close()
4.3 图形界面实现
使用tkinter实现图形界面:
python复制import tkinter as tk
from PIL import Image, ImageTk
class GUI:
def __init__(self):
self.root = tk.Tk()
self.root.title("石头剪刀布")
self.game = Game()
# 加载图片
self.images = {
0: ImageTk.PhotoImage(Image.open("rock.png").resize((100,100))),
1: ImageTk.PhotoImage(Image.open("scissors.png").resize((100,100))),
2: ImageTk.PhotoImage(Image.open("paper.png").resize((100,100)))
}
# 创建界面元素
self.create_widgets()
def create_widgets(self):
# 比分显示
self.score_label = tk.Label(self.root, text="玩家 0 - 0 电脑", font=("Arial", 16))
self.score_label.pack(pady=10)
# 电脑选择显示
self.computer_label = tk.Label(self.root, text="电脑出拳:")
self.computer_label.pack()
self.computer_image = tk.Label(self.root)
self.computer_image.pack()
# 玩家按钮
btn_frame = tk.Frame(self.root)
btn_frame.pack(pady=20)
self.rock_btn = tk.Button(btn_frame, image=self.images[0],
command=lambda: self.play_round(0))
self.rock_btn.grid(row=0, column=0, padx=10)
self.scissors_btn = tk.Button(btn_frame, image=self.images[1],
command=lambda: self.play_round(1))
self.scissors_btn.grid(row=0, column=1, padx=10)
self.paper_btn = tk.Button(btn_frame, image=self.images[2],
command=lambda: self.play_round(2))
self.paper_btn.grid(row=0, column=2, padx=10)
# 结果显示
self.result_label = tk.Label(self.root, text="", font=("Arial", 14))
self.result_label.pack(pady=10)
def play_round(self, player_choice):
computer_choice = random.randint(0, 2)
self.computer_image.config(image=self.images[computer_choice])
result = determine_winner(player_choice, computer_choice)
if result == "玩家获胜":
self.game.player_score += 1
elif result == "电脑获胜":
self.game.computer_score += 1
self.score_label.config(text=f"玩家 {self.game.player_score} - {self.game.computer_score} 电脑")
self.result_label.config(text=f"结果: {result}")
if self.game.player_score >= 2 or self.game.computer_score >= 2:
self.end_game()
def end_game(self):
if self.game.player_score > self.game.computer_score:
message = "恭喜玩家获胜!"
else:
message = "电脑获胜!"
self.result_label.config(text=message)
# 禁用按钮
self.rock_btn.config(state=tk.DISABLED)
self.scissors_btn.config(state=tk.DISABLED)
self.paper_btn.config(state=tk.DISABLED)
# 添加重新开始按钮
restart_btn = tk.Button(self.root, text="重新开始", command=self.restart)
restart_btn.pack(pady=10)
def restart(self):
self.game = Game()
self.score_label.config(text="玩家 0 - 0 电脑")
self.result_label.config(text="")
self.computer_image.config(image="")
# 启用按钮
self.rock_btn.config(state=tk.NORMAL)
self.scissors_btn.config(state=tk.NORMAL)
self.paper_btn.config(state=tk.NORMAL)
def run(self):
self.root.mainloop()
5. 测试与调试
5.1 单元测试
为关键功能编写单元测试:
python复制import unittest
class TestGameLogic(unittest.TestCase):
def test_determine_winner(self):
# 测试平局
self.assertEqual(determine_winner(0, 0), "平局")
self.assertEqual(determine_winner(1, 1), "平局")
self.assertEqual(determine_winner(2, 2), "平局")
# 测试玩家获胜
self.assertEqual(determine_winner(0, 1), "玩家获胜") # 石头胜剪刀
self.assertEqual(determine_winner(1, 2), "玩家获胜") # 剪刀胜布
self.assertEqual(determine_winner(2, 0), "玩家获胜") # 布胜石头
# 测试电脑获胜
self.assertEqual(determine_winner(1, 0), "电脑获胜") # 剪刀输石头
self.assertEqual(determine_winner(2, 1), "电脑获胜") # 布输剪刀
self.assertEqual(determine_winner(0, 2), "电脑获胜") # 石头输布
class TestAI(unittest.TestCase):
def test_computer_ai(self):
# 测试初级AI
choices = set()
for _ in range(100):
choices.add(computer_choice_easy())
self.assertEqual(choices, {0, 1, 2})
# 测试中级AI
player_history = [0, 0, 0] # 玩家一直出石头
choice = computer_choice_medium(player_history)
self.assertEqual(choice, 2) # 电脑应该出布
if __name__ == '__main__':
unittest.main()
5.2 性能优化
对于高频次游戏,可以考虑以下优化:
- 使用位运算替代模运算:
python复制def determine_winner_optimized(player, computer):
# 使用位运算优化
return ((player - computer + 3) % 3) - 1
- 缓存电脑AI计算结果:
python复制from functools import lru_cache
@lru_cache(maxsize=128)
def computer_choice_hard_cached(player_history_tuple):
# 将历史记录转换为元组以便缓存
return computer_choice_hard(list(player_history_tuple))
- 使用numpy加速统计计算:
python复制import numpy as np
def analyze_patterns_numpy(history):
arr = np.array(history)
transition_matrix = np.zeros((3,3), dtype=int)
for i in range(1, len(arr)):
transition_matrix[arr[i-1], arr[i]] += 1
return transition_matrix
6. 项目部署与发布
6.1 打包为可执行文件
使用PyInstaller将游戏打包为独立可执行文件:
bash复制pyinstaller --onefile --windowed rock_paper_scissors.py
6.2 创建Web版本
使用Pyodide或Transcrypt将Python代码转换为JavaScript:
html复制<!DOCTYPE html>
<html>
<head>
<title>石头剪刀布</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
</head>
<body>
<h1>石头剪刀布游戏</h1>
<button onclick="play(0)">石头</button>
<button onclick="play(1)">剪刀</button>
<button onclick="play(2)">布</button>
<div id="result"></div>
<script>
async function main() {
let pyodide = await loadPyodide();
await pyodide.loadPackage("micropip");
await pyodide.runPythonAsync(`
import micropip
await micropip.install("numpy")
def determine_winner(player, computer):
if player == computer:
return "平局"
elif (player - computer) % 3 == 1:
return "电脑获胜"
else:
return "玩家获胜"
`);
window.play = async function(playerChoice) {
let computerChoice = Math.floor(Math.random() * 3);
let result = await pyodide.runPythonAsync(`
determine_winner(${playerChoice}, ${computerChoice})
`);
document.getElementById("result").innerHTML = `
玩家出: ${['石头','剪刀','布'][playerChoice]}<br>
电脑出: ${['石头','剪刀','布'][computerChoice]}<br>
结果: ${result}
`;
};
}
main();
</script>
</body>
</html>
6.3 移动端适配
使用Kivy框架创建跨平台移动应用:
python复制from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.image import Image
import random
class RPSApp(App):
def build(self):
self.player_score = 0
self.computer_score = 0
self.choices = ["石头", "剪刀", "布"]
layout = BoxLayout(orientation='vertical')
self.score_label = Label(text="玩家 0 - 0 电脑", font_size=24)
layout.add_widget(self.score_label)
self.computer_image = Image(source='question.png')
layout.add_widget(self.computer_image)
btn_layout = BoxLayout()
self.rock_btn = Button(text="石头", on_press=lambda x: self.play(0))
self.scissors_btn = Button(text="剪刀", on_press=lambda x: self.play(1))
self.paper_btn = Button(text="布", on_press=lambda x: self.play(2))
btn_layout.add_widget(self.rock_btn)
btn_layout.add_widget(self.scissors_btn)
btn_layout.add_widget(self.paper_btn)
layout.add_widget(btn_layout)
self.result_label = Label(text="", font_size=20)
layout.add_widget(self.result_label)
return layout
def play(self, player_choice):
computer_choice = random.randint(0, 2)
self.computer_image.source = [
'rock.png', 'scissors.png', 'paper.png'
][computer_choice]
result = determine_winner(player_choice, computer_choice)
if result == "玩家获胜":
self.player_score += 1
elif result == "电脑获胜":
self.computer_score += 1
self.score_label.text = f"玩家 {self.player_score} - {self.computer_score} 电脑"
self.result_label.text = f"结果: {result}"
if self.player_score >= 2 or self.computer_score >= 2:
self.end_game()
def end_game(self):
if self.player_score > self.computer_score:
message = "恭喜玩家获胜!"
else:
message = "电脑获胜!"
self.result_label.text = message
self.rock_btn.disabled = True
self.scissors_btn.disabled = True
self.paper_btn.disabled = True
restart_btn = Button(text="重新开始", on_press=self.restart)
self.root.add_widget(restart_btn)
def restart(self, instance):
self.player_score = 0
self.computer_score = 0
self.score_label.text = "玩家 0 - 0 电脑"
self.result_label.text = ""
self.computer_image.source = 'question.png'
self.rock_btn.disabled = False
self.scissors_btn.disabled = False
self.paper_btn.disabled = False
self.root.remove_widget(instance)
if __name__ == '__main__':
RPSApp().run()
7. 项目总结与扩展思路
在实现这个石头剪刀布游戏的过程中,有几个关键点值得注意:
-
胜负判定算法的选择会直接影响代码的简洁性和可读性。模运算方法虽然简洁,但对于初学者可能不太直观,需要适当注释。
-
电脑AI设计的复杂度应该与目标用户匹配。对于休闲玩家,简单随机可能就足够了;而对于想要挑战的玩家,可以增加更智能的预测算法。
-
用户输入验证是保证游戏稳定性的关键。必须考虑所有可能的错误输入情况。
扩展思路:
-
多玩家对战:可以扩展为支持多个玩家同时游戏的版本,采用淘汰赛制。
-
特殊道具系统:引入道具卡,如"重赛卡"、"双倍积分卡"等增加游戏趣味性。
-
成就系统:设置各种成就,如"连胜3局"、"使用每种手势获胜"等,增加游戏粘性。
-
手势识别:结合OpenCV实现真实手势识别,让玩家可以通过摄像头实际做出手势来游戏。
-
区块链积分:为游戏添加区块链积分系统,获胜可以获得通证奖励(需注意合规性)。
