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


    摘要:本程序共封装了五个类,分别是主类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扫雷,用户在图形化用户界面内利用鼠标监听事件标记雷区,左上角表示剩余雷的数量,右上角动态显示使用的时间。用户可选择中间组件按钮重新游戏。为了解决程序窗口闪动的问题,本程序采用了双缓冲技术。

    程序的总体界面布局:

    项目结构:

    程序测试:

    请大家指正!

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

  • 相关阅读:
    Linux 网络性能的 15 个优化建议
    一图读懂腾讯云EdgeOne Open Edge平台
    Matter理论教程-通用-1-01:理论详解
    NX二次开发-几款辅助学习NX开发的搜索软件
    什么是抽象类
    AWTK UI 自动化测试工具发布
    淘宝API接口商品详情,关键词搜索
    flutter系列之:flutter中常用的GridView layout详解
    Redis高可用之哨兵模式、集群
    Unity 3D 2022.1 AND UnityHub 3.2 Patch
  • 原文地址:https://blog.csdn.net/devcloud/article/details/125546346