石頭剪刀布遊戲技術實作

深入了解石頭剪刀布遊戲的完整實作,包含回合制遊戲邏輯、動畫效果系統、統計分析功能、鍵盤快捷鍵和雙語界面整合

概述

石頭剪刀布遊戲是一個經典的回合制對戰遊戲,專為提供流暢的互動體驗而設計。支援玩家與電腦對戰、實時動畫效果、詳細統計分析和鍵盤快捷鍵操作。 採用純前端 JavaScript 實作,包含完整的遊戲邏輯、狀態管理、動畫系統和本地數據持久化。 提供視覺化的遊戲介面、即時反饋系統和豐富的統計功能。

🎮 經典遊戲體驗

完整重現經典石頭剪刀布遊戲的樂趣,包含精美的動畫效果、直觀的操作介面和詳盡的統計分析,讓每局遊戲都充滿挑戰性。

技術架構與核心設計

整體架構設計

技術項目 實作方式 設計考量
遊戲邏輯 回合制狀態機 清晰的遊戲流程控制
動畫系統 CSS 動畫 + JavaScript 控制 流暢的視覺反饋體驗
統計分析 本地存儲 + 實時計算 持久化數據與即時反饋
用戶介面 響應式設計 + 雙語系統 跨設備兼容性與國際化
交互控制 點擊操作 + 鍵盤快捷鍵 多種操作方式提升可用性

核心類別架構

class RockPaperScissorsGame {
    constructor() {
        this.currentLanguage = 'zh';
        this.gameStats = this.loadStats();
        this.choiceStats = this.loadChoiceStats();
        this.isPlaying = false;
        
        this.choices = {
            rock: { emoji: '✊', beats: 'scissors' },
            paper: { emoji: '🖐', beats: 'rock' },
            scissors: { emoji: '✌️', beats: 'paper' }
        };
        
        this.init();
    }
    
    init() {
        this.setupEventListeners();
        this.updateLanguage();
        this.updateScoreDisplay();
        this.updateStatsDisplay();
    }
}

關鍵功能實現

  • 🎯 回合制遊戲邏輯:完整的石頭剪刀布規則實現,包含勝負判定和平手處理
  • 🎨 豐富動畫效果:選擇按鈕高亮、結果顯示動畫、勝負特效等視覺反饋
  • 📊 詳細統計分析:勝負記錄、選擇偏好分析、勝率計算等數據統計
  • ⌨️ 鍵盤快捷鍵:支援 R/P/S 或 1/2/3 快速選擇,N 鍵開新遊戲
  • 🎮 遊戲狀態管理:等待中/遊戲中狀態切換,防止重複操作
  • 💾 本地數據持久化:使用 localStorage 儲存遊戲統計和選擇記錄
  • 🌐 完整雙語支援:中英文介面無縫切換,包含所有遊戲文字
  • 📱 響應式設計:完美適配手機、平板、桌面各種設備

🎲 遊戲公平性

電腦選擇採用 Math.random() 實現真隨機生成,確保每局遊戲的公平性。遊戲不存在任何作弊機制,純粹依靠運氣和策略。

完整程式碼實作

以下是石頭剪刀布遊戲的完整程式碼實作,按功能模組分類展示。點擊卡片標題可展開查看詳細程式碼:

📦 核心遊戲類別

石頭剪刀布遊戲的核心類別實作,包含完整的建構函數、遊戲狀態管理、選擇規則定義和雙語翻譯系統。 這個類別是整個遊戲系統的基礎,負責管理所有遊戲邏輯和狀態。

class RockPaperScissorsGame {
    constructor() {
        // 基本遊戲狀態 / Basic game state
        this.currentLanguage = 'zh';       // 當前語言設定
        this.gameStats = this.loadStats(); // 載入遊戲統計數據
        this.choiceStats = this.loadChoiceStats(); // 載入選擇統計
        this.isPlaying = false;            // 遊戲進行中標記
        
        // 遊戲規則定義 / Game rules definition
        this.choices = {
            rock: { 
                emoji: '✊', 
                beats: 'scissors'    // 石頭勝剪刀
            },
            paper: { 
                emoji: '🖐', 
                beats: 'rock'        // 布勝石頭
            },
            scissors: { 
                emoji: '✌️', 
                beats: 'paper'       // 剪刀勝布
            }
        };
        
        // 完整的雙語翻譯系統 / Complete bilingual translation system
        this.translations = {
            zh: {
                gameTitle: '✂️ 石頭剪刀布',
                gameSubtitle: '與電腦進行經典對戰,看看誰的運氣更好!',
                choiceInstruction: '請選擇你的手勢:',
                stateMessageWaiting: '請選擇你的手勢開始遊戲',
                stateMessagePlaying: '遊戲進行中...',
                playerLabel: '你的選擇',
                computerLabel: '電腦選擇',
                resultTextStart: '開始你的第一局遊戲吧!',
                rockLabel: '石頭',
                paperLabel: '布',
                scissorsLabel: '剪刀',
                winsLabel: '勝利',
                lossesLabel: '失敗',
                tiesLabel: '平手',
                newGameBtn: '🔄 新遊戲',
                resetStatsBtn: '📊 重置統計',
                toggleStatsBtn: '📈 顯示統計',
                hideStatsBtn: '📈 隱藏統計',
                statsTitle: '遊戲統計',
                winRateLabel: '總勝率',
                youWin: '🎉 你贏了!',
                youLose: '😢 你輸了!',
                itsTie: '🤝 平手!',
                rockChoice: '石頭',
                paperChoice: '布',
                scissorsChoice: '剪刀',
                timesText: '次'
            },
            en: {
                gameTitle: '✂️ Rock Paper Scissors',
                gameSubtitle: 'Play the classic game against computer and test your luck!',
                choiceInstruction: 'Choose your move:',
                stateMessageWaiting: 'Choose your move to start the game',
                stateMessagePlaying: 'Game in progress...',
                playerLabel: 'Your Choice',
                computerLabel: 'Computer Choice',
                resultTextStart: 'Start your first game!',
                rockLabel: 'Rock',
                paperLabel: 'Paper',
                scissorsLabel: 'Scissors',
                winsLabel: 'Wins',
                lossesLabel: 'Losses',
                tiesLabel: 'Ties',
                newGameBtn: '🔄 New Game',
                resetStatsBtn: '📊 Reset Stats',
                toggleStatsBtn: '📈 Show Stats',
                hideStatsBtn: '📈 Hide Stats',
                statsTitle: 'Game Statistics',
                winRateLabel: 'Win Rate',
                youWin: '🎉 You Win!',
                youLose: '😢 You Lose!',
                itsTie: '🤝 It\'s a Tie!',
                rockChoice: 'Rock',
                paperChoice: 'Paper',
                scissorsChoice: 'Scissors',
                timesText: ' times'
            }
        };
        
        // 初始化遊戲 / Initialize game
        this.init();
    }
    
    /**
     * 遊戲初始化流程 / Game initialization process
     * 按順序執行:事件監聽器 → 語言更新 → 顯示更新 → 統計更新
     */
    init() {
        this.setupEventListeners();  // 設置事件監聽器
        this.updateLanguage();       // 更新語言顯示
        this.updateScoreDisplay();   // 更新分數顯示
        this.updateStatsDisplay();   // 更新統計顯示
    }
}

🌐 事件監聽器與快捷鍵

實現與 Tool Master 全域語言系統的完美整合,並提供豐富的鍵盤快捷鍵操作。 包含語言切換監聽器、鍵盤事件處理和時序控制機制。

/**
 * 事件監聽器設置 / Event listeners setup
 * 包含語言切換監聽和鍵盤快捷鍵支援
 */
setupEventListeners() {
    // 設置全域語言監聽器 / Setup global language listener
    window.addEventListener('languageChanged', (event) => {
        this.currentLanguage = event.detail.language;
        this.updateLanguage();
        console.log(`石頭剪刀布遊戲語言已切換至: ${this.currentLanguage}`);
    });
    
    // 初始化時從全域系統獲取語言 / Get language from global system on init
    const checkGlobalLanguage = () => {
        if (window.globalI18n) {
            this.currentLanguage = window.globalI18n.currentLanguage;
            this.updateLanguage();
            console.log(`同步全域語言設定: ${this.currentLanguage}`);
            return true;
        }
        return false;
    };
    
    // 立即檢查,如果沒有就延遲重試 / Immediate check, retry if not available
    if (!checkGlobalLanguage()) {
        setTimeout(() => {
            checkGlobalLanguage();
        }, 100);
    }
    
    // 鍵盤快捷鍵支援 / Keyboard shortcuts support
    document.addEventListener('keydown', (e) => {
        // 只在遊戲未進行時響應快捷鍵 / Only respond to shortcuts when game is not in progress
        if (!this.isPlaying) {
            const key = e.key.toLowerCase();
            
            switch(key) {
                case 'r':        // R 鍵選擇石頭
                case '1':        // 數字 1 選擇石頭
                    this.playerChoice('rock');
                    console.log('快捷鍵:選擇石頭');
                    break;
                    
                case 'p':        // P 鍵選擇布
                case '2':        // 數字 2 選擇布
                    this.playerChoice('paper');
                    console.log('快捷鍵:選擇布');
                    break;
                    
                case 's':        // S 鍵選擇剪刀
                case '3':        // 數字 3 選擇剪刀
                    this.playerChoice('scissors');
                    console.log('快捷鍵:選擇剪刀');
                    break;
                    
                case 'n':        // N 鍵開始新遊戲
                    this.newGame();
                    console.log('快捷鍵:新遊戲');
                    break;
                    
                case 'h':        // H 鍵顯示幫助(隱藏功能)
                    this.showKeyboardHelp();
                    break;
                    
                default:
                    // 其他按鍵不處理
                    break;
            }
        } else {
            // 遊戲進行中時提示用戶等待
            if (['r', 'p', 's', '1', '2', '3'].includes(e.key.toLowerCase())) {
                console.log('遊戲進行中,請等待當前回合結束');
            }
        }
    });
    
    // 視窗獲得焦點時重新檢查語言設定 / Recheck language when window gains focus
    window.addEventListener('focus', () => {
        if (window.globalI18n && this.currentLanguage !== window.globalI18n.currentLanguage) {
            this.currentLanguage = window.globalI18n.currentLanguage;
            this.updateLanguage();
        }
    });
}

/**
 * 顯示鍵盤快捷鍵幫助 / Show keyboard shortcuts help
 * 隱藏功能:按 H 鍵顯示快捷鍵說明
 */
showKeyboardHelp() {
    const t = this.translations[this.currentLanguage];
    const helpText = this.currentLanguage === 'zh' ? 
        `鍵盤快捷鍵幫助:
        
R 或 1 - 選擇石頭 ✊
P 或 2 - 選擇布 🖐  
S 或 3 - 選擇剪刀 ✌️
N - 開始新遊戲 🔄
H - 顯示此幫助

提示:遊戲進行中時快捷鍵會暫時停用` :
        `Keyboard Shortcuts Help:
        
R or 1 - Choose Rock ✊
P or 2 - Choose Paper 🖐
S or 3 - Choose Scissors ✌️  
N - New Game 🔄
H - Show this help

Note: Shortcuts are disabled during game play`;
    
    alert(helpText);
}

/**
 * 語言更新方法 / Language update method
 * 更新所有介面元素的文字內容
 */
updateLanguage() {
    const t = this.translations[this.currentLanguage];
    
    // 更新主要介面元素 / Update main interface elements
    const elementsToUpdate = [
        { id: 'game-title', content: t.gameTitle },
        { id: 'game-subtitle', content: t.gameSubtitle },
        { id: 'choice-instruction', content: t.choiceInstruction },
        { id: 'state-message', content: t.stateMessageWaiting },
        { id: 'player-label', content: t.playerLabel },
        { id: 'computer-label', content: t.computerLabel },
        { id: 'result-text', content: t.resultTextStart },
        { id: 'rock-label', content: t.rockLabel },
        { id: 'paper-label', content: t.paperLabel },
        { id: 'scissors-label', content: t.scissorsLabel },
        { id: 'wins-label', content: t.winsLabel },
        { id: 'losses-label', content: t.lossesLabel },
        { id: 'ties-label', content: t.tiesLabel },
        { id: 'new-game-btn', content: t.newGameBtn },
        { id: 'reset-stats-btn', content: t.resetStatsBtn },
        { id: 'stats-title', content: t.statsTitle },
        { id: 'win-rate-label', content: t.winRateLabel }
    ];
    
    // 批次更新所有元素 / Batch update all elements
    elementsToUpdate.forEach(({ id, content }) => {
        const element = document.getElementById(id);
        if (element) {
            element.textContent = content;
        }
    });
    
    // 更新統計按鈕文字(根據當前顯示狀態)/ Update stats button text (based on current display state)
    const statsSection = document.getElementById('stats-section');
    const toggleBtn = document.getElementById('toggle-stats-btn');
    if (statsSection && toggleBtn) {
        const isHidden = statsSection.classList.contains('hidden');
        toggleBtn.textContent = isHidden ? t.toggleStatsBtn : t.hideStatsBtn;
    }
    
    // 更新統計顯示 / Update statistics display
    this.updateStatsDisplay();
    
    console.log(`介面語言已更新為: ${this.currentLanguage}`);
}

🎯 遊戲核心邏輯

石頭剪刀布遊戲的核心邏輯實現,包含玩家選擇處理、電腦選擇生成、勝負判定算法和遊戲狀態控制。 實現完整的回合制遊戲流程,確保遊戲的公平性和流暢性。

/**
 * 玩家選擇處理 / Player choice handling
 * 核心遊戲邏輯,處理整個遊戲回合流程
 */
playerChoice(choice) {
    // 防止遊戲進行中重複操作 / Prevent duplicate operations during game
    if (this.isPlaying) {
        console.log('遊戲進行中,請等待當前回合結束');
        return;
    }
    
    // 設置遊戲狀態為進行中 / Set game state to playing
    this.isPlaying = true;
    console.log(`玩家選擇: ${choice}`);
    
    // 更新遊戲狀態提示 / Update game state message
    const stateMessage = document.getElementById('state-message');
    const gameState = document.getElementById('game-state');
    const t = this.translations[this.currentLanguage];
    
    if (stateMessage) {
        stateMessage.textContent = t.stateMessagePlaying;
    }
    if (gameState) {
        gameState.className = 'game-state playing';
    }
    
    // 高亮顯示選中的按鈕 / Highlight selected button
    this.highlightSelectedChoice(choice);
    
    // 顯示玩家選擇 / Display player choice
    const playerChoiceDisplay = document.getElementById('player-choice');
    if (playerChoiceDisplay) {
        playerChoiceDisplay.textContent = this.choices[choice].emoji;
    }
    
    // 生成電腦選擇 / Generate computer choice
    const computerChoice = this.getComputerChoice();
    console.log(`電腦選擇: ${computerChoice}`);
    
    // 延遲顯示電腦選擇(增加懸念)/ Delay showing computer choice (build suspense)
    setTimeout(() => {
        this.showComputerChoice(computerChoice);
        
        // 判斷勝負 / Determine winner
        const result = this.determineWinner(choice, computerChoice);
        console.log(`遊戲結果: ${result}`);
        
        // 顯示結果 / Display result
        this.displayResult(result, choice, computerChoice);
        
        // 更新統計數據 / Update statistics
        this.updateStats(result, choice);
        this.updateScoreDisplay();
        this.updateStatsDisplay();
        
        // 延遲重置遊戲狀態 / Delay reset game state
        setTimeout(() => {
            this.resetGameState();
        }, 2000);
        
    }, 1000); // 1秒延遲,讓玩家有時間觀察自己的選擇
}

/**
 * 高亮顯示選中的選擇按鈕 / Highlight selected choice button
 */
highlightSelectedChoice(choice) {
    // 移除所有按鈕的選中狀態 / Remove selected state from all buttons
    document.querySelectorAll('.choice-btn').forEach(btn => {
        btn.classList.remove('selected');
    });
    
    // 高亮當前選中的按鈕 / Highlight current selected button
    const selectedBtn = document.querySelector(`[data-choice="${choice}"]`);
    if (selectedBtn) {
        selectedBtn.classList.add('selected');
    }
}

/**
 * 電腦選擇生成 / Computer choice generation
 * 使用真隨機算法,確保遊戲公平性
 */
getComputerChoice() {
    const choices = ['rock', 'paper', 'scissors'];
    
    // 使用 Math.random() 生成真隨機選擇
    const randomIndex = Math.floor(Math.random() * choices.length);
    const computerChoice = choices[randomIndex];
    
    // 記錄選擇概率(用於調試)/ Log choice probability (for debugging)
    console.log(`電腦隨機選擇: ${computerChoice} (索引: ${randomIndex})`);
    
    return computerChoice;
}

/**
 * 顯示電腦選擇 / Show computer choice
 * 包含動畫效果
 */
showComputerChoice(computerChoice) {
    const computerDisplay = document.getElementById('computer-choice');
    if (computerDisplay) {
        computerDisplay.textContent = this.choices[computerChoice].emoji;
        computerDisplay.classList.add('animate');
        
        // 2秒後移除動畫類別 / Remove animation class after 2 seconds
        setTimeout(() => {
            computerDisplay.classList.remove('animate');
        }, 2000);
    }
}

/**
 * 勝負判定算法 / Win/lose determination algorithm
 * 實現完整的石頭剪刀布規則
 */
determineWinner(playerChoice, computerChoice) {
    // 平手情況 / Tie situation
    if (playerChoice === computerChoice) {
        return 'tie';
    }
    
    // 玩家獲勝情況 / Player wins situation
    if (this.choices[playerChoice].beats === computerChoice) {
        return 'win';
    }
    
    // 否則玩家失敗 / Otherwise player loses
    return 'lose';
}

/**
 * 重置遊戲狀態 / Reset game state
 * 恢復等待狀態,準備下一回合
 */
resetGameState() {
    this.isPlaying = false;
    
    const stateMessage = document.getElementById('state-message');
    const gameState = document.getElementById('game-state');
    const t = this.translations[this.currentLanguage];
    
    if (stateMessage) {
        stateMessage.textContent = t.stateMessageWaiting;
    }
    if (gameState) {
        gameState.className = 'game-state waiting';
    }
    
    // 移除所有按鈕的選中狀態 / Remove selected state from all buttons
    document.querySelectorAll('.choice-btn').forEach(btn => {
        btn.classList.remove('selected');
    });
    
    console.log('遊戲狀態已重置,準備下一回合');
}

⚙️ 結果顯示與動畫系統

遊戲結果顯示和動畫效果的完整實現,包含勝負結果展示、動畫觸發控制、視覺反饋系統。 提供豐富的動畫效果,包含慶祝、失敗、平手等不同情況的視覺反饋。

/**
 * 結果顯示系統 / Result display system
 * 處理遊戲結果的視覺展示和動畫效果
 */
displayResult(result, playerChoice, computerChoice) {
    const resultMessage = document.getElementById('result-message');
    const resultText = document.getElementById('result-text');
    const t = this.translations[this.currentLanguage];
    
    if (!resultMessage || !resultText) {
        console.error('找不到結果顯示元素');
        return;
    }
    
    // 移除所有現有類別,確保動畫能重新觸發 / Remove all existing classes to ensure animation can retrigger
    resultMessage.className = 'result-message';
    
    // 強制重排以確保類別確實被移除 / Force reflow to ensure classes are actually removed
    resultMessage.offsetHeight;
    
    // 添加新結果動畫類別 / Add new result animation class
    resultMessage.classList.add('new-result');
    
    // 根據結果類型設置不同的樣式和文字 / Set different styles and text based on result type
    switch(result) {
        case 'win':
            resultMessage.classList.add('win');
            resultText.textContent = t.youWin;
            this.triggerWinAnimation();
            console.log('🎉 玩家獲勝!');
            break;
            
        case 'lose':
            resultMessage.classList.add('lose');
            resultText.textContent = t.youLose;
            this.triggerLoseAnimation();
            console.log('😢 玩家失敗!');
            break;
            
        case 'tie':
            resultMessage.classList.add('tie');
            resultText.textContent = t.itsTie;
            this.triggerTieAnimation();
            console.log('🤝 平手!');
            break;
            
        default:
            console.error('未知的遊戲結果:', result);
            break;
    }
    
    // 在動畫完成後移除新結果動畫類別 / Remove new result animation class after animation completes
    setTimeout(() => {
        resultMessage.classList.remove('new-result');
    }, 600);
    
    // 記錄遊戲結果到統計 / Log game result to statistics
    this.logGameResult(result, playerChoice, computerChoice);
}

/**
 * 勝利動畫效果 / Win animation effect
 * 觸發勝利時的特殊視覺效果
 */
triggerWinAnimation() {
    // 為勝利添加特殊效果 / Add special effects for victory
    const winEffects = document.querySelectorAll('.choice-btn');
    winEffects.forEach(btn => {
        btn.style.animation = 'celebration 0.8s ease-in-out';
        setTimeout(() => {
            btn.style.animation = '';
        }, 800);
    });
    
    // 短暫的頁面震動效果 / Brief page shake effect
    document.body.style.animation = 'shake 0.3s ease-in-out';
    setTimeout(() => {
        document.body.style.animation = '';
    }, 300);
}

/**
 * 失敗動畫效果 / Lose animation effect
 * 觸發失敗時的視覺反饋
 */
triggerLoseAnimation() {
    // 為失敗添加震動效果 / Add shake effect for loss
    const gameArea = document.querySelector('.game-area');
    if (gameArea) {
        gameArea.style.animation = 'shake 0.6s ease-in-out';
        setTimeout(() => {
            gameArea.style.animation = '';
        }, 600);
    }
}

/**
 * 平手動畫效果 / Tie animation effect
 * 觸發平手時的彈跳效果
 */
triggerTieAnimation() {
    // 為平手添加彈跳效果 / Add bounce effect for tie
    const battleDisplay = document.querySelector('.battle-display');
    if (battleDisplay) {
        battleDisplay.style.animation = 'bounce 0.5s ease-in-out';
        setTimeout(() => {
            battleDisplay.style.animation = '';
        }, 500);
    }
}

/**
 * 記錄遊戲結果 / Log game result
 * 用於調試和分析遊戲數據
 */
logGameResult(result, playerChoice, computerChoice) {
    const gameLog = {
        timestamp: new Date().toISOString(),
        result: result,
        playerChoice: playerChoice,
        computerChoice: computerChoice,
        language: this.currentLanguage
    };
    
    console.log('遊戲記錄:', gameLog);
    
    // 可選:將遊戲記錄保存到本地存儲(用於長期分析)
    // Optional: Save game log to local storage (for long-term analysis)
    const gameLogs = JSON.parse(localStorage.getItem('rps-game-logs') || '[]');
    gameLogs.push(gameLog);
    
    // 只保留最近100局的記錄 / Keep only the last 100 game records
    if (gameLogs.length > 100) {
        gameLogs.splice(0, gameLogs.length - 100);
    }
    
    localStorage.setItem('rps-game-logs', JSON.stringify(gameLogs));
}

/**
 * 新遊戲功能 / New game functionality
 * 重置遊戲顯示狀態,準備新的遊戲回合
 */
newGame() {
    console.log('開始新遊戲');
    
    // 重置顯示元素 / Reset display elements
    const playerChoice = document.getElementById('player-choice');
    const computerChoice = document.getElementById('computer-choice');
    const resultText = document.getElementById('result-text');
    const resultMessage = document.getElementById('result-message');
    
    if (playerChoice) playerChoice.textContent = '❓';
    if (computerChoice) computerChoice.textContent = '❓';
    
    const t = this.translations[this.currentLanguage];
    if (resultText) resultText.textContent = t.resultTextStart;
    
    // 確保移除所有動畫和狀態類別 / Ensure all animation and state classes are removed
    if (resultMessage) {
        resultMessage.className = 'result-message';
    }
    
    // 重置遊戲狀態 / Reset game state
    const stateMessage = document.getElementById('state-message');
    const gameState = document.getElementById('game-state');
    
    if (stateMessage) stateMessage.textContent = t.stateMessageWaiting;
    if (gameState) gameState.className = 'game-state waiting';
    
    // 移除所有按鈕的選中狀態 / Remove selected state from all buttons
    document.querySelectorAll('.choice-btn').forEach(btn => {
        btn.classList.remove('selected');
    });
    
    // 重置遊戲進行標記 / Reset game playing flag
    this.isPlaying = false;
    
    console.log('新遊戲已準備就緒');
}

🎨 統計系統與資料管理

完整的統計系統實現,包含遊戲統計更新、選擇偏好分析、勝率計算、本地數據持久化。 提供詳細的數據分析功能,幫助玩家了解自己的遊戲習慣和表現。

/**
 * 統計數據更新 / Statistics data update
 * 更新遊戲統計和選擇偏好統計
 */
updateStats(result, playerChoice) {
    // 更新遊戲結果統計 / Update game result statistics
    switch(result) {
        case 'win':
            this.gameStats.wins++;
            console.log(`勝利次數更新: ${this.gameStats.wins}`);
            break;
        case 'lose':
            this.gameStats.losses++;
            console.log(`失敗次數更新: ${this.gameStats.losses}`);
            break;
        case 'tie':
            this.gameStats.ties++;
            console.log(`平手次數更新: ${this.gameStats.ties}`);
            break;
    }
    
    // 更新選擇偏好統計 / Update choice preference statistics
    this.choiceStats[playerChoice]++;
    console.log(`${playerChoice} 選擇次數更新: ${this.choiceStats[playerChoice]}`);
    
    // 保存統計數據到本地存儲 / Save statistics to local storage
    this.saveStats();
    this.saveChoiceStats();
    
    // 計算並記錄當前統計概況 / Calculate and log current statistics overview
    this.logStatisticsSummary();
}

/**
 * 更新分數顯示 / Update score display
 * 更新遊戲主介面的分數統計顯示
 */
updateScoreDisplay() {
    const winsElement = document.getElementById('wins-count');
    const lossesElement = document.getElementById('losses-count');
    const tiesElement = document.getElementById('ties-count');
    
    if (winsElement) winsElement.textContent = this.gameStats.wins;
    if (lossesElement) lossesElement.textContent = this.gameStats.losses;
    if (tiesElement) tiesElement.textContent = this.gameStats.ties;
    
    // 更新分數顯示的動畫效果 / Add animation effect to score display updates
    [winsElement, lossesElement, tiesElement].forEach(element => {
        if (element) {
            element.style.animation = 'pulse 0.3s ease-in-out';
            setTimeout(() => {
                element.style.animation = '';
            }, 300);
        }
    });
}

/**
 * 更新統計圖表顯示 / Update statistics chart display
 * 更新詳細的統計圖表和百分比資訊
 */
updateStatsDisplay() {
    const total = this.choiceStats.rock + this.choiceStats.paper + this.choiceStats.scissors;
    const t = this.translations[this.currentLanguage];
    
    // 如果沒有遊戲記錄,顯示初始狀態 / If no game records, show initial state
    if (total === 0) {
        this.displayInitialStats(t);
        return;
    }
    
    // 計算並顯示選擇百分比 / Calculate and display choice percentages
    const rockPercentage = Math.round((this.choiceStats.rock / total) * 100);
    const paperPercentage = Math.round((this.choiceStats.paper / total) * 100);
    const scissorsPercentage = Math.round((this.choiceStats.scissors / total) * 100);
    
    // 更新百分比顯示 / Update percentage display
    this.updateElement('rock-percentage', rockPercentage + '%');
    this.updateElement('paper-percentage', paperPercentage + '%');
    this.updateElement('scissors-percentage', scissorsPercentage + '%');
    
    // 更新次數顯示 / Update count display
    this.updateElement('rock-count', this.choiceStats.rock + t.timesText);
    this.updateElement('paper-count', this.choiceStats.paper + t.timesText);
    this.updateElement('scissors-count', this.choiceStats.scissors + t.timesText);
    
    // 計算並顯示總勝率 / Calculate and display overall win rate
    const totalGames = this.gameStats.wins + this.gameStats.losses + this.gameStats.ties;
    const winRate = totalGames === 0 ? 0 : Math.round((this.gameStats.wins / totalGames) * 100);
    this.updateElement('win-rate-value', winRate + '%');
    
    // 更新勝率顏色 / Update win rate color
    const winRateElement = document.getElementById('win-rate-value');
    if (winRateElement) {
        winRateElement.style.color = this.getWinRateColor(winRate);
    }
    
    console.log(`統計更新 - 總局數: ${totalGames}, 勝率: ${winRate}%`);
}

/**
 * 顯示初始統計狀態 / Display initial statistics state
 */
displayInitialStats(t) {
    const initialStats = [
        { id: 'rock-percentage', value: '0%' },
        { id: 'paper-percentage', value: '0%' },
        { id: 'scissors-percentage', value: '0%' },
        { id: 'rock-count', value: '0' + t.timesText },
        { id: 'paper-count', value: '0' + t.timesText },
        { id: 'scissors-count', value: '0' + t.timesText },
        { id: 'win-rate-value', value: '0%' }
    ];
    
    initialStats.forEach(({ id, value }) => {
        this.updateElement(id, value);
    });
}

/**
 * 更新元素內容的輔助方法 / Helper method to update element content
 */
updateElement(id, content) {
    const element = document.getElementById(id);
    if (element) {
        element.textContent = content;
    }
}

/**
 * 根據勝率獲取顏色 / Get color based on win rate
 */
getWinRateColor(winRate) {
    if (winRate >= 60) return '#28a745';      // 綠色 - 高勝率
    if (winRate >= 40) return '#ffc107';      // 黃色 - 中等勝率
    return '#dc3545';                         // 紅色 - 低勝率
}

/**
 * 記錄統計概況 / Log statistics summary
 */
logStatisticsSummary() {
    const totalGames = this.gameStats.wins + this.gameStats.losses + this.gameStats.ties;
    const totalChoices = this.choiceStats.rock + this.choiceStats.paper + this.choiceStats.scissors;
    
    console.log('=== 遊戲統計概況 ===');
    console.log(`總局數: ${totalGames}`);
    console.log(`勝利: ${this.gameStats.wins} (${totalGames ? Math.round((this.gameStats.wins / totalGames) * 100) : 0}%)`);
    console.log(`失敗: ${this.gameStats.losses} (${totalGames ? Math.round((this.gameStats.losses / totalGames) * 100) : 0}%)`);
    console.log(`平手: ${this.gameStats.ties} (${totalGames ? Math.round((this.gameStats.ties / totalGames) * 100) : 0}%)`);
    console.log('--- 選擇偏好 ---');
    console.log(`石頭: ${this.choiceStats.rock} (${totalChoices ? Math.round((this.choiceStats.rock / totalChoices) * 100) : 0}%)`);
    console.log(`布: ${this.choiceStats.paper} (${totalChoices ? Math.round((this.choiceStats.paper / totalChoices) * 100) : 0}%)`);
    console.log(`剪刀: ${this.choiceStats.scissors} (${totalChoices ? Math.round((this.choiceStats.scissors / totalChoices) * 100) : 0}%)`);
    console.log('==================');
}

🛠️ 統計控制與用戶交互

統計資料的用戶交互功能,包含統計重置、統計顯示切換、確認對話框處理。 提供完整的統計管理功能,讓玩家能夠控制自己的遊戲數據。

/**
 * 重置統計數據 / Reset statistics data
 * 清空所有遊戲統計和選擇記錄
 */
resetStats() {
    // 根據當前語言顯示確認對話框 / Show confirmation dialog based on current language
    const confirmMessage = this.currentLanguage === 'zh' ? 
        '確定要重置所有統計數據嗎?\n\n這將清空:\n• 勝負記錄\n• 選擇偏好統計\n• 遊戲歷史記錄\n\n此操作無法撤銷!' :
        'Are you sure you want to reset all statistics?\n\nThis will clear:\n• Win/Loss records\n• Choice preference statistics\n• Game history records\n\nThis action cannot be undone!';
    
    const confirmed = confirm(confirmMessage);
    
    if (confirmed) {
        // 重置所有統計數據 / Reset all statistics data
        this.gameStats = { wins: 0, losses: 0, ties: 0 };
        this.choiceStats = { rock: 0, paper: 0, scissors: 0 };
        
        // 清空本地存儲的遊戲記錄 / Clear game logs in local storage
        localStorage.removeItem('rps-game-logs');
        
        // 保存重置後的統計數據 / Save reset statistics data
        this.saveStats();
        this.saveChoiceStats();
        
        // 更新顯示 / Update display
        this.updateScoreDisplay();
        this.updateStatsDisplay();
        
        // 顯示重置成功提示 / Show reset success message
        const successMessage = this.currentLanguage === 'zh' ? 
            '統計數據已成功重置!' : 
            'Statistics data has been successfully reset!';
        
        // 使用自定義提示而非 alert / Use custom notification instead of alert
        this.showNotification(successMessage, 'success');
        
        console.log('統計數據已重置');
    } else {
        console.log('用戶取消了統計重置操作');
    }
}

/**
 * 切換統計顯示 / Toggle statistics display
 * 控制統計圖表的顯示和隱藏
 */
toggleStats() {
    const statsSection = document.getElementById('stats-section');
    const toggleBtn = document.getElementById('toggle-stats-btn');
    const t = this.translations[this.currentLanguage];
    
    if (!statsSection || !toggleBtn) {
        console.error('找不到統計相關元素');
        return;
    }
    
    const isCurrentlyHidden = statsSection.classList.contains('hidden');
    
    if (isCurrentlyHidden) {
        // 顯示統計 / Show statistics
        statsSection.classList.remove('hidden');
        toggleBtn.textContent = t.hideStatsBtn;
        
        // 添加淡入動畫 / Add fade-in animation
        statsSection.style.opacity = '0';
        statsSection.style.transform = 'translateY(20px)';
        
        // 強制重排後開始動畫 / Force reflow then start animation
        statsSection.offsetHeight;
        
        statsSection.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
        statsSection.style.opacity = '1';
        statsSection.style.transform = 'translateY(0)';
        
        console.log('統計圖表已顯示');
    } else {
        // 隱藏統計 / Hide statistics
        statsSection.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
        statsSection.style.opacity = '0';
        statsSection.style.transform = 'translateY(-20px)';
        
        // 動畫完成後隱藏元素 / Hide element after animation completes
        setTimeout(() => {
            statsSection.classList.add('hidden');
            statsSection.style.transition = '';
            statsSection.style.transform = '';
            statsSection.style.opacity = '';
        }, 300);
        
        toggleBtn.textContent = t.toggleStatsBtn;
        console.log('統計圖表已隱藏');
    }
}

/**
 * 顯示自定義通知 / Show custom notification
 * 替代瀏覽器原生 alert 的美化提示
 */
showNotification(message, type = 'info') {
    // 創建通知元素 / Create notification element
    const notification = document.createElement('div');
    notification.className = `custom-notification ${type}`;
    notification.textContent = message;
    
    // 設置通知樣式 / Set notification styles
    Object.assign(notification.style, {
        position: 'fixed',
        top: '20px',
        right: '20px',
        padding: '15px 20px',
        borderRadius: '8px',
        color: 'white',
        fontWeight: '600',
        fontSize: '14px',
        zIndex: '9999',
        boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
        transform: 'translateX(100%)',
        transition: 'transform 0.3s ease',
        maxWidth: '300px',
        wordWrap: 'break-word'
    });
    
    // 根據類型設置背景色 / Set background color based on type
    const colors = {
        success: '#28a745',
        error: '#dc3545',
        warning: '#ffc107',
        info: '#17a2b8'
    };
    notification.style.backgroundColor = colors[type] || colors.info;
    
    // 添加到頁面 / Add to page
    document.body.appendChild(notification);
    
    // 觸發進入動畫 / Trigger enter animation
    setTimeout(() => {
        notification.style.transform = 'translateX(0)';
    }, 100);
    
    // 3秒後自動移除 / Auto remove after 3 seconds
    setTimeout(() => {
        notification.style.transform = 'translateX(100%)';
        setTimeout(() => {
            if (notification.parentNode) {
                notification.parentNode.removeChild(notification);
            }
        }, 300);
    }, 3000);
}

/**
 * 獲取遊戲統計報告 / Get game statistics report
 * 生成詳細的統計報告(可用於導出或分析)
 */
getStatisticsReport() {
    const totalGames = this.gameStats.wins + this.gameStats.losses + this.gameStats.ties;
    const totalChoices = this.choiceStats.rock + this.choiceStats.paper + this.choiceStats.scissors;
    
    const report = {
        summary: {
            totalGames: totalGames,
            winRate: totalGames ? Math.round((this.gameStats.wins / totalGames) * 100) : 0,
            createdAt: new Date().toISOString()
        },
        gameResults: {
            wins: this.gameStats.wins,
            losses: this.gameStats.losses,
            ties: this.gameStats.ties,
            winPercentage: totalGames ? Math.round((this.gameStats.wins / totalGames) * 100) : 0,
            lossPercentage: totalGames ? Math.round((this.gameStats.losses / totalGames) * 100) : 0,
            tiePercentage: totalGames ? Math.round((this.gameStats.ties / totalGames) * 100) : 0
        },
        choicePreferences: {
            rock: {
                count: this.choiceStats.rock,
                percentage: totalChoices ? Math.round((this.choiceStats.rock / totalChoices) * 100) : 0
            },
            paper: {
                count: this.choiceStats.paper,
                percentage: totalChoices ? Math.round((this.choiceStats.paper / totalChoices) * 100) : 0
            },
            scissors: {
                count: this.choiceStats.scissors,
                percentage: totalChoices ? Math.round((this.choiceStats.scissors / totalChoices) * 100) : 0
            }
        },
        analysis: {
            mostUsedChoice: this.getMostUsedChoice(),
            leastUsedChoice: this.getLeastUsedChoice(),
            isBalanced: this.isChoiceBalanced(),
            playStyle: this.getPlayStyle()
        }
    };
    
    return report;
}

/**
 * 獲取最常使用的選擇 / Get most used choice
 */
getMostUsedChoice() {
    const choices = Object.entries(this.choiceStats);
    const maxChoice = choices.reduce((max, current) => 
        current[1] > max[1] ? current : max
    );
    return maxChoice[0];
}

/**
 * 獲取最少使用的選擇 / Get least used choice
 */
getLeastUsedChoice() {
    const choices = Object.entries(this.choiceStats);
    const minChoice = choices.reduce((min, current) => 
        current[1] < min[1] ? current : min
    );
    return minChoice[0];
}

/**
 * 判斷選擇是否平衡 / Check if choices are balanced
 */
isChoiceBalanced() {
    const total = this.choiceStats.rock + this.choiceStats.paper + this.choiceStats.scissors;
    if (total === 0) return true;
    
    const rockPercent = (this.choiceStats.rock / total) * 100;
    const paperPercent = (this.choiceStats.paper / total) * 100;
    const scissorsPercent = (this.choiceStats.scissors / total) * 100;
    
    // 如果任何一個選擇的比例超過50%,認為不平衡
    return Math.max(rockPercent, paperPercent, scissorsPercent) < 50;
}

/**
 * 獲取遊戲風格分析 / Get play style analysis
 */
getPlayStyle() {
    const winRate = this.gameStats.wins / (this.gameStats.wins + this.gameStats.losses + this.gameStats.ties);
    const mostUsed = this.getMostUsedChoice();
    
    if (winRate > 0.6) return 'aggressive';      // 激進型
    if (winRate < 0.4) return 'defensive';       // 防守型
    if (this.isChoiceBalanced()) return 'balanced'; // 平衡型
    return 'predictable';                        // 可預測型
}

🚀 本地存儲與工具初始化

本地數據持久化和工具的完整初始化流程。包含統計資料的儲存載入、DOM就緒檢測、全域函數設定和實例化。 確保遊戲在各種載入情況下都能正確初始化並保持數據持久性。

/**
 * 載入遊戲統計數據 / Load game statistics data
 * 從本地存儲中載入遊戲統計,如果不存在則返回預設值
 */
loadStats() {
    try {
        const savedStats = localStorage.getItem('rps-game-stats');
        if (savedStats) {
            const parsedStats = JSON.parse(savedStats);
            
            // 驗證數據完整性 / Validate data integrity
            if (this.validateStatsData(parsedStats)) {
                console.log('成功載入遊戲統計:', parsedStats);
                return parsedStats;
            } else {
                console.warn('載入的統計數據格式不正確,使用預設值');
                return this.getDefaultStats();
            }
        } else {
            console.log('沒有找到已保存的統計數據,使用預設值');
            return this.getDefaultStats();
        }
    } catch (error) {
        console.error('載入統計數據時發生錯誤:', error);
        return this.getDefaultStats();
    }
}

/**
 * 保存遊戲統計數據 / Save game statistics data
 * 將當前遊戲統計數據保存到本地存儲
 */
saveStats() {
    try {
        const statsToSave = {
            ...this.gameStats,
            lastUpdated: new Date().toISOString()
        };
        
        localStorage.setItem('rps-game-stats', JSON.stringify(statsToSave));
        console.log('遊戲統計已保存:', statsToSave);
    } catch (error) {
        console.error('保存統計數據時發生錯誤:', error);
    }
}

/**
 * 載入選擇統計數據 / Load choice statistics data
 * 從本地存儲中載入選擇偏好統計
 */
loadChoiceStats() {
    try {
        const savedChoiceStats = localStorage.getItem('rps-choice-stats');
        if (savedChoiceStats) {
            const parsedChoiceStats = JSON.parse(savedChoiceStats);
            
            // 驗證數據完整性 / Validate data integrity
            if (this.validateChoiceStatsData(parsedChoiceStats)) {
                console.log('成功載入選擇統計:', parsedChoiceStats);
                return parsedChoiceStats;
            } else {
                console.warn('載入的選擇統計數據格式不正確,使用預設值');
                return this.getDefaultChoiceStats();
            }
        } else {
            console.log('沒有找到已保存的選擇統計數據,使用預設值');
            return this.getDefaultChoiceStats();
        }
    } catch (error) {
        console.error('載入選擇統計數據時發生錯誤:', error);
        return this.getDefaultChoiceStats();
    }
}

/**
 * 保存選擇統計數據 / Save choice statistics data
 * 將當前選擇統計數據保存到本地存儲
 */
saveChoiceStats() {
    try {
        const choiceStatsToSave = {
            ...this.choiceStats,
            lastUpdated: new Date().toISOString()
        };
        
        localStorage.setItem('rps-choice-stats', JSON.stringify(choiceStatsToSave));
        console.log('選擇統計已保存:', choiceStatsToSave);
    } catch (error) {
        console.error('保存選擇統計數據時發生錯誤:', error);
    }
}

/**
 * 驗證統計數據格式 / Validate statistics data format
 */
validateStatsData(data) {
    return data && 
           typeof data.wins === 'number' && data.wins >= 0 &&
           typeof data.losses === 'number' && data.losses >= 0 &&
           typeof data.ties === 'number' && data.ties >= 0;
}

/**
 * 驗證選擇統計數據格式 / Validate choice statistics data format
 */
validateChoiceStatsData(data) {
    return data && 
           typeof data.rock === 'number' && data.rock >= 0 &&
           typeof data.paper === 'number' && data.paper >= 0 &&
           typeof data.scissors === 'number' && data.scissors >= 0;
}

/**
 * 獲取預設統計數據 / Get default statistics data
 */
getDefaultStats() {
    return { wins: 0, losses: 0, ties: 0 };
}

/**
 * 獲取預設選擇統計數據 / Get default choice statistics data
 */
getDefaultChoiceStats() {
    return { rock: 0, paper: 0, scissors: 0 };
}

/**
 * 程式碼卡片切換功能 / Code card toggle functionality
 * 為技術文檔頁面提供展開/收合功能
 */
function toggleCodeCard(header) {
    const content = header.nextElementSibling;
    const icon = header.querySelector('.toggle-icon');
    
    if (content.style.display === 'none' || content.style.display === '') {
        content.style.display = 'block';
        header.classList.add('expanded');
        icon.textContent = '▼';
    } else {
        content.style.display = 'none';
        header.classList.remove('expanded');
        icon.textContent = '▶';
    }
}
}

// 全域函數設定 / Global function setup
// 保持向後兼容性和外部調用接口
let game;

function playerChoice(choice) {
    if (game) {
        game.playerChoice(choice);
    }
}

function newGame() {
    if (game) {
        game.newGame();
    }
}

function resetStats() {
    if (game) {
        game.resetStats();
    }
}

function toggleStats() {
    if (game) {
        game.toggleStats();
    }
}

/**
 * 遊戲初始化 / Game initialization
 * 確保 DOM 完全載入後再初始化石頭剪刀布遊戲
 */
document.addEventListener('DOMContentLoaded', () => {
    console.log('DOM 已載入,開始初始化石頭剪刀布遊戲');
    
    try {
        // 檢查必要的 DOM 元素是否存在 / Check if necessary DOM elements exist
        const requiredElements = [
            'game-title',
            'player-choice',
            'computer-choice',
            'result-message'
        ];
        
        const missingElements = requiredElements.filter(id => !document.getElementById(id));
        
        if (missingElements.length > 0) {
            console.warn('缺少必要元素:', missingElements);
            // 延遲重試初始化 / Retry initialization with delay
            setTimeout(() => {
                game = new RockPaperScissorsGame();
                console.log('石頭剪刀布遊戲延遲初始化完成');
            }, 500);
        } else {
            // 立即初始化 / Initialize immediately
            game = new RockPaperScissorsGame();
            console.log('✂️ 石頭剪刀布遊戲已初始化');
        }
        
        // 添加頁面卸載時的清理 / Add cleanup when page unloads
        window.addEventListener('beforeunload', () => {
            if (game) {
                game.saveStats();
                game.saveChoiceStats();
                console.log('遊戲數據已在頁面卸載前保存');
            }
        });
        
    } catch (error) {
        console.error('石頭剪刀布遊戲初始化失敗:', error);
        
        // 最後一次重試 / Final retry attempt
        setTimeout(() => {
            try {
                game = new RockPaperScissorsGame();
                console.log('石頭剪刀布遊戲最終重試初始化完成');
            } catch (retryError) {
                console.error('石頭剪刀布遊戲最終重試初始化也失敗:', retryError);
            }
        }, 1000);
    }
});

總結

石頭剪刀布遊戲展示了經典遊戲的現代 Web 實現,結合了流暢的遊戲邏輯、豐富的動畫效果、詳細的統計分析和完整的雙語支援。 通過本地數據持久化和響應式設計,提供了優秀的用戶體驗。完整的鍵盤快捷鍵支援和統計分析功能, 讓這個簡單的遊戲具備了專業級的功能深度。