全文手敲代码,教你用Java实现扫雷小游戏

x33g5p2x  于2022-07-01 转载在 Java  
字(10.6k)|赞(0)|评价(0)|浏览(539)

**摘要:**本程序共封装了五个类,分别是主类GameWin类,绘制底层地图和绘制顶层地图的类MapBottom类和MapTop类,绘制底层数字的类BottomNum类,以及初始化地雷的BottomRay类和工具GameUtil类,用于存静态参数和方法。

本文分享自华为云社区《Java实现扫雷小游戏【完整版】》,作者:橙子!。

效果展示

主类:GameWin类

  1. package com.sxt;
  2. import javax.swing.*;
  3. import java.awt.*;
  4. import java.awt.event.MouseAdapter;
  5. import java.awt.event.MouseEvent;
  6. public class GameWin extends JFrame {
  7. int width = 2 * GameUtil.OFFSET + GameUtil.MAP_W * GameUtil.SQUARE_LENGTH;
  8. int height = 4 * GameUtil.OFFSET + GameUtil.MAP_H * GameUtil.SQUARE_LENGTH;
  9. Image offScreenImage = null;
  10. MapBottom mapBottom = new MapBottom();
  11. MapTop mapTop = new MapTop();
  12. void launch(){
  13. GameUtil.START_TIME=System.currentTimeMillis();
  14. this.setVisible(true);
  15. this.setSize(width,height);
  16. this.setLocationRelativeTo(null);
  17. this.setTitle("Java扫雷小游戏");
  18. this.setDefaultCloseOperation(EXIT_ON_CLOSE);
  19. //鼠标事件
  20. this.addMouseListener(new MouseAdapter() {
  21. @Override
  22. public void mouseClicked(MouseEvent e) {
  23. super.mouseClicked(e);
  24. switch (GameUtil.state){
  25. case 0 :
  26. if(e.getButton()==1){
  27. GameUtil.MOUSE_X = e.getX();
  28. GameUtil.MOUSE_Y = e.getY();
  29. GameUtil.LEFT = true;
  30. }
  31. if(e.getButton()==3) {
  32. GameUtil.MOUSE_X = e.getX();
  33. GameUtil.MOUSE_Y = e.getY();
  34. GameUtil.RIGHT = true;
  35. }
  36. //去掉break,任何时候都监听鼠标事件
  37. case 1 :
  38. case 2 :
  39. if(e.getButton()==1){
  40. if(e.getX()>GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W/2)
  41. && e.getX()<GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W/2) + GameUtil.SQUARE_LENGTH
  42. && e.getY()>GameUtil.OFFSET
  43. && e.getY()<GameUtil.OFFSET+GameUtil.SQUARE_LENGTH){
  44. mapBottom.reGame();
  45. mapTop.reGame();
  46. GameUtil.FLAG_NUM=0;
  47. GameUtil.START_TIME=System.currentTimeMillis();
  48. GameUtil.state=0;
  49. }
  50. }
  51. break;
  52. default:
  53. }
  54. }
  55. });
  56. while (true){
  57. repaint();
  58. try {
  59. Thread.sleep(40);
  60. } catch (InterruptedException e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. }
  65. @Override
  66. public void paint(Graphics g) {
  67. offScreenImage = this.createImage(width,height);
  68. Graphics gImage = offScreenImage.getGraphics();
  69. //设置背景颜色
  70. gImage.setColor(Color.lightGray);
  71. gImage.fillRect(0,0,width,height);
  72. mapBottom.paintSelf(gImage);
  73. mapTop.paintSelf(gImage);
  74. g.drawImage(offScreenImage,0,0,null);
  75. }
  76. public static void main(String[] args) {
  77. GameWin gameWin = new GameWin();
  78. gameWin.launch();
  79. }
  80. }

底层地图MapBottom类

  1. //底层地图:绘制游戏相关组件
  2. package com.sxt;
  3. import java.awt.*;
  4. public class MapBottom {
  5. BottomRay bottomRay = new BottomRay();
  6. BottomNum bottomNum = new BottomNum();
  7. {
  8. bottomRay.newRay();
  9. bottomNum.newNum();
  10. }
  11. //重置游戏
  12. void reGame(){
  13. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  14. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  15. GameUtil.DATA_BOTTOM[i][j]=0;
  16. }
  17. }
  18. bottomRay.newRay();
  19. bottomNum.newNum();
  20. }
  21. //绘制方法
  22. void paintSelf(Graphics g){
  23. g.setColor(Color.BLACK);
  24. //画竖线
  25. for (int i = 0; i <= GameUtil.MAP_W; i++) {
  26. g.drawLine(GameUtil.OFFSET + i * GameUtil.SQUARE_LENGTH,
  27. 3*GameUtil.OFFSET,
  28. GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH,
  29. 3*GameUtil.OFFSET+GameUtil.MAP_H*GameUtil.SQUARE_LENGTH);
  30. }
  31. //画横线
  32. for (int i = 0; i <=GameUtil.MAP_H; i++){
  33. g.drawLine(GameUtil.OFFSET,
  34. 3*GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH,
  35. GameUtil.OFFSET+GameUtil.MAP_W*GameUtil.SQUARE_LENGTH,
  36. 3*GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH);
  37. }
  38. for (int i = 1; i <= GameUtil.MAP_W ; i++) {
  39. for (int j = 1; j <= GameUtil.MAP_H; j++) {
  40. //雷
  41. if (GameUtil.DATA_BOTTOM[i][j] == -1) {
  42. g.drawImage(GameUtil.lei,
  43. GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
  44. GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
  45. GameUtil.SQUARE_LENGTH - 2,
  46. GameUtil.SQUARE_LENGTH - 2,
  47. null);
  48. }
  49. //数字
  50. if (GameUtil.DATA_BOTTOM[i][j] >=0) {
  51. g.drawImage(GameUtil.images[GameUtil.DATA_BOTTOM[i][j]],
  52. GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 15,
  53. GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 5,
  54. null);
  55. }
  56. }
  57. }
  58. //绘制数字,剩余雷数,倒计时
  59. GameUtil.drawWord(g,""+(GameUtil.RAY_MAX-GameUtil.FLAG_NUM),
  60. GameUtil.OFFSET,
  61. 2*GameUtil.OFFSET,30,Color.red);
  62. GameUtil.drawWord(g,""+(GameUtil.END_TIME-GameUtil.START_TIME)/1000,
  63. GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W-1),
  64. 2*GameUtil.OFFSET,30,Color.red);
  65. switch (GameUtil.state){
  66. case 0:
  67. GameUtil.END_TIME=System.currentTimeMillis();
  68. g.drawImage(GameUtil.face,
  69. GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),
  70. GameUtil.OFFSET,
  71. null);
  72. break;
  73. case 1:
  74. g.drawImage(GameUtil.win,
  75. GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),
  76. GameUtil.OFFSET,
  77. null);
  78. break;
  79. case 2:
  80. g.drawImage(GameUtil.over,
  81. GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),
  82. GameUtil.OFFSET,
  83. null);
  84. break;
  85. default:
  86. }
  87. }
  88. }

顶层地图MapTop类

  1. 顶层地图类:绘制顶层组件
  2. package com.sxt;
  3. import java.awt.*;
  4. public class MapTop {
  5. //格子位置
  6. int temp_x;
  7. int temp_y;
  8. //重置游戏
  9. void reGame(){
  10. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  11. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  12. GameUtil.DATA_TOP[i][j]=0;
  13. }
  14. }
  15. }
  16. //判断逻辑
  17. void logic(){
  18. temp_x=0;
  19. temp_y=0;
  20. if(GameUtil.MOUSE_X>GameUtil.OFFSET && GameUtil.MOUSE_Y>3*GameUtil.OFFSET){
  21. temp_x = (GameUtil.MOUSE_X - GameUtil.OFFSET)/GameUtil.SQUARE_LENGTH+1;
  22. temp_y = (GameUtil.MOUSE_Y - GameUtil.OFFSET * 3)/GameUtil.SQUARE_LENGTH+1;
  23. }
  24. if(temp_x>=1 && temp_x<=GameUtil.MAP_W
  25. && temp_y>=1 && temp_y<=GameUtil.MAP_H){
  26. if(GameUtil.LEFT){
  27. //覆盖,则翻开
  28. if(GameUtil.DATA_TOP[temp_x][temp_y]==0){
  29. GameUtil.DATA_TOP[temp_x][temp_y]=-1;
  30. }
  31. spaceOpen(temp_x,temp_y);
  32. GameUtil.LEFT=false;
  33. }
  34. if(GameUtil.RIGHT){
  35. //覆盖则插旗
  36. if(GameUtil.DATA_TOP[temp_x][temp_y]==0){
  37. GameUtil.DATA_TOP[temp_x][temp_y]=1;
  38. GameUtil.FLAG_NUM++;
  39. }
  40. //插旗则取消
  41. else if(GameUtil.DATA_TOP[temp_x][temp_y]==1){
  42. GameUtil.DATA_TOP[temp_x][temp_y]=0;
  43. GameUtil.FLAG_NUM--;
  44. }
  45. else if(GameUtil.DATA_TOP[temp_x][temp_y]==-1){
  46. numOpen(temp_x,temp_y);
  47. }
  48. GameUtil.RIGHT=false;
  49. }
  50. }
  51. boom();
  52. victory();
  53. }
  54. //数字翻开
  55. void numOpen(int x,int y){
  56. //记录旗数
  57. int count=0;
  58. if(GameUtil.DATA_BOTTOM[x][y]>0){
  59. for (int i = x-1; i <=x+1 ; i++) {
  60. for (int j = y-1; j <=y+1 ; j++) {
  61. if(GameUtil.DATA_TOP[i][j]==1){
  62. count++;
  63. }
  64. }
  65. }
  66. if(count==GameUtil.DATA_BOTTOM[x][y]){
  67. for (int i = x-1; i <=x+1 ; i++) {
  68. for (int j = y-1; j <=y+1 ; j++) {
  69. if(GameUtil.DATA_TOP[i][j]!=1){
  70. GameUtil.DATA_TOP[i][j]=-1;
  71. }
  72. //必须在雷区当中
  73. if(i>=1&&j>=1&&i<=GameUtil.MAP_W&&j<=GameUtil.MAP_H){
  74. spaceOpen(i,j);
  75. }
  76. }
  77. }
  78. }
  79. }
  80. }
  81. //失败判定 t 表示失败 f 未失败
  82. boolean boom(){
  83. if(GameUtil.FLAG_NUM==GameUtil.RAY_MAX){
  84. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  85. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  86. if(GameUtil.DATA_TOP[i][j]==0){
  87. GameUtil.DATA_TOP[i][j]=-1;
  88. }
  89. }
  90. }
  91. }
  92. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  93. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  94. if(GameUtil.DATA_BOTTOM[i][j]==-1&&GameUtil.DATA_TOP[i][j]==-1){
  95. GameUtil.state = 2;
  96. seeBoom();
  97. return true;
  98. }
  99. }
  100. }
  101. return false;
  102. }
  103. //失败显示
  104. void seeBoom(){
  105. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  106. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  107. //底层是雷,顶层不是旗,显示
  108. if(GameUtil.DATA_BOTTOM[i][j]==-1&&GameUtil.DATA_TOP[i][j]!=1){
  109. GameUtil.DATA_TOP[i][j]=-1;
  110. }
  111. //底层不是雷,顶层是旗,显示差错旗
  112. if(GameUtil.DATA_BOTTOM[i][j]!=-1&&GameUtil.DATA_TOP[i][j]==1){
  113. GameUtil.DATA_TOP[i][j]=2;
  114. }
  115. }
  116. }
  117. }
  118. //胜利判断 t 表示胜利 f 未胜利
  119. boolean victory(){
  120. //统计未打开格子数
  121. int count=0;
  122. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  123. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  124. if(GameUtil.DATA_TOP[i][j]!=-1){
  125. count++;
  126. }
  127. }
  128. }
  129. if(count==GameUtil.RAY_MAX){
  130. GameUtil.state=1;
  131. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  132. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  133. //未翻开,变成旗
  134. if(GameUtil.DATA_TOP[i][j]==0){
  135. GameUtil.DATA_TOP[i][j]=1;
  136. }
  137. }
  138. }
  139. return true;
  140. }
  141. return false;
  142. }
  143. //打开空格
  144. void spaceOpen(int x,int y){
  145. if(GameUtil.DATA_BOTTOM[x][y]==0){
  146. for (int i = x-1; i <=x+1 ; i++) {
  147. for (int j = y-1; j <=y+1 ; j++) {
  148. //覆盖,才递归
  149. if(GameUtil.DATA_TOP[i][j]!=-1){
  150. if(GameUtil.DATA_TOP[i][j]==1){GameUtil.FLAG_NUM--;}
  151. GameUtil.DATA_TOP[i][j]=-1;
  152. //必须在雷区当中
  153. if(i>=1&&j>=1&&i<=GameUtil.MAP_W&&j<=GameUtil.MAP_H){
  154. spaceOpen(i,j);
  155. }
  156. }
  157. }
  158. }
  159. }
  160. }
  161. //绘制方法
  162. void paintSelf(Graphics g){
  163. logic();
  164. for (int i = 1; i <= GameUtil.MAP_W ; i++) {
  165. for (int j = 1; j <= GameUtil.MAP_H; j++) {
  166. //覆盖
  167. if (GameUtil.DATA_TOP[i][j] == 0) {
  168. g.drawImage(GameUtil.top,
  169. GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
  170. GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
  171. GameUtil.SQUARE_LENGTH - 2,
  172. GameUtil.SQUARE_LENGTH - 2,
  173. null);
  174. }
  175. //插旗
  176. if (GameUtil.DATA_TOP[i][j] == 1) {
  177. g.drawImage(GameUtil.flag,
  178. GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
  179. GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
  180. GameUtil.SQUARE_LENGTH - 2,
  181. GameUtil.SQUARE_LENGTH - 2,
  182. null);
  183. }
  184. //差错旗
  185. if (GameUtil.DATA_TOP[i][j] == 2) {
  186. g.drawImage(GameUtil.noflag,
  187. GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
  188. GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
  189. GameUtil.SQUARE_LENGTH - 2,
  190. GameUtil.SQUARE_LENGTH - 2,
  191. null);
  192. }
  193. }
  194. }
  195. }
  196. }

底层数字BottomNum类

  1. //底层数字类
  2. package com.sxt;
  3. public class BottomNum {
  4. void newNum() {
  5. for (int i = 1; i <=GameUtil.MAP_W ; i++) {
  6. for (int j = 1; j <=GameUtil.MAP_H ; j++) {
  7. if(GameUtil.DATA_BOTTOM[i][j]==-1){
  8. for (int k = i-1; k <=i+1 ; k++) {
  9. for (int l = j-1; l <=j+1 ; l++) {
  10. if(GameUtil.DATA_BOTTOM[k][l]>=0){
  11. GameUtil.DATA_BOTTOM[k][l]++;
  12. }
  13. }
  14. }
  15. }
  16. }
  17. }
  18. }
  19. }

初始化地雷BottomRay类

  1. //初始化地雷类
  2. package com.sxt;
  3. public class BottomRay {
  4. //存放坐标
  5. int[] rays = new int[GameUtil.RAY_MAX*2];
  6. //地雷坐标
  7. int x,y;
  8. //是否放置 T 表示可以放置 F 不可放置
  9. boolean isPlace = true;
  10. //生成雷
  11. void newRay() {
  12. for (int i = 0; i < GameUtil.RAY_MAX*2 ; i=i+2) {
  13. x= (int) (Math.random()*GameUtil.MAP_W +1);//1-12
  14. y= (int) (Math.random()*GameUtil.MAP_H +1);//1-12
  15. //判断坐标是否存在
  16. for (int j = 0; j < i ; j=j+2) {
  17. if(x==rays[j] && y==rays[j+1]){
  18. i=i-2;
  19. isPlace = false;
  20. break;
  21. }
  22. }
  23. //将坐标放入数组
  24. if(isPlace){
  25. rays[i]=x;
  26. rays[i+1]=y;
  27. }
  28. isPlace = true;
  29. }
  30. for (int i = 0; i < GameUtil.RAY_MAX*2; i=i+2) {
  31. GameUtil.DATA_BOTTOM[rays[i]][rays[i+1]]=-1;
  32. }
  33. }
  34. }

工具GameUtil类

  1. //工具类:存放静态参数,工具方法
  2. package com.sxt;
  3. import java.awt.*;
  4. public class GameUtil {
  5. //地雷个数
  6. static int RAY_MAX = 5;
  7. //地图的宽
  8. static int MAP_W = 11;
  9. //地图的高
  10. static int MAP_H = 11;
  11. //雷区偏移量
  12. static int OFFSET = 45;
  13. //格子边长
  14. static int SQUARE_LENGTH = 50;
  15. //插旗数量
  16. static int FLAG_NUM = 0;
  17. //鼠标相关
  18. //坐标
  19. static int MOUSE_X;
  20. static int MOUSE_Y;
  21. //状态
  22. static boolean LEFT = false;
  23. static boolean RIGHT = false;
  24. //游戏状态 0 表示游戏中 1 胜利 2 失败
  25. static int state = 0;
  26. //倒计时
  27. static long START_TIME;
  28. static long END_TIME;
  29. //底层元素 -1 雷 0 空 1-8 表示对应数字
  30. static int[][] DATA_BOTTOM = new int[MAP_W+2][MAP_H+2];
  31. //顶层元素 -1 无覆盖 0 覆盖 1 插旗 2 差错旗
  32. static int[][] DATA_TOP = new int[MAP_W+2][MAP_H+2];
  33. //载入图片
  34. static Image lei = Toolkit.getDefaultToolkit().getImage("imgs/lei.png");
  35. static Image top = Toolkit.getDefaultToolkit().getImage("imgs/top.gif");
  36. static Image flag = Toolkit.getDefaultToolkit().getImage("imgs/flag.gif");
  37. static Image noflag = Toolkit.getDefaultToolkit().getImage("imgs/noflag.png");
  38. static Image face = Toolkit.getDefaultToolkit().getImage("imgs/face.png");
  39. static Image over = Toolkit.getDefaultToolkit().getImage("imgs/over.png");
  40. static Image win = Toolkit.getDefaultToolkit().getImage("imgs/win.png");
  41. static Image[] images = new Image[9];
  42. static {
  43. for (int i = 1; i <=8 ; i++) {
  44. images[i] = Toolkit.getDefaultToolkit().getImage("imgs/num/"+i+".png");
  45. }
  46. }
  47. static void drawWord(Graphics g,String str,int x,int y,int size,Color color){
  48. g.setColor(color);
  49. g.setFont(new Font("仿宋",Font.BOLD,size));
  50. g.drawString(str,x,y);
  51. }
  52. }

总结

在使用Java编写扫雷小游戏时遇到了很多问题,在解决问题时,确实对java的面向对象编程有了更加深入的理解。虽然GUI现在并没有很大的市场,甚至好多初学者已经放弃了学习GUI,但是利用GUI编程的过程对于培养编程兴趣,深入理解Java编程有很大的作用。

本程序共封装了五个类,分别是主类GameWin类,绘制底层地图和绘制顶层地图的类MapBottom类和MapTop类,绘制底层数字的类BottomNum类,以及初始化地雷的BottomRay类和工具GameUtil类,用于存静态参数和方法。

游戏的设计类似windows扫雷,用户在图形化用户界面内利用鼠标监听事件标记雷区,左上角表示剩余雷的数量,右上角动态显示使用的时间。用户可选择中间组件按钮重新游戏。为了解决程序窗口闪动的问题,本程序采用了双缓冲技术。

程序的总体界面布局:

项目结构:

程序测试:

请大家指正!

点击关注,第一时间了解华为云新鲜技术~

相关文章

最新文章

更多