在看了上面优秀的源码之后,我开始反思为什么自己的代码 既不易于维护,也不易于加壳.到现在使用JAVAX的时候,自己还是在为那些跳不完的莫名其妙的swing方面的bugs苦恼.(考虑过自己曾经在设计的时候,根本就没有考虑过要加上图形界面的想法,但是这样始终不能算作一个理由--"别看我识字不多!懒和穷永远是一个字!"-).虽然遵循了MVC的开发思路,但是始终没有走出"想到哪儿写到哪儿的思路".这样不仅在效率上会受到严重的影响,而且一旦要在原有的基础上扩展,或让整个程序的代码变得越来越笨拙.最后走向瘫痪.而且实际上除了将函数给封装到类的内部,自己没有做出其他方面 面向对象的 使用,其实质 背离了学院开设 课程的初衷.
这是一个很好的例子,自己在代码中的错误,希望后人不要再犯,前人不要鄙夷;希望能够让闪光点和优秀的习惯得以保留,而那些坏的编码还有不良的习惯会慢慢的减少,消失,从而一点点成长,一点点蜕变.
package com.Cards.model;
/**
* @author Rock Lee
* */
public class CardSetting
{
final public static int CARD_COUNT=13;
final public static int INDEX_HEART=0;//红桃的角标 ♠
final public static int INDEX_SPADE=1;//黑桃的 角标♥
final public static int INDEX_DIAMOND=2;//方片的角标 ♦
final public static int INDEX_CLUB=3;//草花的角标 ♣
//final public static String[] COLOR={"HEART ♥","SPADE ♠","DIAMOND ♦","CLUB ♣"};
final public static String[] COLOR={"♥","♠","♦","♣"};
final public static String[] NUM={"A","2","3","4","5","6","7","8","9","10","J","Q","K"};
}
package com.Cards.model;
/**
* @author Rock Lee
* @version 2012-10-21 16:51:49
* @see CardSetting.java
* @proposal Build A Class Contain All the info for ONE Card in the poker
* @fix 2012-12-12 13:01:04
* */
public class Card
{
private String color = null;
private String num = null;
private boolean visible = false;
private boolean red = false;
public Card(String color, String num)
{
this.color = color;
this.num = num;
// if the color is diamond or heart ,then red is true,else false
if (color.equals(CardSetting.COLOR[CardSetting.INDEX_HEART])
|| color.equals(CardSetting.COLOR[CardSetting.INDEX_DIAMOND]))
this.red = true;
else
this.red = false;
}
/*
* 若花色,牌面大小相同,则认为是同一张牌
* */
public boolean equals(Object obj)
{
Card anotherCard = (Card) obj;
return this.color.equals((anotherCard.color))
&& this.num.equals(anotherCard.num);
}
public void setVisiable(boolean visible)
{
this.visible = visible;
}
public boolean isVisible()
{
return visible;
}
public boolean isRed()
{
return red;
}
/*
* 1.此方法仅用于 命令行,或调试模式中2.重载了 object的使用方法.在整个列,或者deck suit输出的时候,方便 以字符串的方式拼接
*/
public String toString()
{
if (this == null)
return "EMPTY";
else
{
if (this.isVisible())
{
return (color + " " + num + " ");
}
else
return "███ ";
}
}
public String getColor()
{
return color;
}
/* 以数字的方式,返回牌面的大小 */
public int getNum()
{
char ch = num.charAt(0);
switch (ch)
{
case 'A':
return 1;
case 'J':
return 11;
case 'Q':
return 12;
case 'K':
return 13;
default:
return Integer.parseInt(num);
}
}
/* 以String的方式,返回牌面的大小 */
public String getNumInString()
{
return num;
}
}
package com.Cards.model;
import java.util.Vector;
/**
* @author Rock Lee
* @version 2012-10-21 17:04:34
* @proposal Support the initialization for the whole set of poker,and the Cards left on the DECK
* */
public class CardHeap
{
//这里使用了vector来维护一套 "刚刚从扑克牌盒子里取出的 52张牌"
private Vector vector=null;
public CardHeap()
{
this.initialize();
}
/*初始化13*4=52张牌*/
public void initialize()
{
vector=new Vector();
Card tmp=null;
for (int i = 0; i < 13; i++)//13 cards in Color Heart
{
tmp=new Card(CardSetting.COLOR[CardSetting.INDEX_HEART], CardSetting.NUM[i]);
tmp.setVisiable(false);
vector.add(tmp);
}
for (int i = 0; i < 13; i++)//13 cards in Color SPADE
{
tmp=new Card(CardSetting.COLOR[CardSetting.INDEX_SPADE], CardSetting.NUM[i]);
tmp.setVisiable(false);
vector.add(tmp);
}
for (int i = 0; i < 13; i++)//13 cards in Color DIAMOND
{
tmp=new Card(CardSetting.COLOR[CardSetting.INDEX_DIAMOND], CardSetting.NUM[i]);
tmp.setVisiable(false);
vector.add(tmp);
}
for (int i = 0; i < 13; i++)//13 cards in Color CLUB
{
tmp=new Card(CardSetting.COLOR[CardSetting.INDEX_CLUB], CardSetting.NUM[i]);
tmp.setVisiable(false);
vector.add(tmp);
}
}
/*分发牌堆里现有的第一张牌*/
public Card giveOutOneCard()
{
return (vector.remove(0));
}
/*洗牌算法,这里参考了 当时张剑的连连看的实现方法
*
* 其思想就是for(i=0:size-1) 交换第2张牌和 牌堆里任意一张牌的顺序,循环结束后,牌堆就已经随机排序
* */
public void shuffle()
{
for (int i = 0; i < vector.size(); i++)
{
this.swap(i, (int)(Math.random()*vector.size()));
}
}
private void swap(int i,int j)
{//支持洗牌shuffle方法
Card tmp=vector.get(i);
vector.set(i, vector.get(j));
vector.set(j,tmp);
}
public boolean isEmpty()
{
return vector.isEmpty();
}
}
当时考虑的时候,还在想能不能够把deck和这个heap结合到一个类中,让deck/heap发完牌后,剩下的直接就放在游戏中准备一张张翻开备用,后来因为考虑到了如果要每次展示三张牌到discard,实现的代码量会有些大,所以还是觉得再多封装一个类...package com.Cards.model;
import java.util.*;
/**
* @author Rock Lee
* @version 2012-10-22 12:54:02
* @see Card.java CardHeap.java
* @proposal to support the view of the groups and the way of controlling the
* deck
* */
public class CardDeck
{
/*
* 该类应该只保留对外 stackShown的展示,而不应该将其他的属性暴露出去.其他的类只能通过访问接口,看到他应该展示的内容
*/
public final static int DECK_SIZE = 3;
private Vector vector_previous = null;
private Vector vector_changed = null;
private Stack stackShown = null;
/*
* 0->表示deck 中开没有开始被取用.可认为是 GUI中deck没有被点击时的状态 1~size()/3 表示显示的现在是哪一套(一套3张)
*/
private int currentGroupPosition = 0;// remember this doens't start with
// zero 0!<0 means
// "not started yet",the
// index is from 1 to size()/3>
private int groupCount = 0;// the number of the groups
public CardDeck()
{
this.initialize();
}
private void initialize()
{
vector_changed = new Vector();
vector_previous = new Vector();
stackShown = new Stack();
}
public void addOneCard(Card card)
{
// receive a card from CardHeap before the game start
vector_previous.add(card);
}
public Stack getCurrentGroup()
{
return this.stackShown;
}
/*
* 如果group还没有被初始化,那么就对deck调用startGroup方法,让deck初始化起来
* 如果已被初始化,变换stackShown的内容,加载下一套(3张)牌 并修改对应的指针
*
* 倘若到了未被展示的牌堆(vec_pre)的底部(最后了),就重新将vec_changed回倒给vec_pre,开始下一轮的deck循环
*/
public Stack getNextGroup()
{
/*
* if the group hasn't been established yet,you need to initialize the
* Groups and return the first one
*
* else copy the stackShown to vector_changed and move the iterator to
* the next group
*/
if (currentGroupPosition == 0)
return this.startGroup();
/*
* Judge if this is the end of all the Groups
*/
else if (currentGroupPosition == groupCount)// this is the last group
{
vector_changed.addAll(stackShown);
stackShown.clear();
vector_previous.clear();
vector_previous.addAll(vector_changed);
vector_changed.clear();
return this.startGroup();// reborn
}
else
{
vector_changed.addAll(stackShown);// copy the last group to the
// changed_vector
stackShown.clear();
for (int i = 0; i < DECK_SIZE; i++)
{
if (!this.loadOneCard())
break;
}
this.currentGroupPosition++;// begin with 1,not 0
}
return stackShown;
}
/*
* 1.计算出第一套(3张)应该被展示的牌,并将currentGroupPosition 这个游标拨到"1"的地方 2.同时返回
* 第一个待被处理的stackShown
*/
private Stack startGroup()
{
/*
* group and calculate the number of the group.present the first group
*/
this.groupCount = (int) (vector_previous.size() / 3);
for (int i = 0; i < DECK_SIZE; i++)
{
if (!this.loadOneCard())
break;// the end of the vec_pre
}
this.currentGroupPosition = 1;
return stackShown;
}
/*
* 如果违背展示的牌堆(vector_pre)已经空了,便返回false 否则加载 第0号牌至stackShown这个对外展示的窗口中,并返回ture
*/
private boolean loadOneCard()
{
/*
* Load one card from vector_pre to stackShown
*
* Return true if load successfully
*
* return false if the previous vector is empty
*/
if (vector_previous.isEmpty())
return false;
Card tmp = this.vector_previous.remove(0);
stackShown.add(tmp);
return true;
}
/*
* 从stackShown中获取最上方的一张牌,若为空则返回null
*/
public Card getOneCard()
{
/*
* this might be used to reapOneCard Or to send to one CardList
*/
if (stackShown.empty())
return null;
return stackShown.pop();// might throw Exception
}
/* 看看stackShown中最上面的一张牌是神马 */
public Card peekOneCard()
{
return stackShown.peek();
}
/* 访问stackShown的对象,能够对其进行一定的操作 */
public Stack getStackShown()
{
return this.stackShown;
}
}
这里多一句嘴:以后开发除了真的要到外企去工作,或者文档是写给某些特殊要求人群看的,文档不要再用英文写了啊.自己都觉得蛋疼.英文要表达出明白的意思,自己的词汇量又不够,而且生僻的词汇量反倒误了文档或者注释本来的意义.说来好笑,分不清到底哪里应该多用英语,哪里应该尽量使用自己的母语.一个很好的原则:"莫装X,装X遭雷劈" 此外,还有几个问题,当初只是觉得stack比较符合自己在设计过程中的思路,却没有宏观的考虑到 其他 类对其的操作.而且这个机制让人蛋疼的地方就在于,牌必须来回的倒腾,我开始想想用队列吧.后来觉得如果牌只剩下五张了,你两次翻页应该怎么去实现??这个不佳的设置,直接导致了算法的复杂.和代码的不易读...<试着想想更好的实现机制,否则在第三篇文章中,直接将这个 方案给删除..= =|>package com.Cards.model;
import java.util.LinkedList;
import java.util.List;
/**
* @author Rock Lee
* @version 2012-10-21 19:38:26
* @aim Encapsulate a List and offer more interfaces for the GameModel
* */
public class CardList
{
private LinkedList list = null;
public CardList()
{
this.initialize();
}
private void initialize()
{
list = new LinkedList();
}
public String toString()
{
/*
* To Print the whole card list to the screen,this method would be
* called
*/
String str = "";
for (int i = 0; i < list.size(); i++)
{
str += list.get(i);
}
return str;
}
public void addOneCard(Card card)
{
/*
* this method receive a card and add it to the end of the list
*/
this.list.add(card);
}
public Card getOneCard()
{
/*
* The last card is going be polled out and send to one of the 4 stacks
*/
if (list.isEmpty())
return null;
Card result = this.list.pollLast();
this.turnLastCard();
return (result);
}
public Card peekOneCard()
{
return this.list.peekLast();
}
public void turnLastCard()
{
/*
* If the last card of the list is covered,you need to turn it over
*/
if (list.isEmpty())
return;
if (!list.peekLast().isVisible())
{
list.peekLast().setVisiable(true);
}
}
public boolean moveTo(CardList aimCardList)
{
int breakPoint = this.allowToMove(aimCardList);
if (breakPoint == -1)
return false;
List sublist = list.subList(breakPoint, list.size());
aimCardList.list.addAll(sublist);// add the content to the aim
sublist.clear();// delete the sublist part of the original
this.turnLastCard();
return true;
}
/*
* 这个算法有优化的可能,不必把两个卡片的情况都列出来,再一一加以寻找, 更便捷的办法,是先在fromList中,从"牌面大小"上索引到比
* toList的末尾那张牌下一号的牌.
*
* 接下来再来检验花色相异 和 可见(visible)两个属性 如果 都符合要求,就说明能够搬运.
*
* 自己的算法底子薄,思考方案的时候一定要问问自己,有没有神马更简洁明了的实现方案(= =| 话说这个方案还是别人的源码启发的囧)
*/
private int allowToMove(CardList aimCardList)
{
/*
* return the break point of the original list if 2 lists can call the
* function moveTo()
*
*
* if it does not match,return -1;
*/
// the break point is the end of the aimCardList
int subListHeadCardNumber = aimCardList.list.peekLast() == null ? 1
: aimCardList.list.peekLast().getNum() - 1;
if (subListHeadCardNumber > 13 || subListHeadCardNumber < 1)
return -1;
int breakPoint = -1;
// get 2 possible headCards
Card subListCard_0 = null;
Card subListCard_1 = null;
if (subListHeadCardNumber != 1)
{// aimCardList is not empty
if (aimCardList.list.peekLast().isRed())
{// then the subhead shall be black
subListCard_0 = new Card("♠",
CardSetting.NUM[subListHeadCardNumber - 1]);
subListCard_1 = new Card("♣",
CardSetting.NUM[subListHeadCardNumber - 1]);
}
else
{// then the subhead shall be red
subListCard_0 = new Card("♥",
CardSetting.NUM[subListHeadCardNumber - 1]);
subListCard_1 = new Card("♦",
CardSetting.NUM[subListHeadCardNumber - 1]);
}
breakPoint = this.list.indexOf(subListCard_0);
if (breakPoint == -1)
breakPoint = this.list.indexOf(subListCard_1);
if (breakPoint == -1)
return -1;
} else
// aim card list is empty
{
for (int i = 0; i < this.list.size(); i++)
{
if (this.list.get(i).isVisible()
&& this.list.get(i).getNum() == 13)
breakPoint = i;
}
}
return breakPoint;
}
public boolean allowToAdd(Card card)
{
Card endCard = list.peekLast();
if (endCard.isRed() == card.isRed())// same red or black
return false;
return (card.getNum() == endCard.getNum() - 1);
}
public LinkedList getLinkedList()
{
return this.list;
}
}
接下来,鸡肋的CardStack.java 其实如果设计的时候,注意到了的话,根本就不用维护一个stack的数据结构,因为只需要获得一个suit的最上面一张牌就行,父类或者一个简单的函数就判断是否能够添加,而又何必引入stack?!一下是苦逼的代码:
package com.Cards.model;
import java.util.Stack;
/**
* @author Rock Lee
* @version 2012-10-21 20:35:59
* @aim to support the stack operation of the cards which are abandoned
* */
public class CardStack
{
private Stack stack=null;
private String color=null;
public CardStack(String color)
{
stack=new Stack();
this.color=color;
}
public boolean allowToPush(Card card)
{
/*
* is it possible to push the card into the stack
* */
if(!card.getColor().equalsIgnoreCase(this.color))//colors match?
return false;
if(stack.empty()&&(card.getNum()==1))//is the card an ACE?
return true;
else
{
if(stack.peek().getNum()+1==card.getNum())//nums match?
return true;
return false;
}
}
public Card peek()
{
return this.stack.peek();
}
public void push(Card card)
{
stack.push(card);
}
public String toString()
{
if (stack.isEmpty())
return color+" Empty";
else return color+" "+stack.peek().getNumInString();
}
public Stack getStack()
{
return this.stack;
}
}
package com.Cards.model;
/**
* @author Rock Lee
* @version 2012-10-21 17:33:31
* @proposal to establish a game model for the game,leaving all the possible interfaces to control the game
*
* */
public class GameModel
{
public final static int LISTS_NUM=7;
public final static int STACKS_NUM=4;
public CardHeap heap=null;
public CardList lists[]=null;
public CardStack stacks[]=null;
public CardDeck deck=null;
public GameModel()
{
this.initialize();
}
private void initialize()
{
heap=new CardHeap();
lists=new CardList[LISTS_NUM];
stacks=new CardStack[STACKS_NUM];
}
public void newGame()
{
heap.initialize();//get a new set of cards
heap.shuffle();
for (int i = 0; i < lists.length; i++)
{//7 times to get 7 Card Lists
lists[i]=new CardList();
for (int j = 0; j < i+1; j++)
{//i+1 times to give out the enough Cards to build list[i]
Card tmp=heap.giveOutOneCard();
lists[i].addOneCard(tmp);
}
lists[i].turnLastCard();//turn the end card in the list
}
for (int i = 0; i < stacks.length; i++)
{//4 times to get 4 stacks
stacks[i]=new CardStack(CardSetting.COLOR[i]);
}
//only one deck to set up
//All the cards left in the heap sent to deck
deck=new CardDeck();
while(!heap.isEmpty())
{
Card tmp=heap.giveOutOneCard();
tmp.setVisiable(true);
deck.addOneCard(tmp);
}
this.print();
}
public void print()
{
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
System.out.println("The Status of Deck is :\n\t"+deck.getCurrentGroup());
System.out.println("The Status of 7 lists are:");
for (int i = 0; i < lists.length; i++)
{
System.out.println("\t"+lists[i]+"\n");
}
System.out.println("The Status of 4 stacks are:");
for (int i = 0; i < stacks.length; i++)
{
System.out.println("\t"+stacks[i]);
}
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
}
是的,正如你看到的,这个类除了初始化一句新游戏他什么也没干..而那几个散了架的数据结构(list stack deck等等)由于没有ArrayList[]这样的而管理而各自为政.要进行操作,还要到整个模型外去自己编写方法,而按照常理,像movePile(from,to)都应该被封装成一个函数,而在control的部分直接调用即可,可是我在这里却什么都没干.如果我是 SVN的同一小组开发的同事,我也会骂一句"去你麻痹的!" 苦逼的Command.java.他成了GameModel的替身,完成了大部分操作GameModel的逻辑,可是就好像没有放进机箱的组装机器一样.身兼model和control功能的他存在的样子十分丑陋 阿西莫多command
package com.Cards.model;
import java.util.Scanner;
/**
* @author Rock Lee
* @version 2012-10-23 10:57:17
* @see Card.java
* @proposal To support the command line controls,and the manual of playing the Game
* @mail_to if you have any bugs to report ,mail to [email protected]
* */
public class Command
{
/*
* ============================================================
* This is a manual of playing the game using command line
* ============================================================
* OK...I admit that I am too lazy to write a GUI...
* There are so much other Course Designs and sucking experiments
* to be finished.
*
* ------------------------------------------------------------
* 1.ltl :move a sublist from list to another e.g.0 6
* 2.rpd :reap from the deck (no arg)
* 3.rpl :reap from a list e.g. 6
* 4.dtl :move one card from the deck to a list e.g. 0
* 5.help :help manual (no arg)
* 6.pl :print the current status for the game (no arg)
* 7.n :turn the next group on the deck (no arg)
* 8.exit :exit the whole game (no arg)
* 9.new :start a new game (no arg)
* ------------------------------------------------------------
* */
final static String[] CMD={"ltl","rpd","rpl","dtl","help","pl","n","exit","new"};
final static String HELP=" * ------------------------------------------------------------\n"+
"* 1.ltl :move a sublist from list to another e.g.0 6\n"+
"* 2.rpd :reap from the deck (no arg)\n"+
"* 3.rpl :reap from a list e.g. 6\n"+
"* 4.dtl :move one card from the deck to a list e.g. 0\n"+
"* 5.help :help to the manul (no arg)\n"+
"* 6.pl :print the current status for the game (no arg)\n"+
"* 7.n :turn the next group on the deck (no arg)\n"+
"* 8.exit :exit the whole game (no arg)\n"+
"* 9.new :start a new game (no arg)\n"+
" * ------------------------------------------------------------\n"+
"* */\n";
Scanner scan_cmd=null;
String cmd=null;
private int []args={0,0};
private GameModel model=null;
public Command(GameModel model)
{
this.model=model;
scan_cmd=new Scanner(System.in);
cmd="\0";
}
private void getArgument(int index)
{
/*
* Get the arguments for the command just given
* */
switch (index)
{
case 0:
args[0]=scan_cmd.nextInt();
args[1]=scan_cmd.nextInt();
break;
case 2:
case 3:
args[0]=scan_cmd.nextInt();
break;
default://there is no arguments needed
break;
}
}
public void funtion()
{
/*
* make the Command Scanner start to work
* */
while(true)
{
cmd=scan_cmd.next();
int index=this.getCommandIndex(cmd);
if(index==-1)
{
System.out.println("Invalid input command,try it again.\n");
continue;
}
this.getArgument(index);
this.process(index);
}
}
private int getCommandIndex(String cmd)
{
/*
* return the index of the command input
* if the input is invalid,return -1
* */
cmd.toLowerCase();
for (int i = 0; i < CMD.length; i++)
{
if(cmd.equals(CMD[i]))
{
return (i);
}
}
return -1;
}
private void process(int index)
{
/*
* The Core Code to do the real job,calling the methods of the fields in GameModel
* */
Card tmp=null;
switch (index)
{
case 0://ltl
{
model.lists[args[0]].moveTo(model.lists[args[1]]);
model.print();
break;
}
case 1://rpd
{
tmp=model.deck.peekOneCard();
for (int i = 0; i < model.stacks.length; i++)
{
if(model.stacks[i].allowToPush(tmp))
{
model.stacks[i].push(model.deck.getOneCard());
break;
}
}model.print();
break;
}
case 2://rpl
{
tmp=model.lists[args[0]].peekOneCard();
for (int i = 0; i < model.stacks.length; i++)
{
if(model.stacks[i].allowToPush(tmp))
{
model.stacks[i].push(model.lists[args[0]].getOneCard());
break;
}
} model.print();
break;
}
case 3://dtl
{
tmp=model.deck.peekOneCard();
if(model.lists[args[0]].allowToAdd(tmp))
{
model.lists[args[0]].addOneCard(model.deck.getOneCard());
}model.print();
break;
}
case 4://help
{
System.out.print(Command.HELP);
break;
}
case 5://pl
{
model.print();
break;
}
case 6://next group
{
model.deck.getNextGroup();
model.print();
break;
}
case 7:
{
System.exit(0);
}
case 8:
{
model.newGame();
break;
}
default:
System.out.println("Command index wrong....");
break;
}
}
}
process做了太多本不应该他干的事情.而且由于这种command和model上的耦合,自己无法很好地把控制的部分 重新编写,把 命令行的控制 转移到GUI上面!这才是整个设计中无法原谅的部分!最后贴一张运行的示例图,捂脸+叹气:
====================================================================================================
未完待续