Tommorow
单人纸牌游戏,牌桌上有7个堆共28张牌,第一堆1张牌,第二堆2张,。。。第7堆7张,每一堆的第一张牌朝上,其他朝下。牌桌上还有4个suitpiles,一个deck card堆和一个discard card堆,布局如下(参考Windows的纸牌游戏)
设计一个简单的CardGames程序,运用面向对象封装、继承、抽象类、抽象方法、多态、动态绑定等概念。
PIII 450以上
4G内存
5GB硬盘空间
Windows 8.1
eclipse
首先我设计了UML类图,写了一个继承Jpanel类的Pile类当做主类,它有下图所示几个子类:
DeckCardPile:还未使用的牌堆
DestCardPile:目标牌堆,应该有4个对象,并用该堆牌的数量判断输赢
DiscardPile:从Deck牌堆中选出的牌的牌堆,我这里我设置的选出3张牌
TabCardPile:放置一系列牌的牌堆,正中位置,应该有7个对象
TempCardPile:当移动牌时候,处于移动状态下的牌的牌堆
这些子类都作为容器添加到了界面主Jpanel中,每次执行改动界面操作,都统一调用update方法来重画。
Pile类首先有一个ArrayList来当牌列,是个抽象类,定义了3个抽象方法,他的子类都应该实现,check方法是检查某个牌列拖进该Pile时,是否可以添加到自己的牌列中,checkXY是判定你点击牌的时候,该牌列是否可以从当前Pile中拖走,最后一个抽象方法setPilePounds是因为我的每个Pile子类都是继承了Jpanel类,每次改变牌列,该Pile的区域范围都将随之改变,并且改变不一样。
比如TempCardPile类重写了该抽象方法,那么每次他自己的牌移走,调用setPilePounds重新划定自己的区域范围,那么在结束时的update中,他作为Jpanel子类,在paintComponent方法中,只需要简单写绘图语句即可,不用担心自己的画图对主Jpanel背景图片造成的影响。
下面我先展示一下游戏的运行截图:
由于每个Pile子类,功能不同,里面的方法内容也是不同的,下面我就截取一下TabCardPile类的代码来说明一下,其他的pile类也大同小异:
public class TabcardPile extends Pile {
// 正面纸牌垂直间隔
int vInterval_up = 30;
// 反面纸牌垂直间隔
int vInterval_down = 10;
public TabcardPile() {
resizeList(13);//该牌堆牌列初始化最大为13张
}
@Override
public boolean check(ArrayList<Card> checkList) {
// TODO 自动生成的方法存根
Card checkCard = checkList.get(0);
if (mySize() == 0) {
if (checkCard.getName() % 13 == 12)//如果这张牌是K,就可以放入
return true;
return false;
} else {//如果当前拖动的牌列第一张比当前牌列最后一张小1,并且花色不同,就可以放入,
Card curCard = getList().get(mySize() - 1);
int checkType = checkCard.getType() + curCard.getType();
int checkName = curCard.getName() - checkCard.getName();
if (checkType > 1000 && checkType < 2000 && checkName == 1)
return true;
return false;
}
}
@Override
public void remove(int n) {
// TODO 自动生成的方法存根
super.remove(n);
}
@Override
protected void paintComponent(Graphics g) {
// TODO 自动生成的方法存根
// super.paintComponent(g);
Image image;
if (mySize() == 0) {//没有牌,画NUll的图片
image = new ImageIcon("src/images/null.png").getImage();
g.drawImage(image, 0, 0, this);
} else {
for (int i = 0; i < last_rev_index() + 1; i++) {//先画背面朝上的牌的图片
image = new ImageIcon("src/images/back.jpg").getImage();
g.drawImage(image, 0, i * vInterval_down, this);
}
if (fir_up_index() != -1) {
for (int i = fir_up_index(); i < mySize(); i++) {//接着画正面朝上的牌的图片
image = getList().get(i).getImage();
//这里绘画区域的判断是根据正面朝上和背面朝上的牌的数量,和牌的长度,和牌被折叠的长度,计算得来的
g.drawImage(image, 0, (last_rev_index() + 1) * vInterval_down + ((i - fir_up_index()) * vInterval_up), this);
}
}
}
}
@Override
public ArrayList<Card> checkXY(int x, int y) {
// TODO 自动生成的方法存根
if (mySize() == 0)
return null;
int n;
//这里对于选定牌列来移动的判断也是根据正面朝上和背面朝上的牌的数量,和牌的长度,
//和牌被折叠的长度来分别判断的 ,比较复杂,多次测试和改动才得到的最终结果
if (y > (last_rev_index() + 1) * vInterval_down + (mySize() - fir_up_index() - 1) * vInterval_up) {
ArrayList<Card> list = new ArrayList<>(1);
list.add(getList().get(mySize() - 1));
return list;
}
if (y - (last_rev_index() + 1) * vInterval_down < 0)
return null;
n = (y - (last_rev_index() + 1) * vInterval_down) / vInterval_up;
if (n < 12 && n >= 0 && fir_up_index() >= 0) {
ArrayList<Card> list = new ArrayList<>();
for (int i = n + fir_up_index(); i < mySize(); i++) {
list.add(getList().get(i));
}
return list;
} else
return null;
}
@Override
public boolean setPileBounds(int x, int y) {
// TODO 自动生成的方法存根
if (mySize() == 0)
setBounds(x, y, 80, 120);
else//牌列不为0,根据正面朝上和背面朝上的牌的数量和牌的长度,折叠长度,计算得出总共的区域长度
setBounds(x, y, 80, vInterval_down * (last_rev_index() + 1) + vInterval_up * (mySize() - fir_up_index() - 1) + 120);
return true;
}
}
在Card类中,保存了52张牌每张的名字,花色,和是否朝上以及图片。
在start类中,是程序执行的开始:初始化为52张牌绑定图片及大小,花色。
public void initialize() {
// TODO 自动生成的方法存根
orderMap = new HashMap<>(13);
card = new Card[52];
orderMap.put(1, "A");
orderMap.put(2, "2");
orderMap.put(3, "3");
orderMap.put(4, "4");
orderMap.put(5, "5");
orderMap.put(6, "6");
orderMap.put(7, "7");
orderMap.put(8, "8");
orderMap.put(9, "9");
orderMap.put(10, "10");
orderMap.put(11, "J");
orderMap.put(12, "Q");
orderMap.put(13, "K");
String str;
for (int i = 0; i < 13; i++) {
// type数字关系到判断算法
str=String.format("src/images/%d.jpg", i+1);
card[i] = new Card(i, 100, false, new ImageIcon(str).getImage());
str=String.format("src/images/%d.jpg", i+14);
card[i + 13] = new Card(i, 200, false, new ImageIcon(str).getImage());
str=String.format("src/images/%d.jpg", i+27);
card[i + 26] = new Card(i, 1000, false, new ImageIcon(str).getImage());
str=String.format("src/images/%d.jpg", i+40);
card[i + 39] = new Card(i, 1200, false, new ImageIcon(str).getImage());
}
}
然后执行洗牌和发牌操作:
private void begin() {
// TODO 自动生成的方法存根
ArrayList<Card> templist = new ArrayList<>();
int random[] = new int[7];
Random ran = new Random();
int a, b;
int index = 7;
//利用生成随机数来洗牌
for (int i = 0; i < 10000; i++) {
a = ran.nextInt(52);
b = ran.nextInt(52);
while (a == b)
b = ran.nextInt(52);
swap(a, b);
}
//选出7张正面朝上的牌分别放到TabCardPile中
for (int i = 0; i < 7; i++) {
card[i].setUpwards(true);
}
//为7个TabCardPile分别放1,2,3,4...7张牌
for (int i = 0; i < 7; i++) {
templist.clear();
for (int n = i; n > 0; n--) {
templist.add(card[index++]);
}
templist.add(card[i]);
tab[i].add(templist);
}
templist.clear();
//剩余牌加到DeckCardPile中
for (int i = index; i < 52; i++)
templist.add(card[i]);
deck.add(templist);
setBounds();
addLister();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// jp.repaint();
jf.setVisible(true);
}
最后在看一下部分关于鼠标事件处理方法:
在这里我犯了一个错误并学习到了很多,开始的时候我是在mouseEntered()这个方法中写的是dest[destindex].check(curList),然后发现这样鼠标进入完全没反应,后来如下面这个方法所示,把它改成curDest.check(curList),就可以了。
最后分析是因为对于某一个DestcardPile,当mouseEntered事件发生,调用这个事件处理函数,这时候如果你函数写的是dest[destindex].check(curList),这里面的destindex其实是3,也就是在你添加事件时候最终的destindex值,而把他改成从MouseEvent传进来的的e参数获取(DestcardPile)e.getSource(),这时候的this对象就是恰恰发生mouseEntered事件的对象。
for (destindex = 0; destindex < 4; destindex++) {
dest[destindex].addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
if(curList!=null){
DestcardPile curDest=(DestcardPile)e.getSource();
if (curDest.check(curList))
correctPile = curDest;
checkWin();
}
}
@Override
public void mouseExited(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseExited(e);
correctPile = null;
}
@Override
public void mousePressed(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
// System.out.println("dragin");
if(e.getButton()!=1)
return;
DestcardPile curDest=(DestcardPile)e.getSource();
dragFlag = true;
pressX = e.getX();
pressY = e.getY();
curList = curDest.checkXY(e.getX(), e.getY());
// System.out.println(e.getX());
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
// System.out.println("dragin");
if(e.getButton()!=1)
return;
if(releaseFlag){
DestcardPile curDest=(DestcardPile)e.getSource();
boolean f=false;
if(e.getX()>0&&e.getX()<curDest.getWidth()&&e.getY()>0&&e.getY()<curDest.getHeight())
f=true;
if (correctPile == null||f) {
curDest.add(curList);
temp.remove(temp.mySize());
temp.setPileBounds(0, 0);
} else {
correctPile.add(curList);
temp.remove(temp.mySize());
temp.setPileBounds(0, 0);
}
setBounds();
jp.repaint();
}
releaseFlag=false;
}
});
}
for (destindex = 0; destindex < 4; destindex++) {
dest[destindex].addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
DestcardPile curDest=(DestcardPile)e.getSource();
if (curList != null) {
if (dragFlag == true) {
dragFlag = false;
releaseFlag=true;
temp.add(curList);
curDest.remove(curList.size());
}
setTempBounds(e.getX(), e.getY(), e.getSource());
setBounds();
jp.repaint();
}}});}
for (tabindex = 0; tabindex < 7; tabindex++) {
tab[tabindex].addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
if(curList!=null){
TabcardPile curTab=(TabcardPile)e.getSource();
if (curTab.check(curList))
correctPile = curTab;
}
}
@Override
public void mouseExited(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseExited(e);
correctPile = null;
}
@Override
public void mousePressed(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
// System.out.println("dragin");
if(e.getButton()!=1)
return;
TabcardPile curTab=(TabcardPile)e.getSource();
dragFlag = true;
pressX = e.getX();
pressY = e.getY();
curList = curTab.checkXY(e.getX(), e.getY());
// System.out.println(e.getX());
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
// System.out.println("dragin");
if(e.getButton()!=1)
return;
if(releaseFlag){
TabcardPile curTab=(TabcardPile)e.getSource();
boolean f=false;
if(e.getX()>0&&e.getX()<curTab.getWidth()&&e.getY()>0&&e.getY()<curTab.getHeight())
f=true;
if (correctPile == null||f) {
curTab.add(curList);
temp.remove(temp.mySize());
temp.setPileBounds(0, 0);
} else {
correctPile.add(curList);
curTab.reverse();
temp.remove(temp.mySize());
temp.setPileBounds(0, 0);
}
setBounds();
jp.repaint();
}
releaseFlag=false;
}
});
}
for (tabindex = 0; tabindex < 7; tabindex++) {
tab[tabindex].addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
// TODO 自动生成的方法存根
// super.mouseEntered(e);
TabcardPile curTab=(TabcardPile)e.getSource();
if (curList != null) {
if (dragFlag == true) {
dragFlag = false;
releaseFlag=true;
temp.add(curList);
curTab.remove(curList.size());
}
setTempBounds(e.getX(), e.getY(), e.getSource());
setBounds();
jp.repaint();
}
}
});
}
}
这次的纸牌游戏,也算是锻炼了自己的代码量,学到了多态继承思想的重要性和好处,加以运用,了解的更深刻,还有对于java的swing有了更好的认识,更好的运用。
keyboard_arrow_left上一篇 : 基于C++的图书管理系统 基于JSP和MYSQL实现的学生成绩管理系统 : 下一篇keyboard_arrow_right