
1. 项目概述从“石头剪刀布”看编程入门“石头剪刀布”这个游戏几乎刻在每个人的童年记忆里。它规则简单胜负分明但你可能没想过它恰恰是编程初学者理解程序逻辑、掌握一门新语言语法的绝佳“练手沙包”。为什么这么说因为实现这个游戏几乎涵盖了编程入门阶段的所有核心概念变量定义、用户输入、条件判断、随机数生成、循环控制以及最基础的输入输出。对于Python、JavaScript、C#、Java这些主流语言的新手而言亲手敲出一个能运行的“石头剪刀布”游戏其成就感不亚于搭建起第一个“Hello World”。它不像复杂的业务系统那样令人望而生畏却能让你立刻看到代码如何与现实世界的简单规则互动。今天我们就抛开那些枯燥的语法手册直接动手用这几种语言分别实现这个小游戏在对比中快速掌握它们的核心语法特点和编程思维。2. 核心逻辑拆解与设计思路在动手写代码之前我们必须先把游戏规则翻译成计算机能理解的逻辑。这步想清楚了代码写起来就水到渠成。2.1 游戏规则的程序化表达“石头剪刀布”的胜负规则是一个典型的三元循环克制关系石头赢剪刀剪刀赢布布赢石头。在程序中我们需要为这三种选择赋予一个标识。最直观的方式是使用数字如1代表石头2代表剪刀3代表布或字符串如“rock”、“scissors”、“paper”。为了代码清晰和避免“魔法数字”我们通常会定义常量或枚举。核心的胜负判断逻辑就是一个多分支的条件语句if-else或switch。计算机程序需要扮演其中一个玩家这就要求我们能生成一个随机的选择。因此整个程序的流程可以固化为一个清晰的循环准备选项 - 获取用户输入 - 生成电脑随机选择 - 判断胜负 - 输出结果 - 询问是否继续。2.2 多语言实现的共性与差异虽然逻辑相同但不同语言的实现会展现出其语法特色和生态倾向。Python以其简洁著称代码量往往最少适合快速验证想法。JavaScript在浏览器和Node.js环境下略有不同浏览器端涉及DOM操作而Node.js或控制台版本则更接近其他后端语言。C#和Java作为强类型的面向对象语言结构会更严谨需要定义类和方法但逻辑同样清晰。 我们将逐一实现并重点关注各自的语言特性比如Python的input()和random模块JavaScript的prompt()和Math.random()C#的Console.ReadLine()和Random类以及Java的Scanner和Random类。注意一个健壮的程序必须考虑用户的“花式”输入。用户可能输入大小写不一的单词、中文、甚至乱码。因此在判断前对用户输入进行清洗和标准化如转为小写、去除空格是必不可少的步骤这也是初学者容易忽略的“边界情况”处理。3. Python版本实现详解Python版本以其极致的简洁性非常适合作为第一个实现的例子。3.1 环境准备与代码实现你只需要安装好Python建议3.6以上版本使用任何文本编辑器或IDE如VS Code, PyCharm即可。下面是一个功能完整、带有输入验证和循环游戏的版本。import random # 定义选项映射便于输入和展示 CHOICES {‘1‘: ’石头‘, ’2‘: ’剪刀‘, ’3‘: ’布‘} WINNING_RULES { ’石头‘: ’剪刀‘, # 石头赢剪刀 ’剪刀‘: ’布‘, # 剪刀赢布 ’布‘: ’石头‘ # 布赢石头 } def get_user_choice(): 获取并验证用户输入 while True: print(\n请出拳1. 石头 2. 剪刀 3. 布) user_input input(请输入数字1/2/3或直接输入名称).strip() # 处理数字输入 if user_input in CHOICES: return CHOICES[user_input] # 处理中文名称输入不区分大小写 for name in [’石头‘, ’剪刀‘, ’布‘]: if user_input.lower() name.lower(): return name print(输入无效请重新输入) def get_computer_choice(): 随机获取电脑选择 return random.choice([’石头‘, ’剪刀‘, ’布‘]) def determine_winner(user, computer): 判断胜负 if user computer: return ’平局‘ elif WINNING_RULES[user] computer: return ’你赢了‘ else: return ’电脑赢了‘ def play_game(): 主游戏循环 print(欢迎来到石头剪刀布游戏) total_rounds 0 win_count 0 while True: total_rounds 1 user_choice get_user_choice() computer_choice get_computer_choice() print(f你出了{user_choice}) print(f电脑出了{computer_choice}) result determine_winner(user_choice, computer_choice) print(f结果{result}) if result ’你赢了‘: win_count 1 # 询问是否继续 again input(\n再来一局(输入 y 继续其他任意键退出): ).strip().lower() if again ! ’y‘: break print(f\n游戏结束你一共玩了 {total_rounds} 局赢了 {win_count} 局。) if __name__ __main__: play_game()3.2 关键语法点与避坑指南random.choice()这是从列表中随机选取一个元素的便捷方法比用random.randint()生成索引再取列表元素更直观。输入处理input()函数获取的是字符串。代码中通过strip()去除首尾空格通过lower()统一转为小写进行比对极大地提升了容错性。字典映射使用CHOICES字典将数字输入映射为中文名称使用WINNING_RULES字典清晰表达了“谁克谁”的规则这种“数据驱动”的逻辑比写一堆if-else更易于维护。例如如果想增加“蜥蜴”和“史波克”生活大爆炸版只需修改这两个字典即可。循环与退出while True构建了一个无限循环直到用户输入非’y’才通过break跳出。这是控制台交互程序的常见模式。实操心得在Python中处理用户输入时一定要有“防御性编程”思维。永远不要假设用户会按你的指示来。上面的get_user_choice函数中的while循环确保了只有输入有效时才会返回这是一个非常实用的模式。4. JavaScript版本实现Node.js环境JavaScript可以在浏览器和Node.js环境中运行。这里我们先实现一个Node.js控制台版本其逻辑与Python版类似但语法是典型的JavaScript风格。4.1 Node.js环境搭建与代码实现首先确保你安装了Node.js。你可以新建一个game.js文件。Node.js使用readline模块来从控制台读取用户输入这与浏览器端的prompt()不同。// 引入所需模块 const readline require(’readline‘); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); const CHOICES {’1‘: ’石头‘, ’2‘: ’剪刀‘, ’3‘: ’布‘}; const WINNING_RULES { ’石头‘: ’剪刀‘, ’剪刀‘: ’布‘, ’布‘: ’石头‘ }; function getComputerChoice() { const choices Object.values(CHOICES); // [’石头‘, ’剪刀‘, ’布‘] const randomIndex Math.floor(Math.random() * choices.length); return choices[randomIndex]; } function determineWinner(user, computer) { if (user computer) return ’平局‘; if (WINNING_RULES[user] computer) return ’你赢了‘; return ’电脑赢了‘; } function askQuestion(query) { return new Promise(resolve rl.question(query, resolve)); } async function playGame() { console.log(欢迎来到石头剪刀布游戏Node.js版); let totalRounds 0; let winCount 0; while (true) { totalRounds; let userChoice ’’; // 获取有效用户输入 while (true) { console.log(\n请出拳1. 石头 2. 剪刀 3. 布); const input await askQuestion(请输入数字1/2/3或直接输入名称); const cleanedInput input.trim().toLowerCase(); if (CHOICES[cleanedInput]) { userChoice CHOICES[cleanedInput]; break; } // 检查是否直接输入了名称 const validNames Object.values(CHOICES); if (validNames.some(name name.toLowerCase() cleanedInput)) { userChoice validNames.find(name name.toLowerCase() cleanedInput); break; } console.log(输入无效请重新输入); } const computerChoice getComputerChoice(); console.log(你出了${userChoice}); console.log(电脑出了${computerChoice}); const result determineWinner(userChoice, computerChoice); console.log(结果${result}); if (result ’你赢了‘) winCount; const again await askQuestion(\n再来一局(输入 y 继续其他任意键退出): ); if (again.trim().toLowerCase() ! ’y‘) { break; } } console.log(\n游戏结束你一共玩了 ${totalRounds} 局赢了 ${winCount} 局。); rl.close(); } // 启动游戏 playGame().catch(err console.error(’游戏运行出错‘, err));4.2 异步交互与常见问题readline模块Node.js的标准输入输出是异步的。我们使用readline.createInterface创建接口并用rl.question方法提问。为了在循环中顺序执行这里使用了async/await语法并将rl.question封装成返回Promise的askQuestion函数这样代码逻辑更清晰。随机数生成Math.random()返回一个[0, 1)的浮点数。Math.floor(Math.random() * array.length)是获取数组随机索引的标准写法。对象操作Object.values(CHOICES)可以直接获取字典所有值组成的数组方便随机选取。数组方法在验证输入时使用了some()和find()这两个数组方法它们是JavaScript中处理数组查询的利器。注意事项Node.js中处理同步循环和异步输入是初学者的一大难点。如果不用async/await很容易陷入“回调地狱”或输入混乱。上面的模式——将异步提问封装成Promise然后在async函数中用await调用——是解决此类问题的推荐做法。另外别忘了在游戏结束时调用rl.close()来关闭输入流否则进程可能不会正常退出。5. C#版本实现控制台应用C#作为一款强类型、优雅的语言其控制台应用结构清晰。我们将在一个简单的控制台项目中实现游戏。5.1 创建项目与核心代码使用Visual Studio或VS Code with C# Dev Kit创建一个新的“控制台应用”项目。以下是Program.cs的主要内容。using System; namespace RockPaperScissors { class Program { // 定义常量与规则 private const string Rock 石头; private const string Scissors 剪刀; private const string Paper 布; private static readonly Dictionarystring, string WinningRules new Dictionarystring, string { { Rock, Scissors }, { Scissors, Paper }, { Paper, Rock } }; static void Main(string[] args) { Console.WriteLine(欢迎来到石头剪刀布游戏C#版); int totalRounds 0; int winCount 0; Random random new Random(); // 创建Random实例 string[] choices { Rock, Scissors, Paper }; while (true) { totalRounds; string userChoice GetUserChoice(); string computerChoice choices[random.Next(choices.Length)]; // 随机选择 Console.WriteLine($\n你出了{userChoice}); Console.WriteLine($电脑出了{computerChoice}); string result DetermineWinner(userChoice, computerChoice); Console.WriteLine($结果{result}); if (result 你赢了) winCount; Console.Write(\n再来一局(输入 y 继续其他任意键退出): ); if (Console.ReadLine().Trim().ToLower() ! y) break; } Console.WriteLine($\n游戏结束你一共玩了 {totalRounds} 局赢了 {winCount} 局。); Console.ReadKey(); // 等待按键退出 } static string GetUserChoice() { while (true) { Console.WriteLine(\n请出拳1. 石头 2. 剪刀 3. 布); Console.Write(请输入数字1/2/3或直接输入名称); string input Console.ReadLine().Trim(); // 处理数字输入 switch (input) { case 1: return Rock; case 2: return Scissors; case 3: return Paper; } // 处理文字输入不区分大小写 string inputLower input.ToLower(); if (inputLower Rock.ToLower()) return Rock; if (inputLower Scissors.ToLower()) return Scissors; if (inputLower Paper.ToLower()) return Paper; Console.WriteLine(输入无效请重新输入); } } static string DetermineWinner(string user, string computer) { if (user computer) return 平局; if (WinningRules[user] computer) return 你赢了; return 电脑赢了; } } }5.2 面向对象初探与细节处理Random类C#中生成随机数需要使用Random类。注意Random实例最好在循环外创建一次并重复使用。如果在循环内频繁创建new Random()而系统时钟种子变化不够快可能导致生成的随机数序列不够“随机”。字符串插值C# 6.0引入的$“”字符串插值语法让拼接字符串和变量变得非常方便类似于JavaScript的模板字符串。Dictionary字典这里使用字典来存储胜负规则使得判断逻辑WinningRules[user] computer非常直观。控制台交互Console.ReadLine()会阻塞直到用户按下回车。Trim()和ToLower()用于规范化输入。踩坑记录Random类的使用有个经典陷阱。如果像Random random new Random();这样的语句写在循环体内在极短时间内连续创建多个Random实例它们可能会使用相同的系统时钟种子导致生成的“随机”数完全相同。因此务必在循环外部如Main方法开头实例化一次Random对象然后在循环内反复调用它的Next方法。6. Java版本实现控制台应用Java的实现与C#在思路上非常相似都是强类型的面向对象语言但语法细节和标准库有所不同。6.1 项目结构与完整代码使用IntelliJ IDEA、Eclipse或任何文本编辑器配合命令行均可。创建一个RockPaperScissors.java文件。import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Scanner; public class RockPaperScissors { // 定义常量 private static final String ROCK 石头; private static final String SCISSORS 剪刀; private static final String PAPER 布; // 定义胜负规则映射 private static final MapString, String WINNING_RULES new HashMap(); static { WINNING_RULES.put(ROCK, SCISSORS); WINNING_RULES.put(SCISSORS, PAPER); WINNING_RULES.put(PAPER, ROCK); } private static final String[] CHOICES {ROCK, SCISSORS, PAPER}; public static void main(String[] args) { Scanner scanner new Scanner(System.in); Random random new Random(); System.out.println(欢迎来到石头剪刀布游戏Java版); int totalRounds 0; int winCount 0; while (true) { totalRounds; String userChoice getUserChoice(scanner); String computerChoice CHOICES[random.nextInt(CHOICES.length)]; System.out.println(\n你出了 userChoice); System.out.println(电脑出了 computerChoice); String result determineWinner(userChoice, computerChoice); System.out.println(结果 result); if (你赢了.equals(result)) { winCount; } System.out.print(\n再来一局(输入 y 继续其他任意键退出): ); String playAgain scanner.nextLine().trim().toLowerCase(); if (!playAgain.equals(y)) { break; } } System.out.printf(\n游戏结束你一共玩了 %d 局赢了 %d 局。\n, totalRounds, winCount); scanner.close(); } private static String getUserChoice(Scanner scanner) { while (true) { System.out.println(\n请出拳1. 石头 2. 剪刀 3. 布); System.out.print(请输入数字1/2/3或直接输入名称); String input scanner.nextLine().trim(); // 处理数字输入 switch (input) { case 1: return ROCK; case 2: return SCISSORS; case 3: return PAPER; } // 处理文字输入不区分大小写 String inputLower input.toLowerCase(); if (ROCK.toLowerCase().equals(inputLower)) return ROCK; if (SCISSORS.toLowerCase().equals(inputLower)) return SCISSORS; if (PAPER.toLowerCase().equals(inputLower)) return PAPER; System.out.println(输入无效请重新输入); } } private static String determineWinner(String user, String computer) { if (user.equals(computer)) { return 平局; } if (WINNING_RULES.get(user).equals(computer)) { return 你赢了; } return 电脑赢了; } }6.2 Java特性解析与资源管理Scanner与RandomJava使用java.util.Scanner来读取控制台输入使用java.util.Random来生成随机数。random.nextInt(CHOICES.length)生成一个[0, length)范围内的随机整数作为索引。HashMap规则映射使用static代码块初始化WINNING_RULES这个HashMap这是Java中初始化静态复杂常量的常见方式。字符串比较在Java中比较字符串内容是否相等必须使用equals()方法而不是运算符。比较的是对象引用内存地址。资源关闭像Scanner这样的I/O资源在使用完毕后调用close()方法关闭是一个好习惯尽管对于标准输入流System.in关闭后可能无法再次打开但在简单的单次程序运行中无妨。常见问题排查很多Java新手会在字符串比较上栽跟头。记住黄金法则比较字符串内容永远用equals()。例如if (result “你赢了”)这个判断很可能失败即使result的值看起来就是“你赢了”。因为对于对象类型比较的是引用。正确的写法是if (“你赢了”.equals(result))。这里将常量写在前面还可以有效避免result为null时抛出NullPointerException。7. 功能扩展与思路启发实现基础版本只是第一步。一个完整的练手项目可以围绕它进行多角度扩展这能极大地锻炼你的编程能力。7.1 可扩展方向建议图形化界面GUIPython可以使用tkinter内置、PyQt/PySide或Kivy库。JavaScript天然适合在浏览器中用HTML/CSS构建界面通过DOM操作更新结果。C#可以使用Windows Forms、WPF或跨平台的MAUI。Java可以使用Swing或JavaFX。 将控制台的文本交互变为点击按钮体验完全不同。网络对战功能学习Socket编程TCP/UDP实现一个客户端-服务器架构让两个玩家通过网络对战。或者使用WebSocket对于JavaScript/Node.js尤其方便实现一个实时对战网页。游戏逻辑增强三局两胜/五局三胜制增加局数统计判断整场比赛的胜负。胜率统计与历史记录将每局结果保存到文件或数据库下次启动时显示历史胜率。更多手势实现“蜥蜴-史波克”扩展版规则更复杂对数据结构设计是很好的练习。代码结构优化使用面向对象思想重构可以创建Player类包含姓名、得分、出拳方法、Game类包含规则、裁判逻辑、回合管理等。引入设计模式例如使用“策略模式”来封装不同的胜负规则算法便于扩展。7.2 从控制台到网页JavaScript的华丽转身这里简要展示如何将之前的Node.js逻辑快速迁移到一个极简的HTML网页中体验前端开发的魅力。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title石头剪刀布 - 网页版/title style body { font-family: sans-serif; text-align: center; margin-top: 50px; } button { font-size: 20px; margin: 10px; padding: 15px 30px; cursor: pointer; } #result { margin-top: 30px; font-size: 24px; font-weight: bold; } #score { margin-top: 20px; color: #666; } /style /head body h1石头剪刀布/h1 div button onclickplay(’石头‘)✊ 石头/button button onclickplay(’剪刀‘)✌️ 剪刀/button button onclickplay(’布‘)✋ 布/button /div div idresult请点击上方按钮出拳/div div idscore战绩0 胜 / 0 负 / 0 平/div script const rules { ’石头‘: ’剪刀‘, ’剪刀‘: ’布‘, ’布‘: ’石头‘ }; const choices [’石头‘, ’剪刀‘, ’布‘]; let score { win: 0, lose: 0, draw: 0 }; function getComputerChoice() { const randomIndex Math.floor(Math.random() * choices.length); return choices[randomIndex]; } function play(userChoice) { const computerChoice getComputerChoice(); let resultText 你出了${userChoice}br电脑出了${computerChoice}br; if (userChoice computerChoice) { resultText “span style‘color: blue;’平局/span”; score.draw; } else if (rules[userChoice] computerChoice) { resultText “span style‘color: green;’你赢了/span”; score.win; } else { resultText “span style‘color: red;’电脑赢了/span”; score.lose; } document.getElementById(’result‘).innerHTML resultText; document.getElementById(’score‘).innerHTML 战绩${score.win} 胜 / ${score.lose} 负 / ${score.draw} 平; } /script /body /html这个网页版将逻辑移到了浏览器中通过点击按钮触发事件并直接操作DOM元素来更新页面内容。你可以看到同样的核心判断逻辑rules[userChoice] computerChoice在不同的环境下Node.js控制台 vs 浏览器是如何被应用的。