C#实现石头剪刀布游戏:从基础语法到设计模式实战
1. 项目概述与核心价值最近在带新人或者自己回顾基础的时候发现“石头剪刀布”这个小游戏是个绝佳的练手项目。别看它规则简单但用C#完整实现一遍尤其是要处理好玩家与电脑的对战逻辑能串联起从基础语法到面向对象设计再到随机数生成和状态判断的多个核心知识点。很多面试里所谓的“设计模式”考察比如状态机其最直观的入门理解就可以从这个游戏开始。我这次就带大家从头到尾用控制台应用的形式实现一个结构清晰、可扩展性强的石头剪刀布游戏。我们不止于实现功能更会探讨如何写出易于维护和扩展的代码比如未来如果想增加“蜥蜴、史波克”这种扩展规则或者改成图形界面现在的结构是否能轻松应对。这个项目非常适合C#初学者巩固基础也适合有一定经验的开发者思考如何将简单需求设计得更加优雅。我们会从最直白的“if-else”堆叠开始然后逐步重构引入枚举、类设计甚至触摸一下策略模式的思想让你看到代码是如何一步步“进化”的。整个过程你会清晰地看到变量定义、方法封装、随机数应用、条件判断以及控制台输入输出这些基础技能是如何被有机组合起来的。2. 游戏核心逻辑与初始设计2.1 规则定义与状态枚举任何游戏开始前明确定义规则是第一步。石头剪刀布的规则很简单石头赢剪刀剪刀赢布布赢石头相同则为平局。在代码里我们首先需要定义出“手势”这个核心概念。用整数0、1、2来代表虽然直接但代码可读性极差。这时候C#的枚举enum就派上用场了。public enum HandGesture { Rock, // 石头 Scissors, // 剪刀 Paper // 布 }使用枚举后代码里到处都是HandGesture.Rock、HandGesture.Scissors这样的字眼意图一目了然完全避免了“魔法数字”的出现。接下来我们需要一个裁判它的职责是根据两个手势判断胜负。最直接的实现就是一个方法里面用switch或if-else穷举所有情况。public static class GameRule { public static GameResult Judge(HandGesture player, HandGesture computer) { if (player computer) return GameResult.Draw; if ((player HandGesture.Rock computer HandGesture.Scissors) || (player HandGesture.Scissors computer HandGesture.Paper) || (player HandGesture.Paper computer HandGesture.Rock)) { return GameResult.PlayerWin; } return GameResult.ComputerWin; } } public enum GameResult { PlayerWin, ComputerWin, Draw }注意这里将胜负判断逻辑单独封装在GameRule静态类中是一个很好的实践。它遵循了单一职责原则未来如果规则变化比如加入新的手势我们只需要修改这一个类而不会影响到玩家或电脑类的代码。2.2 玩家与电脑的抽象游戏中有两个参与者玩家和电脑。他们有一些共同行为比如“出拳”GetGesture但具体实现方式截然不同。玩家需要通过控制台输入而电脑则需要随机生成。这立刻让我们想到面向对象中的继承与多态。我们可以设计一个基类Player。public abstract class Player { public string Name { get; protected set; } public abstract HandGesture GetGesture(); }基类Player包含一个名字属性和一个抽象方法GetGesture。这样具体的玩家类和电脑类就可以继承它并实现各自获取手势的逻辑。public class HumanPlayer : Player { public HumanPlayer(string name) { Name name; } public override HandGesture GetGesture() { while (true) { Console.WriteLine(${Name}请出拳 (0-石头, 1-剪刀, 2-布)); string input Console.ReadLine(); if (int.TryParse(input, out int choice) choice 0 choice 2) { return (HandGesture)choice; // 利用枚举和整数的转换 } Console.WriteLine(输入无效请输入0、1或2); } } } public class ComputerPlayer : Player { private readonly Random _random new Random(); public ComputerPlayer() { Name 电脑; } public override HandGesture GetGesture() { // Random.Next(3) 生成 0, 1, 2 之间的随机数 int choice _random.Next(Enum.GetValues(typeof(HandGesture)).Length); return (HandGesture)choice; } }这里有几个关键点输入验证在HumanPlayer.GetGesture()中我们用一个while循环来确保玩家输入的是有效数字并给出了清晰的提示。这是提升用户体验和程序健壮性的基本操作。随机数生成ComputerPlayer使用Random类来生成随机手势。这里_random被定义为只读字段并在构造函数中初始化这是一个好习惯。注意Random的实例化不宜在方法内频繁进行否则在极短时间内连续调用可能导致生成的随机数不够“随机”。枚举转换我们巧妙利用了枚举底层是整型的特性通过(HandGesture)choice进行转换代码非常简洁。3. 游戏主流程与循环结构实现3.1 游戏引擎类的设计有了玩家和规则我们需要一个“导演”来串联整个流程这就是游戏引擎或主控类GameEngine。它的核心是一个游戏循环。public class GameEngine { private readonly HumanPlayer _human; private readonly ComputerPlayer _computer; private int _playerScore; private int _computerScore; private int _drawCount; public GameEngine(string playerName) { _human new HumanPlayer(playerName); _computer new ComputerPlayer(); _playerScore 0; _computerScore 0; _drawCount 0; } public void Start() { Console.WriteLine($ 石头剪刀布游戏开始 ); Console.WriteLine($玩家{_human.Name} vs {_computer.Name}); bool continuePlaying true; while (continuePlaying) { PlayOneRound(); Console.WriteLine($当前比分{_human.Name} {_playerScore} : {_computerScore} {_computer.Name} 平局 {_drawCount}); Console.WriteLine(是否继续(y/n)); continuePlaying Console.ReadLine().ToLower() y; } PrintFinalResult(); } private void PlayOneRound() { // 获取手势 HandGesture playerGesture _human.GetGesture(); HandGesture computerGesture _computer.GetGesture(); Console.WriteLine(${_human.Name} 出了 {playerGesture}); Console.WriteLine(${_computer.Name} 出了 {computerGesture}); // 判定胜负 GameResult result GameRule.Judge(playerGesture, computerGesture); // 更新比分并显示结果 switch (result) { case GameResult.PlayerWin: _playerScore; Console.WriteLine(${_human.Name} 赢了); break; case GameResult.ComputerWin: _computerScore; Console.WriteLine(${_computer.Name} 赢了); break; case GameResult.Draw: _drawCount; Console.WriteLine(平局); break; } Console.WriteLine(); // 空行分隔 } private void PrintFinalResult() { Console.WriteLine(\n 游戏结束 ); Console.WriteLine($最终比分{_human.Name} {_playerScore} : {_computerScore} {_computer.Name}); if (_playerScore _computerScore) Console.WriteLine($恭喜 {_human.Name} 获得最终胜利); else if (_playerScore _computerScore) Console.WriteLine($很遗憾{_computer.Name} 获得了胜利。); else Console.WriteLine(最终双方战平); } }GameEngine类封装了游戏的所有状态比分和行为开始、进行一轮、结束。Start方法中的while循环构成了游戏的主循环每轮结束后询问玩家是否继续。这种结构清晰地将初始化、游戏循环和结束处理分开。3.2 程序入口与启动最后在控制台应用的Main方法中我们只需要创建游戏引擎并启动它。class Program { static void Main(string[] args) { Console.WriteLine(欢迎来到石头剪刀布游戏); Console.Write(请输入你的名字); string playerName Console.ReadLine(); GameEngine game new GameEngine(playerName); game.Start(); Console.WriteLine(按任意键退出...); Console.ReadKey(); } }至此一个功能完整、结构清晰的石头剪刀布游戏就完成了。你可以编译并运行它体验与电脑对战的乐趣。4. 代码优化与设计模式初探上面的实现已经是一个合格的作品。但如果我们从软件工程和可维护性的角度审视还有不少可以优化和深入思考的地方。这部分内容对于希望提升代码质量的开发者尤为重要。4.1 引入“策略模式”优化电脑AI目前的电脑出拳是完全随机的这很公平但也很“笨”。如果我们想让电脑有点“智能”比如根据历史出拳记录来猜测玩家的习惯该怎么办直接修改ComputerPlayer类的GetGesture方法会违反“开闭原则”对扩展开放对修改关闭。这时策略模式Strategy Pattern就非常适合。策略模式定义了一系列算法并将每个算法封装起来使它们可以相互替换。对于电脑玩家来说“出拳策略”就是一个可以替换的算法。我们首先定义一个策略接口public interface IGestureStrategy { HandGesture GetNextGesture(GameHistory history); }GameHistory是一个新类用于记录历史对战数据比如最近10轮双方的手势。然后我们可以实现不同的策略// 随机策略原来的逻辑 public class RandomStrategy : IGestureStrategy { private static readonly Random _rand new Random(); public HandGesture GetNextGesture(GameHistory history) { return (HandGesture)_rand.Next(3); } } // 简单模式策略总是出石头 public class RockOnlyStrategy : IGestureStrategy { public HandGesture GetNextGesture(GameHistory history) HandGesture.Rock; } // 基于历史的策略分析玩家最常出的手势并出能克制它的手势 public class HistoryBasedStrategy : IGestureStrategy { private static readonly Random _rand new Random(); public HandGesture GetNextGesture(GameHistory history) { if (history.Rounds.Count 0) return (HandGesture)_rand.Next(3); // 简化逻辑找出玩家最频繁出的手势 var playerGestures history.Rounds.Select(r r.PlayerGesture); var mostFrequent playerGestures.GroupBy(g g) .OrderByDescending(g g.Count()) .First().Key; // 假设玩家会继续出这个手势我们出能赢它的手势 // 这是一个非常简单的反制逻辑 return mostFrequent switch { HandGesture.Rock HandGesture.Paper, HandGesture.Scissors HandGesture.Rock, HandGesture.Paper HandGesture.Scissors, _ (HandGesture)_rand.Next(3) }; } }然后修改ComputerPlayer类使其接受一个策略接口public class ComputerPlayer : Player { private readonly IGestureStrategy _strategy; private readonly GameHistory _history; public ComputerPlayer(IGestureStrategy strategy, GameHistory history) { Name 电脑; _strategy strategy; _history history; } public override HandGesture GetGesture() { HandGesture gesture _strategy.GetNextGesture(_history); // 电脑出手后也可以选择是否记录到历史这里电脑是后手记录由引擎统一处理更合适 return gesture; } }最后在GameEngine中创建电脑玩家时注入具体的策略// 在GameEngine构造函数中 GameHistory history new GameHistory(); _computer new ComputerPlayer(new HistoryBasedStrategy(), history); // 或者 new RandomStrategy()这样我们无需改动ComputerPlayer的核心代码只需创建不同的策略对象就能让电脑拥有截然不同的行为模式。这极大地增强了程序的扩展性。4.2 游戏历史的记录与持久化为了支持基于历史的策略以及让玩家可以回顾战绩我们需要记录游戏历史。创建一个GameRound记录单轮信息和一个GameHistory管理所有记录。public class GameRound { public HandGesture PlayerGesture { get; set; } public HandGesture ComputerGesture { get; set; } public GameResult Result { get; set; } public DateTime PlayTime { get; set; } DateTime.Now; } public class GameHistory { public ListGameRound Rounds { get; private set; } new ListGameRound(); public void AddRound(HandGesture player, HandGesture computer, GameResult result) { Rounds.Add(new GameRound { PlayerGesture player, ComputerGesture computer, Result result }); // 可选只保留最近N条记录防止内存无限增长 if (Rounds.Count 100) Rounds.RemoveAt(0); } public void PrintHistory() { Console.WriteLine(\n 对战历史 ); foreach (var round in Rounds) { Console.WriteLine(${round.PlayTime:HH:mm:ss} - 玩家:{round.PlayerGesture} vs 电脑:{round.ComputerGesture} - {round.Result}); } } }在GameEngine.PlayOneRound方法中在判定胜负后调用_history.AddRound(...)将本轮记录保存下来。你还可以在游戏结束时或者在游戏中增加一个命令如输入“history”来调用PrintHistory方法展示所有记录。实操心得历史记录功能看似简单但引入了状态管理。在设计时要考虑清楚GameHistory的生命周期和归属。是作为GameEngine的成员还是通过依赖注入传递给需要它的对象如策略类这里我们将其作为GameEngine的成员并传递给电脑策略是一个比较清晰的设计。4.3 配置化与扩展性思考一个健壮的程序应该易于配置。例如游戏总局数、是否允许平局、不同的计分规则等都可以从硬编码中提取出来放到配置文件如appsettings.json或通过启动参数设置。我们可以创建一个GameSettings类public class GameSettings { public int MaxRounds { get; set; } 10; // 默认最多10局 public bool EnableExtendedRules { get; set; } false; // 是否启用“蜥蜴史波克”扩展规则 public string ComputerStrategy { get; set; } Random; // 电脑策略配置 }在GameEngine中读取这个配置。当EnableExtendedRules为true时我们需要动态切换GameRule和HandGesture枚举。这提示我们最初的GameRule.Judge方法写死了三种手势的判断扩展性不足。更好的做法是定义一个规则接口IGameRule并为基本规则和扩展规则分别实现。这又一次体现了面向接口编程和策略模式的威力。虽然由于篇幅我们不会在此完全实现扩展规则但这个思考过程至关重要。它迫使你思考当需求变化时你的代码哪些地方需要大动干戈哪些地方可以像换零件一样轻松适配提前进行这种抽象是区分普通代码和优秀设计的关键。5. 常见问题、调试技巧与进阶方向5.1 开发中常见问题与解决输入处理异常玩家可能输入非数字字符或者超出范围。我们的HumanPlayer类中已经通过int.TryParse和范围检查做了处理。这是一个必须有的防御性编程步骤。随机数“不随机”在快速循环中创建new Random()由于系统时钟作为种子变化不大可能导致生成的随机数序列高度相关。解决方案是像我们之前做的那样将Random实例作为类的静态或实例字段只初始化一次。枚举转换的陷阱我们使用(HandGesture)choice将整数转为枚举。但如果choice的值不在枚举定义的范围内比如-1或3转换仍然会成功得到一个无效的枚举值这可能在后续比较中引发难以察觉的错误。更安全的方法是使用Enum.IsDefined方法进行检查。int choice ...; if (Enum.IsDefined(typeof(HandGesture), choice)) { return (HandGesture)choice; } else { // 处理无效输入例如返回一个默认值或抛出异常 return HandGesture.Rock; }对象引用与状态管理确保在正确的时机初始化对象。例如GameHistory应该在GameEngine开始前创建并贯穿整个游戏生命周期传递给需要它的对象如策略类而不是在每轮游戏中新建。5.2 调试与测试技巧单元测试这个项目是练习单元测试的绝佳对象。你可以为GameRule.Judge方法编写测试用例覆盖所有9种3x3手势组合确保胜负判断逻辑百分百正确。也可以为不同的IGestureStrategy实现编写测试验证其输出是否符合预期。控制台输出调试在策略类中添加详细的日志输出例如Console.WriteLine($[策略] 分析历史玩家常出{mostFrequent}因此我出{gesture})这能帮助你直观理解AI的“思考”过程。使用断点在GetGesture、Judge等关键方法内部设置断点逐步执行观察变量的值是理解程序流程和排查逻辑错误最有效的方法。5.3 项目进阶方向如果你已经完成了基础版本并尝试了上述优化还可以挑战以下方向让这个项目成为你简历上的一个亮点图形界面WPF/WinForms将控制台应用迁移到图形界面。玩家通过点击按钮出拳电脑的手势和结果以图片形式展示。这涉及到事件处理、UI线程与后台逻辑的分离。网络对战将项目改造成一个简单的客户端-服务器应用支持两个真人玩家通过网络对战。这会引入Socket编程、异步通信、数据序列化等网络编程核心知识。数据持久化将玩家的历史战绩、胜率等信息保存到数据库如SQLite或文件中。每次游戏后更新数据并可以提供一个排行榜功能。更复杂的AI实现更强大的电脑AI例如使用马尔可夫链预测玩家的出拳序列或者使用简单的机器学习模型虽然有点杀鸡用牛刀但作为学习很有趣。依赖注入使用像Microsoft.Extensions.DependencyInjection这样的库来管理GameEngine、Player、IGestureStrategy等对象的创建和生命周期使代码耦合度更低更易于测试。从一个小小的石头剪刀布游戏出发竟然可以深入到设计模式、架构设计、网络通信、数据持久化等多个层面。这正是编程的魅力所在从一个简单的需求开始不断挖掘、重构和扩展你的代码和设计能力就在这个过程中得到了实实在在的锻炼。

相关新闻

TI bq2750x电量计开发实战:从评估软件到Golden Image生成

TI bq2750x电量计开发实战:从评估软件到Golden Image生成

1. 项目概述与核心价值如果你正在开发一款使用锂电池供电的产品,无论是智能手表、无线耳机还是电动工具,那么电池管理的精度和可靠性直接决定了用户体验和产品口碑。在电池管理系统的核心,有一个被称为“电量计”或“Gas Gauge”的芯片&#…

2026/7/24 4:36:26 阅读更多 →
基于TI阻抗跟踪技术的主机侧高精度电量计系统设计与实践

基于TI阻抗跟踪技术的主机侧高精度电量计系统设计与实践

1. 项目概述:为什么我们需要更聪明的电池“管家”?你有没有遇到过这种情况:手机明明显示还有20%的电,结果一个电话进来或者打开相机,屏幕瞬间就黑了。或者,新买的蓝牙耳机用了一年,感觉续航时间…

2026/7/24 4:36:26 阅读更多 →
Unity游戏模组集成实战:从BepInEx框架到AI-HF_Patch优化方案

Unity游戏模组集成实战:从BepInEx框架到AI-HF_Patch优化方案

1. 项目概述:AI-HF_Patch是什么,以及它解决了什么问题如果你是一位《AI少女》的资深玩家,或者对Unity引擎开发的游戏模组(Mod)集成有浓厚兴趣,那么“AI-HF_Patch”这个名字你大概率不会陌生。它不是一个官方…

2026/7/24 4:35:26 阅读更多 →

最新新闻

AI文本生成中星号问题的成因与处理方案

AI文本生成中星号问题的成因与处理方案

1. 为什么AI生成的文本中会出现星号? 在各类AI对话系统和文本生成工具中,星号(*)的出现通常源于以下几个技术原因: 1.1 内容安全过滤机制 大多数AI系统都内置了多层次的内容安全防护: 敏感词替换 :当系统检测到可能…

2026/7/24 4:43:29 阅读更多 →
C++状压BFS算法精解:网格收集类问题的通用解法与信奥实战

C++状压BFS算法精解:网格收集类问题的通用解法与信奥实战

1. 项目概述与核心思路拆解“打卡信奥刷题(2084)用C实现信奥 P11594 [NOISG 2018 Finals] Collecting Mushrooms”这个标题,对于信奥(信息学奥林匹克)的选手或C算法学习者来说,一看就知道是个硬核任务。它不…

2026/7/24 4:43:29 阅读更多 →
TLV320ADC3101音频ADC:集成DSP的低功耗设计实战与调试

TLV320ADC3101音频ADC:集成DSP的低功耗设计实战与调试

1. 项目概述:为什么选择TLV320ADC3101?在便携式音频设备的设计中,我们总是在功耗、音质和功能集成度之间走钢丝。几年前,当我为一个超低功耗的无线录音笔项目选型时,市面上大多数高性能立体声ADC要么功耗太高&#xff…

2026/7/24 4:43:29 阅读更多 →
知识蒸馏技术:大模型压缩与轻量化实践

知识蒸馏技术:大模型压缩与轻量化实践

1. 蒸馏算法:大模型知识传承的工程实践 在AI模型开发领域,我们经常面临一个现实困境:那些表现惊艳的千亿参数大模型,在实际业务部署时往往因为计算资源消耗过大而难以落地。而蒸馏算法就像一位精通"化功大法"的武林高手…

2026/7/24 4:43:29 阅读更多 →
TSC2117触摸屏主机控制模式时序详解与嵌入式HMI设计实践

TSC2117触摸屏主机控制模式时序详解与嵌入式HMI设计实践

1. 项目概述与核心价值在嵌入式系统,尤其是便携设备和工业HMI的设计中,触摸屏控制器是连接物理交互与数字世界的桥梁。它负责将用户手指或触控笔在屏幕上的压力或电容变化,精准地转换为微控制器可以理解的X、Y坐标数据。这个过程看似简单&…

2026/7/24 4:43:29 阅读更多 →
光学动作捕捉技术演进与2026趋势前瞻

光学动作捕捉技术演进与2026趋势前瞻

1. 光学动作捕捉技术演进与2026趋势前瞻 动作捕捉技术从早期的机械式传感器发展到如今的计算机视觉方案,光学三维动作捕捉系统凭借其非接触式测量和高精度特性,已成为影视制作、运动科学和虚拟现实等领域的黄金标准。2026年的技术迭代呈现出三个显著特征…

2026/7/24 4:42:28 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻