摘要:本程序共封装了五个类,分别是主类GameWin类,绘制底层地图和绘制顶层地图的类MapBottom类和MapTop类,绘制底层数字的类BottomNum类,以及初始化地雷的BottomRay类和工具GameUtil类,用于存静态参数和方法。
本文分享自华为云社区《Java实现扫雷小游戏【完整版】》,作者:橙子!。
- package com.sxt;
- import javax.swing.*;
- import java.awt.*;
- import java.awt.event.MouseAdapter;
- import java.awt.event.MouseEvent;
-
- public class GameWin extends JFrame {
- int width = 2 * GameUtil.OFFSET + GameUtil.MAP_W * GameUtil.SQUARE_LENGTH;
- int height = 4 * GameUtil.OFFSET + GameUtil.MAP_H * GameUtil.SQUARE_LENGTH;
-
- Image offScreenImage = null;
- MapBottom mapBottom = new MapBottom();
- MapTop mapTop = new MapTop();
-
- void launch(){
- GameUtil.START_TIME=System.currentTimeMillis();
- this.setVisible(true);
- this.setSize(width,height);
- this.setLocationRelativeTo(null);
- this.setTitle("Java扫雷小游戏");
- this.setDefaultCloseOperation(EXIT_ON_CLOSE);
- //鼠标事件
- this.addMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- super.mouseClicked(e);
- switch (GameUtil.state){
- case 0 :
- if(e.getButton()==1){
- GameUtil.MOUSE_X = e.getX();
- GameUtil.MOUSE_Y = e.getY();
- GameUtil.LEFT = true;
- }
- if(e.getButton()==3) {
- GameUtil.MOUSE_X = e.getX();
- GameUtil.MOUSE_Y = e.getY();
- GameUtil.RIGHT = true;
- }
- //去掉break,任何时候都监听鼠标事件
- case 1 :
- case 2 :
- if(e.getButton()==1){
- if(e.getX()>GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W/2)
- && e.getX()<GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W/2) + GameUtil.SQUARE_LENGTH
- && e.getY()>GameUtil.OFFSET
- && e.getY()<GameUtil.OFFSET+GameUtil.SQUARE_LENGTH){
- mapBottom.reGame();
- mapTop.reGame();
- GameUtil.FLAG_NUM=0;
- GameUtil.START_TIME=System.currentTimeMillis();
- GameUtil.state=0;
- }
- }
- break;
- default:
- }
- }
- });
-
- while (true){
- repaint();
- try {
- Thread.sleep(40);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- @Override
- public void paint(Graphics g) {
- offScreenImage = this.createImage(width,height);
- Graphics gImage = offScreenImage.getGraphics();
- //设置背景颜色
- gImage.setColor(Color.lightGray);
- gImage.fillRect(0,0,width,height);
-
- mapBottom.paintSelf(gImage);
- mapTop.paintSelf(gImage);
- g.drawImage(offScreenImage,0,0,null);
- }
-
- public static void main(String[] args) {
- GameWin gameWin = new GameWin();
- gameWin.launch();
- }
- }
- //底层地图:绘制游戏相关组件
- package com.sxt;
- import java.awt.*;
-
- public class MapBottom {
- BottomRay bottomRay = new BottomRay();
- BottomNum bottomNum = new BottomNum();
- {
- bottomRay.newRay();
- bottomNum.newNum();
- }
-
- //重置游戏
- void reGame(){
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- GameUtil.DATA_BOTTOM[i][j]=0;
- }
- }
- bottomRay.newRay();
- bottomNum.newNum();
- }
-
- //绘制方法
- void paintSelf(Graphics g){
- g.setColor(Color.BLACK);
- //画竖线
- for (int i = 0; i <= GameUtil.MAP_W; i++) {
- g.drawLine(GameUtil.OFFSET + i * GameUtil.SQUARE_LENGTH,
- 3*GameUtil.OFFSET,
- GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH,
- 3*GameUtil.OFFSET+GameUtil.MAP_H*GameUtil.SQUARE_LENGTH);
- }
- //画横线
- for (int i = 0; i <=GameUtil.MAP_H; i++){
- g.drawLine(GameUtil.OFFSET,
- 3*GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH,
- GameUtil.OFFSET+GameUtil.MAP_W*GameUtil.SQUARE_LENGTH,
- 3*GameUtil.OFFSET+i*GameUtil.SQUARE_LENGTH);
- }
- for (int i = 1; i <= GameUtil.MAP_W ; i++) {
- for (int j = 1; j <= GameUtil.MAP_H; j++) {
- //雷
- if (GameUtil.DATA_BOTTOM[i][j] == -1) {
- g.drawImage(GameUtil.lei,
- GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.SQUARE_LENGTH - 2,
- GameUtil.SQUARE_LENGTH - 2,
- null);
- }
- //数字
- if (GameUtil.DATA_BOTTOM[i][j] >=0) {
- g.drawImage(GameUtil.images[GameUtil.DATA_BOTTOM[i][j]],
- GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 15,
- GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 5,
- null);
- }
- }
- }
-
- //绘制数字,剩余雷数,倒计时
- GameUtil.drawWord(g,""+(GameUtil.RAY_MAX-GameUtil.FLAG_NUM),
- GameUtil.OFFSET,
- 2*GameUtil.OFFSET,30,Color.red);
- GameUtil.drawWord(g,""+(GameUtil.END_TIME-GameUtil.START_TIME)/1000,
- GameUtil.OFFSET + GameUtil.SQUARE_LENGTH*(GameUtil.MAP_W-1),
- 2*GameUtil.OFFSET,30,Color.red);
- switch (GameUtil.state){
- case 0:
- GameUtil.END_TIME=System.currentTimeMillis();
- g.drawImage(GameUtil.face,
- GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),
- GameUtil.OFFSET,
- null);
- break;
- case 1:
- g.drawImage(GameUtil.win,
- GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),
- GameUtil.OFFSET,
- null);
- break;
- case 2:
- g.drawImage(GameUtil.over,
- GameUtil.OFFSET + GameUtil.SQUARE_LENGTH * (GameUtil.MAP_W/2),
- GameUtil.OFFSET,
- null);
- break;
- default:
- }
- }
- }
- 顶层地图类:绘制顶层组件
- package com.sxt;
- import java.awt.*;
-
- public class MapTop {
- //格子位置
- int temp_x;
- int temp_y;
-
- //重置游戏
- void reGame(){
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- GameUtil.DATA_TOP[i][j]=0;
- }
- }
- }
-
- //判断逻辑
- void logic(){
- temp_x=0;
- temp_y=0;
- if(GameUtil.MOUSE_X>GameUtil.OFFSET && GameUtil.MOUSE_Y>3*GameUtil.OFFSET){
- temp_x = (GameUtil.MOUSE_X - GameUtil.OFFSET)/GameUtil.SQUARE_LENGTH+1;
- temp_y = (GameUtil.MOUSE_Y - GameUtil.OFFSET * 3)/GameUtil.SQUARE_LENGTH+1;
- }
-
- if(temp_x>=1 && temp_x<=GameUtil.MAP_W
- && temp_y>=1 && temp_y<=GameUtil.MAP_H){
- if(GameUtil.LEFT){
- //覆盖,则翻开
- if(GameUtil.DATA_TOP[temp_x][temp_y]==0){
- GameUtil.DATA_TOP[temp_x][temp_y]=-1;
- }
- spaceOpen(temp_x,temp_y);
- GameUtil.LEFT=false;
- }
- if(GameUtil.RIGHT){
- //覆盖则插旗
- if(GameUtil.DATA_TOP[temp_x][temp_y]==0){
- GameUtil.DATA_TOP[temp_x][temp_y]=1;
- GameUtil.FLAG_NUM++;
- }
- //插旗则取消
- else if(GameUtil.DATA_TOP[temp_x][temp_y]==1){
- GameUtil.DATA_TOP[temp_x][temp_y]=0;
- GameUtil.FLAG_NUM--;
- }
- else if(GameUtil.DATA_TOP[temp_x][temp_y]==-1){
- numOpen(temp_x,temp_y);
- }
- GameUtil.RIGHT=false;
- }
- }
- boom();
- victory();
- }
- //数字翻开
- void numOpen(int x,int y){
- //记录旗数
- int count=0;
- if(GameUtil.DATA_BOTTOM[x][y]>0){
- for (int i = x-1; i <=x+1 ; i++) {
- for (int j = y-1; j <=y+1 ; j++) {
- if(GameUtil.DATA_TOP[i][j]==1){
- count++;
- }
- }
- }
- if(count==GameUtil.DATA_BOTTOM[x][y]){
- for (int i = x-1; i <=x+1 ; i++) {
- for (int j = y-1; j <=y+1 ; j++) {
- if(GameUtil.DATA_TOP[i][j]!=1){
- GameUtil.DATA_TOP[i][j]=-1;
- }
- //必须在雷区当中
- if(i>=1&&j>=1&&i<=GameUtil.MAP_W&&j<=GameUtil.MAP_H){
- spaceOpen(i,j);
- }
- }
- }
- }
- }
- }
- //失败判定 t 表示失败 f 未失败
- boolean boom(){
- if(GameUtil.FLAG_NUM==GameUtil.RAY_MAX){
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- if(GameUtil.DATA_TOP[i][j]==0){
- GameUtil.DATA_TOP[i][j]=-1;
- }
- }
- }
- }
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- if(GameUtil.DATA_BOTTOM[i][j]==-1&&GameUtil.DATA_TOP[i][j]==-1){
- GameUtil.state = 2;
- seeBoom();
- return true;
- }
- }
- }
- return false;
- }
- //失败显示
- void seeBoom(){
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- //底层是雷,顶层不是旗,显示
- if(GameUtil.DATA_BOTTOM[i][j]==-1&&GameUtil.DATA_TOP[i][j]!=1){
- GameUtil.DATA_TOP[i][j]=-1;
- }
- //底层不是雷,顶层是旗,显示差错旗
- if(GameUtil.DATA_BOTTOM[i][j]!=-1&&GameUtil.DATA_TOP[i][j]==1){
- GameUtil.DATA_TOP[i][j]=2;
- }
- }
- }
- }
- //胜利判断 t 表示胜利 f 未胜利
- boolean victory(){
- //统计未打开格子数
- int count=0;
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- if(GameUtil.DATA_TOP[i][j]!=-1){
- count++;
- }
- }
- }
- if(count==GameUtil.RAY_MAX){
- GameUtil.state=1;
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- //未翻开,变成旗
- if(GameUtil.DATA_TOP[i][j]==0){
- GameUtil.DATA_TOP[i][j]=1;
- }
- }
- }
- return true;
- }
- return false;
- }
-
- //打开空格
- void spaceOpen(int x,int y){
- if(GameUtil.DATA_BOTTOM[x][y]==0){
- for (int i = x-1; i <=x+1 ; i++) {
- for (int j = y-1; j <=y+1 ; j++) {
- //覆盖,才递归
- if(GameUtil.DATA_TOP[i][j]!=-1){
- if(GameUtil.DATA_TOP[i][j]==1){GameUtil.FLAG_NUM--;}
- GameUtil.DATA_TOP[i][j]=-1;
- //必须在雷区当中
- if(i>=1&&j>=1&&i<=GameUtil.MAP_W&&j<=GameUtil.MAP_H){
- spaceOpen(i,j);
- }
- }
- }
- }
- }
- }
- //绘制方法
- void paintSelf(Graphics g){
- logic();
- for (int i = 1; i <= GameUtil.MAP_W ; i++) {
- for (int j = 1; j <= GameUtil.MAP_H; j++) {
- //覆盖
- if (GameUtil.DATA_TOP[i][j] == 0) {
- g.drawImage(GameUtil.top,
- GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.SQUARE_LENGTH - 2,
- GameUtil.SQUARE_LENGTH - 2,
- null);
- }
- //插旗
- if (GameUtil.DATA_TOP[i][j] == 1) {
- g.drawImage(GameUtil.flag,
- GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.SQUARE_LENGTH - 2,
- GameUtil.SQUARE_LENGTH - 2,
- null);
- }
- //差错旗
- if (GameUtil.DATA_TOP[i][j] == 2) {
- g.drawImage(GameUtil.noflag,
- GameUtil.OFFSET + (i - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.OFFSET * 3 + (j - 1) * GameUtil.SQUARE_LENGTH + 1,
- GameUtil.SQUARE_LENGTH - 2,
- GameUtil.SQUARE_LENGTH - 2,
- null);
- }
- }
- }
- }
- }
- //底层数字类
- package com.sxt;
-
- public class BottomNum {
- void newNum() {
- for (int i = 1; i <=GameUtil.MAP_W ; i++) {
- for (int j = 1; j <=GameUtil.MAP_H ; j++) {
- if(GameUtil.DATA_BOTTOM[i][j]==-1){
- for (int k = i-1; k <=i+1 ; k++) {
- for (int l = j-1; l <=j+1 ; l++) {
- if(GameUtil.DATA_BOTTOM[k][l]>=0){
- GameUtil.DATA_BOTTOM[k][l]++;
- }
- }
- }
- }
- }
- }
- }
- }
- //初始化地雷类
- package com.sxt;
-
- public class BottomRay {
- //存放坐标
- int[] rays = new int[GameUtil.RAY_MAX*2];
- //地雷坐标
- int x,y;
- //是否放置 T 表示可以放置 F 不可放置
- boolean isPlace = true;
-
- //生成雷
- void newRay() {
- for (int i = 0; i < GameUtil.RAY_MAX*2 ; i=i+2) {
- x= (int) (Math.random()*GameUtil.MAP_W +1);//1-12
- y= (int) (Math.random()*GameUtil.MAP_H +1);//1-12
- //判断坐标是否存在
- for (int j = 0; j < i ; j=j+2) {
- if(x==rays[j] && y==rays[j+1]){
- i=i-2;
- isPlace = false;
- break;
- }
- }
- //将坐标放入数组
- if(isPlace){
- rays[i]=x;
- rays[i+1]=y;
- }
- isPlace = true;
- }
-
- for (int i = 0; i < GameUtil.RAY_MAX*2; i=i+2) {
- GameUtil.DATA_BOTTOM[rays[i]][rays[i+1]]=-1;
- }
- }
- }
- //工具类:存放静态参数,工具方法
- package com.sxt;
- import java.awt.*;
-
- public class GameUtil {
- //地雷个数
- static int RAY_MAX = 5;
- //地图的宽
- static int MAP_W = 11;
- //地图的高
- static int MAP_H = 11;
- //雷区偏移量
- static int OFFSET = 45;
- //格子边长
- static int SQUARE_LENGTH = 50;
-
- //插旗数量
- static int FLAG_NUM = 0;
-
- //鼠标相关
- //坐标
- static int MOUSE_X;
- static int MOUSE_Y;
- //状态
- static boolean LEFT = false;
- static boolean RIGHT = false;
-
- //游戏状态 0 表示游戏中 1 胜利 2 失败
- static int state = 0;
-
- //倒计时
- static long START_TIME;
- static long END_TIME;
-
- //底层元素 -1 雷 0 空 1-8 表示对应数字
- static int[][] DATA_BOTTOM = new int[MAP_W+2][MAP_H+2];
- //顶层元素 -1 无覆盖 0 覆盖 1 插旗 2 差错旗
- static int[][] DATA_TOP = new int[MAP_W+2][MAP_H+2];
-
- //载入图片
- static Image lei = Toolkit.getDefaultToolkit().getImage("imgs/lei.png");
- static Image top = Toolkit.getDefaultToolkit().getImage("imgs/top.gif");
- static Image flag = Toolkit.getDefaultToolkit().getImage("imgs/flag.gif");
- static Image noflag = Toolkit.getDefaultToolkit().getImage("imgs/noflag.png");
-
- static Image face = Toolkit.getDefaultToolkit().getImage("imgs/face.png");
- static Image over = Toolkit.getDefaultToolkit().getImage("imgs/over.png");
- static Image win = Toolkit.getDefaultToolkit().getImage("imgs/win.png");
-
- static Image[] images = new Image[9];
- static {
- for (int i = 1; i <=8 ; i++) {
- images[i] = Toolkit.getDefaultToolkit().getImage("imgs/num/"+i+".png");
- }
- }
-
- static void drawWord(Graphics g,String str,int x,int y,int size,Color color){
- g.setColor(color);
- g.setFont(new Font("仿宋",Font.BOLD,size));
- g.drawString(str,x,y);
- }
- }
在使用Java编写扫雷小游戏时遇到了很多问题,在解决问题时,确实对java的面向对象编程有了更加深入的理解。虽然GUI现在并没有很大的市场,甚至好多初学者已经放弃了学习GUI,但是利用GUI编程的过程对于培养编程兴趣,深入理解Java编程有很大的作用。
本程序共封装了五个类,分别是主类GameWin类,绘制底层地图和绘制顶层地图的类MapBottom类和MapTop类,绘制底层数字的类BottomNum类,以及初始化地雷的BottomRay类和工具GameUtil类,用于存静态参数和方法。
游戏的设计类似windows扫雷,用户在图形化用户界面内利用鼠标监听事件标记雷区,左上角表示剩余雷的数量,右上角动态显示使用的时间。用户可选择中间组件按钮重新游戏。为了解决程序窗口闪动的问题,本程序采用了双缓冲技术。
请大家指正!