这个审查请求现在通过自定义UI和其他更改演变为本审查请求。
我写了一个程序来模拟喝纸牌游戏国王。这是我的第三个Java项目。我不太明白如何创建类Card
和Deck
。我很难创建它们,所以我继续使用了一些在教程中编写它们的代码。但我想我现在明白了。
我计划在PSVM
中的while-true
loop
下添加一个主菜单,其结构与playGame()
中的结构相似。
我想回顾一下我的代码,以及一些关于如何使代码更高效、更干净、更容易阅读或其他任何通用指针的提示。
游戏的解释在下面的代码注释框中:
/**
* @author :KyleMHB
* Project Number :0003
* Project Name :Kings
* IDE :NETBEANS
* Goal of Project -
* Kings is a rule based drinking game using cards for 4+ players.
* The Rules are read in from a rules.txt so that one can easily change the rules.
* How the game works:
* Players shuffle a deck of cards, place a glass between them and circle the
* cards around the base of the glass.
* The players then take turns picking cards, each card has its own rule associated to it.
* Most importantly, there are 4 Kings, each time a King is picked,
* the player who picked it can pour as much of his/her drink into the glass between
* them as they wish.
* The game ends when the fourth and final King is picked.
* The player to pick the final King must down the glass in the center of table.
*/
public static class Card {
private int rank, suit;
private String[] suits = { "Hearts", "Spades", "Diamonds", "Clubs" };
private String[] ranks = { "Ace", "2", "3", "4", "5", "6", "7", "8",
"9", "10", "Jack", "Queen", "King" };
Card(int suit, int rank){
this.rank=rank;
this.suit=suit;
}
public @Override String toString(){
return ranks[rank] + " of " + suits[suit];
}
public int getRank() {
return rank;
}
public int getSuit() {
return suit;
}
}
public static class Deck {
private static ArrayList<Card> cards;
Deck() {
cards=new ArrayList<Card>();
for (int a=0; a<=3; a++){
for (int b=0; b<=12; b++){
cards.add( new Card(a,b) );
}
}
Collections.shuffle(cards, new Random());
Collections.shuffle(cards, new Random(System.nanoTime()));
//double shuffle for randomness
}
public Card drawFromDeck(){
return cards.remove( 0 );
}
public int getTotalCards(){
return cards.size();
}
}
PSVM()
和rules
声明:private static List<String> rules;
public static void main(String[] args) throws FileNotFoundException, IOException {
setGameRules(new File("rules.txt"));
playGame(getNum("How many people are going to play","Number of Players"));
}
setRules()
方法:/**
* Rules are not hard-coded because people often have different ones,
* Therefore I made an easily editable rules.txt file.
* Also my rule file has formatting in using the \n,
* However when the file is read it is read as \ and n
* Hence why I used the replaceAll( "\\\\n","\n");
*/
private static void setRules(File f) throws FileNotFoundException, IOException {
rules = Files.readAllLines(f.toPath(), Charset.defaultCharset());
for(int i=0; i!=rules.size(); i++){
rules.set(i, rules.get(i).replaceAll( "\\\\n","\n"));
}
}
getNum()
方法://This method was left as getNum because I will use it later for a Main Menu
private static int getNum(String prompt,String title) {
return Integer.parseInt(JOptionPane.showInputDialog(null,prompt,title,3));
}
playGame()
方法:private static void playGame(int players) {
int playerTurn;
int choice;
int kings=0;
Card cardDrawn;
Deck deck=new Deck();
while(true){//loop to run the game till the 4th king is drawn
playerTurn=0;
while (playerTurn!=players){//used to give each player a turn
choice=getChoice("Player "+(playerTurn+1),
"Would you like to skip or draw?","Draw","Skip","Exit");
if (choice==0){
cardDrawn=deck.drawFromDeck();
System.out.println(cardDrawn);
kings+=showCard(cardDrawn,kings,playerTurn+1);
playerTurn++;
}
else if(choice==1)
playerTurn++;
else
System.exit(0);
}//Turn reset loop
}//continuous loop
}
getChoice()
方法://this method is used so that I can reuse it later in the main menu.
private static int getChoice(String title, String prompt,
String a, String b, String c) {
Object[] options = { a, b, c};
return JOptionPane.showOptionDialog(null, prompt, title,0,2,
null,options,options[0]);
}
showCard()
方法://The method name was originally checkIfKing(), I think this is better?
private static int showCard(Card a, int kings, int player) {
if(a.rank==12)//checks if the card is a King
if(kings==3){//checks if the card is the final King
JOptionPane.showMessageDialog(null,
"Player "+player+" has Drawn the Final King\n\n"
+ "Restart the Program to Play again",a.toString(),1);
System.exit(0);
return 0;
}
else{
JOptionPane.showMessageDialog(null,"Player "+player+
" has drawn the "+a.toString()+"\n"
+ "Which is King "+(kings+1)+"/4\n\n"
+rules.get(a.rank),a.toString(),1);
return 1;
}
else{
JOptionPane.showMessageDialog(null,"Player "+player+
" has drawn the "+a.toString()+"\n\n"
+ rules.get(a.rank),a.toString(),1);
return 0;
}
}
发布于 2013-10-05 14:50:40
您的代码在业务代码(游戏的实际实现)和用户界面代码之间有着非常紧密的耦合。这意味着我不能轻易地重用您的代码,例如,在命令行上播放它,而不是您已经实现的GUI。
您可以通过为所有UI操作定义一个接口来实现这种分离。在您的main
中,您可以决定要使用哪个UI实现。
interface UserInterface {
public int numberOfPlayers();
public boolean playerWantsToDrawCard(int player); // if false: skip
public void showCard(Card card, int player, boolean last);
}
然后,将现有的方法重构为GraphicalUserInterface implements UserInterface
。请注意,这是一个与您的不同的抽象-您已经展示的实现抽象了所显示的内容,但硬编码了这是如何做到的。我宁愿反过来做这件事。
使用这个新接口,您的playGame
将更改为:
private static void playGame(UserInterface ui) {
int players = ui.numberOfPlayers();
int kings = 0
Deck deck = new Deck();
while (!deck.isEmpty()) {
for (int player = 0; player < players; player++) {
// players do not have to draw a card
if (!ui.playerWantsToDrawCard(player)) {
continue;
}
Card drawnCard = deck.draw();
if (drawnCard.getRank() == Rank.KING) {
kings++;
}
ui.showCard(drawnCard, player, kings == 4 || deck.isEmpty());
// exit if we've seen all kings
if (kings == 4) {
return;
}
}
}
}
虽然这假定了一些进一步的更改(例如,对Deck
API),但主要的一点是业务逻辑不需要直接处理UI。其他重要的变化是:
for
循环来迭代所有玩家,而不是通过另一个while
循环混淆这一点。drawnCard
,所以我在那里声明它。根据经验,在不直接初始化变量的情况下,不应该声明变量。有一件事不是很明显,但我让ui.showCard
方法知道这张卡是否是国王,而这是否是最后一个国王。我要提供的唯一提示是这是否是游戏中的最后一张牌。
有一件事我还没有解释,那就是Rank.KING
。在原始代码中,您有硬编码的12
,它不会向代码的读者解释任何东西。目前,您使用整数指定每张卡的匹配项和级别。这可以说是错误的,您应该使用枚举:
enum Rank {
ACE ("Ace"),
TWO ("2"),
THREE ("3"),
FOUR ("4"),
FIVE ("5"),
SIX ("6"),
SEVEN ("7"),
EIGHT ("8"),
NINE ("9"),
TEN ("10"),
JACK ("Jack"),
QUEEN ("Queen"),
KING ("King");
String name;
Rank(String name) {
this.name = name;
}
}
Suit
也是。现在我们的Card
看起来就像
class Card {
private Rank rank;
private Suit suit;
public String toString() {
return rank.toString() + " of " + suit.toString();
}
...
}
其他方法已经更新了它们的类型。使用枚举可以提供更多的类型安全性,以及所有可能的值的可比较性和可迭代视图等自动添加。这样我们就可以建造类似的甲板
cards = new ArrayList<Card>();
for (Suit suit : Suit.values()) {
for (Rank rank : Rank.values()) {
cards.add(new Card(suit, rank));
}
}
Collections.shuffle(cards, new Random());
顺便说一下,洗牌一次是“随机的”,而额外的洗牌并不会增加随机性。如果你需要一个真正的密码学级熵源,new Random()
给你的内部伪随机数生成器是不够的。但是为了这个游戏的目的,它的使用是相当好的。
https://codereview.stackexchange.com/questions/32281
复制相似问题