天天看點

Java遊戲開發:有趣的撲克牌模組化程式

羅大佑有歌雲:“無聊的日子總是會寫點無聊的歌曲......”,我不是歌手,我是程式員,于是無聊的日子總是會寫點無聊的程式。程式不能太大,不然沒有時間完成;程式應該有趣,不然就達不到消磨時間的目的;程式應該有那麼一點挑戰性,不然即使寫完了也沒有進步。

  金鈎釣魚遊戲是我兒時經常玩的一種撲克牌遊戲,規則非常簡單,兩個玩家,一旦牌發到手裡之後,接下來每個人出什麼牌基本上已經就定了,玩家沒有自己做決策的機會,是以這個遊戲很容易用程式自動模拟出來。

  (一)關于金鈎釣魚遊戲

  基本規則(簡化版):兩個玩家(Player),一副撲克(Deck),大小王(Joker)可要可不要,我們的遊戲假定包含大小王,洗牌(Shuffle)之後,每個玩家得到同樣數目的牌(27張),玩家任何時候不能看自己手裡的牌,玩家依次出牌,每次出一張,輪到自己出牌時,抽出自己手中最底下的一張牌放到牌桌(Board)上,牌桌上的牌按照玩家出牌的順序擺成一條長鍊。J(鈎)是最特殊的一張牌,當某個玩家出到J時,便将牌桌上的所有牌都歸為己有,并放到自己牌池的最上面(與出牌時恰恰相反),此即所謂“金鈎釣魚”,此時牌桌清空,再由此玩家重新出牌。另外,當自己出的牌與牌桌上的某張牌點數相同時,便将牌桌中那張牌及其之後的牌都歸為己有(包含自己剛出的那張),再由此玩家重新出牌,比如牌桌上的牌為3,7,8,4,9,當某個玩家出了8,便将牌桌上的8,4,9連同自己剛出的8一并收回,派桌上剩下3,7。最後,誰手中的牌最先出完,誰就輸了。

  (二)對于一副牌的模組化

  由于花色(Suit)對于此遊戲并不重要,是以對撲克牌模組化時省略了對花色的模組化,同樣,由于不需要比較大小,牌的點數(Rank)可以用String來表示(其中王用"W"表示)。

Card.java

  package com.thoughtworks.davenkin.simplefishinggame;

  public class Card {

  private String rank;

  public Card(String rank) {

  this.rank = rank;

  }

  public String getRank() {

  return rank;

一副撲克(Deck)由54張牌組成:

Deck.java

  import java.util.ArrayList;

  import java.util.Collections;

  public class Deck {

  ArrayList cards = new ArrayList();

  public Deck() {

  buildDeck();

  private void buildDeck() {

  buildNumberCards();

  buildCard("J");

  buildCard("Q");

  buildCard("K");

  buildCard("A");

  buildJokerCard();

  private void buildJokerCard() {

  cards.add(new Card("W"));

  private void buildNumberCards() {

  for (int rank = 2; rank <= 10; rank++) {

  buildCard(rank);

  private void buildCard(int rank) {

  for (int index = 1; index <= 4; index++) {

  cards.add(new Card(String.valueOf(rank)));

  private void buildCard(String rank) {

  cards.add(new Card(rank));

  public ArrayList getCards() {

  return cards;

  public void shuffle() {

  Collections.shuffle(cards);

  Deck不僅包含54張牌,還定義了洗牌(shuffle)等方法。

  (三)對玩家的模組化

  玩家(Player)有自己的名字和自己手中所剩的牌,最重要的是出牌(playCard)成員方法:

package com.thoughtworks.davenkin.simplefishinggame;

import java.util.ArrayList;

import java.util.List;

public class Player {

    ArrayList<Card> cards = new ArrayList<Card>();

    String name;

    public Player(String name) {

        this.name = name;

    }

    public String getName() {

        return name;

    public ArrayList<Card> getCards() {

        return cards;

    public void obtainCards(List<Card> cardsToAdd) {

        cards.addAll(cardsToAdd);

    public void playCard(Board board) {

        board.addCard(cards.get(0));

        System.out.println(name + " played " + cards.get(0).getRank());

        board.displayCards();

        cards.remove(0);

    public void displayCards() {

        System.out.print("Cards for " + name + ": ");

        for (Card card : cards) {

            System.out.print(card.getRank() + " ");

        }

        System.out.println();

}

  遊戲開始需要發牌,專門定義了一個CardDistributor來發牌,每個玩家得到相同數量的牌。當然,發牌動作應該在洗牌之後:

public class CardDistributor {

    public void distributeCards(Deck deck, List<Player> players) {

        int cardsPerPlayer = deck.getCards().size() / players.size();

        int startIndex = 0;

        for (Player player : players) {

            player.obtainCards(deck.getCards().subList(startIndex, cardsPerPlayer + startIndex));

            startIndex += cardsPerPlayer;

  玩家在出牌時,需要将自己手中的一張牌轉移到牌桌上(Board),而當Player出牌之後,牌桌應該确定是否有将被Player“釣”進的牌,于是在Borad中還定義了getCardsToBeFished方法:

public class Board {

ArrayList<Card> cards = new ArrayList<Card>();

public ArrayList<Card> getCards() {

return cards;

public void addCard(Card card) {

cards.add(card);

public List<Card> getCardsToBeFished() {

if (cards.size() == 1)

return null;

List<Card> cardsToBeFished;

Card lastCard = cards.get(cards.size() - 1);

if (lastCard.getRank().equals("J")) {

cardsToBeFished = cards;

} else {

cardsToBeFished = getCardsOfRangeFishing(lastCard);

return cardsToBeFished;

public void displayCards() {

System.out.print("Current cards on board:");

for (Card card : cards) {

System.out.print(card.getRank() + " ");

System.out.println();

public void removeFishedCards(List<Card> cardsToBeFished) {

int endIndex = getCards().indexOf(cardsToBeFished.get(0));

ArrayList<Card> newCards = new ArrayList<Card>();

newCards.addAll(cards.subList(0, endIndex));

cards = newCards;

private List<Card> getCardsOfRangeFishing(Card lastCard) {

int startIndex = -1;

if (card == lastCard)

break;

if (card.getRank().equals(lastCard.getRank())) {

startIndex = cards.indexOf(card);

if (startIndex != -1)

return cards.subList(startIndex, cards.indexOf(lastCard) + 1);

  (四) 對整個遊戲的模組化

  整個遊戲定義了一個FishingManager來集中管理,FishingManager包括所有玩家,牌桌等成員變量。

import java.util.ListIterator;

public class FishingManager implements FishingRuleChecker, AfterPlayListener {

ArrayList<Player> players = new ArrayList<Player>();

private Player currentPlayer;

Board board;

private ListIterator<Player> iterator;

public FishingManager() {

board = new Board();

private void resetPlayerIterator() {

iterator = players.listIterator();

public void addPlayers(ArrayList<Player> players) {

this.players.addAll(players);

resetPlayerIterator();

@Override

public Player nextPlayer() {

if (iterator.hasNext()) {

return iterator.next();

return nextPlayer();

public Player whoFailed() {

ListIterator<Player> listIterator = players.listIterator();

while (listIterator.hasNext()) {

Player currentPlayer = listIterator.next();

if (currentPlayer.getCards().size() == 0)

return currentPlayer;

public void afterPlay() {

if (board.getCardsToBeFished() == null)

return;

doFish();

nextPlayer();

private void doFish() {

System.out.println(currentPlayer.getName() + " fished cards");

currentPlayer.obtainCards(board.getCardsToBeFished());

board.removeFishedCards(board.getCardsToBeFished());

currentPlayer.displayCards();

board.displayCards();

public void start() {

int count = 0;

while (true) {

currentPlayer = nextPlayer();

currentPlayer.playCard(board);

afterPlay();

count++;

if (whoFailed() != null) {

System.out.println(whoFailed().getName() + " has failed.");

System.out.println("Total: " + count + " rounds");

public static void main(String[] args) {

FishingManager manager = new FishingManager();

Player player1 = new Player("Kayla");

Player player2 = new Player("Samuel");

players.add(player1);

players.add(player2);

Deck deck = new Deck();

deck.shuffle();

CardDistributor distributor = new CardDistributor();

distributor.distributeCards(deck, players);

manager.addPlayers(players);

manager.start();

  FishingManager還應該包含遊戲規則,比如決定輸赢和玩家出牌順序等,于是定義一個遊戲規則接口FishingRuleChecker,并使FishingManager實作FishingRuleChecker接口:

public interface FishingRuleChecker {

Player nextPlayer();

Player whoFailed();

同時,當每個玩家出牌之後,FishingManager應該決定是否有魚上鈎,并執行釣魚操作,于是定義了一個AfterPlayListener接口,FishingManager也實作了

  AfterPlayListener接口:

public interface AfterPlayListener {

public void afterPlay();

  (五)有趣的現象

  運作FinshingManager便可以自動模拟整個遊戲過程,筆者比較感興趣的是:所有玩家一共出多少手牌之後遊戲結束?于是筆者做了10000次模拟試驗,得到的結果為:最大14023手,最小66手,平均1303手,請數學高手幫忙證明一下是否有個統計學意義上的期望值。出牌次數分布圖如下:

上圖中,橫軸為遊戲輪次(一共10000次),縱軸為每次遊戲所對應的出牌手數。

本文轉自 wws5201985 51CTO部落格,原文連結:http://blog.51cto.com/wws5201985/817455,如需轉載請自行聯系原作者