每一次点击,都是对2000年博弈论历史的瞬间重演。
你有没有想过,这个看似简单的儿童游戏,实际上是人类最早接触的博弈论原型?从中国古代的“手势令”到日本的“虫拳”,再到全球化的“石头剪刀布”,这个游戏横跨文化、语言和时代,成为人类共通的策略语言。
今天,我们要构建的,正是这种策略语言的数字版本。它看似简单——三种选择,三种结果——但其背后却隐藏着博弈论、随机性、人机交互和心理战的深度数学。这不仅仅是条件判断的练习,更是一场关于如何用代码模拟人类决策的哲学实验。
一、 界面的三元宇宙:HTML如何构建一个策略竞技场
让我们从HTML结构开始,这个界面必须同时服务于5岁儿童和博弈论学者:
<div class="game-container">
<h1>石头剪刀布</h1>
<div class="choices">
<button class="choice" data-choice="rock">石头</button>
<button class="choice" data-choice="paper">布</button>
<button class="choice" data-choice="scissors">剪刀</button>
</div>
<p id="resultDisplay"></p>
</div>
这个极简的结构,实际上是一个精心设计的决策界面:
1. 标题的跨文化共鸣
“石头剪刀布”这个标题使用的是中文,但这个游戏在不同文化中有不同名称:
- 英语:Rock Paper Scissors
- 法语:Pierre Feuille Ciseaux
- 日语:じゃんけん (Janken)
- 韩语:가위 바위 보 (Gawi Bawi Bo)
这种文化差异引出了一个有趣的本地化问题:在全球化应用中,我们可能需要根据用户的语言环境动态显示标题和按钮文本。
2. 按钮作为策略门户
三个按钮代表了游戏的完整策略空间。在博弈论中,这被称为“策略集”——玩家可以选择的所有可能行动。每个按钮都有两个关键属性:
class="choice":样式和事件绑定的钩子
data-choice="rock":数据属性存储策略标识,而不是依赖按钮文本
使用数据属性而非按钮文本来标识选择,这是一个重要的设计决策。它实现了关注点分离:按钮文本是给人看的(可能本地化),数据属性是给代码读的(稳定、可预测)。这也是DOM数据操作中常见的模式。
3. 结果的空白画布
<p id="resultDisplay">开始时是空的,这很重要。空状态创造了期待的空间。在游戏设计中,结果的揭示是一个关键时刻,空白的画布放大了这种期待感。
二、 视觉的均衡三角:CSS如何营造公平竞技场
游戏界面的视觉设计必须传达公平性和均衡性——没有任何选择应该看起来比其他选择更有优势。
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
熟悉的居中布局在这里创造了仪式感。游戏是一种仪式化的冲突,而居中布局将这种仪式置于舞台中心。Arial字体是中立、无个性的选择——不会偏向任何文化或风格。
.game-container {
text-align: center;
background-color: #f0f0f0;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
这个容器的设计语言延续了我们之前项目的模式,但在游戏语境中有特殊含义:
- 圆角:软化了竞争的边缘,暗示这是友好的游戏
- 阴影:创造深度,暗示这是一个重要的交互空间
- 浅灰色背景:中性、无干扰,让用户专注于游戏本身
这种设计反映了现代游戏UI的趋势:将传统游戏的严肃性与数字游戏的友好性相结合。
.choices {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
button.choice {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
选择按钮的布局是关键设计:
display: flex 创建水平布局
justify-content: center 确保三个按钮居中
gap: 10px 提供视觉分离,强调每个选择的独立性
- 三个按钮完全相同的样式,强调了游戏的平衡性
但这里有一个有趣的细节:为什么没有为不同选择使用不同的颜色或图标?在更完整的游戏中,我们可能会为每个选择设计独特的视觉标识(如石头用灰色、布用米色、剪刀用银色),以增强可识别性和视觉吸引力。
三、 博弈论的代码实现:JavaScript如何模拟策略冲突
现在,我们来到这个游戏的核心:JavaScript如何实现这个古老博弈的数字版本。
const choices = ['rock', 'paper', 'scissors'];
这个简单的数组,定义了游戏的完整策略空间。选择将选项定义为字符串数组,而不是对象或映射,这是一个简洁的设计。但在更复杂的游戏中,我们可能需要为每个选项存储更多信息:
const choices = [
{ id: 'rock', name: '石头', beats: 'scissors', icon: '🪨' },
{ id: 'paper', name: '布', beats: 'rock', icon: '📄' },
{ id: 'scissors', name: '剪刀', beats: 'paper', icon: '✂️' }
];
这种结构化的数据更容易扩展和维护,特别是当我们需要添加新功能(如图标、多语言名称、胜率统计)时。
游戏逻辑:条件判断中的数学之美
document.querySelectorAll('.choice').forEach(button => {
button.addEventListener('click', function() {
const userChoice = this.getAttribute('data-choice');
const computerChoice = choices[Math.floor(Math.random() * choices.length)];
let result = '';
if (userChoice === computerChoice) {
result = '平局!';
} else if (
(userChoice === 'rock' && computerChoice === 'scissors') ||
(userChoice === 'paper' && computerChoice === 'rock') ||
(userChoice === 'scissors' && computerChoice === 'paper')
) {
result = '你赢了!';
} else {
result = '你输了!';
}
document.getElementById('resultDisplay').textContent =
`你选择了 ${userChoice}。 计算机选择了 ${computerChoice}。 ${result}`;
});
});
这短短十几行代码,实现了一个完整的博弈系统。让我们逐行解析其中的智慧:
1. 事件绑定:决策的时刻
document.querySelectorAll('.choice') 选择所有选择按钮,.forEach()为每个按钮添加点击事件监听器。这种模式我们已经熟悉,但这里有一个细微差别:事件委托可能更好,特别是如果未来要动态添加更多选择。监听器内部逻辑的执行,是浏览器事件循环处理的一部分。
2. 用户选择的获取
this.getAttribute('data-choice') 从数据属性中获取用户的选择。使用data-*属性是一个最佳实践:
- 分离了内容(按钮文本)和功能(游戏逻辑)
- 支持多语言(按钮文本可以本地化,而数据属性保持不变)
- 易于扩展(可以添加更多数据属性,如data-score、data-icon)
3. 计算机选择的随机生成
choices[Math.floor(Math.random() * choices.length)]
这是经典的随机选择算法:
Math.random() 生成0到1之间的伪随机数
* choices.length 将随机数映射到数组索引范围
Math.floor() 向下取整,得到整数索引
这种算法假设每个选择被选中的概率相等(均匀分布)。在标准石头剪刀布中,这确实是正确的——每个选择应该有1/3的概率。
但这里有一个有趣的问题:真正的随机存在吗?计算机生成的是伪随机数,基于确定性算法。这意味着,理论上,如果知道种子和算法,可以预测计算机的选择。但这对于游戏来说已经足够随机了。
4. 结果判定的条件逻辑
这个if-else条件链实现了游戏的核心规则。让我们用博弈论的视角分析这个逻辑:
平局条件:userChoice === computerChoice这是最简单的条件——选择相同即为平局。
用户胜利条件:三个条件的逻辑或
(userChoice === 'rock' && computerChoice === 'scissors') ||
(userChoice === 'paper' && computerChoice === 'rock') ||
(userChoice === 'scissors' && computerChoice === 'paper')
这三个条件覆盖了所有用户胜利的情况:
用户失败条件:else分支如果既不是平局也不是用户胜利,那么用户失败。
这种逻辑结构是清晰且正确的,但它有一个问题:可维护性。如果我们要扩展游戏(如添加蜥蜴、史波克),条件判断会变得极其复杂。
更优雅的解决方案:规则表
更好的方法是使用一个规则表(或映射),定义每个选择能击败什么:
const rules = {
rock: 'scissors', // 石头击败剪刀
paper: 'rock', // 布击败石头
scissors: 'paper' // 剪刀击败布
};
function determineWinner(userChoice, computerChoice) {
if (userChoice === computerChoice) {
return 'draw';
} else if (rules[userChoice] === computerChoice) {
return 'user';
} else {
return 'computer';
}
}
这种方法更简洁、更易扩展。要添加新选项,只需要更新规则表,而不是添加复杂的条件判断。这种映射思想在算法数据结构中应用广泛。
5. 结果显示的叙事
document.getElementById('resultDisplay').textContent =
`你选择了 ${userChoice}。 计算机选择了 ${computerChoice}。 ${result}`;
这个结果字符串包含三个信息:
- 用户的选择
- 计算机的选择
- 游戏结果
这种叙事结构帮助用户理解发生了什么,特别是当他们输了时,可以清楚地看到对手的选择。
但这里有一个本地化问题:按钮使用英文标识(rock, paper, scissors),但结果显示使用中文。在实际应用中,我们需要确保一致性。
四、 从简单到竞技:石头剪刀布的深度演化
1. 扩展版本:蜥蜴史波克
来自《生活大爆炸》的著名扩展,增加了两个选项:
规则变为:
剪刀剪布,布包石头,石头压蜥蜴,蜥蜴毒史波克,史波克砸剪刀,剪刀斩蜥蜴,蜥蜴吃布,布驳史波克,史波克蒸石头,石头砸剪刀
实现这个扩展版本:
const extendedChoices = ['rock', 'paper', 'scissors', 'lizard', 'spock'];
const extendedRules = {
rock: ['scissors', 'lizard'],
paper: ['rock', 'spock'],
scissors: ['paper', 'lizard'],
lizard: ['paper', 'spock'],
spock: ['rock', 'scissors']
};
function determineExtendedWinner(userChoice, computerChoice) {
if (userChoice === computerChoice) return 'draw';
// 检查用户选择是否击败计算机选择
if (extendedRules[userChoice].includes(computerChoice)) {
return 'user';
} else {
return 'computer';
}
}
这个扩展版本减少了平局的概率(从33%降低到20%),增加了游戏的策略深度。
2. 统计与机器学习
高级版本可以跟踪用户的决策模式,并尝试预测:
class RPSAI {
constructor() {
this.userHistory = [];
this.patterns = {};
}
recordGame(userChoice, computerChoice, result) {
this.userHistory.push({ userChoice, computerChoice, result });
// 分析模式:用户输后经常出什么?赢后经常出什么?
if (this.userHistory.length >= 2) {
const prev = this.userHistory[this.userHistory.length - 2];
const current = this.userHistory[this.userHistory.length - 1];
const patternKey = `${prev.userChoice}_${prev.result}`;
if (!this.patterns[patternKey]) {
this.patterns[patternKey] = { rock: 0, paper: 0, scissors: 0 };
}
this.patterns[patternKey][current.userChoice]++;
}
}
predictUserChoice() {
if (this.userHistory.length === 0) {
// 没有历史,随机选择
return choices[Math.floor(Math.random() * choices.length)];
}
const lastGame = this.userHistory[this.userHistory.length - 1];
const patternKey = `${lastGame.userChoice}_${lastGame.result}`;
if (!this.patterns[patternKey]) {
return choices[Math.floor(Math.random() * choices.length)];
}
// 根据历史模式预测用户最可能的选择
const pattern = this.patterns[patternKey];
const total = pattern.rock + pattern.paper + pattern.scissors;
if (total === 0) return choices[Math.floor(Math.random() * choices.length)];
// 选择能击败用户最可能选择的选项
const userMostLikely = Object.entries(pattern).reduce((a, b) => a[1] > b[1] ? a : b)[0];
// 返回能击败用户最可能选择的选项
const rules = { rock: 'paper', paper: 'scissors', scissors: 'rock' };
return rules[userMostLikely];
}
}
这个简单的AI尝试学习用户的模式并反制,模拟了真实对手的心理博弈。
3. 多人对战与锦标赛
将游戏扩展到多人:
class RPSTournament {
constructor(players) {
this.players = players;
this.scores = {};
players.forEach(player => this.scores[player.id] = 0);
}
playRound() {
const choices = {};
// 每个玩家做出选择
this.players.forEach(player => {
choices[player.id] = player.makeChoice();
});
// 计算每对玩家的胜负
for (let i = 0; i < this.players.length; i++) {
for (let j = i + 1; j < this.players.length; j++) {
const player1 = this.players[i];
const player2 = this.players[j];
const result = determineWinner(choices[player1.id], choices[player2.id]);
if (result === 'player1') {
this.scores[player1.id] += 1;
} else if (result === 'player2') {
this.scores[player2.id] += 1;
} else {
// 平局,双方各得0.5分
this.scores[player1.id] += 0.5;
this.scores[player2.id] += 0.5;
}
}
}
return { choices, scores: {...this.scores} };
}
}
这种锦标赛模式模拟了现实中的石头剪刀布比赛,如世界石头剪刀布协会(WRPS)举办的比赛。
4. 动画与特效
增强游戏体验的视觉反馈:
function animateChoice(choice, element) {
// 重置动画
element.style.animation = 'none';
element.offsetHeight; // 触发重排
element.style.animation = null;
// 添加特定动画
if (choice === 'rock') {
element.classList.add('rock-animation');
} else if (choice === 'paper') {
element.classList.add('paper-animation');
} else if (choice === 'scissors') {
element.classList.add('scissors-animation');
}
// 动画结束后移除类
setTimeout(() => {
element.classList.remove('rock-animation', 'paper-animation', 'scissors-animation');
}, 1000);
}
相应的CSS动画:
@keyframes rockShake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
@keyframes paperWave {
0%, 100% { transform: rotate(0deg); }
50% { transform: rotate(5deg); }
}
@keyframes scissorsCut {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.rock-animation { animation: rockShake 0.5s ease; }
.paper-animation { animation: paperWave 0.5s ease; }
.scissors-animation { animation: scissorsCut 0.5s ease; }
五、 石头剪刀布的数学:概率与策略深度
石头剪刀布看似简单,但有深刻的数学原理:
1. 纳什均衡
在标准石头剪刀布中,混合策略纳什均衡是每个选择以1/3的概率随机选择。任何偏离这个均衡的策略都会被利用。
2. 信息论视角
每次游戏传递的信息量:
- 3种可能结果:赢、输、平局
- 信息量 = log₂(3) ≈ 1.585比特
3. 序列的概率
在n局游戏中,特定序列出现的概率:
- 连续两次出石头的概率:(1/3)² = 1/9 ≈ 11.1%
- 永远不出石头的概率:随着游戏次数增加趋近于0
4. 心理博弈的数学建模
人类的决策不是完全随机的,他们:
- 避免连续出相同选择(赌徒谬误)
- 倾向于模仿对手上一轮的选择
- 输后更可能改变策略
这些模式可以被数学模型捕捉和利用。
六、 石头剪刀布的现实应用
这个简单游戏有出人意料的重要应用:
1. 密码学与安全
// 使用石头剪刀布协议进行安全密钥交换
class RPSKeyExchange {
constructor() {
this.privateChoice = null;
this.publicCommitment = null;
}
// 第一步:提交承诺(哈希)
commit(choice) {
this.privateChoice = choice;
const salt = Math.random().toString(36).substring(2);
this.publicCommitment = this.hash(choice + salt);
return { commitment: this.publicCommitment, salt };
}
// 第二步:揭示选择
reveal(salt) {
const verification = this.hash(this.privateChoice + salt);
if (verification === this.publicCommitment) {
return this.privateChoice;
} else {
throw new Error('承诺验证失败');
}
}
hash(str) {
// 简单哈希函数(实际应用中应使用加密哈希)
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // 转换为32位整数
}
return hash.toString(36);
}
}
这种协议可以用于防止欺骗的安全游戏,或在分布式系统中做随机决定。
2. 决策工具
当面临两个同等吸引力的选项时,用石头剪刀布决定:
- 将选项映射到石头、布、剪刀
- 玩一局游戏
- 赢家对应的选项被选择
3. 教育工具
教授儿童:
4. 人工智能测试
简单的石头剪刀布可以作为AI算法的基准测试:
七、 石头剪刀布的文化与历史
这个游戏有丰富的历史背景:
- 中国起源:汉代(公元前206年-公元220年)的“手势令”
- 日本发展:江户时代(1603-1868年)的“虫拳”(蛇、蛙、蛞蝓)
- 西方传播:19世纪通过贸易路线从日本传到西方
- 现代竞技:2002年成立世界石头剪刀布协会,举办年度锦标赛
这些历史背景提醒我们:即使是最简单的游戏,也有深厚的文化根源。
八、 写给游戏设计者的思考
构建石头剪刀布游戏,教会我们几个关于游戏设计的核心原则:
1. 平衡性
每个选择必须有明确的优势和劣势,没有任何选择应该主导游戏。
2. 简单性
规则应该简单到可以用一句话解释,但策略深度应该足够吸引重复游玩。
3. 即时反馈
每个动作都应该有立即的、清晰的反馈,让玩家理解因果。
4. 重玩价值
随机元素和策略深度创造了重玩价值——玩家想再玩一次。
所以,下次你玩石头剪刀布时,请意识到:
你不仅仅是在做一个简单的选择。你正在参与一个跨越千年的文化传统,一个被数学家研究的博弈系统,一个被计算机科学家模拟的决策模型。
这个简单的游戏,像一面镜子,反映了人类决策的本质:在规则约束下,在不确定性中,试图预测和反制对手。
而当我们从零开始实现这个游戏时,我们实际上在练习一种交互设计的基础语言——如何将复杂规则转化为直观界面,如何将数学逻辑转化为可玩体验,如何将文化传统转化为数字形式。
最终,这个简单的石头剪刀布游戏提醒我们:最好的游戏往往是最简单的——不是因为它缺乏深度,而是因为它将深度隐藏在简单的表面之下。而好的游戏设计,就是创造这种易于学习、难以精通的体验。
在我们日益复杂的数字生活中,理解如何设计好的简单游戏,不仅让我们成为更好的开发者,也让我们成为更有洞察力的体验者。而这,或许就是这个练习最深刻的价值:在学会编写游戏的同时,不忘记游戏最终是为人类的乐趣和智慧服务的。
从简单的条件判断到复杂的博弈算法,从HTML搭建界面到CSS渲染视觉,每一次代码的实践都是对技术的深入理解。想了解更多关于前端实现、算法逻辑或其他有趣的开发内容,欢迎来 云栈社区 一起交流学习。