The Odin Project Ruby 课程实战:用面向对象思想从零实现命令行 Mastermind 猜色游戏

The Odin Project Ruby 课程实战:用面向对象思想从零实现命令行 Mastermind 猜色游戏 The Odin Project Ruby 课程实战用面向对象思想从零实现命令行 Mastermind 猜色游戏【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本篇指南以 The Odin Project Ruby 课程Object Oriented Programming Basics模块的 Mastermind 项目为核心完整拆解一个命令行版 Mastermind猜色游戏从需求分析、第一版实现到策略重构的全过程你将学会如何把游戏拆分为职责单一的类、设计准确的位置/颜色双维度反馈机制、让人类与电脑互换出题与猜题角色并最终让电脑具备可解释的猜题策略。读完本文你能独立用 Ruby 面向对象方式组织并交付一个可运行的命令行游戏项目这也是课程在 Tic Tac Toe 项目之后的第二个 OOP 练手项目。项目背景什么是 MastermindMastermind 是一款经典的猜码游戏玩法类似带彩色钉子的 Hangman猜单词一名玩家出题者秘密设定一组由若干颜色组成的代码另一名玩家猜题者需要在有限回合内猜出这组代码。每一回合猜题者提交一组猜测出题者给出两种维度的反馈某个位置上的颜色完全正确位置和颜色都对某个位置的颜色正确但位置不对猜中了这个颜色但放错了槽位。课程文档 project_mastermind.md 给出的核心任务非常明确Build a Mastermind game from the command line where you have 12 turns to guess the secret code, starting with you guessing the computers random code.即在命令行实现一个 Mastermind 游戏给你 12 次机会猜出秘密代码第一版先让电脑随机生成代码、人类来猜。这个项目与本模块的第一课 object_oriented_programming.md 紧密衔接——该课强调类和方法只做一件事尽量少地向外部暴露接口不要让类与方法互相重度依赖Mastermind 正是把这些原则落到实处的理想题目。它与同模块的 Tic Tac Toe 项目 一样都要求你用棋盘/回合/胜负判定这类天然适合对象建模的领域来练习 OOP。前置知识清单动手前课程希望你已掌握以下技能这些在仓库对应课程中都有完整讲解所需能力仓库中的对应课程在本项目中的用途类、实例变量、方法、作用域object_oriented_programming.md设计Game、Code、Player、Feedback等核心类命令行输入输出input_and_output.md用puts输出棋盘与反馈用gets.chomp读取玩家输入多文件项目组织managing_ruby_projects.mdlib目录 require_relative拆分游戏代码代码风格与检查linting_and_rubocop.md用 RuboCop 保持代码可读、可预测尤其要注意 input_and_output.md 中强调的两个细节puts会在末尾追加换行而print不会两者都返回nilgets会原样返回用户输入含末尾换行符\n因此几乎总是需要配合#chomp去掉换行再使用例如gets.chomp。第一步先思考再动手课程 Assignment 的第一条建议是Think about how you would set this problem up!在写任何代码之前先在纸上把问题拆开。一个典型的拆解是列出对象与它们各自的责任游戏规则常量回合数12、代码长度如 4 个槽位、可选颜色集合如 6 种颜色秘密代码负责随机生成或由玩家设置并负责比较——把一次猜测翻译成反馈玩家/猜题者人类从命令行输入颜色电脑则运行某个猜题算法游戏循环负责回合计数、轮流出题/猜题、判断胜负、处理输入校验反馈封装几个全对、几个颜色对但位置错的结果并负责以人类可读的方式打印。这样先确定什么应该是类、什么是实例变量、什么是方法可以避免花一小时后发现自己把全部逻辑堆在main里。本模块另一项目 project_tic_tac_toe.md 给出了同样的告诫花几分钟思考能省下一小时编码时间并且强调不要在不必要的程度上在类之间共享信息。第一版电脑出题人类来猜按 Assignment 第 2 步的要求先实现最小可玩版本电脑随机选择秘密颜色人类玩家逐回合猜测并接收反馈。定义游戏常量与秘密代码# lib/mastermind.rb module Mastermind COLORS %w[red blue green yellow orange purple].freeze CODE_LENGTH 4 MAX_TURNS 12 end把颜色集合、代码长度、回合上限收敛到常量里方便后续调整规则例如把回合数改成 8 或把颜色加到 8 种。%w[...]是 Ruby 的字符串数组字面量freeze防止常量被意外修改。秘密代码的生成与比较class SecretCode def self.random new(Array.new(Mastermind::CODE_LENGTH) { Mastermind::COLORS.sample }) end def initialize(colors) colors colors end # 返回 [exact_matches, color_matches] def feedback_for(guess) exact colors.zip(guess).count { |secret, g| secret g } # 统计颜色层面总共命中多少含位置对的再减去 exact 即为颜色对但位置错的数量 secret_tally tally(colors) guess_tally tally(guess) total_color_hits Mastermind::COLORS.sum do |color| [secret_tally[color], guess_tally[color]].min end [exact, total_color_hits - exact] end private def tally(colors) colors.each_with_object(Hash.new(0)) { |color, h| h[color] 1 } end end这里的反馈计算是全项目最关键、也最容易写错的部分值得展开说明对应原文档give the proper feedback on how good the guess was each turn的要求位置正确exact把秘密代码与猜测按索引一一比对统计相等个数颜色正确但位置错误color matches不能简单统计猜测里有哪些颜色存在于秘密代码中因为那会重复计数。正确做法是先分别统计秘密代码与猜测中每种颜色出现的次数取每种颜色的较小值求和得到颜色层面总共命中数再减去 exact剩下的就是猜中了颜色但放错位置的数量。例如秘密代码是red, blue, red, green猜测是red, red, green, blueexact索引 0 都是red→ 1 个颜色总命中red秘密有 2 个、猜测有 2 个 → 2blue→ 1green→ 1合计 4减去 exact 后 color matches 3。这种按索引比对 按颜色频数取 min的算法同时适用于人类猜和电脑猜是后续所有策略的判定基石。游戏主循环class Game def initialize secret SecretCode.random turns 0 end def play puts 秘密代码已生成#{Mastermind::CODE_LENGTH} 个位置#{Mastermind::MAX_TURNS} 回合内猜出 while turns Mastermind::MAX_TURNS turns 1 guess human_guess exact, color secret.feedback_for(guess) if exact Mastermind::CODE_LENGTH puts 恭喜你在第 #{turns} 回合猜中了 return end puts 第 #{turns} 回合反馈全对 #{exact} 个颜色对但位置错 #{color} 个 end puts 很遗憾回合用尽。秘密代码是#{secret} end private def human_guess loop do print 请输入 #{Mastermind::CODE_LENGTH} 个颜色空格分隔可选#{Mastermind::COLORS.join(, )} guess gets.chomp.split return guess if valid?(guess) puts 输入无效请重新输入。 end end def valid?(guess) guess.length Mastermind::CODE_LENGTH guess.all? { |color| Mastermind::COLORS.include?(color) } end end要点用loop包裹输入读取直到拿到长度正确且颜色合法的猜测这是命令行游戏的通用输入校验模式gets.chomp.split把用户输入按空格切成颜色数组每回合结束都打印两类反馈全对数量 颜色对但位置错数量这正是原文档要求的每回合给出正确反馈猜到CODE_LENGTH个全对即获胜回合耗尽则揭示秘密代码并结束。运行入口放在项目根目录的main.rb# main.rb require_relative lib/secret_code require_relative lib/game Game.new.play重构让玩家选择角色按 Assignment 第 3 步重构代码让人类玩家可以选择自己是秘密代码的创建者还是猜题者。这意味着谁出题、谁猜题从写死变为可配置def play puts 请选择角色(1) 我出题电脑来猜 (2) 电脑出题我来猜 case gets.chomp when 1 then play_as_creator when 2 then play_as_guesser else puts 无效选择。 play end end角色切换背后其实是对抽象的呼唤人类与电脑扮演的猜题者应该暴露同一套接口例如#make_guess(previous_feedback)这样Game就不需要关心猜题者是人类还是电脑。这正是 object_oriented_programming.md 中对象关系思想的体现——两个类通过一个共同的方法契约协作而不是相互硬编码。把秘密代码的生成逻辑抽象为CodeGenerator随机生成或由玩家输入可以进一步降低Game的耦合。第二版让电脑学会猜题原文档第 46 步是进阶内容给出了两条可选路线二者都允许且鼓励你按自己的理解实现这里逐一展开。路线 A修改规则给电脑额外信息如果你选择修改规则可以给电脑提供关于每次猜测的额外信息文档给出了由浅入深的两级策略随机猜测 保留完全匹配让电脑随机猜但把那些反馈结果好的候选保留下来。最朴素的做法是第一回合完全随机之后每一回合从所有候选代码中筛掉那些如果它是秘密代码不可能产生当前反馈的组合——即把反馈当作过滤器颜色记忆文档中的进阶提示是——if the computer has guessed the right color but the wrong position, its next guess will need to include that color somewhere.即一旦反馈显示某个颜色猜对了但位置错电脑下一回合必须继续包含这个颜色只是换一个位置如果某个颜色完全正确则把它的位置固定住不再移动。实现上可以维护两组约束class ComputerGuesser def initialize fixed {} # 位置 - 已确认正确的颜色 must_include [] # 已知正确但位置未定的颜色 end def make_guess guess Array.new(Mastermind::CODE_LENGTH) guess.each_index { |i| guess[i] fixed[i] } # 先放已固定的 guess.each_index do |i| next if guess[i] guess[i] must_include.pop || Mastermind::COLORS.sample # 再放待定颜色其余随机 end guess end def learn(exact_positions, color_only_positions) # 根据上一回合反馈更新 fixed 与 must_include end end这种逐步锁定的贪心策略在 12 回合内通常能给出不错的胜率且代码直观、容易调试适合作为第一版电脑 AI。路线 B遵循游戏原版规则如果你希望电脑严格遵守游戏规则不给它任何额外信息只看标准的全对/位置错反馈文档指出你需要去研究经典的 Mastermind 求解策略例如 Knuth 的五步算法——选择使最坏情况候选数最小的猜测。这类策略的核心思想是维护候选集所有与已得反馈一致的可能代码每次从候选集中或全部可能代码中选一个信息量最大的猜测——通常是能让下一回合候选集缩到最小的那个用反馈不断缩小候选集直到命中。在 Ruby 中实现时候选集可以用Array#reject 前面实现的feedback_for来朴素表达candidates all_possible_codes loop do guess pick_guess(candidates) exact, color secret.feedback_for(guess) return guess if exact Mastermind::CODE_LENGTH candidates candidates.reject do |candidate| candidate.feedback_for(guess) ! [exact, color] end endall_possible_codes可用颜色集合做Array#repeated_permutation生成6 色 4 位共 6⁴ 1296 种规模很小全量枚举完全可行。这条路线对算法思维的要求更高但换来的是遵守规则的公平对抗体验。两条路线的取舍维度路线 A修改规则路线 B原版规则实现难度低中几行状态维护即可中高需要候选集与选猜策略是否符合原版规则否电脑能看到额外信息是推荐场景想快速看到电脑会进步的效果想练习枚举、筛选与搜索类算法课程态度文档明确允许文档要求自行研究求解策略文档对两条路线都持开放态度你完全可以从路线 A 起步跑通后再升级到路线 B——这正是原文档建议的渐进式开发节奏。项目组织与代码质量按惯例组织文件managing_ruby_projects.md 给出的 Ruby 项目约定是一个类一个文件所有类文件放进lib目录顶层入口文件负责require_relative全部依赖。以本项目的多文件结构为例mastermind ├── lib │ ├── secret_code.rb │ ├── game.rb │ ├── human_player.rb │ └── computer_player.rb └── main.rb入口main.rb只做三件事require_relative所有lib文件、创建Game实例、调用play。注意require_relative是相对于当前文件所在目录解析路径的因此无论在哪个终端目录下执行ruby main.rb都能正确加载而require则是相对你运行命令的目录容易产生找不到文件的困惑——这也是该课程反复强调的差异点。用 RuboCop 自查完成项目后参照 linting_and_rubocop.md 用 Bundler 把 RuboCop 装为本地依赖bundle init bundle add rubocop bundle exec rubocopbundle exec rubocop会检查当前目录及子目录的所有 Ruby 文件。RuboCop 的三类 Cop 与本项目直接相关Style检查风格一致性缩进、命名、字符串引号等确保你的类与方法命名符合 Ruby 惯例Lint检查歧义与潜在错误例如误把局部变量当方法调用Metrics检查可度量指标如类长度、方法长度、循环复杂度——如果Game#play因为塞了太多分支被提示过长那就是方法只做一件事原则在提醒你该拆分私有方法了。完整实现清单与验收标准按 project_mastermind.md 的 Assignment逐条自检你的实现先设计再编码已经明确类、实例变量与方法的职责划分第一版电脑随机选色人类 12 回合内猜测每回合正确输出两类反馈角色切换重构人类可以选择做秘密代码的创建者或猜题者电脑猜题当人类出题时电脑能够给出猜测可选智能升级电脑记住颜色对但位置错的颜色并继续使用固定完全匹配的位置可选原版规则研究经典求解策略实现遵守游戏规则的电脑 AI分享与检查把项目推送到自己的 GitHub 仓库课程习惯是每个项目都建仓、用 git 提交并用bundle exec rubocop完成一次代码体检。建议的验证场景跑一轮电脑出题、人类猜确认反馈数字与手工推演一致可用上文red, blue, red, green的例子做手工对拍再跑一轮人类出题、电脑猜观察路线 A 的电脑是否会在 23 回合后开始记住颜色——这两条验收路径分别覆盖了反馈算法的正确性与 AI 策略的有效性。额外资源说明原文档的 Additional resources 部分提供了一条 Ruby 游戏开发相关的补充链接Ruby Toolbox 的游戏库分类并注明其与本题并非直接相关仅供娱乐参考。它对应的价值在于当你完成本项目、想进一步探索用 Ruby 写游戏时可以了解到生态中已有的游戏库方向。不过对完成本项目的核心目标练习 OOP而言它不是必需的——真正的收获在于你亲手写出的类设计、反馈算法与电脑策略。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考