Skip to Content
🎲 欢迎使用 RandBox - 功能强大的 JavaScript 随机数据生成库! 了解详情
📦 示例代码vueRockPaperScissors - 石头剪刀布

RockPaperScissors - 石头剪刀布

RockPaperScissors 是一个基于Vue 3的经典石头剪刀布游戏组件,提供多种AI策略、游戏统计和自定义选项。支持传统三选项模式以及扩展的五选项模式。

📦 导入

<script setup> import { RockPaperScissors } from '@randbox/vue' // 或者导入类型 import type { RockPaperScissorsProps, RPSResult, RPSStats } from '@randbox/vue' </script>

🚀 基础用法

<template> <RockPaperScissors @result="handleResult" /> </template> <script setup> import { RockPaperScissors } from '@randbox/vue' const handleResult = (result) => { console.log('游戏结果:', result) alert(`你出了${result.emoji.player},电脑出了${result.emoji.computer}\n${result.message}`) } </script>

🎯 高级用法

多种AI策略模式

<template> <div class="strategy-rps"> <div class="strategy-selector"> <label>AI策略: </label> <select v-model="strategy"> <option v-for="(name, key) in strategies" :key="key" :value="key"> {{ name }} </option> </select> </div> <RockPaperScissors :strategy="strategy" :show-stats="true" @result="handleResult" /> <!-- 最近游戏记录 --> <div v-if="gameHistory.length > 0" class="game-history"> <h3>最近对战记录:</h3> <div v-for="(result, index) in gameHistory" :key="index" class="history-item" :class="result.result" > <span>第{{ result.round }}轮</span> <span>{{ result.emoji.player }} vs {{ result.emoji.computer }}</span> <span class="result-text"> {{ result.result === 'win' ? '胜利' : result.result === 'lose' ? '失败' : '平局' }} </span> </div> </div> </div> </template> <script setup> import { ref } from 'vue' const strategy = ref('random') const gameHistory = ref([]) const strategies = { random: '随机策略', counter: '反制策略', pattern: '模式识别' } const handleResult = (result) => { gameHistory.value = [result, ...gameHistory.value.slice(0, 9)] let message = result.message if (strategy.value === 'counter') { message += '\n(AI正在分析你的出招模式)' } else if (strategy.value === 'pattern') { message += '\n(AI基于历史模式进行预测)' } setTimeout(() => alert(message), 500) } </script> <style scoped> .strategy-rps { padding: 20px; } .strategy-selector { margin-bottom: 20px; } .game-history { margin-top: 20px; } .history-item { display: flex; justify-content: space-between; padding: 10px; margin: 5px 0; border-radius: 5px; } .history-item.win { background-color: #d4edda; } .history-item.lose { background-color: #f8d7da; } .history-item.tie { background-color: #fff3cd; } .result-text { font-weight: bold; } </style>

扩展五选项模式

<template> <div class="extended-rps"> <div class="game-title"> <h3>大爆炸理论版石头剪刀布</h3> <div class="subtitle"> 包含蜥蜴🦎和史波克🖖的扩展版本 </div> </div> <!-- 统计面板 --> <div class="stats-panel"> <div class="stat-item win"> <div>胜利</div> <div class="stat-number">{{ wins }}</div> </div> <div class="stat-item lose"> <div>失败</div> <div class="stat-number">{{ losses }}</div> </div> <div class="stat-item tie"> <div>平局</div> <div class="stat-number">{{ ties }}</div> </div> <div class="stat-item rate"> <div>胜率</div> <div class="stat-number">{{ winRate }}%</div> </div> </div> <RockPaperScissors :choices="extendedChoices" :emojis="extendedEmojis" strategy="pattern" @result="handleResult" /> <!-- 规则说明 --> <div class="rules"> <h4>游戏规则:</h4> <div class="rules-grid"> <div>• 石头 → 蜥蜴、剪刀</div> <div>• 纸 → 石头、史波克</div> <div>• 剪刀 → 纸、蜥蜴</div> <div>• 蜥蜴 → 史波克、纸</div> <div>• 史波克 → 剪刀、石头</div> </div> </div> </div> </template> <script setup> import { ref, computed } from 'vue' // 包含蜥蜴和史波克的扩展模式 const extendedChoices = ['rock', 'paper', 'scissors', 'lizard', 'spock'] const extendedEmojis = { rock: '🪨', paper: '📄', scissors: '✂️', lizard: '🦎', spock: '🖖' } const wins = ref(0) const losses = ref(0) const ties = ref(0) const totalGames = computed(() => wins.value + losses.value + ties.value) const winRate = computed(() => totalGames.value > 0 ? ((wins.value / totalGames.value) * 100).toFixed(1) : '0.0' ) const handleResult = (result) => { // 更新统计 if (result.result === 'win') wins.value++ else if (result.result === 'lose') losses.value++ else ties.value++ // 显示扩展规则说明 const rules = { 'rock-lizard': '石头压扁蜥蜴', 'rock-scissors': '石头砸碎剪刀', 'paper-rock': '纸包石头', 'paper-spock': '纸反驳史波克', 'scissors-paper': '剪刀剪纸', 'scissors-lizard': '剪刀砍蜥蜴', 'lizard-spock': '蜥蜴毒死史波克', 'lizard-paper': '蜥蜴吃纸', 'spock-scissors': '史波克砸剪刀', 'spock-rock': '史波克汽化石头' } const combination = `${result.playerChoice}-${result.computerChoice}` const rule = rules[combination] alert( `你: ${extendedEmojis[result.playerChoice]} vs 电脑: ${extendedEmojis[result.computerChoice]}\n` + `${result.message}${rule ? `\n规则: ${rule}` : ''}` ) } </script> <style scoped> .extended-rps { padding: 20px; } .game-title { text-align: center; margin-bottom: 20px; } .subtitle { font-size: 0.9em; color: #666; } .stats-panel { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 20px; text-align: center; } .stat-item { padding: 10px; border-radius: 5px; } .stat-item.win { background-color: #d4edda; } .stat-item.lose { background-color: #f8d7da; } .stat-item.tie { background-color: #fff3cd; } .stat-item.rate { background-color: #d1ecf1; } .stat-number { font-size: 1.5em; font-weight: bold; } .rules { margin-top: 20px; font-size: 0.8em; color: #666; } .rules-grid { columns: 2; column-gap: 20px; } </style>

锦标赛模式

<template> <div class="tournament-rps"> <!-- 锦标赛状态 --> <div class="tournament-status"> <h2>锦标赛模式</h2> <div v-if="!isGameOver" class="current-tournament"> <div class="round-info">第 {{ tournament.currentRound }} / {{ tournament.maxRounds }} 轮</div> <div class="score-board"> <div class="player-section"> <div>玩家</div> <div class="score">{{ tournament.playerScore }}</div> </div> <div class="vs">VS</div> <div class="computer-section"> <div>电脑</div> <div class="score">{{ tournament.computerScore }}</div> </div> </div> </div> <div v-else class="tournament-end"> <h3>锦标赛结束!</h3> <div class="tournament-winner"> {{ winner === 'player' ? '🎉 恭喜获胜!' : winner === 'computer' ? '😢 很遗憾失败!' : '🤝 平局!' }} </div> <div class="final-score">最终比分: {{ tournament.playerScore }} - {{ tournament.computerScore }}</div> <button @click="resetTournament" class="reset-btn"> 重新开始锦标赛 </button> </div> </div> <!-- 游戏区域 --> <div v-if="!isGameOver"> <RockPaperScissors strategy="counter" @result="handleResult" /> </div> <!-- 比赛记录 --> <div v-if="tournament.rounds.length > 0" class="tournament-history"> <h3>比赛记录:</h3> <div class="rounds-container"> <div v-for="round in tournament.rounds" :key="round.round" class="round-result" :class="round.result" > <span>第{{ round.round }}轮</span> <span class="round-emojis"> {{ round.playerEmoji }} vs {{ round.computerEmoji }} </span> <span class="round-winner"> {{ round.result === 'win' ? '玩家胜' : round.result === 'lose' ? '电脑胜' : '平局' }} </span> </div> </div> </div> </div> </template> <script setup> import { ref, computed } from 'vue' const tournament = ref({ currentRound: 1, maxRounds: 5, playerScore: 0, computerScore: 0, rounds: [] }) const isGameOver = ref(false) const winner = computed(() => { const { playerScore, computerScore } = tournament.value if (playerScore > computerScore) return 'player' if (computerScore > playerScore) return 'computer' return 'tie' }) const handleResult = (result) => { const newRound = { round: tournament.value.currentRound, player: result.playerChoice, computer: result.computerChoice, result: result.result, playerEmoji: result.emoji.player, computerEmoji: result.emoji.computer } tournament.value.rounds.push(newRound) tournament.value.currentRound++ // 更新分数 if (result.result === 'win') { tournament.value.playerScore++ } else if (result.result === 'lose') { tournament.value.computerScore++ } // 检查锦标赛是否结束 const winningScore = 3 if (tournament.value.currentRound > tournament.value.maxRounds || tournament.value.playerScore >= winningScore || tournament.value.computerScore >= winningScore) { isGameOver.value = true } } const resetTournament = () => { tournament.value = { currentRound: 1, maxRounds: 5, playerScore: 0, computerScore: 0, rounds: [] } isGameOver.value = false } </script> <style scoped> .tournament-rps { padding: 20px; } .tournament-status { text-align: center; margin-bottom: 20px; padding: 20px; background-color: #f8f9fa; border-radius: 10px; } .score-board { display: flex; justify-content: center; align-items: center; gap: 40px; margin-top: 15px; } .player-section, .computer-section { text-align: center; } .score { font-size: 2em; font-weight: bold; } .player-section .score { color: #007bff; } .computer-section .score { color: #dc3545; } .reset-btn { margin-top: 15px; padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } .tournament-history { margin-top: 20px; } .rounds-container { display: grid; gap: 8px; } .round-result { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-radius: 5px; } .round-result.win { background-color: #d4edda; } .round-result.lose { background-color: #f8d7da; } .round-result.tie { background-color: #fff3cd; } .round-emojis { font-size: 1.2em; } .round-winner { font-weight: bold; } </style>

实时对战模式

<template> <div class="realtime-rps"> <!-- 游戏控制面板 --> <div class="control-panel"> <div class="controls-row"> <div class="control-item"> <label>游戏速度: </label> <select v-model="gameSpeed"> <option v-for="(config, key) in speeds" :key="key" :value="key"> {{ config.label }} </option> </select> </div> <label class="auto-play"> <input v-model="autoPlay" type="checkbox" /> 自动模式 </label> <button @click="startCountdown" :disabled="isCountingDown || autoPlay" class="countdown-btn" > 开始倒计时 </button> </div> <!-- 连胜统计 --> <div class="streak-stats"> <div>当前连胜: <strong>{{ streak.current }}</strong></div> <div>最佳连胜: <strong>{{ streak.best }}</strong></div> </div> </div> <!-- 倒计时显示 --> <Transition name="countdown"> <div v-if="isCountingDown" class="countdown-display" :class="{ urgent: countdown <= 1 }"> {{ countdown > 0 ? countdown : 'GO!' }} </div> </Transition> <RockPaperScissors strategy="random" :disabled="isCountingDown" @result="handleResult" /> </div> </template> <script setup> import { ref, watch, onUnmounted } from 'vue' const countdown = ref(0) const isCountingDown = ref(false) const gameSpeed = ref('normal') const autoPlay = ref(false) const streak = ref({ current: 0, best: 0 }) const speeds = { slow: { time: 5, label: '慢速 (5秒)' }, normal: { time: 3, label: '正常 (3秒)' }, fast: { time: 1, label: '快速 (1秒)' } } let countdownInterval = null let autoPlayInterval = null const handleResult = (result) => { // 更新连胜记录 if (result.result === 'win') { streak.value.current++ streak.value.best = Math.max(streak.value.best, streak.value.current) } else { streak.value.current = 0 } if (result.result === 'win' && streak.value.current > 3) { alert(`🔥 连胜 ${streak.value.current} 场!`) } } const startCountdown = () => { isCountingDown.value = true const time = speeds[gameSpeed.value].time countdown.value = time countdownInterval = setInterval(() => { countdown.value-- if (countdown.value <= 0) { clearInterval(countdownInterval) isCountingDown.value = false } }, 1000) } // 自动游戏 watch(autoPlay, (newValue) => { if (newValue) { autoPlayInterval = setInterval(() => { if (!isCountingDown.value) { startCountdown() } }, (speeds[gameSpeed.value].time + 2) * 1000) } else { clearInterval(autoPlayInterval) } }) onUnmounted(() => { clearInterval(countdownInterval) clearInterval(autoPlayInterval) }) </script> <style scoped> .realtime-rps { padding: 20px; } .control-panel { margin-bottom: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 10px; } .controls-row { display: flex; gap: 20px; align-items: center; margin-bottom: 10px; } .control-item { display: flex; align-items: center; gap: 8px; } .countdown-btn { padding: 8px 16px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } .countdown-btn:disabled { background-color: #6c757d; cursor: not-allowed; } .streak-stats { display: flex; gap: 20px; } .countdown-display { text-align: center; font-size: 3em; margin-bottom: 20px; color: #007bff; } .countdown-display.urgent { color: #dc3545; animation: pulse 0.5s infinite; } .countdown-enter-active, .countdown-leave-active { transition: all 0.3s ease; } .countdown-enter-from, .countdown-leave-to { opacity: 0; transform: scale(0.5); } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } </style>

📋 API参考

Props

属性类型默认值描述
choicesstring[]['rock', 'paper', 'scissors']可选择的选项数组
emojisRecord<string, string>{rock: '🪨', paper: '📄', scissors: '✂️'}选项对应的表情符号
showStatsbooleanfalse是否显示统计信息
strategy'random' | 'counter' | 'pattern''random'AI策略模式
classstring''CSS类名
styleRecord<string, any>{}内联样式
disabledbooleanfalse是否禁用

Events

事件名参数描述
result(result: RPSResult)游戏结果回调
game-start()游戏开始回调
game-end(result: RPSResult)游戏结束回调

RPSResult

interface RPSResult { playerChoice: string // 玩家选择 computerChoice: string // 电脑选择 result: 'win' | 'lose' | 'tie' // 游戏结果 message: string // 结果消息 emoji: { // 表情符号 player: string computer: string } round: number // 轮次编号 }

RPSStats

interface RPSStats { totalGames: number // 总游戏数 wins: number // 胜利数 losses: number // 失败数 ties: number // 平局数 winRate: string // 胜率百分比 }

🎨 样式定制

游戏容器样式

<style scoped> .rps-container { border: 3px solid #6f42c1; border-radius: 20px; box-shadow: 0 10px 25px rgba(111, 66, 193, 0.2); background: linear-gradient(145deg, #f8f4ff, #efe8ff); } .rps-container:hover { transform: scale(1.02); transition: transform 0.3s ease; } </style>

选择按钮样式

<style scoped> .choice-button { background: linear-gradient(145deg, #ffffff, #e6e6e6); border: 2px solid #ddd; border-radius: 50%; width: 80px; height: 80px; font-size: 2em; cursor: pointer; transition: all 0.2s ease; } .choice-button:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(0,0,0,0.2); } .choice-button:active { transform: translateY(0); box-shadow: 0 4px 10px rgba(0,0,0,0.1); } </style>

🔧 高级功能

自定义游戏规则

<script setup> import { ref } from 'vue' // 实现自定义的胜负判定逻辑 const customRules = { rock: ['scissors', 'lizard'], paper: ['rock', 'spock'], scissors: ['paper', 'lizard'], lizard: ['spock', 'paper'], spock: ['scissors', 'rock'] } const checkWinner = (player, computer) => { if (player === computer) return 'tie' return customRules[player]?.includes(computer) ? 'win' : 'lose' } const handleResult = (result) => { const customResult = checkWinner(result.playerChoice, result.computerChoice) console.log('自定义规则结果:', customResult) } </script>

AI学习模式

<template> <div> <div v-if="aiPrediction" class="ai-prediction"> AI预测你下次会出: {{ aiPrediction }} </div> <RockPaperScissors strategy="pattern" @result="handleResult" /> <div class="player-history"> <div>你的出招历史: {{ playerHistory.join(' → ') }}</div> </div> </div> </template> <script setup> import { ref } from 'vue' const playerHistory = ref([]) const aiPrediction = ref(null) const predictNextMove = (history) => { if (history.length < 3) return null // 简单的模式识别:查找最常见的选择 const frequency = {} history.slice(-5).forEach(choice => { frequency[choice] = (frequency[choice] || 0) + 1 }) const mostCommon = Object.entries(frequency) .sort(([,a], [,b]) => b - a)[0] // 预测玩家下次选择,然后选择克制它的选项 const counters = { rock: 'paper', paper: 'scissors', scissors: 'rock' } return mostCommon ? counters[mostCommon[0]] : null } const handleResult = (result) => { playerHistory.value = [...playerHistory.value, result.playerChoice].slice(-10) // 更新AI预测 const prediction = predictNextMove(playerHistory.value) aiPrediction.value = prediction } </script> <style scoped> .ai-prediction { margin-bottom: 20px; padding: 10px; background-color: #fff3cd; border-radius: 8px; text-align: center; } .player-history { margin-top: 20px; font-size: 0.9em; color: #666; } </style>

与Pinia状态管理集成

<template> <RockPaperScissors :choices="gameStore.choices" :disabled="gameStore.isLoading" @result="gameStore.recordResult" /> <div class="game-stats"> <div>总游戏次数: {{ gameStore.totalGames }}</div> <div>胜率: {{ gameStore.winRate }}%</div> </div> </template> <script setup> import { useGameStore } from '@/stores/game' const gameStore = useGameStore() </script>
// stores/game.ts import { defineStore } from 'pinia' export const useGameStore = defineStore('rpsGame', { state: () => ({ choices: ['rock', 'paper', 'scissors'], totalGames: 0, wins: 0, losses: 0, ties: 0, isLoading: false, results: [] }), getters: { winRate: (state) => state.totalGames > 0 ? ((state.wins / state.totalGames) * 100).toFixed(1) : '0' }, actions: { recordResult(result) { this.totalGames++ if (result.result === 'win') this.wins++ else if (result.result === 'lose') this.losses++ else this.ties++ this.results.unshift(result) } } })

🎯 最佳实践

1. 游戏平衡性

<script setup> // 确保AI策略的平衡性 const balancedStrategies = { beginner: { randomness: 0.8, pattern: 0.1, counter: 0.1 }, intermediate: { randomness: 0.5, pattern: 0.3, counter: 0.2 }, expert: { randomness: 0.2, pattern: 0.4, counter: 0.4 } } </script>

2. 用户体验优化

<template> <RockPaperScissors :class="{ 'game-animating': isAnimating }" @game-start="handleGameStart" @game-end="handleGameEnd" /> </template> <script setup> import { ref } from 'vue' const isAnimating = ref(false) const handleGameStart = () => { isAnimating.value = true } const handleGameEnd = () => { isAnimating.value = false } </script> <style scoped> .game-animating { animation: gameShake 0.5s ease-in-out; } @keyframes gameShake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } </style>

3. 数据持久化

<script setup> import { ref, watch } from 'vue' // 保存游戏统计到本地存储 const stats = ref(() => { const saved = localStorage.getItem('rps-stats') return saved ? JSON.parse(saved) : { wins: 0, losses: 0, ties: 0 } }) watch(stats, (newStats) => { localStorage.setItem('rps-stats', JSON.stringify(newStats)) }, { deep: true }) const handleResult = (result) => { if (result.result === 'win') stats.value.wins++ else if (result.result === 'lose') stats.value.losses++ else stats.value.ties++ } </script>

🐛 常见问题

Q: AI策略是如何工作的?

A:

  • 随机策略: 完全随机选择
  • 反制策略: 分析玩家最近的选择,选择克制选项
  • 模式识别: 识别玩家的出招模式并进行预测

Q: 可以自定义游戏规则吗?

A: 是的,可以通过choices和自定义胜负判定逻辑实现任意规则。

Q: 支持多人对战吗?

A: 当前版本主要支持人机对战,多人功能需要额外的状态管理。

Q: 如何实现公平的游戏?

A: 组件使用RandBox确保随机性,AI策略可以调整以保持游戏平衡。

Q: 如何与Vue Router集成?

A: 可以在路由组件中正常使用,或者通过路由参数动态配置游戏模式。

🔗 相关链接

最后更新于: