RockPaperScissors - Rock Paper Scissors Game
RockPaperScissors is a classic rock paper scissors game component that provides multiple AI strategies, game statistics and custom options. It supports traditional three-choice mode as well as extended five-choice mode.
📦 Import
import { RockPaperScissors } from '@randbox/react';
import type { RockPaperScissorsProps, RPSResult, RPSStats } from '@randbox/react';🚀 Basic Usage
import React from 'react';
import { RockPaperScissors } from '@randbox/react';
function BasicRockPaperScissors() {
const handleResult = (result) => {
console.log('Game result:', result);
alert(`You played ${result.emoji.player}, Computer played ${result.emoji.computer}\n${result.message}`);
};
return (
<RockPaperScissors
onResult={handleResult}
/>
);
}🎯 Advanced Usage
Multiple AI Strategy Modes
function StrategyRockPaperScissors() {
const [strategy, setStrategy] = useState('random');
const [gameHistory, setGameHistory] = useState([]);
const strategies = {
random: 'Random Strategy',
counter: 'Counter Strategy',
pattern: 'Pattern Recognition'
};
const handleResult = (result) => {
setGameHistory(prev => [result, ...prev.slice(0, 9)]);
let message = result.message;
if (strategy === 'counter') {
message += '\n(AI is analyzing your move patterns)';
} else if (strategy === 'pattern') {
message += '\n(AI is predicting based on historical patterns)';
}
setTimeout(() => alert(message), 500);
};
return (
<div style={{ padding: '20px' }}>
<div style={{ marginBottom: '20px' }}>
<label>AI Strategy: </label>
<select value={strategy} onChange={(e) => setStrategy(e.target.value)}>
{Object.entries(strategies).map(([key, name]) => (
<option key={key} value={key}>{name}</option>
))}
</select>
</div>
<RockPaperScissors
strategy={strategy}
showStats={true}
onResult={handleResult}
/>
{/* Recent Game History */}
{gameHistory.length > 0 && (
<div style={{ marginTop: '20px' }}>
<h3>Recent Battle History:</h3>
{gameHistory.map((result, index) => (
<div
key={index}
style={{
padding: '10px',
margin: '5px 0',
backgroundColor:
result.result === 'win' ? '#d4edda' :
result.result === 'lose' ? '#f8d7da' : '#fff3cd',
borderRadius: '5px',
display: 'flex',
justifyContent: 'space-between'
}}
>
<span>Round {result.round}</span>
<span>{result.emoji.player} vs {result.emoji.computer}</span>
<span style={{ fontWeight: 'bold' }}>
{result.result === 'win' ? 'Win' :
result.result === 'lose' ? 'Lose' : 'Tie'}
</span>
</div>
))}
</div>
)}
</div>
);
}Extended Five-Choice Mode
function ExtendedRockPaperScissors() {
// Extended mode including Lizard and Spock
const extendedChoices = ['rock', 'paper', 'scissors', 'lizard', 'spock'];
const extendedEmojis = {
rock: '🪨',
paper: '📄',
scissors: '✂️',
lizard: '🦎',
spock: '🖖'
};
const [wins, setWins] = useState(0);
const [losses, setLosses] = useState(0);
const [ties, setTies] = useState(0);
const handleResult = (result) => {
// Update statistics
if (result.result === 'win') setWins(prev => prev + 1);
else if (result.result === 'lose') setLosses(prev => prev + 1);
else setTies(prev => prev + 1);
// Show extended rule explanations
const rules = {
'rock-lizard': 'Rock crushes Lizard',
'rock-scissors': 'Rock crushes Scissors',
'paper-rock': 'Paper covers Rock',
'paper-spock': 'Paper disproves Spock',
'scissors-paper': 'Scissors cuts Paper',
'scissors-lizard': 'Scissors decapitates Lizard',
'lizard-spock': 'Lizard poisons Spock',
'lizard-paper': 'Lizard eats Paper',
'spock-scissors': 'Spock smashes Scissors',
'spock-rock': 'Spock vaporizes Rock'
};
const combination = `${result.playerChoice}-${result.computerChoice}`;
const rule = rules[combination];
alert(
`You: ${extendedEmojis[result.playerChoice]} vs Computer: ${extendedEmojis[result.computerChoice]}\n` +
`${result.message}${rule ? `\nRule: ${rule}` : ''}`
);
};
const totalGames = wins + losses + ties;
const winRate = totalGames > 0 ? ((wins / totalGames) * 100).toFixed(1) : '0.0';
return (
<div style={{ padding: '20px' }}>
<div style={{ marginBottom: '20px', textAlign: 'center' }}>
<h3>Big Bang Theory Rock Paper Scissors</h3>
<div style={{ fontSize: '0.9em', color: '#666' }}>
Extended version including Lizard🦎 and Spock🖖
</div>
</div>
{/* Statistics Panel */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: '10px',
marginBottom: '20px',
textAlign: 'center'
}}>
<div style={{ padding: '10px', backgroundColor: '#d4edda', borderRadius: '5px' }}>
<div>Wins</div>
<div style={{ fontSize: '1.5em', fontWeight: 'bold' }}>{wins}</div>
</div>
<div style={{ padding: '10px', backgroundColor: '#f8d7da', borderRadius: '5px' }}>
<div>Losses</div>
<div style={{ fontSize: '1.5em', fontWeight: 'bold' }}>{losses}</div>
</div>
<div style={{ padding: '10px', backgroundColor: '#fff3cd', borderRadius: '5px' }}>
<div>Ties</div>
<div style={{ fontSize: '1.5em', fontWeight: 'bold' }}>{ties}</div>
</div>
<div style={{ padding: '10px', backgroundColor: '#d1ecf1', borderRadius: '5px' }}>
<div>Win Rate</div>
<div style={{ fontSize: '1.5em', fontWeight: 'bold' }}>{winRate}%</div>
</div>
</div>
<RockPaperScissors
choices={extendedChoices}
emojis={extendedEmojis}
strategy="pattern"
onResult={handleResult}
/>
{/* Rule Explanations */}
<div style={{ marginTop: '20px', fontSize: '0.8em', color: '#666' }}>
<h4>Game Rules:</h4>
<div style={{ columns: 2, columnGap: '20px' }}>
<div>• Rock → Lizard, Scissors</div>
<div>• Paper → Rock, Spock</div>
<div>• Scissors → Paper, Lizard</div>
<div>• Lizard → Spock, Paper</div>
<div>• Spock → Scissors, Rock</div>
</div>
</div>
</div>
);
}Tournament Mode
function TournamentRockPaperScissors() {
const [tournament, setTournament] = useState({
currentRound: 1,
maxRounds: 5,
playerScore: 0,
computerScore: 0,
rounds: []
});
const [isGameOver, setIsGameOver] = useState(false);
const handleResult = (result) => {
const newRound = {
round: tournament.currentRound,
player: result.playerChoice,
computer: result.computerChoice,
result: result.result,
playerEmoji: result.emoji.player,
computerEmoji: result.emoji.computer
};
setTournament(prev => {
const newTournament = {
...prev,
rounds: [...prev.rounds, newRound],
currentRound: prev.currentRound + 1,
playerScore: prev.playerScore + (result.result === 'win' ? 1 : 0),
computerScore: prev.computerScore + (result.result === 'lose' ? 1 : 0)
};
// Check if tournament is over
if (newTournament.currentRound > newTournament.maxRounds) {
setIsGameOver(true);
}
return newTournament;
});
};
const resetTournament = () => {
setTournament({
currentRound: 1,
maxRounds: 5,
playerScore: 0,
computerScore: 0,
rounds: []
});
setIsGameOver(false);
};
const winner = tournament.playerScore > tournament.computerScore ? 'player' :
tournament.computerScore > tournament.playerScore ? 'computer' : 'tie';
return (
<div style={{ padding: '20px' }}>
{/* Tournament Status */}
<div style={{
textAlign: 'center',
marginBottom: '20px',
padding: '20px',
backgroundColor: '#f8f9fa',
borderRadius: '10px'
}}>
<h2>Tournament Mode</h2>
{!isGameOver ? (
<div>
<div style={{ fontSize: '1.2em', marginBottom: '10px' }}>
Round {tournament.currentRound} / {tournament.maxRounds}
</div>
<div style={{ display: 'flex', justifyContent: 'center', gap: '40px' }}>
<div>
<div>Player</div>
<div style={{ fontSize: '2em', color: '#007bff' }}>{tournament.playerScore}</div>
</div>
<div>VS</div>
<div>
<div>Computer</div>
<div style={{ fontSize: '2em', color: '#dc3545' }}>{tournament.computerScore}</div>
</div>
</div>
</div>
) : (
<div>
<h3>Tournament Over!</h3>
<div style={{ fontSize: '1.5em', margin: '10px 0' }}>
{winner === 'player' ? '🎉 Congratulations! You Win!' :
winner === 'computer' ? '😢 Sorry! You Lose!' : '🤝 It\'s a Tie!'}
</div>
<div>Final Score: {tournament.playerScore} - {tournament.computerScore}</div>
<button
onClick={resetTournament}
style={{
marginTop: '15px',
padding: '10px 20px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}
>
Start New Tournament
</button>
</div>
)}
</div>
{/* Game Area */}
{!isGameOver && (
<RockPaperScissors
strategy="counter"
onResult={handleResult}
/>
)}
{/* Match Records */}
{tournament.rounds.length > 0 && (
<div style={{ marginTop: '20px' }}>
<h3>Match Records:</h3>
<div style={{ display: 'grid', gap: '8px' }}>
{tournament.rounds.map((round) => (
<div
key={round.round}
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '10px',
backgroundColor:
round.result === 'win' ? '#d4edda' :
round.result === 'lose' ? '#f8d7da' : '#fff3cd',
borderRadius: '5px'
}}
>
<span>Round {round.round}</span>
<span style={{ fontSize: '1.2em' }}>
{round.playerEmoji} vs {round.computerEmoji}
</span>
<span style={{ fontWeight: 'bold' }}>
{round.result === 'win' ? 'Player Wins' :
round.result === 'lose' ? 'Computer Wins' : 'Tie'}
</span>
</div>
))}
</div>
</div>
)}
</div>
);
}Real-Time Battle Mode
function RealTimeRockPaperScissors() {
const [countdown, setCountdown] = useState(0);
const [isCountingDown, setIsCountingDown] = useState(false);
const [gameSpeed, setGameSpeed] = useState('normal');
const [autoPlay, setAutoPlay] = useState(false);
const [streak, setStreak] = useState({ current: 0, best: 0 });
const speeds = {
slow: { time: 5, label: 'Slow (5s)' },
normal: { time: 3, label: 'Normal (3s)' },
fast: { time: 1, label: 'Fast (1s)' }
};
const handleResult = (result) => {
// Update winning streak
setStreak(prev => {
const newCurrent = result.result === 'win' ? prev.current + 1 : 0;
return {
current: newCurrent,
best: Math.max(prev.best, newCurrent)
};
});
if (result.result === 'win' && streak.current + 1 > 3) {
alert(`🔥 ${streak.current + 1} win streak!`);
}
};
const startCountdown = () => {
setIsCountingDown(true);
const time = speeds[gameSpeed].time;
setCountdown(time);
const interval = setInterval(() => {
setCountdown(prev => {
if (prev <= 1) {
clearInterval(interval);
setIsCountingDown(false);
return 0;
}
return prev - 1;
});
}, 1000);
};
// Auto game
useEffect(() => {
let interval;
if (autoPlay) {
interval = setInterval(() => {
if (!isCountingDown) {
startCountdown();
}
}, (speeds[gameSpeed].time + 2) * 1000);
}
return () => clearInterval(interval);
}, [autoPlay, gameSpeed, isCountingDown]);
return (
<div style={{ padding: '20px' }}>
{/* Game Control Panel */}
<div style={{
marginBottom: '20px',
padding: '15px',
backgroundColor: '#f8f9fa',
borderRadius: '10px'
}}>
<div style={{ display: 'flex', gap: '20px', alignItems: 'center', marginBottom: '10px' }}>
<div>
<label>Game Speed: </label>
<select value={gameSpeed} onChange={(e) => setGameSpeed(e.target.value)}>
{Object.entries(speeds).map(([key, config]) => (
<option key={key} value={key}>{config.label}</option>
))}
</select>
</div>
<label>
<input
type="checkbox"
checked={autoPlay}
onChange={(e) => setAutoPlay(e.target.checked)}
/>
Auto Mode
</label>
<button
onClick={startCountdown}
disabled={isCountingDown || autoPlay}
style={{
padding: '8px 16px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: isCountingDown || autoPlay ? 'not-allowed' : 'pointer'
}}
>
Start Countdown
</button>
</div>
{/* Streak Statistics */}
<div style={{ display: 'flex', gap: '20px' }}>
<div>Current Streak: <strong>{streak.current}</strong></div>
<div>Best Streak: <strong>{streak.best}</strong></div>
</div>
</div>
{/* Countdown Display */}
{isCountingDown && (
<div style={{
textAlign: 'center',
fontSize: '3em',
color: countdown <= 1 ? '#dc3545' : '#007bff',
marginBottom: '20px',
animation: countdown <= 1 ? 'pulse 0.5s infinite' : 'none'
}}>
{countdown > 0 ? countdown : 'GO!'}
</div>
)}
<RockPaperScissors
strategy="random"
disabled={isCountingDown}
onResult={handleResult}
/>
<style jsx>{`
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
`}</style>
</div>
);
}📋 API Reference
RockPaperScissorsProps
| Property | Type | Default | Description |
|---|---|---|---|
choices | string[] | ['rock', 'paper', 'scissors'] | Array of available choices |
emojis | Record<string, string> | {rock: '🪨', paper: '📄', scissors: '✂️'} | Emoji mapping for choices |
showStats | boolean | false | Whether to show statistics |
strategy | 'random' | 'counter' | 'pattern' | 'random' | AI strategy mode |
onResult | (result: RPSResult) => void | undefined | Game result callback |
Inherited BaseGameProps
| Property | Type | Default | Description |
|---|---|---|---|
className | string | '' | CSS class name |
style | React.CSSProperties | {} | Inline styles |
disabled | boolean | false | Whether disabled |
onGameStart | () => void | undefined | Game start callback |
onGameEnd | (result: RPSResult) => void | undefined | Game end callback |
RPSResult
interface RPSResult {
playerChoice: string; // Player's choice
computerChoice: string; // Computer's choice
result: 'win' | 'lose' | 'tie'; // Game result
message: string; // Result message
emoji: { // Emoji symbols
player: string;
computer: string;
};
round: number; // Round number
}RPSStats
interface RPSStats {
totalGames: number; // Total number of games
wins: number; // Number of wins
losses: number; // Number of losses
ties: number; // Number of ties
winRate: string; // Win rate percentage
}🎨 Style Customization
Game Container Styles
.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;
}Choice Button Styles
.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);
}🔧 Advanced Features
Custom Game Rules
function CustomRulesRPS() {
// Implement custom win/lose logic
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';
};
return (
<RockPaperScissors
choices={['rock', 'paper', 'scissors', 'lizard', 'spock']}
onResult={(result) => {
const customResult = checkWinner(result.playerChoice, result.computerChoice);
console.log('Custom rule result:', customResult);
}}
/>
);
}AI Learning Mode
function LearningAI() {
const [playerHistory, setPlayerHistory] = useState([]);
const [aiPrediction, setAiPrediction] = useState(null);
const predictNextMove = (history) => {
if (history.length < 3) return null;
// Simple pattern recognition: find most common choice
const frequency = {};
history.slice(-5).forEach(choice => {
frequency[choice] = (frequency[choice] || 0) + 1;
});
const mostCommon = Object.entries(frequency)
.sort(([,a], [,b]) => b - a)[0];
// Predict player's next choice, then choose counter
const counters = {
rock: 'paper',
paper: 'scissors',
scissors: 'rock'
};
return mostCommon ? counters[mostCommon[0]] : null;
};
const handleResult = (result) => {
setPlayerHistory(prev => [...prev, result.playerChoice].slice(-10));
// Update AI prediction
const prediction = predictNextMove([...playerHistory, result.playerChoice]);
setAiPrediction(prediction);
};
return (
<div>
{aiPrediction && (
<div style={{ marginBottom: '20px', padding: '10px', backgroundColor: '#fff3cd' }}>
AI predicts you'll play: {aiPrediction}
</div>
)}
<RockPaperScissors
strategy="pattern"
onResult={handleResult}
/>
<div style={{ marginTop: '20px', fontSize: '0.9em' }}>
<div>Your move history: {playerHistory.join(' → ')}</div>
</div>
</div>
);
}🎯 Best Practices
1. Game Balance
// Ensure AI strategy balance
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 }
};2. User Experience Optimization
// Add visual feedback and animations
const AnimatedRPS = () => {
const [isAnimating, setIsAnimating] = useState(false);
const handleGameStart = () => {
setIsAnimating(true);
};
const handleGameEnd = () => {
setIsAnimating(false);
};
return (
<RockPaperScissors
className={isAnimating ? 'game-animating' : ''}
onGameStart={handleGameStart}
onGameEnd={handleGameEnd}
/>
);
};3. Data Persistence
// Save game statistics to local storage
const PersistentRPS = () => {
const [stats, setStats] = useState(() => {
const saved = localStorage.getItem('rps-stats');
return saved ? JSON.parse(saved) : { wins: 0, losses: 0, ties: 0 };
});
useEffect(() => {
localStorage.setItem('rps-stats', JSON.stringify(stats));
}, [stats]);
return (
<RockPaperScissors
onResult={(result) => {
setStats(prev => ({
...prev,
[result.result === 'win' ? 'wins' : result.result === 'lose' ? 'losses' : 'ties']:
prev[result.result === 'win' ? 'wins' : result.result === 'lose' ? 'losses' : 'ties'] + 1
}));
}}
/>
);
};🐛 Common Issues
Q: How do AI strategies work?
A:
- Random Strategy: Completely random choices
- Counter Strategy: Analyzes player’s recent choices, selects counter options
- Pattern Recognition: Identifies player’s move patterns and makes predictions
Q: Can I customize game rules?
A: Yes, you can implement any rules through choices and custom win/lose logic.
Q: Does it support multiplayer battles?
A: Current version mainly supports human vs AI battles, multiplayer features require additional state management.
Q: How to ensure fair gameplay?
A: The component uses RandBox to ensure randomness, AI strategies can be adjusted to maintain game balance.
🔗 Related Links
Last updated on: