14.6 Eights类
13.2 节介绍了自上而下的开发,这种程序开发方式确定高层次的目标,如洗牌,并将其分解为更小的问题,如在数组中查找最小的元素或交换两个元素。
本节将介绍自下而上的开发(bottom-up development),它反过来做,即先找出需要的简单部分,再将它们组装成更复杂的算法。
根据 Crazy Eights 的规则,可确定需要的一些方法。
创建整副扑克牌、弃牌堆、储备牌以及表示玩家的对象。
发牌。
检查游戏是否结束。
如果没有了储备牌,就将弃牌堆洗一下,并将其作为储备牌。
取牌。
跟踪轮到谁出牌以及从一个玩家切换到下一个玩家。
显示游戏的状态。
进入下一轮前等待用户。
现在可以开始实现这些部分了。下面是封装游戏状态的 Eights 类定义的开头部分:
public class Eights {private Player one;private Player two;private Hand drawPile;private Hand discardPile;private Scanner in;
这个版本只有两个玩家。本章末尾有一个练习,要求你修改这些代码,以支持更多玩家。
最后一个实例变量是一个 Scanner 对象,我们将在每次出牌后用它提示用户输入。下面的构造函数初始化所有的实例变量并发牌:
public Eights() {Deck deck = new Deck("Deck");deck.shuffle();int handSize = 5;one = new Player("Allen");deck.deal(one.getHand(), handSize);two = new Player("Chris");deck.deal(two.getHand(), handSize);discardPile = new Hand("Discards");deck.deal(discardPile, 1);drawPile = new Hand("Draw pile");deck.dealAll(drawPile);in = new Scanner(System.in);}
接下来需要实现的是检查游戏是否结束的方法。只要有一个玩家手里没牌,游戏便结束了:
public boolean isDone() {return one.getHand().empty() || two.getHand().empty();}
没有储备牌时,我们必须将弃牌堆洗一下。完成这种任务的方法如下:
public void reshuffle() {Card prev = discardPile.popCard();discardPile.dealAll(drawPile);discardPile.addCard(prev);drawPile.shuffle();}
第 1 行保存 discardPile 最上面的那张牌;第 2 行将余下的牌转移到 drawPile。接下来,我们将保存的牌放回到 discardPile,并对 drawPile 执行洗牌操作。
现在可以在 draw 中调用 reshuffle 了:
public Card draw() {if (drawPile.empty()) {reshuffle();}return drawPile.popCard();}
要从一个玩家切换到另一个玩家,我们可以像下面这样做:
public Player nextPlayer(Player current) {if (current == one) {return two;} else {return one;}}
方法 nextPlayer 将当前玩家作为参数,并返回接下来轮到的玩家。
最后两个部分是 displayState 和 waitForUser:
public void displayState() {one.display();two.display();discardPile.display();System.out.println("Draw pile:");System.out.println(drawPile.size() + " cards");}public void waitForUser() {in.nextLine();}
可用这些方法编写 takeTurn,用来执行玩家的一次出牌过程:
public void takeTurn(Player player) {Card prev = discardPile.last();Card next = player.play(this, prev);discardPile.addCard(next);System.out.println(player.getName() + " plays " + next);System.out.println();}
takeTurn 读取弃牌堆最上面的那张牌,将其传递给前一节介绍过的 player.play,并将它返回的牌加入弃牌堆。
最后,我们用 takeTurn 和其他方法来编写 playGame:
public void playGame() {Player player = one;// 不断地玩,直到有人获胜while (!isDone()) {displayState();waitForUser();takeTurn(player);player = nextPlayer(player);}// 显示最终得分one.displayScore();two.displayScore();}
大功告成!注意,自下而上开发的结果与自上而下类似:有一个调用辅助方法的高级方法。主要差别在于实现解决方案的顺序不同。
