一道JAVA 21点纸牌游戏

大三了,刚学JAVA这门课,老师留了一道课后习题:

· 完成一个变形版的纸牌21点游戏。该游戏来源于21点游戏,实现人机对战。

游戏说明如下:

(1)该游戏需要两副牌,没有Joker,共104张。每张“纸牌”应具有花色数字两个属性。

(2)游戏在机器与人类玩家之间进行。游戏一开始应先洗牌(将104张牌打乱)。

(3)机器永远是庄家,所以永远先给机器发牌,机器的牌不可见,只能看到机器要了几张牌。机器停止要牌后,再给人类玩家发牌。

(4)游戏胜利与失败的条件与普通21相同;除此以外,一方在当前牌没有爆掉的前提下,如果下一张牌使得手中有两张完全一样的牌(同数字、同花色)则立刻胜利。

(5)游戏结束时机器的牌要全部显示,并提示谁胜利了。

程序设计要求如下:

(1)程序中应至少有Card类和CardGame类。

(2)Card类需要重写Object类的equals(Object o)函数,用于比较两张牌是否完全一样;重写toString函数,用于输出牌时直接显示牌的花色与数字。

(3)CardGame类应具有shuffle(洗牌)、deal(发牌)、win(胜利判别)等函数。

(4)选择适当的java集合类来实现“发牌牌堆”和“手牌”(不允许都使用数组)。

 

看完题目之后,我是无从下手啊。对于我这种初学JAVA的菜鸟(大一C++又没有学好),估计花个三天三夜都不一定能编出来。有句话说:“高手都是从模仿开始的。”我就上网搜罗到了下面的代码并加以修改,对不懂的地方逐一进行了学习。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package 21pointgame;
import java.util.ArrayList;
import java.util.Scanner;

/**
 *
 * @author Soledad
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        CardGame cg = new CardGame();
        cg.startGame();
// TODO code application logic here
    }
}
enum Color{
    HEARTS, DIAMOND, SPADE, CLUB  //红桃,方块,黑桃,梅花
}//***************学习点之一***************
class Card {
    private Color cardColor;  //牌的花色
    private int number;         //牌的面值

    public Card(){
    }

    public Card(Color c, int num) {
        cardColor = c;
        number = num;
    } //相当于C++中的赋值构造函数
    
    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        else{
            if (obj instanceof Card){
                return ((Card)obj).cardColor == this.cardColor &&((Card)obj).number == this.number ;
            }
            else 
                return false;   
        }      
     }//***************学习点之二***************

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + (this.cardColor != null ? this.cardColor.hashCode() : 0);
        hash = 59 * hash + this.number;
        return hash;
    }//****************学习点之三***************
    @Override
    public String toString() {
        String symbol;
        String numberString = "";

        if (cardColor == Color.HEARTS)
            symbol = "红心";
        else if (cardColor == Color.DIAMOND)
            symbol = "方块";
        else if (cardColor == Color.SPADE)
            symbol = "黑桃";
        else
            symbol = "梅花";

        if (number == 11) {
            numberString += "J";
        } else if (number == 12) {
            numberString += "Q";
        } else if (number == 13) {
            numberString += "K";
        } else if (number == 1){
            numberString += "A";
        } else{
            numberString += number;
        }
        
        return symbol + "  " + numberString + "  ";
    }//****************学习点之四****************

    public Color getCardColor() {
        return cardColor;
    }

    public void setCardColor(Color cardColor) {
        this.cardColor = cardColor;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

class CardGame{
    private Card[] cardHeap;      //排堆
    private int cardHeapPos;      //发到第几张牌了
    private ArrayList<Card>  playerCards ;     //玩家手牌
    private ArrayList<Card>  computerCards;    //电脑手牌
    //***************学习点之五***************

    public CardGame() {
        cardHeap = new Card[104];
        playerCards = new ArrayList<Card>();
        computerCards = new ArrayList<Card>();

        for(int i = 0; i < 104; i += 4 ) {
            for(int j = 0; j < 4; j ++) {
                switch(j){
                    case 0:
                        cardHeap[i + j] = new Card(Color.HEARTS, i % 13 + 1);
                        break;
                    case 1:
                        cardHeap[i + j] = new Card(Color.DIAMOND, i % 13 + 1);
                        break;
                    case 2:
                        cardHeap[i + j] = new Card(Color.CLUB, i % 13 + 1);
                        break;
                    default:
                        cardHeap[i + j] = new Card(Color.SPADE, i % 13 + 1);
                        break;
                }
            }
        }//通过两个循环分配给牌组两副牌(去除Jokers)
    }

    void showCards(ArrayList<Card> cards) {
        for(Card element:cards) {
            System.out.print(element);
        }//****************学习点之六***************
        System.out.println();
    }
    
    void shuffle(){
        cardHeapPos = 0; //归零
        Card[] tempHeap = new Card[104];
        int pos;

        for(int i = 0 ; i < 104; i ++) {
            pos = (int)(Math.random() * 104);//**************学习点之七****************
            for(int j = 0; j < 104; j ++){
                if(null == tempHeap[pos]) { //表示该空位没有分配值
                    tempHeap[pos] = new Card( cardHeap[i].getCardColor(), cardHeap[i].getNumber());
                    break;
                } else {
                    pos = (pos + 1) % 104;
                }
            }
        }//随机发牌堆到tempHeap中

        
        for(int i = 0; i < 104; i ++) {
            cardHeap[i].setCardColor(tempHeap[i].getCardColor());
            cardHeap[i].setNumber(tempHeap[i].getNumber());
        }//复制回原数组
    }

    void deal(ArrayList<Card> cards) {
        cards.add(cardHeap[++cardHeapPos]);
    }

    void gameOver(){
        System.out.println("庄家的牌");
        showCards(computerCards);
        System.out.println("庄家的总点数为 :" + getValue(computerCards));
        System.out.println("你的牌");
        showCards(playerCards);
        System.out.println("你的总点数为 :" + getValue(playerCards));
    }

    int getValue(ArrayList<Card> cards) {
        int value = 0;

        for(Card e:cards) {
            if (e.getNumber() >= 10)
                value += 10;
            else if (e.getNumber() == 1)
                value += 11;         //抽到“A”的时候是加1或加11, 这里先把加11
            else
                value += e.getNumber();
        }

        if(value > 21) {
            for(Card e:cards) {
                if (e.getNumber() == 1){
                    value -= 10;
                    if (value < 21) break;  //如果小于21就跳出了
                }
            }
        }

        if(value > 21)  return -1;
        return value;

    }

 
    boolean haveSame(ArrayList<Card> cards) {
        for(int i = 0; i < cards.size(); i ++) {
            for(int j = i + 1; j < cards.size(); j ++) {
                if (cards.get(i).equals(cards.get(j))) {
                    return true;
                }
            }
        }
        return false;
    }//return true表示cards中出现完全重复的牌

    void startGame() {
        boolean finish = false;
        Scanner in = new Scanner(System.in);//****************学习点之八****************
        String playerInput = "";       

        while(!finish) {
            playerCards.clear();
            computerCards.clear();
            shuffle();
            //庄家摸牌
            System.out.println("庄家摸牌...");
            int computerValue = 0;
            boolean getWinner = false;
            try {
                    Thread.sleep(3000);
            }
            catch(InterruptedException e) {
                    e.printStackTrace();
            }
            //如果庄家的总点数等于或少于16点,则必须拿牌,否则停牌
            while(computerValue <= 16) {
                deal(computerCards);
                getWinner = haveSame(computerCards);
                if (getWinner) {
                    System.out.println("庄家摸到了完全相同的牌,赢得了胜利!!!");
                    gameOver();
                    break;
                }
                computerValue = getValue(computerCards);
                if (computerValue == -1) {
                    System.out.println("你赢了, 庄家牌摸爆了!!!");
                    gameOver();
                    getWinner = true;
                    break;
                }
            }

            if(!getWinner) {
                System.out.println("庄家共有 " + computerCards.size() + " 张牌");

                while(!playerInput.equals("n")) {
                    deal(playerCards);

                    getWinner = haveSame(playerCards);
                    if (getWinner) {
                        System.out.println("你摸到了完全相同的牌,赢得了胜利!!!");
                        gameOver();
                        break;
                    }
                    System.out.println("你的牌:");
                    showCards(playerCards);
                    int playerValue = getValue(playerCards);
                     if (playerValue == -1) {
                        System.out.println("庄家赢了, 你牌摸爆了!!!");
                        gameOver();
                        getWinner = true;
                        break;
                    }
                    System.out.println("总点数为 " + getValue(playerCards));
                    System.out.println("是否继续要牌(y/n)?");
                    playerInput = in.nextLine();
                }
            }

            if(!getWinner) {
                switch(win()) {
                    case 1: //玩家胜利
                        System.out.println("你胜利了!!!");
                        gameOver();
                        break;
                    case 2:
                        System.out.println("平局!!!");
                        gameOver();
                        break;
                    case 3:
                        System.out.println("庄家获胜!!!");
                        gameOver();
                        break;
                }
            }

            System.out.println("是否继续游戏(y/n)?");
            playerInput = in.nextLine();
            
            if(playerInput.equals("n"))
                finish = true;
        }
    }
    //如果是用户不再摸牌而调用win, 则finish 设置为ture
    //返回1,说明玩家胜利
    //返回2,说明平局
    //返回3,说明电脑胜利
    int win() {
        //havaSame和双方是否爆牌已经在之前判断了(一旦有相同牌则获胜,一旦爆牌则失败,可见这两种情形优先级较高)

        int playerValue = getValue(playerCards);
        int computerValue = getValue(computerCards);//获取玩家和电脑的总点数

        if (playerValue > computerValue)
            return 1;
        else if (playerValue == computerValue)
            return 2;
        else
            return 3;
    }
}

· 学习点之一:用enum枚举

enum一般用来枚举一组相同类型的常量。如性别、日期、月份、颜色等。对这些属性用常量的好处是显而易见的,不仅可以保证单例,且要作比较的时候可以用”== ”来替换”equals”,是一种好的习惯(如本例中第45行)。

用法:如:

性别:

publicenum SexEnum {
  male, female;
}

本例中的花色:

enum Color{
    HEARTS, DIAMOND, SPADE, CLUB  //红桃,方块,黑桃,梅花
}

需要注意的是,枚举对象里面的值都必须是唯一的,特别的,我们可以还通过enum类型名直接引用该常量,比如本例中的121,124,127等行通过类型名直接引用。

除此之外,我们还可以往enum中添加新方法,这里就不加介绍了。


· 学习点之二:重写equals函数进行比较

      equals(Object o)函数原本是Object类下面的函数,我查了一下JAVA的API,截图如下:



简而言之,如果JAVA中默认的equals方法跟实际不符的话,就需要重写equals方法。我们这里要对牌是否相同作比较,因此需要重写该方法(第40~50行)

public boolean equals(Object obj) {
        if (obj == null)
            return false;//如果参数为空直接返回false
        else{
            if (obj instanceof Card){
                return ((Card)obj).cardColor == this.cardColor &&((Card)obj).number == this.number ;//如果花色数字均相同则返回true否则false
            }
            else 
                return false;  //类型不一致返回false 
        }      
     }

另外我们注意到API里面写着:

“注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。”这也就是我们下面要学习的第三点。

· 学习点之三:重写hashcode()

问:也许上面的话有些晦涩难懂啊,我们为什么要重写hashcode()方法呢?

答: object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true;

 

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。如下:

(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true

(2)当obj1.hashCode() ==obj2.hashCode()为false时,obj1.equals(obj2)必须为false

如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。特别指出利用equals比较八大包装对象(如int,float等)和String类(因为该类已重写了equals和hashcode方法)对象时,默认比较的是值,在比较其它自定义对象时都是比较的引用地址。

hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。

 

总而言之,这样如果我们对一个对象重写了equals,意思是只要对象的成员变量值都相等那么equals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致,如在存储散列集合时(如Set类),将会存储了两个值一样的对象,导致混淆,因此,就也需要重写hashcode()。

更通俗的说:

Object中的hashcode()方法在我们创建对象的时候为每个对象计算一个散列码,这个散列码是唯一的,所以如果2个对象的散列码相同,那他们一定是同一个对象。

自己定义的类也可以重写hashCode()方法,按照自己定义的算法计算散列码的生成。

Object中的equals()方法,比较的是2个对象的引用地址,包括他们各自的散列码,如果不同,就认为是不同的对象。

String类中重写了equals方法,比较的是他们字符串的内容。

在我们这里自己定义的算法是(第53~58行):

public int hashCode() {
        int hash = 7;
        hash = 59 * hash + (this.cardColor != null ? this.cardColor.hashCode() : 0);
        hash = 59 * hash + this.number;
        return hash;
    }

· 学习点之四:重写toString()函数

因为它是Object里面已经有了的方法,而所有类都是继承Object,所以“所有对象都有这个方法”。

它通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法。然而对于默认的toString()方法往往不能满足需求,需要重写覆盖这个方法。

比较易懂,就不具体说明了,见代码60~86行。

 

·学习点之五:动态数组ArrayList

      动态数组,即可以将 ArrayList想象成一种“会自动扩增容量的Array”。它的优点是可以动态地插入和删除元素,但牺牲效率。

有关使用ArrayList的例子,参考ArrayList用法,里面介绍的很详细。在我们这题中,由于电脑和玩家的手牌都会因为抽牌而增加,因此将二者均设为ArrarList(第114,115行),方便动态插入。

· 学习点之六:增强for循环

      见代码138~140行,很多人没看懂for(Card element:cards) 这段,其实这是JDK5.0的新特性,作用是遍历数组元素。其语法如下:

for(type element: array){
System.out.println(element);
  } 

· 学习点之七:善于使用随机数

      直接调用Math.random()可以产生一个[0,1)之间的随机数,注意区间是前闭后开的。本题当中因为共有104张牌,那么我们直接用(int)Math.random()*104即可以产生[0,104)之间的随机数,对应一下数组的下标前闭后开刚好满足。

 

· 学习点之八:利用Scanner进行输入

      我们都知道,JAVA里输入输出函数十分的麻烦,尤其是输入。可喜的是,从SDK1.5开始,新增了Scanner类,简化了输入函数。如本题的221行:Scanner in=new Scanner(System.in) 首先创建了一个in对象,然后in对象可以调用下列方法,读取用户在命令行输入的各种数据类型: nextDouble(), nextFloat, nextInt(),nextLine(),nextLong()等,上述方法执行时都会造成堵塞,等待用户在命令行输入数据回车确认,例如本题中的第279行,在系统问询是否继续要牌(y/n)后,用in.nextLine()来接受玩家输入的值。


说了这么多,我们来运行下程序:

e….我什么都没干 输了- -;

再来一次。。。

平局。。。行,就这样了,不要欺负电脑了。

我们再回到题目和代码:

· 完成一个变形版的纸牌21点游戏。该游戏来源于21点游戏,实现人机对战。

游戏说明如下:

(1)该游戏需要两副牌,没有Joker,共104张。每张“纸牌”应具有花色数字两个属性。--直接对应代码24~37,60~102,117~134行

(2)游戏在机器与人类玩家之间进行。游戏一开始应先洗牌(将104张牌打乱)。--直接对应代码144~166行

(3)机器永远是庄家,所以永远先给机器发牌,机器的牌不可见,只能看到机器要了几张牌。机器停止要牌后,再给人类玩家发牌。--直接对应代码224~280行

(4)游戏胜利与失败的条件与普通21相同;除此以外,一方在当前牌没有爆掉的前提下,如果下一张牌使得手中有两张完全一样的牌(同数字、同花色)则立刻胜利。--直接对应代码181~205,208~217,311~323行

(5)游戏结束时机器的牌要全部显示,并提示谁胜利了。--直接对应代码172~179,283~297行

程序设计要求如下:

(1)程序中应至少有Card类和CardGame类。

(2)Card类需要重写Object类的equals(Object o)函数,用于比较两张牌是否完全一样;重写toString函数,用于输出牌时直接显示牌的花色与数字。

(3)CardGame类应具有shuffle(洗牌)、deal(发牌)、win(胜利判别)等函数。

(4)选择适当的java集合类来实现“发牌牌堆”和“手牌”(不允许都使用数组)。



总结:这还只是JAVA的初级阶段,作为初学者还希望大家多多指教。

(有疑问的在下面评论交流)










  • 12
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值