面向對象實驗——solitaire紙牌遊戲

項目地址

https://github.com/ccclll777/Windows_Solitaire_game
如果有幫助可以點個star

實驗內容

使用java/C++語言,利用面向對象技術,模擬windows紙牌小遊戲。
單人紙牌遊戲,牌桌上有7個堆共28張牌,第一堆1張牌,第二堆2張,。。。第7堆7張,每一堆的第一張牌朝上,其他朝下。牌桌上還有4個suitpiles,一個deck card堆和一個discard card堆,佈局如下(參考windows的紙牌遊戲)。在這裏插入圖片描述

總體功能設計

一.需求分析
單人紙牌遊戲,牌桌上有7個堆共28張牌,第一堆1張牌,第二堆2張……第7堆7張,每一堆的第一張牌朝上,其他朝下。牌桌上還有4個suitstack,一個deck card堆和一個discard card堆。初始時四個suitstack和一個deck card堆爲空,discard card堆中有24張牌且全部背面朝下,在明確規則之後,需要設計圖形界面,增加與用戶的交互性。爲了用戶提供更好的遊戲體驗,可以爲用戶提供不同難度的紙牌遊戲,重新定義發牌規則。

二.目標分析
首先明確遊戲的規則,遊戲共有十三個牌堆,十三個牌堆被分爲四個區域:桌面堆(TableStack),花色堆(SuitStack),發牌堆(DeckStack),和丟棄堆(DisStack)

桌面堆一共有7個牌堆,紙牌的數量是從1到7共28張,每一個桌面堆初始狀態是,只有最底端的牌爲正面,其他牌爲反面。
花色堆初始狀態爲空。
丟棄堆初始狀態也爲空。
其他剩下的24張牌放入發牌堆且牌面朝下。

遊戲規則:
(1)每次點擊發牌堆,會將一張牌加入丟棄堆,表示發了一張牌。
(2)在桌面堆的玩牌規則:如果要將牌拖向桌面堆,需要滿足的是,拖動牌堆的最頂端,與放入的牌堆的最底端的顏色不同且大小小1,滿足這個條件的牌堆,就可以合在一起。
(3)從丟棄堆向桌面堆拖動牌也需要滿足同樣的要求,拖動牌堆的最頂端,與放入的牌堆的最底端的顏色不同且大小小1。
(4)花色堆有四個,每一個放一種花色的牌,然後從一開始存放,每次只能拖動一個到花色堆。需要滿足的條件是,花色堆頂端的卡牌的花色和需要拖進去卡牌的花色相同,且大小小1.如果滿足這個條件,就可以將牌放入花色堆。

遊戲功能:
(1)實現幾個牌堆的交互,如果符合規則的話可以互相拖動。
(2)如果發牌堆變爲空但是丟棄堆沒空的話,說明牌沒有發完,這時候把丟棄堆的牌在放入發牌堆,然後重新發一遍。直到全部發完爲止。
(3)如果用戶想重新開始一局新的遊戲,可以點擊重新啓動的按鈕,然後重新發牌,開始一局新的遊戲。
(4)爲用戶提供兩個等級的難度。low和high。
low難度表示,如果一個桌面堆是空的,你可以把其他任意牌拖到桌面堆。
high難度表示,如果一個桌面堆是空的,他這裏只能放從K 開始的牌堆。
low難度可以放鬆對用戶的約束,讓用戶可以更容易的通關。
(5)如果你通關,可以爲用戶顯示通關成功之類的東西。

設計思路

  • 有關牌堆類的設計

(1)枚舉類Suit和Rank
我們需要紙牌的四個花色和十三個大小,所以初始化了兩個枚舉類Suit和Rank,分別存放花色和大小,以便卡牌的初始化。

(2)Card類
我們需要卡牌類Card,然後初始化每一張卡牌,Card類實現了Card接口,Card類中有花色,大小,是否正面朝上的屬性,以及各個屬性對應的get和set方法。還提供了一個靜態的方法,在項目編譯時會進行初始化,創建了一個數組,裏邊存了每個花色的每個紙牌的編號,在項目運行過程中,可以通過卡牌編號尋找是那張紙牌,也可以通過卡牌的花色和大小找出卡牌編號,方便我們使用。(靜態類和final類型的變量,在編譯時就已經創建,並且初始化)

(3)CardStack
由於牌堆時從一頭進行出入的,並且是先進後出的,所以我們使用了 ArrayList來模擬了堆棧進行操作。首先創建CardStack類實現CardStack_Interface接口,它是十三個牌堆的父類,它創建了十三個牌堆的屬性,實現了牌堆的基本方法。它的基本屬性有存放卡牌的ArrayList,實現的方法有,獲取ArrayList的大小,獲取最頂部卡牌,往牌堆頂添加一張卡牌,獲取指定位置的牌,push一組卡牌,刪除一組卡牌,刪除頂端卡牌,清空牌堆等等方法。

(4)DescStack發牌堆
繼承自CardStack,由於添加時需要設置紙牌的背面朝上,所以需要重寫父類的push方法。

(5)DiscardStack丟棄堆
繼承自CardStack,由於向 DisCardStack添加卡牌時都是朝上的,所以重寫了父類的push操作。

(6)SuitStack花色堆
花色堆繼承自CardStack,是收集已經有序的牌的地方,由於進入花色堆的牌是正面朝上的,所以重寫了父類的push操作,讓進入牌堆的牌正面朝上。
(7)TableStack桌面堆
桌面堆是玩牌的主要區域,需要根據遊戲的規則堆牌堆進行拖動,達到排序的效果,最終讓牌全部放到花色堆。桌面堆繼承自CardStack,並且添加了兩個其他的方法:
第一可以根據花色和大小,確定這張牌在這個牌堆的位置,在牌堆拖動時,可以用來確定這是那一張牌。
第二可以獲得這個牌堆的子牌堆,根據第一個方法獲取到這個牌子啊牌堆的位置,將這個牌堆內大小比這個牌都小的牌返回,實現牌堆間的拖動。

**

  • 有關遊戲邏輯的設計(各個牌堆內的配合)

**
類GameModel實現了有關遊戲規則,它是一個final類,在程序編譯時將被創建並且初始化,並且該類不能被繼承。

在 GameModel類中,對於遊戲的十三個牌堆進行了初始化,將十三個牌堆加入泛型爲CardStack的ArrayList的中進行管理。

牌堆初始化時,將28張牌分別放入桌面堆中,然後讓他們最頂端的卡牌朝上,然後將剩餘的24張牌放入發牌堆。

由於GameModel是一個final類,初始化後不能改變,所以在GameModel類中提供一個方法,他是一個靜態方法,可以通過類名加函數名訪問,返回值是GameModel類的一個實例,這樣在類外,就可以通過獲取的這個實例來使用這個類中的其他方法了。

GameModel中提供了有關遊戲牌堆拖動是否合法的判斷,如果拖動合法,則進行牌堆拖動的邏輯。

GameModel中實現了,發牌的邏輯,每次將發牌堆DescStack中最頂端的牌加入到DisCardStack中。

GameModel實現了遊戲的重新發牌的調用,實現瞭如果發牌堆變爲空但是丟棄堆沒空的話,說明牌沒有發完,這時候把丟棄堆的牌在放入發牌堆,然後重新發一遍。直到全部發完爲止。

GameModel實現了將牌堆中的牌,通過格式化變成字符串,在拖動時可以放到剪切板內,然後傳到目的牌堆上,可以對傳遞的牌堆進行一系列的處理,看拖動是否合法,是否可以完成相應的拖動。

GameModel實現了對於每一個牌堆的監聽功能,如果牌堆發生改變,監聽器會調用所有牌堆界面類的繪製操作,對於桌面進行重新繪製。

GameModel提供了設置遊戲難度的方法,可以將遊戲設置成不同的難度。

**

  • 有關遊戲界面類的設計

**
(1)定義一個CardImage類
裏面定義兩個方法,一個根據對應的card獲取牌的圖片,另一個獲取牌反面的圖片。
(2)遊戲的界面使用javafx進行繪製
將每一個牌堆的界面都設計爲一個界面類,共有SuitPileView(花色堆界面類),DescPileView(發牌堆界面類),DiscardPileView(丟棄堆界面類),TablePileView(桌面堆界面類)。

DescPileView(發牌堆界面類)
發牌堆界面類繼承字Hbox,然後每次初始化時,初始化一個按鈕,按鈕的外觀是紙牌的背面,並且給按鈕設置點擊事件,每點擊一次發牌堆的紙牌,就會將發牌堆頂端的牌放入丟棄堆,實現發牌功能。

DiscardPileView(丟棄堆界面類)
裏面卡牌都正面朝上放在一起,每次拖走一張會顯示下面的一張,如果沒有牌時牌堆爲空,每次點擊發牌,都會增加一張。

SuitPileView(花色堆界面類)
每次有一個牌放入花色堆,都會在界面上繪製出來。

TablePileView(桌面堆界面類)
在初始化時,根據每張牌的正反進行初始化,如果是正,則屏幕上顯示正面,如果是反面,則屏幕上顯示是卡牌的背面呢,可以支持牌堆互相之間的拖動,如果符合規則會保留在上面。

(3)下面說明牌堆拖動的邏輯
以上所有牌堆的拖動邏輯大致相同,只是放置的規則不同,調用的是不同的函數判斷是否可以放置,可以爲每一張image進行,設置拖動的事件。拖動事件有以下幾種觸發方式:
setOnDragOver:當你拖動到目標上方時會執行。
setOnDragEntered:當你進入目標控件時會執行
setOnDragExited:當你移出一個目標控件時會執行
setOnDragDropped:當你移動到目標控件,並且鬆開鼠標時會執行
setOnDragDetected:當你在某個控件上移動時,會監測到拖動操作,執行這個函數。
setOnDragDetected中的判斷邏輯,當鼠標拖動這個項目時,初始化一個拖放操作,然後創建一個剪切板,將你拖動的所有牌,獲取它對應的名稱(初始化card類時,創建了一個fnial的靜態數組,爲每個牌初始化了一個名稱),然後將他們拼接在一起,放到剪切板中,以便其他牌堆獲取拖動內容。
setOnDragOver中的判斷邏輯:如果你拖動一個牌堆到另一個牌堆上方時,會不斷執行這個函數,他會根據規則判斷此次拖動是否合法(根據桌面堆和花色堆不同的規則),如果合法則返回true,可以進行拖動操作。
setOnDragExited中邏輯,當一堆牌移動出目標控件時,會執行這個調用,設置卡牌的效果。
setOnDragEntered,當牌堆進入另一個牌堆時,會執行這個調用,爲卡牌設置一些效果。
setOnDragDropped:當你移動到目標控件,並且鬆開鼠標時會執行,根據之前移動是否合法的判斷,來看是否可以放置在這個牌堆上,它在兩個牌堆之間傳遞了剪切板上的內容,首先將剪切板上的內容,轉化爲一個牌堆,如果可以移動,就在目的牌堆中添加這個牌堆,在原來牌堆中刪除這個牌堆,否則不進行任何操作。

(4)定義了一個接口GameModelListener
裏面定義了一個方法gameStateChanged() 表示界面做了更新。
我們讓SuitPileView(花色堆界面類),DescPileView(發牌堆界面類),DiscardPileView(丟棄堆界面類),TablePileView(桌面堆界面類)都實現這個接口。然後裏面調用了更新各自界面的方法。
在GameModel中初始化了一個arraylist 存放的GameModelListener爲泛型的變量,然後將每一個初始化的牌堆都加入這個arraylist中,當有界面更新時,通過遍歷這個arraylist就可以調用所有界面內的gameStateChanged()的函數,實現界面的同步更新。

(5)最終在主界面Solitaire_GUI中對於以上類進行初始化以及統一的繪製,主界面使用GridPand進行設計,GridPand是一種網格的佈局方式,卡牌正好和網格相對應,每一個牌堆可以放在不同的網格之中,所以之後的牌堆都被初始化在了一個個的網格中,會比較整齊。
在Solitaire_GUI中對於每個牌堆進行了初始化,共初始化了13個牌堆的View界面,還有調整遊戲難度,以及重新發牌的按鈕。

代碼實現

具體的代碼看github
https://github.com/ccclll777/Windows_Solitaire_game
一.有關Rank和Suit枚舉類的實現
在這裏插入圖片描述
Rank爲卡牌大小的枚舉類,從ACE-KING
Suit爲卡牌花色的枚舉類。
二.有關card_Interface接口和card類的實現在這裏插入圖片描述
Card_Interface定義了五個屬性,分別爲牌的花色Suit,大小Rank,牌面是否朝上faceUp,以及牌面是否爲紅色isRed。
定義了卡牌的五個方法,‘
獲取花色getSuit()
獲取卡牌大小getRank()
牌面是否朝上isFaceUp()
牌面是否爲紅色isRed()
還有設置牌是否正面朝上setFaceUp()。
Card類實現了接口中的方法,並且增加了一個final static的數組CARDS,在初始化時會初始化所有的52張牌到一個二維數組中。
並且可以通過花色和大小獲取相應的牌 Card get(Rank pRank, Suit pSuit)
以及根據編號獲取相應的牌Card get(String pId)

private  final Rank aRank;//大小
private  final Suit aSuit;//花色
private boolean faceUp = false;//撲克牌是否正面朝上
private boolean isRed ;
//方法爲共有的
//52張牌的初始化
  private static final Card[][] CARDS = new Card[Suit.values().length][];
  // 初始化每一張牌的花色和大小,生成一個編號,以便可以通過編號獲取相對應的卡牌
static
{
    for( Suit suit : Suit.values() )
    {
        CARDS[suit.ordinal()] = new Card[Rank.values().length];
        for( Rank rank : Rank.values() )
        {
            CARDS[suit.ordinal()][rank.ordinal()] = new Card(rank, suit);
        }
    }
}
//構造方法
public Card(Rank pRank, Suit pSuit)
{
    aRank = pRank;
    aSuit = pSuit;
    if(pSuit == Suit.HEARTS || pSuit == Suit.DIAMONDS)
    {
        isRed = true;
    }
    else
    {
        isRed = false;
    }

}
//獲取這張牌的編號
public String getIDString()
{
    return Integer.toString(getSuit().ordinal() * Rank.values().length + getRank().ordinal());
}

//返回這張牌是否朝上
public boolean isFaceUp() {
    return faceUp;
}
//設置這張牌的朝向
public void setFaceUp(boolean faceUp) {
    this.faceUp = faceUp;
}
//返回是否爲紅色
public boolean isRed() {
    return isRed;
}
//獲得花色
public Suit getSuit() {
    return this.aSuit;
}
//根據編號獲取對應的牌
public static Card get(String pId)
{ if(pId != null)
    {
        int id = Integer.parseInt(pId);
        return get(Rank.values()[id % Rank.values().length],
                Suit.values()[id / Rank.values().length]);
    }

        return null; }
//根據花色和大小獲取對應的牌
public static Card get(Rank pRank, Suit pSuit)
{
    if( pRank != null && pSuit != null)
    {
        return CARDS[pSuit.ordinal()][pRank.ordinal()];
    }

        return null;
}
//獲取牌的大小
public Rank getRank() {
    return this.aRank;
    }

三.牌堆接口以及牌堆類(Card_Interface 和CardStack以及它的子類。)
在這裏插入圖片描述
(1)CardStack_Interface中定一個一個屬性pokers_card,裏面需要存這個牌堆的所有卡牌。
然後定義了以下方法:
int size();獲取牌堆的發小
ArrayList getPokers_card();獲取牌堆的實例
void init(Card pCard);向牌堆頂端加一張牌(表示桌面堆最下面的一張)
Card peek();獲取最頂端的牌
Card peek(int index);//獲取指定位置的牌
boolean isEmpty();牌堆是否爲空
void push(CardStack pStack, int index);在指定位置添加一組牌
void pop(Card pCard);刪除一張卡牌
void pop();刪除頂端卡牌
void clear();清空牌堆。
這裏的peek()方法和pop()方法都實現了重載解析,他有不同的參數列表
(2)CardStack類實現了CardStack_Interface接口。
實現了它的所有方法

public CardStack()
{
    pokers_card = new ArrayList<Card>();
}
//獲取list的大小
public int size() {
    return this.pokers_card.size();
}
//獲取頂部卡片
public ArrayList<Card> getPokers_card()
{
    return this.pokers_card;
}
public Card peek() {
//如果牌堆有牌則獲得頂部卡牌,否則獲得一張紅桃A
   if(this.pokers_card.size() >0)
   {
       return this.pokers_card.get(this.pokers_card.size() - 1);
   }
   else
   {
       return new Card(Rank.ACE,Suit.HEARTS)   }
  }
   //給牌堆添加一張卡牌,添加到最頂端
public void init(Card pCard)
{
    this.pokers_card.add(pCard);
}
//獲取指定位置的卡片
public Card peek(int index)
{

    if(index >= 0 && index < size())
    {
        return this.pokers_card.get(index);
    }
return null;}
//list是否爲空
public boolean isEmpty() {

    return pokers_card.size() == 0;

}
//push一組卡牌
public void push(CardStack pStack, int index) {

    for(int i = index; i < pStack.size(); i++) {
        this.pokers_card.add(pStack.peek(i));
    }

}

public void push(CardStack pStack) {
    for(int i = 0; i < pStack.size(); i++) {
        this.pokers_card.add(pStack.peek(i));

    }

}
//刪除一組卡牌
public void pop(Card pCard) {
    if(!isEmpty())
    {
        for(int i = 0 ; i <this.pokers_card.size() ; i++)
        {
            if(pCard.getSuit() == this.pokers_card.get(i).getSuit() &&pCard.getRank() == this.pokers_card.get(i).getRank() )
                this.pokers_card.remove(i);
            }
    }

}
//刪除頂部卡牌

public void pop() {
    if(!isEmpty())
        this.pokers_card.remove(pokers_card.size()-1);
    }


public void clear() {
    this.pokers_card.clear();
}

(3)DescStack繼承自CardStack類,它繼承了來自CardStack的所有方法,由於發牌堆與普通牌堆沒有區別,都是背面朝上的牌堆,所以不需要對牌堆進行重寫。
(4)DiscardStack繼承自CardStack類,它繼承了來自CardStack的所有方法,由於丟棄堆最頂端的牌需要正面朝上,所以它重寫了父類的push方法,需要在添加卡牌後將牌堆的第一張牌的isfaceup設置成true。

public void push(CardStack pStack, int index) {
    super.push(pStack, index);
    //添加一組卡牌後   卡牌的頂部應該是朝上的
  if(!this.isEmpty())
      this.peek().setFaceUp(true);
  }

(5)SuitStack繼承自CardStack類,它繼承了來自CardStack的所有方法
,由於花色堆的所有牌的正面都是朝上的,所以重寫了父類的push方法,在添加卡牌後,將添加的所有卡牌的正面全部朝上。

public void push(CardStack pStack, int index) {
    super.push(pStack, index);
    if (!this.isEmpty()) {
        this.peek().setFaceUp(true);
    }
}

(6)TableStack繼承自CardStack類,它繼承了來自CardStack的所有方法
由於桌面堆需要獲取子牌堆(一組牌可以同時拖動,可以將他們一同獲取),所以需要添加一個獲取子牌堆的方法。
我們需要獲取一張牌是這個牌堆中的第幾個,所以添加一個獲取這個牌是牌堆中第幾個的方法。(其實第二個方法完全爲第一個方法服務)

public int  getCardIndex(Rank pRank, Suit pSuit) {

    if(!this.isEmpty())
    {
 for(int i = this.size() -1 ; i >= 0 ; i--)
        {
            if(this.peek(i).getRank() == pRank && this.peek(i).getSuit() == pSuit)
                    return i;
        }
    }

    return -1;
}
public TableStack getSubStack(Card pCard, TableStack stack)
{
    if(pCard != null)
    {
        int index = stack.getCardIndex(pCard.getRank(),pCard.getSuit());
        TableStack temp_stack = new TableStack();
        for(int i = index ; i< stack.size() ; i++)
        {
            temp_stack.init(stack.peek(i));

        }
        return temp_stack;

    }
    return null;
}

四.牌堆的主要拖動的邏輯實現類GameModel
在這裏插入圖片描述
GameModel中對十三個牌堆進行了實例化, 然後將它們存入一個泛型爲CardStack的ArrayList中。這樣已經體現出了面向對象的優越性,可以將子類的對象放入父類變量的泛型之中,便於管理。
並且GameModel是一個final類,在程序編譯時就已經初始化,隨機出了桌面堆和發牌堆的所有紙牌,在GameModel實現了對於紙牌拖動的一些邏輯的處理過程。
GamoModel中的屬性:

private static final GameModel INSTANCE = new GameModel();在final類中實例化了一個GameModel的變量,然後使用靜態方法,將它返回到其他類中,實現通過類名使用GameModel中的函數。

private ArrayList card_Stacks 存放十三個牌堆ArrayList.

private int fromIndex;牌堆從哪裏移動的
private DeckStack deck_Stack;//0 //ArrayList中的第1個牌堆,發牌堆
private DisCardStack disCard_Stack;//1ArrayList中的第2個牌堆,丟棄堆
private TableStack[] table_Stacks;//2-8 ArrayList中的第2個到第8個牌堆,桌面堆

private SuitStack[] suit_Stacks;//9-12 ArrayList中的第9個到第12個牌堆,花色堆

private static final String SEPARATOR = “;”;//進行字符串分割的符號
private String level = “low”;//設置遊戲難度值的符號。
private final List aListeners = new ArrayList(); //存放GameModelListener類的ArrayList,作爲所有牌堆的監聽器,實現觸發後界面的更新。

GameModel中實現的方法:
Void init():初始化十三個牌堆,給桌面堆和玩牌堆隨機發牌。將他們每一個牌堆按順序存入ArrayList中,之後根據序號訪問不同的牌堆。
CardStack getStack()可以根據序號從ArrayList中取出自己需要的牌堆,然後返回。實現了內部數據的保護。
GameModel instance()//在初始化時,初始化了GameModel的實例,通過這個靜態方法,將GameModel的實例返回,在類外,可以通過類名+函數名調用這個方法,獲取GameModel的實例,使用GameModel的函數,方便用戶的使用。
void addListener() 爲每一個牌堆的界面類設置監聽器。
void notifyListeners() 調用時,調用每一個牌堆更新界面的方法,實現界面的同步更新。
CardStack getSubStack() 獲得一個桌面堆的子牌堆,在拖動時可以拖動一堆卡牌。
boolean isLegalMove() 判斷牌在牌堆之間的移動是否符合規則。
boolean canMoveToTableStack () 判斷移動到桌面堆的牌堆是否符合規則。
boolean canMoveToSuitStack() 判斷移動到花色堆的牌是否符合規則
void Desc_to_DisCard() 模擬發牌,每次點擊發牌按鈕,將一張牌從發牌堆加入丟棄堆
boolean moveCard() 牌堆移動的主要邏輯,如果一組卡牌能在兩個牌堆之間移動,那麼通過調用這個類,將這一組卡牌從起始牌堆刪除,然後添加到目的牌堆
void pop_from() 將一組卡牌從起始牌堆刪除
void reset_all() 當發牌堆和丟棄堆都沒有卡牌時,重新發牌,開始一局新的遊戲。
void reset() 當丟棄堆還有卡牌,發牌堆沒有卡牌時,將丟棄堆的牌重新放入發牌堆,重新發牌。
CardStack StringToStack 由於需要移動的卡牌在牌堆間的傳遞,是通過在剪切板中存放字符串實現牌堆間信息的傳遞的,這裏將字符串中表示的卡牌轉化成真實的牌堆。
Card getTop()獲取剪切板中字符串表示的卡牌中,最頂端的一張卡牌。
String serialize()//將牌堆轉換爲字符串的方法,傳入一組牌堆,通過過去每一張牌的唯一名字,獲取卡牌的名稱,拼接在字符串中。
void setFromIndex()設置牌堆拖動時的起始牌堆
setLevel(String level) 設置遊戲的難度

在這裏插入代碼片
public void init()
 {
     this.table_Stacks = new  TableStack[7];
     for(int i = 0 ; i < table_Stacks.length ; i++)
     {
         this.table_Stacks[i] = new TableStack();
     }
     this.deck_Stack = new DeckStack();
     this.disCard_Stack = new DisCardStack();
     this.suit_Stacks = new SuitStack[4];
     for(int i = 0 ; i< suit_Stacks.length ; i++)
     {
         this.suit_Stacks[i] = new SuitStack();
     }
     this.card_Stacks = new ArrayList<CardStack>();
     this.card_Stacks.add(this.deck_Stack);//0
     this.card_Stacks.add(this.disCard_Stack);//1
     for(int i = 0 ; i < table_Stacks.length ; i ++)
     {
         this.card_Stacks.add(this.table_Stacks[i]);//2-8
     }
     for(int i = 0 ; i < suit_Stacks.length ; i ++)
     {
         this.card_Stacks.add(this.suit_Stacks[i]);//9-12
     }
      Random random = new Random();
     ArrayList<Card> normal_Rank = new ArrayList();
     Card temp_card = null;
     // 使用normal_Rank暫存52張卡牌的信息
     for( Suit suit : Suit.values() )
     {
         for( Rank rank : Rank.values() )
         {
             temp_card = new Card(rank, suit);
             normal_Rank.add(temp_card);
         }
     }
     int i;
     //隨機主七個主牌堆的牌
     int index = 0;
     for( i = 0; i < 7; ++i) {
         for(int j = 0; j <= i; ++j) {
             while (true)
             {
                 index = random.nextInt(normal_Rank.size());
                 temp_card = (Card)normal_Rank.get(index);
                 if( !this.table_Stacks[i].isEmpty() && temp_card.isRed() != this.table_Stacks[i].peek().isRed())
                 {
                     this.table_Stacks[i].init(temp_card);
                     normal_Rank.remove(index);
                     break;
                 }
                 if(this.table_Stacks[i].isEmpty() )
                 {
                     this.table_Stacks[i].init(temp_card);
                     normal_Rank.remove(index);
                     break;
                 }
             }
         }
     }
     //將每個牌堆的頂端翻正
     for(i = 0; i < 7; ++i) {
         this.table_Stacks[i].peek().setFaceUp(true);
     }
     //將剩餘牌放入發牌堆
     for(i = 0; i < 24; ++i) {
         index = random.nextInt(normal_Rank.size());
         temp_card = (Card)normal_Rank.get(index);
         this.deck_Stack.init(temp_card);
         normal_Rank.remove(index);
     }
 }
 //獲取牌堆
 public CardStack getStack(int index) {
     return this.card_Stacks.get(index);
 }
 //獲取實例
 public static GameModel instance()
 {
     return INSTANCE;
 }
 //設置卡牌移動的監聽器
 public void addListener(GameModelListener pListener)
 {
    if(pListener != null);
     aListeners.add(pListener);
 }
 //在卡牌移動後,更新桌面
 private void notifyListeners()
 {
     for( GameModelListener listener : aListeners )
     {
         listener.gameStateChanged();
     }
 }
 //返回卡牌的子序列,點擊桌面堆的七個牌堆的子序列。
 public CardStack getSubStack(Card pCard, int aIndex)
 {
     TableStack stack = (TableStack) GameModel.instance().getStack(aIndex);
     TableStack temp_stack = stack.getSubStack(pCard,stack);

         return temp_stack;

 }
 //判斷 移動是否合法
 public boolean isLegalMove(Card pCard,int aIndex )
 {
     if(aIndex >=1 && aIndex<= 8)
     {
             return canMoveToTableStack(pCard,aIndex);
     }
     else if(aIndex>= 9 && aIndex <=12)
     {
             return canMoveToSuitStack(pCard,aIndex);
     }

     return false;
 }
 //是否能移到桌面堆
 public boolean  canMoveToTableStack(Card pCard,int aIndex)
 {
     if(level == "high")
     {
         if(pCard!=null)
         {
             CardStack temp_stack = getStack(aIndex);
             if( temp_stack.isEmpty() )
             {

                 return pCard.getRank() == Rank.KING;
             }
             else
             {
                 return pCard.getRank().ordinal() == temp_stack.peek().getRank().ordinal()-1 &&
                         pCard.isRed() != temp_stack.peek().isRed();
             }

         }
     }
     else if(level == "low")
     {

         if(pCard!=null)
         {
             CardStack temp_stack = getStack(aIndex);
             if( temp_stack.isEmpty() )
             {

                 return true;
             }
             else
             {
                 return pCard.getRank().ordinal() == temp_stack.peek().getRank().ordinal()-1 &&
                         pCard.isRed() != temp_stack.peek().isRed();
             }

         }
     }

     return false;
 }
 //是否能移到花色堆堆
 public boolean  canMoveToSuitStack(Card pCard,int aIndex)
 {
     assert pCard != null ;
     CardStack temp_stack = getStack(aIndex);
     if(temp_stack.isEmpty())
     {
         return  pCard.getRank() == Rank.ACE ;
     }
     else
     {
         return pCard.getRank().ordinal() == temp_stack.peek().getRank().ordinal()+1 &&
                 pCard.getSuit()==temp_stack.peek().getSuit();
     }


 }

 public void Desc_to_DisCard()
 {
     Card temp_card = this.getStack(0).peek();
     this.getStack(1).init(temp_card);
     this.getStack(0).pop();
     notifyListeners();

 }
 //移動牌堆
     public boolean moveCard(CardStack from,int aIndex)
     {
         if
             (aIndex>=2 && aIndex<=8)
         {
             TableStack to = (TableStack)this.getStack(aIndex);
             if (!to.isEmpty())
             {
                 this.getStack(aIndex).push(from);
                 pop_from(from);
                 notifyListeners();
                     return true;


     } else if (from.isEmpty()) {

         return false;
     } else {

                     this.getStack(aIndex).push(from);
                     pop_from(from);
                     notifyListeners();
}
         }
         else  if(aIndex>=9 && aIndex<= 12)
         {
             SuitStack to = (SuitStack) this.getStack(aIndex);
             if (to.isEmpty())
             {//獲取他的下個一卡牌
                 this.getStack(aIndex).push(from);
                 pop_from(from);
                 notifyListeners();
             return true;

              }
              else
                  {
                      this.getStack(aIndex).push(from);
                      pop_from(from);
                      notifyListeners();
                  }
         }
         return true;
     }

 public void pop_from(CardStack to)
 {
     for(int j = 0 ; j < to.size() ; j ++)
         {

             this.getStack(fromIndex).pop(to.peek(j));

         }
         if(!this.getStack(fromIndex).peek().isFaceUp())
    {
        this.getStack(fromIndex).peek().setFaceUp(true);
    }

 }
 //如果發牌區全部發完並且discardstack也沒有牌
 public void reset_all()
 {
     init();
     notifyListeners();
 }
 //如果發牌區全部發完  但是discardstack有牌
 public void reset()
 {
     for(int i = 0 ; i < this.getStack(1).size() ; i++)
     {
         this.getStack(0).init(this.getStack(1).peek(i));
     }

        this.getStack(1).clear();

     notifyListeners();
 }
 public CardStack StringToStack(String pString)
 {

    if(pString != null && pString.length() > 0)
    {
        String[] tokens = pString.split(SEPARATOR);
        CardStack aCards = new CardStack();
        for( int i = 0; i < tokens.length; i++ )
        {
            aCards.init(Card.get(tokens[i]));
        }
        for(int i = 0 ; i<aCards.size() ; i ++)
        {
            aCards.peek(i).setFaceUp(true);
        }
        return aCards;
    }
     return null;
 }
 public Card getTop(String result)
 {
     if( result != null && result.length() > 0)
     {
         String[] tokens = result.split(SEPARATOR);
         Card aCards [];
         aCards = new Card[tokens.length];
         for( int i = 0; i < tokens.length; i++ )
         {
             aCards[i] = Card.get(tokens[i]);
         }
         return aCards[0];
     }
     return null;
 }
 public String serialize(Card pCard, int aIndex)
 {

     CardStack temp_stack =  GameModel.instance().getSubStack(pCard, aIndex);
     String result = "";
     Card temp_card ;
     for(int i = 0 ; i< temp_stack.size() ; i++)
     {
         temp_card = temp_stack.peek(i);
         result += temp_card.getIDString()+SEPARATOR;
     }

     if( result.length() > 0)
     {
         result = result.substring(0, result.length()-1);
     }

     return result;
 }


 public void setFromIndex(int fromIndex) {
     this.fromIndex = fromIndex;
 }

 public void setLevel(String level)
 {
     this.level = level;
 }

五.主要牌堆界面的實現類
在這裏插入圖片描述

首先定義了一個接口GameModelListenter 作爲對於所有牌堆更新的監聽器,裏面定義了一個方法gameStateChanged,爲更新牌堆的方法。
然後桌面堆,花色堆,發牌堆,丟棄堆都實現了這個接口,並且定義了方法 gameStateChanged。
這樣做的用意是,在GameModel中定義一個泛型GameModelListenter爲ArrayList,在每一個牌堆內,由於每個牌堆類都實現了GameModelListenter 的接口,所以可以將他們都加入到ArrayList中,通過 這種方式,如果要觸發牌堆的更新,則遍歷這個ArrayList中的每一個對象,然後調用他們的gameStateChanged方法更新界面,實現了界面的同步更新。
由於初始化ArrayList的每一個對象屬於不同的類,定義了不同的gameStateChanged,調用時,通過多態的思想,會尋找到屬於這個對象對應的gameStateChanged方法進行調用。

之後對於每一個界面類,都有自己界面的繪製方法,繪製方法都大致相同。
對於DescStack,將紙牌的背面初始化爲按鈕,爲按鈕設置點擊事件,每次點擊按鈕,都會發一張牌到丟棄堆。

對於每一個牌堆的拖動的檢測,共有五個回調函數。
createDragDetectedHandler()當你在某個控件上移動時,會監測到拖動操作,執行這個函數。
createDragOverHandler()當你拖動到目標上方時會執行。
createDragEnteredHandler()當你進入目標控件時會執行
createDragExitedHandler()當你移出一個目標控件時會執行
createDragDroppedHandler()當你移動到目標控件,並且鬆開鼠標時會執行

這五個函數之間配合,實現可卡牌的拖動邏輯。

TablePileView的實現如下,其他界面類的實現差不多:

public class TablePileView extends StackPane implements GameModelListener
{
    private static final int PADDING = 5;
    private static final int Y_OFFSET = 17;
    private static final String SEPARATOR = ";";
    private static CardImages cardImages = new CardImages();
    private int aIndex;
    private static final String BORDER_STYLE = "-fx-border-color: lightgray;"
            + "-fx-border-width: 2.8;" + " -fx-border-radius: 10.0";
    TablePileView(TablePile pIndex)
    {
        aIndex = pIndex.ordinal()+2;//返回枚舉類的序數  2-8
        setPadding(new Insets(PADDING));
        setStyle(BORDER_STYLE);
        setAlignment(Pos.TOP_CENTER);
        final ImageView image = new ImageView(cardImages.getBack());
        image.setVisible(false);
        getChildren().add(image);
        buildLayout();
        GameModel.instance().addListener(this);

    }
    private static Image getImage(Card pCard)
    {
            return cardImages.getCard(pCard);

    }
    private void buildLayout()
    {
        getChildren().clear();
        TableStack stack = (TableStack) GameModel.instance().getStack(aIndex);
        if( stack.isEmpty() )
        {
            ImageView image = new ImageView(cardImages.getBack());
            image.setVisible(false);
            getChildren().add(image);
            return;
        }
        for(int i = 0 ; i< stack.size() ; i ++)
        {
            Card cardView = stack.peek(i);
            if(cardView.isFaceUp() == true)
            { final ImageView image = new ImageView(getImage(cardView));
                image.setTranslateY(Y_OFFSET * i);
                getChildren().add(image);
                setOnDragOver(createDragOverHandler(image, cardView));//當你拖動到目標上方的時候,會不停的執行。
                setOnDragEntered(createDragEnteredHandler(image, cardView));// 當你拖動到目標控件的時候,會執行這個事件回調。
                setOnDragExited(createDragExitedHandler(image, cardView));//當你拖動移出目標控件的時候,執行這個操作。
                setOnDragDropped(createDragDroppedHandler(image, cardView));//當你拖動到目標並鬆開鼠標的時候,執行這個DragDropped事件。
                image.setOnDragDetected(createDragDetectedHandler(image, cardView));//當你從一個Node上進行拖動的時候,會檢測到拖動操作,將會執行這個EventHandler。
            }
            else

            {
                final ImageView image = new ImageView(cardImages.getBack());
                image.setTranslateY(Y_OFFSET * i);
                getChildren().add(image);
            }


        }
    }

    private EventHandler<MouseEvent> createDragDetectedHandler(final ImageView pImageView, final Card pCard)
    {//當你從一個Node上進行拖動的時候,會檢測到拖動操作,將會執行這個EventHandler。
        return new EventHandler<MouseEvent>()

        {
            @Override
            public void handle(MouseEvent pMouseEvent)
            {
                Dragboard db = pImageView.startDragAndDrop(TransferMode.ANY);
                ClipboardContent content = new ClipboardContent();
                content.putString(GameModel.instance().serialize(pCard,aIndex));
                db.setContent(content);
                pMouseEvent.consume();
                GameModel.instance().setFromIndex(aIndex);

            }
        };
    }

    private EventHandler<DragEvent> createDragOverHandler(final ImageView pImageView, final Card pCard)
    {
        //當你拖動到目標上方的時候,會不停的執行。
        return new EventHandler<DragEvent>()
        {
            @Override
            public void handle(DragEvent pEvent)
            {
                if(pEvent.getGestureSource() != pImageView && pEvent.getDragboard().hasString())
                {

                    if( GameModel.instance().isLegalMove(GameModel.instance().getTop(pEvent.getDragboard().getString()), aIndex) )
                    {
                        pEvent.acceptTransferModes(TransferMode.MOVE);
                    }
                }
                pEvent.consume();
            }
        };
    }

    private EventHandler<DragEvent> createDragEnteredHandler(final ImageView pImageView, final Card pCard)
    {
        // 當你拖動到目標控件的時候,會執行這個事件回調。
        return new EventHandler<DragEvent>()
        {
            @Override
            public void handle(DragEvent pEvent)
            {
                if( GameModel.instance().isLegalMove(GameModel.instance().getTop(pEvent.getDragboard().getString()), aIndex) )
                {
                    pImageView.setEffect(new DropShadow());


                }
                pEvent.consume();
            }
        };
    }

    private EventHandler<DragEvent> createDragExitedHandler(final ImageView pImageView, final Card pCard)
    {
        //當你拖動移出目標控件的時候,執行這個操作。
        return new EventHandler<DragEvent>()
        {
            @Override
            public void handle(DragEvent pEvent)
            {
                pImageView.setEffect(null);
                pEvent.consume();
            }
        };
    }

    private EventHandler<DragEvent> createDragDroppedHandler(final ImageView pImageView, final Card pCard)
    {
        //當你拖動到目標並鬆開鼠標的時候,執行這個DragDropped事件。
        return new EventHandler<DragEvent>()
        {
            @Override
            public void handle(DragEvent pEvent)
            {
                Dragboard db = pEvent.getDragboard();
                boolean success = false;
                if(db.hasString())
                {
                   boolean a =  GameModel.instance().moveCard(GameModel.instance().StringToStack(db.getString()), aIndex);
                    System.out.println(a);
                    success = true;
                    ClipboardContent content = new ClipboardContent();
                    content.putString(null);
                    db.setContent(content);
                    }
                pEvent.setDropCompleted(success);
                pEvent.consume();
            }
        };
    }
    //獲取移動時最頂端的卡片

    public  void gameStateChanged()
    {
        buildLayout();
    }
}

悔牌的邏輯;將每一步的操作保存到堆棧中,然後每次點擊一次悔牌按鈕,都讓一個arraylist出棧,然後實現了悔牌功能。

if(!listStack.isEmpty())
    {
        ArrayList<CardStack> temp_list = listStack.peek();
        listStack.pop();
        ArrayList<Integer> flag = new ArrayList<Integer>();
        for (int i = 2 ; i<9 ; i++)
        {
            if(this.card_Stacks.get(i).size() != temp_list.get(i).size())
            {
                flag.add(i);
            }
        }
        if(flag.size() == 1)
        {
            if(this.card_Stacks.get(flag.get(0)).size() <temp_list.get(flag.get(0)).size() &&this.card_Stacks.get(flag.get(0)).size() >0 && temp_list.get(flag.get(0)).size() > 1)
            {
                if(temp_list.get(flag.get(0)).peek(temp_list.get(flag.get(0)).size() - 2).isFaceUp())
                {
                    temp_list.get(flag.get(0)).peek(temp_list.get(flag.get(0)).size()-2).setFaceUp(false);
                }
            }

        }
        if(flag.size() ==2)
        {
            if(this.card_Stacks.get(flag.get(0)).size() -temp_list.get(flag.get(0)).size() == 1 )
            {

                temp_list.get(flag.get(1)).peek(temp_list.get(flag.get(1)).size()-2).setFaceUp(false);

            }
            if(this.card_Stacks.get(flag.get(1)).size() -temp_list.get(flag.get(1)).size()  ==1)
            {
                temp_list.get(flag.get(0)).peek(temp_list.get(flag.get(0)).size()-2).setFaceUp(false);
            }
            if(this.card_Stacks.get(flag.get(0)).size() - temp_list.get(flag.get(0)).size() >=2)
            {
                int i = this.card_Stacks.get(flag.get(0)).size() - temp_list.get(flag.get(0)).size();
                temp_list.get(flag.get(1)).peek(temp_list.get(flag.get(1)).size()-i-1).setFaceUp(false);

            }
            if(this.card_Stacks.get(flag.get(1)).size() -temp_list.get(flag.get(1)).size() >=2)
            {
                int i = this.card_Stacks.get(flag.get(1)).size() - temp_list.get(flag.get(1)).size();
                temp_list.get(flag.get(0)).peek(temp_list.get(flag.get(0)).size()-i-1).setFaceUp(false);

            }

        }



        for(int i = 0 ; i< 13 ; i++)
        {
            this.card_Stacks.get(i).clear();
        }
        for(int i = 0 ; i< 13 ; i++)
        {
            for(int j = 0 ; j <temp_list.get(i).size() ; j++)
            {

                this.card_Stacks.get(i).init(temp_list.get(i).peek(j));
            }
        }
        notifyListeners();
    }
    else
    {
        return;
    }

結果展示

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章