大三了,刚学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的初级阶段,作为初学者还希望大家多多指教。
(有疑问的在下面评论交流)