基于JAVA实现的纸牌游戏

Tommorow

发布日期: 2018-11-07 16:59:00 浏览量: 2213
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

1 项目介绍

1.1 背景和目的

单人纸牌游戏,牌桌上有7个堆共28张牌,第一堆1张牌,第二堆2张,。。。第7堆7张,每一堆的第一张牌朝上,其他朝下。牌桌上还有4个suitpiles,一个deck card堆和一个discard card堆,布局如下(参考Windows的纸牌游戏)

设计一个简单的CardGames程序,运用面向对象封装、继承、抽象类、抽象方法、多态、动态绑定等概念。

1.2 硬件环境

  • PIII 450以上

  • 4G内存

  • 5GB硬盘空间

1.3 软件环境

  • Windows 8.1

  • eclipse

2 设计实现

首先我设计了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类也大同小异:

  1. public class TabcardPile extends Pile {
  2. // 正面纸牌垂直间隔
  3. int vInterval_up = 30;
  4. // 反面纸牌垂直间隔
  5. int vInterval_down = 10;
  6. public TabcardPile() {
  7. resizeList(13);//该牌堆牌列初始化最大为13张
  8. }
  9. @Override
  10. public boolean check(ArrayList<Card> checkList) {
  11. // TODO 自动生成的方法存根
  12. Card checkCard = checkList.get(0);
  13. if (mySize() == 0) {
  14. if (checkCard.getName() % 13 == 12)//如果这张牌是K,就可以放入
  15. return true;
  16. return false;
  17. } else {//如果当前拖动的牌列第一张比当前牌列最后一张小1,并且花色不同,就可以放入,
  18. Card curCard = getList().get(mySize() - 1);
  19. int checkType = checkCard.getType() + curCard.getType();
  20. int checkName = curCard.getName() - checkCard.getName();
  21. if (checkType > 1000 && checkType < 2000 && checkName == 1)
  22. return true;
  23. return false;
  24. }
  25. }
  26. @Override
  27. public void remove(int n) {
  28. // TODO 自动生成的方法存根
  29. super.remove(n);
  30. }
  31. @Override
  32. protected void paintComponent(Graphics g) {
  33. // TODO 自动生成的方法存根
  34. // super.paintComponent(g);
  35. Image image;
  36. if (mySize() == 0) {//没有牌,画NUll的图片
  37. image = new ImageIcon("src/images/null.png").getImage();
  38. g.drawImage(image, 0, 0, this);
  39. } else {
  40. for (int i = 0; i < last_rev_index() + 1; i++) {//先画背面朝上的牌的图片
  41. image = new ImageIcon("src/images/back.jpg").getImage();
  42. g.drawImage(image, 0, i * vInterval_down, this);
  43. }
  44. if (fir_up_index() != -1) {
  45. for (int i = fir_up_index(); i < mySize(); i++) {//接着画正面朝上的牌的图片
  46. image = getList().get(i).getImage();
  47. //这里绘画区域的判断是根据正面朝上和背面朝上的牌的数量,和牌的长度,和牌被折叠的长度,计算得来的
  48. g.drawImage(image, 0, (last_rev_index() + 1) * vInterval_down + ((i - fir_up_index()) * vInterval_up), this);
  49. }
  50. }
  51. }
  52. }
  53. @Override
  54. public ArrayList<Card> checkXY(int x, int y) {
  55. // TODO 自动生成的方法存根
  56. if (mySize() == 0)
  57. return null;
  58. int n;
  59. //这里对于选定牌列来移动的判断也是根据正面朝上和背面朝上的牌的数量,和牌的长度,
  60. //和牌被折叠的长度来分别判断的 ,比较复杂,多次测试和改动才得到的最终结果
  61. if (y > (last_rev_index() + 1) * vInterval_down + (mySize() - fir_up_index() - 1) * vInterval_up) {
  62. ArrayList<Card> list = new ArrayList<>(1);
  63. list.add(getList().get(mySize() - 1));
  64. return list;
  65. }
  66. if (y - (last_rev_index() + 1) * vInterval_down < 0)
  67. return null;
  68. n = (y - (last_rev_index() + 1) * vInterval_down) / vInterval_up;
  69. if (n < 12 && n >= 0 && fir_up_index() >= 0) {
  70. ArrayList<Card> list = new ArrayList<>();
  71. for (int i = n + fir_up_index(); i < mySize(); i++) {
  72. list.add(getList().get(i));
  73. }
  74. return list;
  75. } else
  76. return null;
  77. }
  78. @Override
  79. public boolean setPileBounds(int x, int y) {
  80. // TODO 自动生成的方法存根
  81. if (mySize() == 0)
  82. setBounds(x, y, 80, 120);
  83. else//牌列不为0,根据正面朝上和背面朝上的牌的数量和牌的长度,折叠长度,计算得出总共的区域长度
  84. setBounds(x, y, 80, vInterval_down * (last_rev_index() + 1) + vInterval_up * (mySize() - fir_up_index() - 1) + 120);
  85. return true;
  86. }
  87. }

在Card类中,保存了52张牌每张的名字,花色,和是否朝上以及图片。

在start类中,是程序执行的开始:初始化为52张牌绑定图片及大小,花色。

  1. public void initialize() {
  2. // TODO 自动生成的方法存根
  3. orderMap = new HashMap<>(13);
  4. card = new Card[52];
  5. orderMap.put(1, "A");
  6. orderMap.put(2, "2");
  7. orderMap.put(3, "3");
  8. orderMap.put(4, "4");
  9. orderMap.put(5, "5");
  10. orderMap.put(6, "6");
  11. orderMap.put(7, "7");
  12. orderMap.put(8, "8");
  13. orderMap.put(9, "9");
  14. orderMap.put(10, "10");
  15. orderMap.put(11, "J");
  16. orderMap.put(12, "Q");
  17. orderMap.put(13, "K");
  18. String str;
  19. for (int i = 0; i < 13; i++) {
  20. // type数字关系到判断算法
  21. str=String.format("src/images/%d.jpg", i+1);
  22. card[i] = new Card(i, 100, false, new ImageIcon(str).getImage());
  23. str=String.format("src/images/%d.jpg", i+14);
  24. card[i + 13] = new Card(i, 200, false, new ImageIcon(str).getImage());
  25. str=String.format("src/images/%d.jpg", i+27);
  26. card[i + 26] = new Card(i, 1000, false, new ImageIcon(str).getImage());
  27. str=String.format("src/images/%d.jpg", i+40);
  28. card[i + 39] = new Card(i, 1200, false, new ImageIcon(str).getImage());
  29. }
  30. }

然后执行洗牌和发牌操作:

  1. private void begin() {
  2. // TODO 自动生成的方法存根
  3. ArrayList<Card> templist = new ArrayList<>();
  4. int random[] = new int[7];
  5. Random ran = new Random();
  6. int a, b;
  7. int index = 7;
  8. //利用生成随机数来洗牌
  9. for (int i = 0; i < 10000; i++) {
  10. a = ran.nextInt(52);
  11. b = ran.nextInt(52);
  12. while (a == b)
  13. b = ran.nextInt(52);
  14. swap(a, b);
  15. }
  16. //选出7张正面朝上的牌分别放到TabCardPile中
  17. for (int i = 0; i < 7; i++) {
  18. card[i].setUpwards(true);
  19. }
  20. //为7个TabCardPile分别放1,2,3,4...7张牌
  21. for (int i = 0; i < 7; i++) {
  22. templist.clear();
  23. for (int n = i; n > 0; n--) {
  24. templist.add(card[index++]);
  25. }
  26. templist.add(card[i]);
  27. tab[i].add(templist);
  28. }
  29. templist.clear();
  30. //剩余牌加到DeckCardPile中
  31. for (int i = index; i < 52; i++)
  32. templist.add(card[i]);
  33. deck.add(templist);
  34. setBounds();
  35. addLister();
  36. jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  37. // jp.repaint();
  38. jf.setVisible(true);
  39. }

最后在看一下部分关于鼠标事件处理方法:

在这里我犯了一个错误并学习到了很多,开始的时候我是在mouseEntered()这个方法中写的是dest[destindex].check(curList),然后发现这样鼠标进入完全没反应,后来如下面这个方法所示,把它改成curDest.check(curList),就可以了。

最后分析是因为对于某一个DestcardPile,当mouseEntered事件发生,调用这个事件处理函数,这时候如果你函数写的是dest[destindex].check(curList),这里面的destindex其实是3,也就是在你添加事件时候最终的destindex值,而把他改成从MouseEvent传进来的的e参数获取(DestcardPile)e.getSource(),这时候的this对象就是恰恰发生mouseEntered事件的对象。

  1. for (destindex = 0; destindex < 4; destindex++) {
  2. dest[destindex].addMouseListener(new MouseAdapter() {
  3. @Override
  4. public void mouseEntered(MouseEvent e) {
  5. // TODO 自动生成的方法存根
  6. // super.mouseEntered(e);
  7. if(curList!=null){
  8. DestcardPile curDest=(DestcardPile)e.getSource();
  9. if (curDest.check(curList))
  10. correctPile = curDest;
  11. checkWin();
  12. }
  13. }
  14. @Override
  15. public void mouseExited(MouseEvent e) {
  16. // TODO 自动生成的方法存根
  17. // super.mouseExited(e);
  18. correctPile = null;
  19. }
  20. @Override
  21. public void mousePressed(MouseEvent e) {
  22. // TODO 自动生成的方法存根
  23. // super.mouseEntered(e);
  24. // System.out.println("dragin");
  25. if(e.getButton()!=1)
  26. return;
  27. DestcardPile curDest=(DestcardPile)e.getSource();
  28. dragFlag = true;
  29. pressX = e.getX();
  30. pressY = e.getY();
  31. curList = curDest.checkXY(e.getX(), e.getY());
  32. // System.out.println(e.getX());
  33. }
  34. @Override
  35. public void mouseReleased(MouseEvent e) {
  36. // TODO 自动生成的方法存根
  37. // super.mouseEntered(e);
  38. // System.out.println("dragin");
  39. if(e.getButton()!=1)
  40. return;
  41. if(releaseFlag){
  42. DestcardPile curDest=(DestcardPile)e.getSource();
  43. boolean f=false;
  44. if(e.getX()>0&&e.getX()<curDest.getWidth()&&e.getY()>0&&e.getY()<curDest.getHeight())
  45. f=true;
  46. if (correctPile == null||f) {
  47. curDest.add(curList);
  48. temp.remove(temp.mySize());
  49. temp.setPileBounds(0, 0);
  50. } else {
  51. correctPile.add(curList);
  52. temp.remove(temp.mySize());
  53. temp.setPileBounds(0, 0);
  54. }
  55. setBounds();
  56. jp.repaint();
  57. }
  58. releaseFlag=false;
  59. }
  60. });
  61. }
  62. for (destindex = 0; destindex < 4; destindex++) {
  63. dest[destindex].addMouseMotionListener(new MouseAdapter() {
  64. @Override
  65. public void mouseDragged(MouseEvent e) {
  66. // TODO 自动生成的方法存根
  67. // super.mouseEntered(e);
  68. DestcardPile curDest=(DestcardPile)e.getSource();
  69. if (curList != null) {
  70. if (dragFlag == true) {
  71. dragFlag = false;
  72. releaseFlag=true;
  73. temp.add(curList);
  74. curDest.remove(curList.size());
  75. }
  76. setTempBounds(e.getX(), e.getY(), e.getSource());
  77. setBounds();
  78. jp.repaint();
  79. }}});}
  80. for (tabindex = 0; tabindex < 7; tabindex++) {
  81. tab[tabindex].addMouseListener(new MouseAdapter() {
  82. @Override
  83. public void mouseEntered(MouseEvent e) {
  84. // TODO 自动生成的方法存根
  85. // super.mouseEntered(e);
  86. if(curList!=null){
  87. TabcardPile curTab=(TabcardPile)e.getSource();
  88. if (curTab.check(curList))
  89. correctPile = curTab;
  90. }
  91. }
  92. @Override
  93. public void mouseExited(MouseEvent e) {
  94. // TODO 自动生成的方法存根
  95. // super.mouseExited(e);
  96. correctPile = null;
  97. }
  98. @Override
  99. public void mousePressed(MouseEvent e) {
  100. // TODO 自动生成的方法存根
  101. // super.mouseEntered(e);
  102. // System.out.println("dragin");
  103. if(e.getButton()!=1)
  104. return;
  105. TabcardPile curTab=(TabcardPile)e.getSource();
  106. dragFlag = true;
  107. pressX = e.getX();
  108. pressY = e.getY();
  109. curList = curTab.checkXY(e.getX(), e.getY());
  110. // System.out.println(e.getX());
  111. }
  112. @Override
  113. public void mouseReleased(MouseEvent e) {
  114. // TODO 自动生成的方法存根
  115. // super.mouseEntered(e);
  116. // System.out.println("dragin");
  117. if(e.getButton()!=1)
  118. return;
  119. if(releaseFlag){
  120. TabcardPile curTab=(TabcardPile)e.getSource();
  121. boolean f=false;
  122. if(e.getX()>0&&e.getX()<curTab.getWidth()&&e.getY()>0&&e.getY()<curTab.getHeight())
  123. f=true;
  124. if (correctPile == null||f) {
  125. curTab.add(curList);
  126. temp.remove(temp.mySize());
  127. temp.setPileBounds(0, 0);
  128. } else {
  129. correctPile.add(curList);
  130. curTab.reverse();
  131. temp.remove(temp.mySize());
  132. temp.setPileBounds(0, 0);
  133. }
  134. setBounds();
  135. jp.repaint();
  136. }
  137. releaseFlag=false;
  138. }
  139. });
  140. }
  141. for (tabindex = 0; tabindex < 7; tabindex++) {
  142. tab[tabindex].addMouseMotionListener(new MouseAdapter() {
  143. @Override
  144. public void mouseDragged(MouseEvent e) {
  145. // TODO 自动生成的方法存根
  146. // super.mouseEntered(e);
  147. TabcardPile curTab=(TabcardPile)e.getSource();
  148. if (curList != null) {
  149. if (dragFlag == true) {
  150. dragFlag = false;
  151. releaseFlag=true;
  152. temp.add(curList);
  153. curTab.remove(curList.size());
  154. }
  155. setTempBounds(e.getX(), e.getY(), e.getSource());
  156. setBounds();
  157. jp.repaint();
  158. }
  159. }
  160. });
  161. }
  162. }

3 总结

这次的纸牌游戏,也算是锻炼了自己的代码量,学到了多态继承思想的重要性和好处,加以运用,了解的更深刻,还有对于java的swing有了更好的认识,更好的运用。

上传的附件 cloud_download 基于JAVA实现的纸牌游戏.7z ( 1.69mb, 173次下载 )
error_outline 下载需要10点积分

发送私信

成功其实很简单,就是当你坚持不住的时候,再坚持一下

10
文章数
8
评论数
最近文章
eject