跳转至

Java 实现 21 点游戏:从基础到最佳实践

简介

21 点游戏(Blackjack)是一种经典的纸牌游戏,玩家与庄家竞争,目标是使手中牌的点数之和接近但不超过 21 点。在 Java 中实现 21 点游戏可以帮助开发者更好地理解面向对象编程、集合框架、随机数生成等知识。本文将详细介绍 Java 实现 21 点游戏的基础概念、使用方法、常见实践以及最佳实践,帮助读者深入理解并高效使用 Java 实现该游戏。

目录

  1. 基础概念
    • 21 点游戏规则
    • Java 实现的关键元素
  2. 使用方法
    • 项目搭建
    • 核心类的创建
    • 游戏流程的实现
  3. 常见实践
    • 牌组的管理
    • 玩家和庄家的逻辑处理
    • 胜负判断
  4. 最佳实践
    • 代码的可维护性和扩展性
    • 异常处理
    • 性能优化
  5. 小结
  6. 参考资料

基础概念

21 点游戏规则

  • 游戏使用一副或多副标准的 52 张扑克牌。
  • 玩家和庄家初始各发两张牌,玩家的牌是明牌,庄家有一张牌是暗牌。
  • 玩家可以选择“要牌”(Hit)以增加手中牌的数量,或者“停牌”(Stand)停止要牌。
  • 玩家手中牌的点数之和接近但不超过 21 点为胜,如果超过 21 点则“爆牌”(Bust)输掉游戏。
  • 庄家在玩家停牌后,根据规则决定是否继续要牌,通常庄家在点数小于 17 时必须要牌。
  • 比较玩家和庄家手中牌的点数,点数大且不超过 21 点的一方获胜。

Java 实现的关键元素

  • 牌(Card):表示一张扑克牌,包含花色和点数。
  • 牌组(Deck):由多张牌组成,用于发牌。
  • 玩家(Player):持有手中的牌,并可以进行要牌、停牌等操作。
  • 庄家(Dealer):与玩家类似,但有特定的行为规则。
  • 游戏(Game):管理整个游戏流程,包括发牌、判断胜负等。

使用方法

项目搭建

首先,创建一个 Java 项目,可以使用 IDE(如 IntelliJ IDEA 或 Eclipse)。在项目中创建以下包和类:

com.example.blackjack
├── Card.java
├── Deck.java
├── Player.java
├── Dealer.java
├── Game.java
└── Main.java

核心类的创建

Card.java

package com.example.blackjack;

public class Card {
    private final String suit;
    private final String rank;

    public Card(String suit, String rank) {
        this.suit = suit;
        this.rank = rank;
    }

    public String getSuit() {
        return suit;
    }

    public String getRank() {
        return rank;
    }

    @Override
    public String toString() {
        return rank + " of " + suit;
    }
}

Deck.java

package com.example.blackjack;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Deck {
    private final List<Card> cards;

    public Deck() {
        cards = new ArrayList<>();
        String[] suits = {"Hearts", "Diamonds", "Clubs", "Spades"};
        String[] ranks = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"};

        for (String suit : suits) {
            for (String rank : ranks) {
                cards.add(new Card(suit, rank));
            }
        }
        shuffle();
    }

    public void shuffle() {
        Collections.shuffle(cards);
    }

    public Card dealCard() {
        if (cards.isEmpty()) {
            return null;
        }
        return cards.remove(0);
    }
}

Player.java

package com.example.blackjack;

import java.util.ArrayList;
import java.util.List;

public class Player {
    private final List<Card> hand;

    public Player() {
        hand = new ArrayList<>();
    }

    public void receiveCard(Card card) {
        hand.add(card);
    }

    public List<Card> getHand() {
        return hand;
    }

    public int getHandValue() {
        int value = 0;
        int aces = 0;

        for (Card card : hand) {
            String rank = card.getRank();
            switch (rank) {
                case "2":
                case "3":
                case "4":
                case "5":
                case "6":
                case "7":
                case "8":
                case "9":
                    value += Integer.parseInt(rank);
                    break;
                case "10":
                case "Jack":
                case "Queen":
                case "King":
                    value += 10;
                    break;
                case "Ace":
                    aces++;
                    value += 11;
                    break;
            }
        }

        while (value > 21 && aces > 0) {
            value -= 10;
            aces--;
        }

        return value;
    }
}

Dealer.java

package com.example.blackjack;

public class Dealer extends Player {
    public boolean shouldHit() {
        return getHandValue() < 17;
    }
}

Game.java

package com.example.blackjack;

public class Game {
    private final Deck deck;
    private final Player player;
    private final Dealer dealer;

    public Game() {
        deck = new Deck();
        player = new Player();
        dealer = new Dealer();
    }

    public void start() {
        // 发初始牌
        player.receiveCard(deck.dealCard());
        dealer.receiveCard(deck.dealCard());
        player.receiveCard(deck.dealCard());
        dealer.receiveCard(deck.dealCard());

        // 玩家回合
        while (true) {
            System.out.println("Player's hand: " + player.getHand());
            System.out.println("Player's hand value: " + player.getHandValue());
            System.out.println("Do you want to hit (h) or stand (s)?");
            java.util.Scanner scanner = new java.util.Scanner(System.in);
            String choice = scanner.nextLine();
            if (choice.equalsIgnoreCase("h")) {
                player.receiveCard(deck.dealCard());
                if (player.getHandValue() > 21) {
                    System.out.println("Player busts! Dealer wins.");
                    return;
                }
            } else if (choice.equalsIgnoreCase("s")) {
                break;
            }
        }

        // 庄家回合
        while (dealer.shouldHit()) {
            dealer.receiveCard(deck.dealCard());
            if (dealer.getHandValue() > 21) {
                System.out.println("Dealer busts! Player wins.");
                return;
            }
        }

        // 判断胜负
        int playerValue = player.getHandValue();
        int dealerValue = dealer.getHandValue();
        System.out.println("Player's hand value: " + playerValue);
        System.out.println("Dealer's hand value: " + dealerValue);
        if (playerValue > dealerValue) {
            System.out.println("Player wins!");
        } else if (playerValue < dealerValue) {
            System.out.println("Dealer wins!");
        } else {
            System.out.println("It's a tie!");
        }
    }
}

Main.java

package com.example.blackjack;

public class Main {
    public static void main(String[] args) {
        Game game = new Game();
        game.start();
    }
}

游戏流程的实现

Main.java 中创建 Game 对象并调用 start() 方法,即可启动游戏。游戏会先给玩家和庄家发初始牌,然后玩家可以选择要牌或停牌,最后庄家根据规则行动,最终判断胜负。

常见实践

牌组的管理

Deck 类中,使用 ArrayList 来存储牌,并提供 shuffle() 方法来洗牌,dealCard() 方法来发牌。当牌组中的牌发完后,可以重新洗牌或添加新的牌组。

玩家和庄家的逻辑处理

  • 玩家:玩家可以通过 receiveCard() 方法接收牌,并通过 getHandValue() 方法计算手中牌的点数。玩家可以根据自己的意愿选择要牌或停牌。
  • 庄家:庄家继承自 Player 类,通过 shouldHit() 方法判断是否要继续要牌,规则是点数小于 17 时必须要牌。

胜负判断

Game 类的 start() 方法中,根据玩家和庄家手中牌的点数判断胜负。如果玩家或庄家的点数超过 21 点,则判为爆牌输掉游戏;如果双方都未爆牌,则比较点数大小,点数大的一方获胜;如果点数相同,则为平局。

最佳实践

代码的可维护性和扩展性

  • 面向对象设计:将不同的功能封装在不同的类中,如 CardDeckPlayerDealerGame,提高代码的可维护性和扩展性。
  • 注释和文档:为代码添加详细的注释,解释每个类和方法的功能和使用方法,方便其他开发者理解和维护代码。

异常处理

Deck 类的 dealCard() 方法中,当牌组中的牌发完时,返回 null。在实际应用中,可以抛出自定义异常,如 EmptyDeckException,让调用者更好地处理这种情况。

package com.example.blackjack;

public class EmptyDeckException extends Exception {
    public EmptyDeckException(String message) {
        super(message);
    }
}

// 在 Deck 类的 dealCard() 方法中修改
public Card dealCard() throws EmptyDeckException {
    if (cards.isEmpty()) {
        throw new EmptyDeckException("The deck is empty.");
    }
    return cards.remove(0);
}

性能优化

  • 使用合适的数据结构:在 Deck 类中使用 ArrayList 存储牌,因为 ArrayList 支持随机访问和删除操作,适合发牌和洗牌的需求。
  • 避免不必要的计算:在 Player 类的 getHandValue() 方法中,只在需要时计算手中牌的点数,避免重复计算。

小结

本文详细介绍了 Java 实现 21 点游戏的基础概念、使用方法、常见实践以及最佳实践。通过创建 CardDeckPlayerDealerGame 等类,实现了 21 点游戏的基本功能。在实现过程中,要注意代码的可维护性、扩展性、异常处理和性能优化。希望本文能帮助读者深入理解并高效使用 Java 实现 21 点游戏。

参考资料

  • 《Effective Java》(第三版),Joshua Bloch 著