找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2281

积分

0

好友

307

主题
发表于 3 小时前 | 查看: 3| 回复: 0

每一次点击,都是对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}`;

这个结果字符串包含三个信息:

  1. 用户的选择
  2. 计算机的选择
  3. 游戏结果

这种叙事结构帮助用户理解发生了什么,特别是当他们输了时,可以清楚地看到对手的选择。

但这里有一个本地化问题:按钮使用英文标识(rock, paper, scissors),但结果显示使用中文。在实际应用中,我们需要确保一致性。

四、 从简单到竞技:石头剪刀布的深度演化

1. 扩展版本:蜥蜴史波克
来自《生活大爆炸》的著名扩展,增加了两个选项:

  • 蜥蜴(Lizard)
  • 史波克(Spock)

规则变为:
剪刀剪布,布包石头,石头压蜥蜴,蜥蜴毒史波克,史波克砸剪刀,剪刀斩蜥蜴,蜥蜴吃布,布驳史波克,史波克蒸石头,石头砸剪刀

实现这个扩展版本:

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渲染视觉,每一次代码的实践都是对技术的深入理解。想了解更多关于前端实现、算法逻辑或其他有趣的开发内容,欢迎来 云栈社区 一起交流学习。




上一篇:Node.js实用技巧:使用对象提升代码可读性的键值对存储方案
下一篇:Ubuntu系统中安装Gnome、Gedit文本编辑器与GIMP图形处理工具指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-12 11:16 , Processed in 0.538096 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表