Claude Sonnet 4.5:
import React, { useState, useEffect } from 'react';
import { AlertCircle, Heart, Package, Target, Users, Zap, TrendingUp } from 'lucide-react';
const RouletteGameAdvisor = () => {
// 游戏状态
const [gameState, setGameState] = useState({
playerAHP: 5,
playerBHP: 5,
trueCount: 0,
falseCount: 0,
remainingBullets: 0,
currentTurn: 'A', // A or B
roundNumber: 1,
isFirstTurnOfRound: true
});
// 道具状态
const [playerAItems, setPlayerAItems] = useState({
Z: 0, C: 0, Y: 0, L: 0, T: 0, F: 0, J: 0
});
const [playerBItems, setPlayerBItems] = useState({
Z: 0, C: 0, Y: 0, L: 0, T: 0, F: 0, J: 0
});
// 已知信息
const [knownBullets, setKnownBullets] = useState([]); // 已知的炮弹序列
const [currentBulletIndex, setCurrentBulletIndex] = useState(0);
const [usedItems, setUsedItems] = useState([]);
const [isBombActive, setIsBombActive] = useState(false);
const [isSkipNextTurn, setIsSkipNextTurn] = useState(false);
// UI状态
const [showRoundSetup, setShowRoundSetup] = useState(true);
const [showItemSetup, setShowItemSetup] = useState(false);
const [showAction, setShowAction] = useState(false);
const [recommendation, setRecommendation] = useState(null);
const [gameLog, setGameLog] = useState([]);
const itemNames = {
Z: '炸弹', C: '鸡腿', Y: '药瓶',
L: '笼子', T: '转换器', F: '放大镜', J: '假人'
};
// 添加日志
const addLog = (message, type = 'info') => {
setGameLog(prev => [...prev, { message, type, time: new Date().toLocaleTimeString() }]);
};
// 计算当前概率
const calculateProbability = () => {
const remaining = gameState.remainingBullets;
if (remaining === 0) return { trueProb: 0, falseProb: 0 };
const trueProb = gameState.trueCount / remaining;
const falseProb = gameState.falseCount / remaining;
return { trueProb, falseProb };
};
// 评估期望收益
const evaluateExpectedValue = (action, useItems) => {
const { trueProb, falseProb } = calculateProbability();
const damage = useItems.includes('Z') ? 2 : 1;
let expectedValue = 0;
if (action === 'shootSelf') {
// 向自己开枪
// 假弹:获得额外回合(价值高),真弹:损失生命
expectedValue = falseProb * 2.0 - trueProb * damage * 1.5;
} else if (action === 'shootOpponent') {
// 向对方开枪
// 真弹:对方损失生命(好),假弹:浪费回合
expectedValue = trueProb * damage * 1.5 - falseProb * 0.5;
}
return expectedValue;
};
// 生成决策建议
const generateRecommendation = () => {
const { trueProb, falseProb } = calculateProbability();
if (gameState.remainingBullets === 0) {
return {
action: 'wait',
message: '本轮炮弹已用完,等待下一轮',
confidence: 100
};
}
let recommendations = [];
// 1. 检查是否应该使用转换器或放大镜
if (playerAItems.F > 0) {
recommendations.push({
priority: 10,
action: 'useItem',
item: 'F',
message: '建议使用放大镜查看当前炮弹'
});
}
// 2. 检查是否需要回血
if (gameState.playerAHP <= 2) {
if (playerAItems.C > 0) {
recommendations.push({
priority: 9,
action: 'useItem',
item: 'C',
message: '血量危险,建议使用鸡腿回血'
});
} else if (playerAItems.Y > 0 && gameState.playerAHP === 1) {
recommendations.push({
priority: 8,
action: 'useItem',
item: 'Y',
message: '血量极度危险,考虑使用药瓶(50%概率回2滴血)'
});
}
}
// 3. 分析最佳射击策略
const shootSelfEV = evaluateExpectedValue('shootSelf', []);
const shootOpponentEV = evaluateExpectedValue('shootOpponent', []);
const shootOpponentWithBombEV = playerAItems.Z > 0 ?
evaluateExpectedValue('shootOpponent', ['Z']) : -Infinity;
// 如果有转换器且概率已知
let bestAction = null;
let bestEV = -Infinity;
// 策略1: 向自己开枪(适合假弹概率高时)
if (shootSelfEV > bestEV) {
bestEV = shootSelfEV;
bestAction = {
action: 'shootSelf',
useItems: [],
message: `建议向自己开枪 (真弹概率: ${(trueProb * 100).toFixed(1)}%, 假弹概率: ${(falseProb * 100).toFixed(1)}%)`,
confidence: falseProb * 100,
reasoning: falseProb > 0.6 ? '假弹概率高,有机会获得额外回合' : '根据期望值分析'
};
}
// 策略2: 向对方开枪
if (shootOpponentEV > bestEV) {
bestEV = shootOpponentEV;
bestAction = {
action: 'shootOpponent',
useItems: [],
message: `建议向对方开枪 (真弹概率: ${(trueProb * 100).toFixed(1)}%)`,
confidence: trueProb * 100,
reasoning: trueProb > 0.6 ? '真弹概率高,造成伤害期望高' : '根据期望值分析'
};
}
// 策略3: 使用炸弹向对方开枪
if (shootOpponentWithBombEV > bestEV && trueProb > 0.5) {
bestEV = shootOpponentWithBombEV;
bestAction = {
action: 'shootOpponent',
useItems: ['Z'],
message: `建议使用炸弹后向对方开枪 (真弹概率: ${(trueProb * 100).toFixed(1)}%, 伤害: 2)`,
confidence: trueProb * 100,
reasoning: '真弹概率高且有炸弹,造成2点伤害'
};
}
// 策略4: 使用转换器
if (playerAItems.T > 0) {
if (trueProb > 0.7 && shootSelfEV < 0) {
recommendations.push({
priority: 9,
action: 'useItem',
item: 'T',
message: '真弹概率很高,考虑使用转换器后向自己开枪',
confidence: trueProb * 100
});
} else if (falseProb > 0.7 && shootOpponentEV < 0) {
recommendations.push({
priority: 9,
action: 'useItem',
item: 'T',
message: '假弹概率很高,考虑使用转换器后向对方开枪',
confidence: falseProb * 100
});
}
}
// 策略5: 使用假人跳过不利的炮弹
if (playerAItems.J > 0 && trueProb > 0.8 && gameState.playerAHP <= 2) {
recommendations.push({
priority: 8,
action: 'useItem',
item: 'J',
message: '真弹概率极高且血量低,建议使用假人卸掉炮弹',
confidence: trueProb * 100
});
}
// 策略6: 使用笼子限制对手
if (playerAItems.L > 0 && gameState.playerBHP <= 2 && trueProb > 0.6) {
recommendations.push({
priority: 7,
action: 'useItem',
item: 'L',
message: '对方血量低且真弹概率高,考虑使用笼子跳过对方回合',
confidence: 60
});
}
if (bestAction) {
recommendations.push({
priority: 6,
...bestAction
});
}
// 排序并返回最佳建议
recommendations.sort((a, b) => b.priority - a.priority);
return recommendations[0] || {
action: 'shootOpponent',
message: '建议向对方开枪',
confidence: 50
};
};
// 开始新轮
const startNewRound = (trueCount, falseCount, firstPlayer) => {
setGameState(prev => ({
...prev,
trueCount: parseInt(trueCount),
falseCount: parseInt(falseCount),
remainingBullets: parseInt(trueCount) + parseInt(falseCount),
currentTurn: firstPlayer,
roundNumber: prev.roundNumber === 1 ? 1 : prev.roundNumber + 1,
isFirstTurnOfRound: true
}));
setKnownBullets([]);
setCurrentBulletIndex(0);
setIsBombActive(false);
setIsSkipNextTurn(false);
setShowRoundSetup(false);
setShowItemSetup(true);
addLog(`第${gameState.roundNumber}轮开始: ${trueCount}真 ${falseCount}假`, 'success');
};
// 设置道具
const setupItems = (player, items) => {
if (player === 'A') {
setPlayerAItems(items);
addLog('玩家A道具已设置', 'info');
} else {
setPlayerBItems(items);
addLog('玩家B道具已设置', 'info');
}
};
// 执行射击
const executeShot = (target, isTrue) => {
const damage = isBombActive ? 2 : 1;
const currentPlayer = gameState.currentTurn;
// 更新炮弹计数
setGameState(prev => ({
...prev,
trueCount: isTrue ? prev.trueCount - 1 : prev.trueCount,
falseCount: !isTrue ? prev.falseCount - 1 : prev.falseCount,
remainingBullets: prev.remainingBullets - 1
}));
setIsBombActive(false);
if (target === 'self') {
if (isTrue) {
// 真弹打自己,扣血并换人
setGameState(prev => ({
...prev,
playerAHP: currentPlayer === 'A' ? prev.playerAHP - damage : prev.playerAHP,
playerBHP: currentPlayer === 'B' ? prev.playerBHP - damage : prev.playerBHP,
currentTurn: currentPlayer === 'A' ? 'B' : 'A',
isFirstTurnOfRound: false
}));
addLog(`玩家${currentPlayer}向自己开了真弹,损失${damage}点生命`, 'warning');
} else {
// 假弹打自己,不换人(额外回合)
addLog(`玩家${currentPlayer}向自己开了假弹,获得额外回合`, 'success');
}
} else {
// 打对方
if (isTrue) {
setGameState(prev => ({
...prev,
playerAHP: currentPlayer === 'B' ? prev.playerAHP - damage : prev.playerAHP,
playerBHP: currentPlayer === 'A' ? prev.playerBHP - damage : prev.playerBHP
}));
addLog(`玩家${currentPlayer}向对方开了真弹,造成${damage}点伤害`, 'success');
} else {
addLog(`玩家${currentPlayer}向对方开了假弹`, 'info');
}
// 换人
setGameState(prev => ({
...prev,
currentTurn: currentPlayer === 'A' ? 'B' : 'A',
isFirstTurnOfRound: false
}));
}
setShowAction(false);
};
// 使用道具
const useItem = (item) => {
if (gameState.currentTurn !== 'A') return;
const newItems = { ...playerAItems };
if (newItems[item] <= 0) return;
newItems[item]--;
setPlayerAItems(newItems);
switch(item) {
case 'Z':
setIsBombActive(true);
addLog('使用炸弹,下一炮伤害+1', 'info');
break;
case 'C':
setGameState(prev => ({ ...prev, playerAHP: Math.min(5, prev.playerAHP + 1) }));
addLog('使用鸡腿,回复1点生命', 'success');
break;
case 'F':
addLog('使用放大镜,请查看当前炮弹(需要实际查看)', 'info');
break;
case 'L':
setIsSkipNextTurn(true);
addLog('使用笼子,对方将跳过下一回合', 'success');
break;
default:
addLog(`使用${itemNames[item]}`, 'info');
}
};
// 生成建议按钮
const handleGenerateRecommendation = () => {
const rec = generateRecommendation();
setRecommendation(rec);
};
return (
<div className="w-full max-w-6xl mx-auto p-6 bg-gradient-to-br from-gray-900 to-gray-800 text-white rounded-xl shadow-2xl">
<h1 className="text-3xl font-bold mb-6 text-center flex items-center justify-center gap-3">
<Target className="w-8 h-8 text-red-500" />
轮盘游戏决策助手
<Target className="w-8 h-8 text-red-500" />
</h1>
{/* 游戏状态显示 */}
<div className="grid grid-cols-2 gap-4 mb-6">
<div className="bg-blue-900/50 p-4 rounded-lg border-2 border-blue-500">
<div className="flex items-center justify-between mb-2">
<h3 className="text-xl font-bold flex items-center gap-2">
<Users className="w-5 h-5" />
玩家 A (你)
</h3>
<div className="flex items-center gap-1">
{[...Array(5)].map((_, i) => (
<Heart
key={i}
className={`w-6 h-6 ${i < gameState.playerAHP ? 'fill-red-500 text-red-500' : 'text-gray-600'}`}
/>
))}
</div>
</div>
<div className="text-sm space-y-1">
<div>道具: {Object.entries(playerAItems).map(([k, v]) => v > 0 ? `${itemNames[k]}×${v} ` : '').join('')}</div>
</div>
</div>
<div className="bg-red-900/50 p-4 rounded-lg border-2 border-red-500">
<div className="flex items-center justify-between mb-2">
<h3 className="text-xl font-bold flex items-center gap-2">
<Users className="w-5 h-5" />
玩家 B (对手)
</h3>
<div className="flex items-center gap-1">
{[...Array(5)].map((_, i) => (
<Heart
key={i}
className={`w-6 h-6 ${i < gameState.playerBHP ? 'fill-red-500 text-red-500' : 'text-gray-600'}`}
/>
))}
</div>
</div>
</div>
</div>
{/* 炮弹状态 */}
<div className="bg-gray-800/50 p-4 rounded-lg mb-6 border-2 border-yellow-500">
<h3 className="text-lg font-bold mb-3 flex items-center gap-2">
<Zap className="w-5 h-5 text-yellow-400" />
炮弹状态
</h3>
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-3xl font-bold text-red-400">{gameState.trueCount}</div>
<div className="text-sm text-gray-400">剩余真弹</div>
</div>
<div>
<div className="text-3xl font-bold text-green-400">{gameState.falseCount}</div>
<div className="text-sm text-gray-400">剩余假弹</div>
</div>
<div>
<div className="text-3xl font-bold text-blue-400">
{gameState.remainingBullets > 0 ?
`${(calculateProbability().trueProb * 100).toFixed(0)}%` : '-'}
</div>
<div className="text-sm text-gray-400">真弹概率</div>
</div>
</div>
{isBombActive && (
<div className="mt-3 text-center text-yellow-400 font-bold">
⚠️ 炸弹已激活 - 下一炮伤害 +1
</div>
)}
</div>
{/* 回合设置 */}
{showRoundSetup && (
<div className="bg-gray-800/70 p-6 rounded-lg mb-6 border-2 border-purple-500">
<h3 className="text-xl font-bold mb-4">设置新回合</h3>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm mb-2">真弹数量</label>
<input
type="number"
id="trueCount"
min="0"
max="6"
defaultValue="3"
className="w-full px-3 py-2 bg-gray-700 rounded border border-gray-600"
/>
</div>
<div>
<label className="block text-sm mb-2">假弹数量</label>
<input
type="number"
id="falseCount"
min="0"
max="6"
defaultValue="3"
className="w-full px-3 py-2 bg-gray-700 rounded border border-gray-600"
/>
</div>
</div>
{gameState.roundNumber === 1 && (
<div>
<label className="block text-sm mb-2">首回合先手</label>
<select id="firstPlayer" className="w-full px-3 py-2 bg-gray-700 rounded border border-gray-600">
<option value="A">玩家 A</option>
<option value="B">玩家 B</option>
</select>
</div>
)}
<button
onClick={() => {
const tc = document.getElementById('trueCount').value;
const fc = document.getElementById('falseCount').value;
const fp = gameState.roundNumber === 1 ? document.getElementById('firstPlayer').value : 'A';
startNewRound(tc, fc, fp);
}}
className="w-full py-3 bg-purple-600 hover:bg-purple-700 rounded-lg font-bold"
>
开始回合
</button>
</div>
</div>
)}
{/* 道具设置 */}
{showItemSetup && (
<div className="bg-gray-800/70 p-6 rounded-lg mb-6 border-2 border-green-500">
<h3 className="text-xl font-bold mb-4">设置玩家A道具</h3>
<div className="grid grid-cols-4 gap-3 mb-4">
{Object.keys(itemNames).map(item => (
<div key={item}>
<label className="block text-xs mb-1">{itemNames[item]}</label>
<input
type="number"
id={`item-${item}`}
min="0"
max="6"
defaultValue="0"
className="w-full px-2 py-1 bg-gray-700 rounded border border-gray-600 text-sm"
/>
</div>
))}
</div>
<button
onClick={() => {
const items = {};
Object.keys(itemNames).forEach(item => {
items[item] = parseInt(document.getElementById(`item-${item}`).value) || 0;
});
setupItems('A', items);
setShowItemSetup(false);
setShowAction(true);
}}
className="w-full py-3 bg-green-600 hover:bg-green-700 rounded-lg font-bold"
>
确认道具
</button>
</div>
)}
{/* 决策建议 */}
{showAction && gameState.currentTurn === 'A' && (
<div className="bg-gray-800/70 p-6 rounded-lg mb-6 border-2 border-blue-500">
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-bold flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-blue-400" />
决策建议
</h3>
<button
onClick={handleGenerateRecommendation}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-bold"
>
生成建议
</button>
</div>
{recommendation && (
<div className="bg-blue-900/30 p-4 rounded-lg border border-blue-500">
<div className="flex items-start gap-3">
<AlertCircle className="w-6 h-6 text-blue-400 flex-shrink-0 mt-1" />
<div className="flex-1">
<p className="text-lg font-semibold mb-2">{recommendation.message}</p>
{recommendation.reasoning && (
<p className="text-sm text-gray-300 mb-2">原因: {recommendation.reasoning}</p>
)}
<div className="flex items-center gap-2">
<div className="text-sm text-gray-400">
置信度: {recommendation.confidence?.toFixed(0)}%
</div>
<div className="flex-1 bg-gray-700 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: `${recommendation.confidence}%` }}
/>
</div>
</div>
</div>
</div>
</div>
)}
{/* 道具使用 */}
<div className="mt-4">
<h4 className="font-semibold mb-2">使用道具:</h4>
<div className="grid grid-cols-4 gap-2">
{Object.entries(playerAItems).map(([item, count]) => (
count > 0 && (
<button
key={item}
onClick={() => useItem(item)}
className="px-3 py-2 bg-purple-600 hover:bg-purple-700 rounded text-sm"
>
{itemNames[item]} ({count})
</button>
)
))}
</div>
</div>
{/* 射击选择 */}
<div className="mt-4 space-y-3">
<h4 className="font-semibold">执行射击:</h4>
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => {
const isTrue = window.confirm('这一枪是真弹吗?(确定=真弹, 取消=假弹)');
executeShot('self', isTrue);
}}
className="px-4 py-3 bg-yellow-600 hover:bg-yellow-700 rounded-lg font-bold"
>
向自己开枪
</button>
<button
onClick={() => {
const isTrue = window.confirm('这一枪是真弹吗?(确定=真弹, 取消=假弹)');
executeShot('opponent', isTrue);
}}
className="px-4 py-3 bg-red-600 hover:bg-red-700 rounded-lg font-bold"
>
向对方开枪
</button>
</div>
</div>
</div>
)}
{/* 记录对方行动 */}
{showAction && gameState.currentTurn === 'B' && (
<div className="bg-gray-800/70 p-6 rounded-lg mb-6 border-2 border-red-500">
<h3 className="text-xl font-bold mb-4">记录玩家B的行动</h3>
<div className="space-y-3">
<button
onClick={() => {
const isTrue = window.confirm('玩家B向自己开枪,是真弹吗?');
executeShot('self', isTrue);
}}
className="w-full px-4 py-3 bg-yellow-600 hover:bg-yellow-700 rounded-lg font-bold"
>
B向自己开枪
</button>
<button
onClick={() => {
const isTrue = window.confirm('玩家B向你开枪,是真弹吗?');
executeShot('opponent', isTrue);
}}
className="w-full px-4 py-3 bg-red-600 hover:bg-red-700 rounded-lg font-bold"
>
B向你开枪
</button>
</div>
</div>
)}
{/* 游戏日志 */}
<div className="bg-gray-800/50 p-4 rounded-lg border border-gray-700">
<h3 className="font-bold mb-3 flex items-center gap-2">
<Package className="w-4 h-4" />
游戏日志
</h3>
<div className="space-y-1 max-h-60 overflow-y-auto text-sm">
{gameLog.slice(-10).reverse().map((log, i) => (
<div
key={i}
className={`p-2 rounded ${
log.type === 'success' ? 'bg-green-900/30' :
log.type === 'warning' ? 'bg-yellow-900/30' :
log.type === 'error' ? 'bg-red-900/30' :
'bg-gray-700/30'
}`}
>
<span className="text-gray-400 text-xs">{log.time}</span> - {log.message}
</div>
))}
</div>
</div>
{/* 重置按钮 */}
<div className="mt-6 flex gap-3">
<button
onClick={() => {
setShowRoundSetup(true);
setShowAction(false);
addLog('准备开始新回合', 'info');
}}
className="flex-1 py-3 bg-green-600 hover:bg-green-700 rounded-lg font-bold"
>
开始新回合
</button>
<button
onClick={() => {
if (window.confirm('确定要重置游戏吗?')) {
window.location.reload();
}
}}
className="flex-1 py-3 bg-gray-600 hover:bg-gray-700