使用ES7+ React实现21点页面

1. 初始化和导入

  • 导入必要的React库组件,如useStateuseEffectButtonmessageInputNumberModal,以及自定义的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;

  }

  • 31
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值