1. 初始化和导入
- 导入必要的React库组件,如
useState
,useEffect
,Button
,message
,InputNumber
,Modal
,以及自定义的CSS模块。 - 定义
createCard
函数来创建具有特定值的卡片对象。 - 定义
BLACKJACK_VALUES
对象,用于映射黑杰克游戏中每张牌的点数。
2. 状态管理
- 使用
useState
钩子管理游戏中的各种状态,包括玩家和庄家的手牌、分数、游戏状态、剩余牌堆、下注金额、游戏结果、是否显示下注输入框、是否显示转账模态框,以及玩家是否获胜。
3. 牌的生成与洗牌
generateDeck
函数用于生成完整的扑克牌组。shuffleCards
函数通过Fisher-Yates洗牌算法对牌组进行随机排序。
4. 游戏流程控制
startGame
函数在确认下注金额有效后,初始化游戏状态,包括洗牌、发牌、计算初始分数,并根据初始手牌判断是否有天生21点。handleHit
允许玩家在不爆牌的情况下继续要牌。dealerAction
函数控制庄家自动要牌直到达到至少17点或爆牌。compareHands
在游戏结束时比较玩家和庄家的分数,确定游戏结果。handleTransfer
模拟转账操作,实际应用中需替换为真实的转账逻辑。
5. 辅助函数
calculateScore
计算手牌总点数,考虑了A牌可以作为1或11点的规则。hasBlackjack
检查玩家是否有天生21点(即两张牌为A和10点牌)。
6. UI渲染与交互
- 使用Ant Design组件展示游戏界面,包括按钮、输入框、模态框等。
- 根据游戏状态动态显示玩家和庄家的手牌、分数以及操作按钮。
- 结束游戏时弹出模态框显示结果,并提供重新开始或返回主页的选项。
- 当玩家获胜时,显示转账按钮以模拟赢得游戏后的奖励操作。
7. 副作用处理
- 使用
useEffect
监听游戏状态变化,以在游戏结束或庄家行动时执行相应的逻辑,如显示结果信息、重置游戏状态等。
// 创建卡片
const createCard = (value) => ({ value });
// 黑杰克值映射
const BLACKJACK_VALUES = {
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'10': 10,
'J': 10,
'Q': 10,
'K': 10,
'A': 1,
};
// 黑杰克组件
const Blackjack = () => {
const [playerHand, setPlayerHand] = useState([]);
const [dealerHand, setDealerHand] = useState([]);
const [playerScore, setPlayerScore] = useState(0);
const [dealerScore, setDealerScore] = useState(0);
const [gameState, setGameState] = useState('等待开始');
const [remainingDeck, setRemainingDeck] = useState([]);
const [bet, setBet] = useState(0);
const [gameResult, setGameResult] = useState(null);
const [betInputVisible, setBetInputVisible] = useState(false);
const [isWinner, setIsWinner] = React.useState(false); // 新增状态,用于判断玩家是否获胜
const [transferVisible, setTransferVisible] = React.useState(false); // 控制转账模态框的显示
const navigate = useNavigate();
// 生成一副牌
const generateDeck = () => {
const deck = [];
for (let suit in BLACKJACK_VALUES) {
for (let value in BLACKJACK_VALUES) {
deck.push(createCard(value));
}
}
return deck;
};
// 洗牌
const shuffleCards = (deck) => {
let currentIndex = deck.length,
temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = deck[currentIndex];
deck[currentIndex] = deck[randomIndex];
deck[randomIndex] = temporaryValue;
}
return deck;
};
// 开始游戏
const startGame = () => {
if (bet <= 0) {
message.error('请输入下注金额');
return;
}
// 重置玩家手牌和分数
setPlayerHand([]);
setDealerHand([]);
setPlayerScore(0);
setDealerScore(0);
const deck = generateDeck();
const shuffledDeck = shuffleCards(deck);
// 发牌
const [playerFirstCard, playerSecondCard] = [shuffledDeck.pop(), shuffledDeck.pop()];
const [dealerFirstCard, dealerSecondCard] = [shuffledDeck.pop(), shuffledDeck.pop()];
setPlayerHand([playerFirstCard, playerSecondCard]);
setDealerHand([dealerFirstCard, { value: dealerSecondCard.value, faceUp: true }]);
setRemainingDeck(shuffledDeck); // 将剩余的牌分配给剩余牌堆
// 计算初始得分
setPlayerScore(calculateScore([playerFirstCard, playerSecondCard]));
setDealerScore(calculateScore([dealerFirstCard, dealerSecondCard]));
if (hasBlackjack(playerHand)) {
setGameState('结束');
message.success('你有天生21点,你赢了!');
} else {
setGameState('玩家行动');
}
};
// 玩家要牌
const handleHit = () => {
if (playerScore >= 21) {
setGameState('庄家行动'); // 当玩家爆牌时,直接进入庄家行动阶段
dealerAction(); // 调用庄家行动函数
return;
}
const card = remainingDeck.pop(); // 从剩余牌堆中取牌
if (!card) {
message.error('没有牌可发了!');
return;
}
setPlayerHand([...playerHand, card]); // 先将新牌添加到玩家手牌
setPlayerScore(calculateScore([...playerHand, card])); // 在添加新牌后立即计算得分
if (playerScore >= 21 || !remainingDeck.length) {
setGameState('庄家行动');
}
};
// 庄家行动
const dealerAction = () => {
while (calculateScore(dealerHand) < 17) {
const card = remainingDeck.pop(); // 从剩余牌堆中取牌
if (!card) {
break; // 错误情况,理论上不应该发生,但作为保护措施
}
setDealerHand([...dealerHand, card]); // 先添加新牌到手牌
// 计算并设置新的得分
setDealerScore(calculateScore(dealerHand));
}
if (calculateScore(dealerHand) <= 21) {
setGameState('结束');
compareHands();
}
};
// 比较手牌
const compareHands = () => {
if (playerScore > 21) {
setGameResult('你爆牌了,你输了!');
return;
}
if (dealerScore > 21) {
setGameResult('庄家爆牌,你赢了!');
setIsWinner(true); // 玩家获胜时设置isWinner为true
return;
}
if (playerScore === dealerScore) {
setGameResult('平局!');
return;
}
if (playerScore > dealerScore) {
setGameResult('你赢了!');
setIsWinner(true); // 玩家获胜时设置isWinner为true
return;
}
setGameResult('你输了!');
};
const handleTransfer = () => {
// 转账逻辑
console.log("转账操作...");
setTransferVisible(false); // 关闭模态框
};
// 计算手牌点数
const calculateScore = (hand) => {
let total = 0;
let hasAce = false;
hand.forEach((card) => {
total += BLACKJACK_VALUES[card.value];
if (card.value === 'A') hasAce = true;
});
if (hasAce && total + 10 <= 21) {
total += 10;
}
return total;
};
// 是否天生21点
const hasBlackjack = (hand) => {
const aceCount = hand.filter((card) => card.value === 'A').length;
const tenCount = hand.filter((card) => BLACKJACK_VALUES[card.value] === 10).length;
return aceCount > 0 && tenCount > 0;
};
// 显示下注输入框
const showBetInput = () => {
setBetInputVisible(true);
};
// 提交下注
const submitBet = (value) => {
if (typeof value !== 'number' || value <= 0) {
message.error('下注金额无效,请输入正整数');
return;
}
setBet(value);
setBetInputVisible(false);
startGame();
};
// 游戏结束后显示结果和操作选项
useEffect(() => {
if (gameState === '结束') {
const resultMessage = gameResult || '游戏结束';
message.info(resultMessage);
Modal.confirm({
title: '游戏结果',
content: `你的得分:${playerScore}\n庄家得分:${dealerScore}`,
okText: '再来一局',
cancelText: '返回主页',
onOk: () => {
setGameState('等待开始');
setBet(0);
setPlayerHand([]);
setDealerHand([]);
setPlayerScore(0);
setDealerScore(0);
setGameResult(null);
setRemainingDeck([]); // 确保牌堆也被重置
},
onCancel: () => {
navigate('/zy');
alert('返回主页');
},
});
} else if (gameState === '庄家行动') {
dealerAction(); // 在游戏状态变为'庄家行动'时调用庄家行动函数
}
}, [gameState, playerScore, dealerScore, gameResult]);
return (
<div className="game-container">
<Button type="primary" onClick={showBetInput} disabled={gameState !== '等待开始'}>
开始游戏(下注)
</Button>
<Modal
title="下注"
visible={betInputVisible}
onOk={(e) => submitBet(Number(e.target.value))} // 将e.target.value转换为Number
onCancel={() => setBetInputVisible(false)}
>
<InputNumber
min={1}
placeholder="请输入下注金额"
defaultValue={bet}
onChange={setBet}
/>
</Modal>
<div className="scoreboard">
<div className="hand-section">
<h3>玩家手牌:</h3>
<ul className="card-display">
{playerHand.map((card, index) => (
<li key={index} className="card-item">{card.value}</li>
))}
</ul>
<h3>得分:{playerScore}</h3>
</div>
<div className="hand-section">
<h3>庄家手牌:</h3>
<ul className="card-display">
{dealerHand.map((card, index) => (
<li key={index} className={`card-item ${!card.faceUp ? 'hidden-card' : ''}`}>
{index === 0 && !card.faceUp ? 'HIDDEN' : card.value}
</li>
))}
</ul>
</div>
</div>
<div className="action-buttons">
{gameState === '玩家行动' && (
<Button type="primary" className="primary-btn" onClick={handleHit} disabled={playerScore >= 21}>
要牌
</Button>
)}
{gameState === '玩家行动' && (
<Button type="primary" className="primary-btn" onClick={() => setGameState('庄家行动')}>
停止要牌
</Button>
)}
</div>
{gameState === '游戏结束' && isWinner && (
<button className="transfer-button" onClick={handleTransfer}>
转账
</button>
)}
</div>
);
};
export default Blackjack;
css代码
.game-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
background-color: #f0e8d5;
padding: 2rem;
border-radius: 10px;
}
.scoreboard {
display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: 1rem;
}
.hand-section {
text-align: center;
}
.hand-section h3 {
color: #4a235a;
margin-bottom: 0.5rem;
}
/* 基础卡片样式调整 */
.card-display {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
/* 保证卡片之间有适当的间距 */
gap: 10px;
}
.card-item {
background-color: #fff;
border: 1px solid #000;
/* 调整卡片尺寸,使其更宽且保持长方形比例 */
width: 120px; /* 根据需要调整宽度 */
height: 90px; /* 调整高度以维持长方形形状 */
padding: 15px; /* 适当增加内边距以适应更大的尺寸 */
margin: 5px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5em; /* 增加字体大小以匹配卡片尺寸 */
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
/* 确保文本垂直居中 */
flex-direction: column;
text-align: center;
}
.action-buttons {
margin-top: 1rem;
}
/* Ant Design按钮自定义 */
.primary-btn {
background-color: #4a235a !important;
border-color: #4a235a !important;
color: #fff !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
}
.primary-btn:hover {
background-color: #633974 !important;
border-color: #633974 !important;
}
.hidden-card {
background-color: #333;
color: #666;
}
.hidden-card:before {
content: "HIDDEN";
font-style: italic;
}
.hand-section:hover .card-item {
transform: scale(1.05);
transition: transform 0.7s;
}