📌 版本说明:本教程支持 Python 3.8+,其中match-case特性需要 Python 3.10+,如果您使用旧版本,文末提供了兼容写法。

🎯 教程说明:本教程分为基础部分进阶部分,零基础用户可以只学习基础部分完成游戏,有基础的用户可以深入学习测试与模块化设计。

创建的文件

  1. guess_number.py - 游戏主程序

    • 包含三个难度级别:简单 (1-10)、中等 (1-50)、困难 (1-100)

    • 支持模式匹配(Python3.10+)处理用户输入

    • 完善的输入验证(非数字、超出范围等)

    • 支持单局游戏和多局游戏循环,支持中途退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import random
from typing import Tuple

Difficulties = {
    "easy": (1, 10, 6),
    "medium": (1, 50, 4),
    "hard": (1, 100, 3)
}


def choose_difficulty() -> Tuple[int, int, int]:
    print("请选择难度级别:")
    for level, (min_n, max_n, max_attempts) in Difficulties.items():
        print(f"- {level}: 范围 {min_n}-{max_n},最大尝试次数 {max_attempts}")

    while True:
        choice = input("\n请输入难度级别 (easy/medium/hard) 或输入 quit 退出: ").lower()

        match choice:
            case "easy" | "medium" | "hard" as level:
                return Difficulties[level]
            case "quit":
                print("游戏已退出。")
                exit(0)
            case _:
                print("无效的难度级别,请重新输入。")

def game_engine(min_num: int, max_num: int, max_attempts: int) -> None:
    secret_number = random.randint(min_num, max_num)
    attempt_count = 0
    guess_history = []

    while attempt_count < max_attempts:
        try:
            user_input = input(f"请输入 {min_num}{max_num} 之间的数字(输入q退出): ")
            if user_input.lower() == 'q':
                print("游戏已退出。")
                return

            guess = int(user_input)

            if str(user_input).startswith('0') and len(str(user_input)) > 1:
                print("错误:不能输入以0开头的数字,请重新输入。")
                continue

            if guess < min_num or guess > max_num:
                print(f"错误:数字必须在 {min_num}{max_num} 之间,请重新输入。")
                continue

            attempt_count += 1
            guess_history.append(guess)

            if guess == secret_number:
                print(f"恭喜你!你猜对了数字 {secret_number},只用了 {attempt_count} 次尝试。")
                print(f"你的猜测历史:{guess_history}")
                return
            elif guess < secret_number:
                print("猜小了,再试一次!")
            else:
                print("猜大了,再试一次!")

        except ValueError:
            print("错误:请输入有效的整数。")


    print(f"\n很遗憾,你已经用完了 {max_attempts} 次尝试机会。")
    print(f"正确答案是:{secret_number}")
    print(f"你的猜测历史:{guess_history}")

def main() -> None:
    print("===== 欢迎来到猜数字游戏 =====")
    print("规则:系统将生成一个随机整数,你需要在有限次数内猜出这个数字。")
    while True:
        min_num, max_num, max_attempts = choose_difficulty()
       
        print(f"\n本次游戏范围:{min_num}{max_num}")
        print(f"最大尝试次数:{max_attempts}")

        game_engine(min_num, max_num, max_attempts)

        while True:
            playAgain = input("\n是否再次游戏?(输入 yes 继续,输入 no 退出): ").lower()

            match playAgain:
                case "yes":
                    print("\n" + "="*30 + "\n")
                    break
                case "no":
                    print("\n游戏结束,感谢游玩!")
                    return
                case _:
                    print("无效输入,请输入 yes 或 no。")

if __name__ == "__main__":
    main()
  1. test_guess_number.py - 进阶测试文件(可选,有基础用户可学习)

    • 包含 8 个测试用例

    • 覆盖难度配置、正确 / 错误猜测、边界条件、异常输入等场景

    • 所有测试通过 ✅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import pytest
from io import StringIO
from unittest.mock import patch
import sys
from guess_number import Difficulties, choose_difficulty, game_engine

def test_difficulties_config():
    difficulties = {
        "easy": (1, 10, 6),
        "medium": (1, 50, 4),
        "hard": (1, 100, 3)
    }
    assert Difficulties == difficulties

def test_difficulty_choice():
    inputs = iter(["easy"])
    with patch('builtins.input', side_effect=inputs):
        min_n, max_n, max_attempts = choose_difficulty()
        assert (min_n, max_n, max_attempts) == (1, 10, 6)

    inputs = iter(["invalid", "medium"])
    with patch('builtins.input', side_effect=inputs):
        min_n, max_n, max_attempts = choose_difficulty()
        assert (min_n, max_n, max_attempts) == (1, 50, 4)

def test_game_engine_correct_guess():
    with patch('random.randint', return_value=5):
        inputs = iter(["5"])
        with patch('builtins.input', side_effect=inputs):
            with patch('sys.stdout', new=StringIO()) as output:
                game_engine(1, 10, 3)
                assert "恭喜你!" in output.getvalue()
                assert "猜对了数字 5" in output.getvalue()

def test_game_engine_wrong_guess():
    with patch('random.randint', return_value=5):
        inputs = iter(["3", "7", "5"])
        with patch('builtins.input', side_effect=inputs):
            with patch('sys.stdout', new=StringIO()) as output:
                game_engine(1, 10, 3)
                assert "猜小了" in output.getvalue()
                assert "猜大了" in output.getvalue()
                assert "恭喜你!" in output.getvalue()

def test_game_engine_max_attempts():
    with patch('random.randint', return_value=5):
        inputs = iter(["1", "2", "3"])
        with patch('builtins.input', side_effect=inputs):
            with patch('sys.stdout', new=StringIO()) as output:
                game_engine(1, 10, 3)
                assert "很遗憾" in output.getvalue()
                assert "正确答案是:5" in output.getvalue()

def test_invalid_input():
    with patch('random.randint', return_value=5):
        inputs = iter(["abc", "5"])
        with patch('builtins.input', side_effect=inputs):
            with patch('sys.stdout', new=StringIO()) as output:
                game_engine(1, 10, 3)
                assert "请输入有效的整数" in output.getvalue()
                assert "恭喜你!" in output.getvalue()

def test_out_of_range_input():
    with patch('random.randint', return_value=5):
        inputs = iter(["15", "5"])
        with patch('builtins.input', side_effect=inputs):
            with patch('sys.stdout', new=StringIO()) as output:
                game_engine(1, 10, 3)
                assert "数字必须在 1 到 10 之间" in output.getvalue()
                assert "恭喜你!" in output.getvalue()

def test_octal_input():
    with patch('random.randint', return_value=8):
        inputs = iter(["08", "8"])
        with patch('builtins.input', side_effect=inputs):
            with patch('sys.stdout', new=StringIO()) as output:
                game_engine(1, 10, 3)
                assert "不能输入以0开头的数字" in output.getvalue()
                assert "恭喜你!" in output.getvalue()

==提示:test_guess_number.py 和 guess_number.py 需要在同一目录==

运行方式

你可以通过以下命令运行游戏:

1
python guess_number.py

零基础用户:以上就是你需要的全部运行命令,下面的测试命令是进阶内容,你可以暂时跳过。
有基础用户:可以运行以下命令执行测试:

1
python -m pytest test_guess_number.py -v

Python 猜数字游戏


猜数字游戏作为编程入门的经典项目,不仅能帮助初学者掌握 Python 的基础语法,还能深入理解程序逻辑和控制流的设计。本文将从需求分析、逻辑实现到测试验证,系统地指导你构建一个功能完整且交互友好的猜数字游戏,包含难度分级、输入验证等特性。

1. 需求分析

1.1 核心功能需求

猜数字游戏的核心功能包括:

  • 随机数生成:系统随机生成一个指定范围内的整数作为目标数字

  • 用户交互:接收玩家输入的猜测数字,提供友好的提示信息,支持中途退出

  • 猜测结果判断:比较玩家输入与目标数字,给出 “过大”、”过小” 或 “猜对” 的反馈

  • 次数统计:记录玩家的猜测次数,在游戏结束时显示

  • 游戏循环控制:支持单局游戏循环和多局游戏循环

1.2 扩展功能需求

为了提升游戏的可玩性和教学价值,我们添加以下扩展功能:

  • 难度分级:提供简单(1-10)、中等(1-50)和困难(1-100)三种难度级别,不同难度对应不同的数字范围和最大尝试次数

  • 输入验证:确保玩家输入的是有效数字,范围在指定范围内,且处理非数字输入的情况

  • 模式匹配:使用 Python 3.10+ 的 match-case 语句简化用户命令和难度选择的处理(旧版本可兼容)

  • 游戏结束选项:单局游戏结束后,询问玩家是否继续游戏

1.3 系统架构

游戏整体采用模块化设计,包含以下主要模块:

  • 主程序模块:负责游戏启动、难度选择、多局游戏循环控制

  • 游戏引擎模块:实现单局游戏的核心逻辑,包括随机数生成、猜测循环、结果判断等

  • 用户交互模块:处理用户输入和系统输出,提供友好的交互体验

  • 配置模块:存储游戏难度参数

  • 测试模块:(进阶)验证游戏各功能模块的正确性

2. 游戏逻辑实现

零基础用户:你可以直接复制下面的代码到文件中运行,我们会逐步解释每一部分的作用。

2.1 难度配置与选择

我们使用字典存储不同难度级别的参数,并通过 match-case 语句处理难度选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import random
from typing import Tuple

# 游戏难度配置:键为难度名,值为(最小值, 最大值, 最大尝试次数)
Difficulties = {
"easy": (1, 10, 6), # 简单:1-10,6次机会
"medium": (1, 50, 10), # 中等:1-50,10次机会
"hard": (1, 100, 15) # 困难:1-100,15次机会
}

def choose_difficulty() -> Tuple[int, int, int]:
"""让用户选择难度级别,并返回对应的参数"""
print("请选择难度级别:")
for level, (min_n, max_n, max_attempts) in Difficulties.items():
print(f"- {level}: 范围 {min_n}-{max_n},最大尝试次数 {max_attempts}")
while True:
choice = input("\n请输入难度级别 (easy/medium/hard) 或输入 quit 退出: ").lower()
# 使用模式匹配处理用户输入(Python3.10+支持)
match choice:
case "easy" | "medium" | "hard" as level:
return Difficulties[level]
case "quit":
print("游戏已退出。")
exit(0)
case _:
print("无效的难度级别,请重新输入。")

旧版本兼容写法:如果你使用的 Python 低于 3.10,可以把上面的 match-case 部分替换为:

1
2
3
4
5
6
7
8

if choice in ["easy", "medium", "hard"]:
return Difficulties[choice]
elif choice == "quit":
print("游戏已退出。")
exit(0)
else:
print("无效的难度级别,请重新输入。")

2.2 游戏引擎实现

游戏引擎负责单局游戏的逻辑控制,包括随机数生成、猜测循环和结果判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

def game_engine(min_num: int, max_num: int, max_attempts: int) -> None:
"""单局游戏引擎,负责猜测循环和结果判断"""
# 生成目标数字:random.randint会返回包含min_num和max_num的整数
secret_number = random.randint(min_num, max_num)
attempt_count = 0
guess_history = []

while attempt_count < max_attempts:
try:
# 获取用户输入,先处理退出命令
user_input = input(f"请输入 {min_num}{max_num} 之间的数字(输入q退出): ").strip()
# 处理用户的退出请求
if user_input.lower() == 'q':
print("已退出本局游戏。")
return

# 转换为整数
guess = int(user_input)

# 检查输入范围
if guess < min_num or guess > max_num:
print(f"错误:数字必须在 {min_num}{max_num} 之间,请重新输入。")
continue

attempt_count += 1
guess_history.append(guess)

# 判断猜测结果
if guess == secret_number:
print(f"恭喜你!你猜对了数字 {secret_number},只用了 {attempt_count} 次尝试。")
print(f"你的猜测历史:{guess_history}")
return
elif guess < secret_number:
print("猜小了,再试一次!")
else:
print("猜大了,再试一次!")
except ValueError:
# 处理非数字输入
print("错误:请输入有效的整数,或者输入q退出。")

# 用户用完所有尝试次数仍未猜中
print(f"\n很遗憾,你已经用完了 {max_attempts} 次尝试机会。")
print(f"正确答案是:{secret_number}")
print(f"你的猜测历史:{guess_history}")

零基础小提示:这里的try...except是用来处理用户输入错误的情况,比如用户输入了字母,我们就提示他重新输入,而不是让程序直接崩溃。

2.3 主程序控制流

主程序负责游戏的整体流程控制,包括难度选择、单局游戏循环和多局游戏循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

def main() -> None:
"""游戏主程序,控制整体游戏流程"""
print("===== 欢迎来到猜数字游戏 =====")
print("规则:系统将生成一个随机整数,你需要在有限次数内猜出这个数字。")
while True:
# 选择难度级别
min_num, max_num, max_attempts = choose_difficulty()
print(f"\n本次游戏范围:{min_num}{max_num}")
print(f"最大尝试次数:{max_attempts}")

# 开始单局游戏
game_engine(min_num, max_num, max_attempts)

# 游戏结束后询问是否继续
while True:
playAgain = input("\n是否再次游戏?(输入 yes 继续,输入 no 退出): ").lower()
# 使用模式匹配处理继续游戏选项
match playAgain:
case "yes":
print("\n" + "="*30 + "\n") # 分隔符
break
case "no":
print("\n游戏结束,感谢游玩!")
return
case _:
print("无效输入,请输入 yes 或 no。")

# 当这个文件被直接运行时,执行主程序
if __name__ == "__main__":
main()

零基础小提示:if __name__ == "__main__":的作用是,只有当你直接运行这个文件的时候,才会执行游戏,如果这个文件被其他文件导入的话,就不会自动运行,这是 Python 的常用写法。

3. 进阶:游戏测试📚

零基础用户:这部分是进阶内容,你可以暂时跳过,等你掌握了基础之后再来学习。
有基础用户:我们使用 Pytest 作为测试框架,验证游戏的各个功能是否正常。

3.1 测试代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

import pytest
import os
from io import StringIO
from unittest.mock import patch
from guess_number import main, game_engine, choose_difficulty, Difficulties

def test_difficulties_config():
"""测试难度级别配置是否正确"""
difficulties = {
"easy": (1, 10, 6),
"medium": (1, 50, 10),
"hard": (1, 100, 15)
}
assert Difficulties == difficulties

def test_normal_game_flow():
"""测试正常游戏流程,猜测正确时游戏结束"""
# 模拟输入:8(正确猜测)
inputs = iter(["8"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
game_engine(1, 10, 3)
assert "恭喜你!" in output.getvalue()
assert "猜对了数字 8" in output.getvalue()

def test_boundary_conditions():
"""测试范围边界值和最大尝试次数的场景"""
# 测试最小值
inputs = iter(["1"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
game_engine(1, 10, 3)
assert "恭喜你!" in output.getvalue()
# 测试最大值
inputs = iter(["10"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
game_engine(1, 10, 3)
assert "恭喜你!" in output.getvalue()
# 测试达到最大尝试次数
inputs = iter(["2", "3", "4"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
game_engine(1, 10, 3)
assert "很遗憾" in output.getvalue()
assert "用完了 3 次尝试机会" in output.getvalue()

def test_invalid_input():
"""测试游戏对各种异常输入的处理能力"""
# 测试非数字输入
inputs = iter(["abc", "8"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
game_engine(1, 10, 3)
assert "请输入有效的整数" in output.getvalue()
assert "恭喜你!" in output.getvalue()
# 测试超出范围的输入
inputs = iter(["15", "5"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
game_engine(1, 10, 3)
assert "数字必须在 1 到 10 之间" in output.getvalue()
assert "恭喜你!" in output.getvalue()

def test_difficulty_choice():
"""测试难度选择功能是否正确响应用户输入"""
# 测试有效难度级别输入
inputs = iter(["easy"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
min_n, max_n, max_attempts = choose_difficulty()
assert (min_n, max_n, max_attempts) == (1, 10, 6)
assert "范围 1-10" in output.getvalue()
# 测试无效输入
inputs = iter(["invalid", "easy"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
choose_difficulty()
assert "无效的难度级别" in output.getvalue()
# 测试退出命令
inputs = iter(["quit"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
with pytest.raises(SystemExit):
choose_difficulty()
assert "游戏已退出" in output.getvalue()

def test_quit_command():
"""测试中途退出命令是否正常工作"""
inputs = iter(["q"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
game_engine(1, 10, 3)
assert "已退出本局游戏" in output.getvalue()