Java 实现 21 点游戏:从基础到最佳实践
简介
21 点游戏(Blackjack)是一种经典的纸牌游戏,玩家与庄家竞争,目标是使手中牌的点数之和接近但不超过 21 点。在 Java 中实现 21 点游戏可以帮助开发者更好地理解面向对象编程、集合框架、随机数生成等知识。本文将详细介绍 Java 实现 21 点游戏的基础概念、使用方法、常见实践以及最佳实践,帮助读者深入理解并高效使用 Java 实现该游戏。
目录
- 基础概念
- 21 点游戏规则
- Java 实现的关键元素
- 使用方法
- 项目搭建
- 核心类的创建
- 游戏流程的实现
- 常见实践
- 牌组的管理
- 玩家和庄家的逻辑处理
- 胜负判断
- 最佳实践
- 代码的可维护性和扩展性
- 异常处理
- 性能优化
- 小结
- 参考资料
基础概念
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 点,则判为爆牌输掉游戏;如果双方都未爆牌,则比较点数大小,点数大的一方获胜;如果点数相同,则为平局。
最佳实践
代码的可维护性和扩展性
- 面向对象设计:将不同的功能封装在不同的类中,如
Card
、Deck
、Player
、Dealer
和Game
,提高代码的可维护性和扩展性。 - 注释和文档:为代码添加详细的注释,解释每个类和方法的功能和使用方法,方便其他开发者理解和维护代码。
异常处理
在 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 点游戏的基础概念、使用方法、常见实践以及最佳实践。通过创建 Card
、Deck
、Player
、Dealer
和 Game
等类,实现了 21 点游戏的基本功能。在实现过程中,要注意代码的可维护性、扩展性、异常处理和性能优化。希望本文能帮助读者深入理解并高效使用 Java 实现 21 点游戏。
参考资料
- 《Effective Java》(第三版),Joshua Bloch 著