当前位置: 首页 > web >正文

Python案例解析 : 函数模块化编程的实践应用

Python实战案例 : 21点 小游戏


文章目录

  • Python实战案例 : 21点 小游戏
    • 案例背景
    • 一、案例源码
    • 二、函数设计
      • 1. 基础工具函数:牌堆操作与手牌生成
      • 2. 计分逻辑:`get_score(cards)`
      • 3. 玩家交互:生成器与回合控制
      • 4. 庄家(电脑)逻辑:自动化决策
      • 5. 日志系统:装饰器的应用
    • 三、主函数流程
    • 四、关键编程思想总结
      • 1. 单一职责原则
      • 2. 代码复用与模块化
      • 3. 状态管理
      • 4. 进阶特性应用
    • 五、案例总结


案例背景

在掌握函数的基础语法(定义、参数、返回值)、函数复用、作用域等核心概念后,
我们需要通过一个综合案例来理解如何将复杂问题拆解为模块化的函数设计思想。
本文以21点扑克牌游戏为例,通过分析代码中函数的设计逻辑与协作方式,深入理解函数在实际项目中的应用场景。

一、案例源码

# 定义扑克牌花色和数字列表
list1 = ['黑桃', '红桃', '梅花', '方块']  # 四种花色
list2 = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']  # 13种牌面
# 初始化牌堆列表
list3 = [i + j for i in list1 for j in list2]  def get_card(list3):"""从牌堆中随机抽取一张牌并移除,如果牌堆为空则重新生成参数: list3 - 当前牌堆列表返回: 抽取的牌字符串"""import randomif not list3:  # 检查牌堆是否为空print("牌堆已空")# 重新生成完整牌堆list3.clear()list3.extend([i + j for i in list1 for j in list2])  # 生成所有可能的牌组合print("重新洗牌")   card = random.choice(list3)  # 随机选择一张牌list3.remove(card)  # 从牌堆中移除该牌print('当前牌堆剩余纸牌数目: {}'.format(len(list3)))  # 显示剩余牌数return card  # 返回抽取的牌def get_user_cards(num, list3):"""获取指定数量的手牌参数: num - 需要获取的牌数,list3 - 牌堆列表返回: 包含指定数量牌的列表"""user_cards = []for _ in range(num):user_cards.append(get_card(list3))  # 循环抽取指定数量的牌return user_cardsdef get_score(cards):"""计算手牌的21点分数参数: cards - 手牌列表返回: 计算后的分数"""score = 0aces = 0  # 记录A的数量for card in cards:# 提取牌面值rank = card[2:]  # 例如"红桃10"取"10","黑桃A"取"A"if rank in ['J', 'Q', 'K']:  # 人头牌计10分score += 10elif rank == 'A':  # A先计11分aces += 1score += 11else:  # 数字牌按面值计分score += int(rank)# 处理A的灵活计分(当总分超过21时,将A视为1分)while score > 21 and aces > 0:score -= 10  # 将A从11分转为1分,相当于总分减10aces -= 1return scoredef user_card_gen(user_cards, list3):"""玩家要牌生成器参数: user_cards - 当前手牌,list3 - 牌堆列表生成: 更新后的手牌列表"""while True:new_card = get_card(list3)  # 抽取新牌user_cards.append(new_card)print(f'抽到新牌: {new_card}')yield user_cards  # 使用生成器保持状态,每次yield返回当前手牌def user_play(user_cards, list3):"""玩家回合处理参数: user_cards - 初始手牌,list3 - 牌堆列表返回: 最终手牌列表"""print('玩家回合'.center(30, '-'))user_gen = user_card_gen(user_cards, list3)  # 初始化生成器while True:current_score = get_score(user_cards)print('当前手牌:', user_cards, '当前分数:', current_score)if current_score > 21:  # 先检查是否已爆牌print('爆牌!')return user_cardsif current_score == 21:  # 检查是否已达到21点print('恭喜你,已是最优点数:21点!') print('已自动为你停止要牌')return user_cardschoice = input('是否要牌?(y/n): ').strip().lower()if choice == 'y':print('~~玩家选择继续要牌~~')user_cards = next(user_gen)  # 通过生成器获取新牌if get_score(user_cards) > 21:  # 抽牌后检查分数return user_cardselif choice == 'n':print('~~玩家停止要牌~~')return user_cardselse:print('输入无效,请重新输入')def print_pc_cards(computer_cards):"""打印庄家包含隐藏牌面的手牌参数: computer_cards - 庄家手牌列表返回: 隐藏第二张牌的手牌列表"""# 复制当前手牌用于显示pc_cards = computer_cards.copy() pc_cards[1] = '暗牌'  # 隐藏第二张牌# 返回庄家手牌(隐藏第二张牌)return pc_cards def computer_play(computer_cards, list3):"""庄家回合处理(根据规则自动要牌)参数: computer_cards - 初始手牌,list3 - 牌堆列表返回: 最终手牌列表"""print('庄家回合'.center(30, '-'))print('庄家手牌:', print_pc_cards(computer_cards))while get_score(computer_cards) < 17:  # 分数小于17必须要牌new_card = get_card(list3)computer_cards.append(new_card)print(f'庄家要牌: {new_card}')# 这里使用了一个lambda函数来打印庄家的手牌pc_cards = (lambda x: print_pc_cards(x))(computer_cards)  print('庄家当前手牌:', pc_cards)return computer_cards# 游戏记录装饰器
def game_logger(func):"""记录游戏结果的装饰器,将结果写入日志文件"""import timedef wrapper(*args, **kwargs):result = func(*args, **kwargs)# 构建日志内容log_text = (f'时间: {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}\n'f"游戏结果: {result.get('winner', '无')}\n"f"玩家分数: {result.get('player_score', 0)}\n"f"庄家分数: {result.get('computer_score', 0)}\n"f"玩家手牌: {', '.join(result.get('player_cards', []))}\n"f"庄家手牌: {', '.join(result.get('computer_cards', []))}\n""-----------------------------\n")# 追加写入日志文件with open("game_logs.txt", "a", encoding="utf-8") as f:f.write(log_text)return resultreturn wrapper@game_logger
def play_game():"""主游戏函数,控制游戏流程"""import timeprint("欢迎来到21点游戏!\n")print("游戏规则".center(30, '='))print('''1. A可算作1或112. J、Q、K算10点3. 超过21点直接判负4. 庄家(电脑)分数小于17必须继续要牌5. 玩家可以选择要牌或停牌 ''')print("游戏开始".center(30, '='))# 初始化游戏结果字典game_result = {'winner': None,'player_score': 0,'computer_score': 0,'player_cards': [],'computer_cards': []}# 初始化牌堆print('洗牌中...')# get_card(list3)time.sleep(2)  # 模拟洗牌过程print('洗牌完成')print('当前牌堆剩余纸牌数目: {}'.format(len(list3)))# 发牌阶段print('玩家发牌中...')user = get_user_cards(2, list3)  # 玩家初始两张time.sleep(1)print('庄家发牌中...')computer = get_user_cards(2, list3)  # 庄家初始两张computer_score = get_score(computer)game_result.update({  # 记录庄家初始卡牌信息'computer_score': computer_score,'computer_cards': computer.copy()})time.sleep(1)# 显示初始牌局print('初始手牌'.center(30, '='))print('玩家手牌:', user, '当前分数:', get_score(user))print('庄家手牌: ', print_pc_cards(computer))  # 隐藏庄家第二张牌# 玩家回合user = user_play(user, list3)user_score = get_score(user)game_result.update({  # 记录玩家信息'player_score': user_score,'player_cards': user.copy()})print('\n玩家最终手牌:', user, '最终分数:', user_score)if user_score > 21:  # 玩家爆牌直接结束game_result['winner'] = '玩家爆牌,庄家获胜!'print('玩家爆牌,庄家获胜!')return game_result# 庄家回合computer = computer_play(computer, list3)computer_score = get_score(computer)game_result.update({  # 记录庄家信息'computer_score': computer_score,'computer_cards': computer.copy()})print('\n庄家最终手牌:', computer, '最终分数:', computer_score)# 胜负判定if computer_score > 21:game_result['winner'] = '庄家爆牌,玩家获胜!'print('庄家爆牌,玩家获胜!')elif user_score > computer_score:game_result['winner'] = '玩家获胜!'print('玩家获胜!')elif user_score < computer_score:game_result['winner'] = '庄家获胜!'print('庄家获胜!')else:game_result['winner'] = '平局!'print('平局!')return game_resultif __name__ == "__main__":"""主程序入口"""while True:play_game()  # 开始游戏# 询问是否继续if input('\n是否再玩一局?(Y 继续/其他任意键退出): ').lower() != 'y':print('游戏结束')break

二、函数设计

1. 基础工具函数:牌堆操作与手牌生成

  • get_card(list3):核心抽牌逻辑
    功能:从牌堆中随机抽牌并移除,牌堆为空时自动重新生成(洗牌)。
    边界条件处理:通过if not list3检查牌堆是否为空,体现防御性编程思想。
    列表操作:使用random.choice()随机选牌,list.remove()移除已抽牌,list.extend()重置牌堆。
    复用性:被get_user_cardsuser_card_gen等函数调用,实现“抽牌”功能的单一职责封装。

  • get_user_cards(num, list3):批量获取手牌
    功能:循环调用get_card获取指定数量的手牌。
    设计思想:通过参数num灵活控制手牌数量,体现函数的通用性。例如:
    user = get_user_cards(2, list3) 实现发两张初始手牌。

2. 计分逻辑:get_score(cards)

  • 核心算法:处理A的灵活计分
    J/Q/K计10分,A初始计11分,数字牌按面值计分。
    若总分超过21且存在A,将A从11分转为1分(每次减10分,直到总分≤21或无A)。

    aces = 0  # 记录A的数量
    for card in cards:rank = card[2:]  # 提取牌面值(如“红桃A”→“A”)if rank == 'A': aces += 1score += 11# ...其他计分逻辑
    while score > 21 and aces > 0:  # 调整A的计分score -= 10aces -= 1  
    
  • 函数价值:将计分规则封装为独立函数,便于后续玩家/庄家回合调用,避免代码重复。

3. 玩家交互:生成器与回合控制

  • user_card_gen(user_cards, list3):要牌生成器
    生成器优势:通过yield保持状态,每次调用next()时抽取新牌并返回当前手牌,实现“按需抽牌”的惰性加载。
    使用场景:配合user_play函数的循环逻辑,玩家每选择“要牌”时,通过生成器动态更新手牌,避免频繁创建临时变量。

  • user_play(user_cards, list3):玩家回合主逻辑
    交互流程
    1. 显示当前手牌与分数,判断是否爆牌(>21)或 blackjack(=21)。
    2. 接收用户输入(要牌/停牌),通过生成器获取新牌或结束回合。

4. 庄家(电脑)逻辑:自动化决策

  • computer_play(computer_cards, list3):庄家自动要牌
    规则实现:庄家分数<17时必须要牌,≥17时停牌。
    隐藏牌设计:通过print_pc_cards函数隐藏庄家第二张牌(显示“暗牌”),模拟真实游戏体验。
    代码细节:使用lambda函数简化手牌打印逻辑,体现函数式编程的简洁性:
    pc_cards = (lambda x: print_pc_cards(x))(computer_cards)  # 匿名函数调用
    

5. 日志系统:装饰器的应用

  • @game_logger装饰器
    功能:记录游戏结果到日志文件,包括时间、胜负、分数、手牌等信息。
    闭包与函数包装:通过嵌套函数wrapper包裹被装饰函数(如play_game),在不修改原函数的前提下添加日志功能。
    文件操作:使用with open追加写入日志,确保程序崩溃时数据不丢失。

三、主函数流程

  • play_game():核心流程调度
    1. 初始化:生成牌堆、显示游戏规则、模拟洗牌过程。
    2. 发牌阶段:调用get_user_cards为玩家和庄家各发2张牌。
    3. 玩家回合:调用user_play处理交互逻辑,若玩家爆牌直接结束游戏。
    4. 庄家回合:调用computer_play执行自动化要牌规则。
    5. 胜负判定:比较双方分数,处理平局、爆牌等边界情况。
    6. 结果返回:通过字典game_result存储游戏数据,供装饰器记录日志。

  • 函数调用关系图

    play_game()
    ├─ get_user_cards() → get_card()
    ├─ user_play() → user_card_gen() → get_card()
    │  └─ get_score()
    ├─ computer_play() → get_card()
    │  └─ get_score()
    └─ game_logger装饰器 → 日志写入
    

四、关键编程思想总结

1. 单一职责原则

每个函数专注于一个独立功能:
get_card负责抽牌,get_score负责计分,user_play负责玩家交互,避免“大而全”的函数设计。

2. 代码复用与模块化

通过函数调用实现逻辑复用,例如get_card被玩家、庄家、生成器共同使用,减少冗余代码。

3. 状态管理

  • 可变对象(如list3牌堆)作为参数传递时,函数可直接修改其状态(如抽牌后移除元素)
  • 生成器通过yield保存状态,避免使用全局变量维护玩家手牌。

4. 进阶特性应用

  • 生成器:适用于需要逐步生成结果的场景(如玩家按需抽牌)。
  • 装饰器:实现功能扩展(日志记录等),提升代码可维护性。

五、案例总结

本案例展示了函数在实际项目中的完整应用:
从基础工具函数到复杂业务逻辑,从单一功能封装到多函数协作,再到进阶特性(生成器、装饰器)的灵活运用。
通过这种模块化设计,代码结构清晰、易于调试,且方便后续扩展。


http://www.xdnf.cn/news/10338.html

相关文章:

  • CTFHub-RCE 命令注入-过滤目录分隔符
  • 解决8080端口被占问题
  • python学习day34
  • 学习海康VisionMaster之表面缺陷滤波
  • Cesium快速入门到精通系列教程
  • 【KWDB 创作者计划】_探秘浪潮KWDB数据库:从时间索引到前沿技术
  • 用户认证的魔法配方:从模型设计到密码安全的奇幻之旅
  • ApiHug 1.3.9 支持 Spring 3.5.0 + Plugin 0.7.4 内置小插件升级!儿童节快乐!!!
  • vue-08(使用slot进行灵活的组件渲染)
  • Java Spring 之监听器(Listener)详解与实战
  • 如何查看电脑电池性能
  • 对蚁群算法的理解和实例详解
  • [笔记]一般小信号测量方法
  • 企业微信接入说明
  • proteus美观与偏好设置
  • Qq空间照片视频批量下载工具
  • TomSolver 库 | 入门及使用
  • docker安装和镜像源替换
  • Python训练营---Day41
  • GoogLeNet网络模型
  • 【求A类B类月】2022-2-9
  • 【python】uv管理器
  • PS裁剪后像素未删除?5步解决“删除裁剪像素”失效问题
  • Role of ISMEAR in insulator-conductor transition
  • Java-Character类静态方法深度剖析
  • Day08
  • 每日算法刷题Day19 5.31:leetcode二分答案3道题,用时1h
  • 本地部署基于 Kibana 开源搜索引擎 Elasticsearch 并实现外部访问
  • acwing刷题
  • 【Rust 轻松构建轻量级多端桌面应用】