概述
石頭剪刀布遊戲是一個經典的回合制對戰遊戲,專為提供流暢的互動體驗而設計。支援玩家與電腦對戰、實時動畫效果、詳細統計分析和鍵盤快捷鍵操作。 採用純前端 JavaScript 實作,包含完整的遊戲邏輯、狀態管理、動畫系統和本地數據持久化。 提供視覺化的遊戲介面、即時反饋系統和豐富的統計功能。
🎮 經典遊戲體驗
🎮 Classic Game Experience
完整重現經典石頭剪刀布遊戲的樂趣,包含精美的動畫效果、直觀的操作介面和詳盡的統計分析,讓每局遊戲都充滿挑戰性。
技術架構與核心設計
整體架構設計
Overall Architecture Design
技術項目 | 實作方式 | 設計考量 |
---|---|---|
遊戲邏輯 | 回合制狀態機 | 清晰的遊戲流程控制 |
動畫系統 | CSS 動畫 + JavaScript 控制 | 流暢的視覺反饋體驗 |
統計分析 | 本地存儲 + 實時計算 | 持久化數據與即時反饋 |
用戶介面 | 響應式設計 + 雙語系統 | 跨設備兼容性與國際化 |
交互控制 | 點擊操作 + 鍵盤快捷鍵 | 多種操作方式提升可用性 |
核心類別架構
Core Class Architecture
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 儲存遊戲統計和選擇記錄
- 🌐 完整雙語支援:中英文介面無縫切換,包含所有遊戲文字
- 📱 響應式設計:完美適配手機、平板、桌面各種設備
🎲 遊戲公平性
🎲 Game Fairness
電腦選擇採用 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 實現,結合了流暢的遊戲邏輯、豐富的動畫效果、詳細的統計分析和完整的雙語支援。 通過本地數據持久化和響應式設計,提供了優秀的用戶體驗。完整的鍵盤快捷鍵支援和統計分析功能, 讓這個簡單的遊戲具備了專業級的功能深度。