Java游戏开发——推箱子

旧文链接:http://note.youdao.com/noteshare?id=c822c4b9983eeb4dda87aebcec65bbf6&sub=C9578CDF3388455BB6A38D63E0B56975

最近心血来潮,发觉以前写的代码结构实在混乱,为了不祸害更多的小伙伴,决定重构一遍代码。。。。。。。

为了避免重构版和第一版发生混淆,之前的文章我转移到有道云笔记了,诸君可以通过笔记链接查看旧文。
*******************************************************************************************************************************************************************

先上运行效果:

源代码链接:https://pan.baidu.com/s/1KEGyGMYgtO45A0oIdH62zA
提取码:2ik3

重构后的推箱子项目工程目录结构图如下:

资源文件已经内置到项目中了,直接运行即可。

我做了我力所能及的代码分层,现在GameClient类是窗口类,GamePanel类是游戏面板类,GameMapSet类存放关卡数据数组,GameMusicUtil类是音乐助手类。

不废话,我挑核心逻辑讲一遍,剩余的看代码和注释就可以了,不懂再问我哈。

核心逻辑

游戏元素:墙、空地、箱子、目标点、人的上下左右状态、人在目标点上的上下左右状态,一共用13个常量来表示。

private static final byte WALL = 1, BOX = 2, BOXONEND = 3, END = 4, MANDOWN = 5, MANLEFT = 6, MANRIGHT = 7,MANUP = 8, GRASS = 9, MANDOWNONEND = 10, MANLEFTONEND = 11, MANRIGHTONEND = 12, MANUPONEND = 13;

每一关的地图是都是由一个二维数组,具体内容就是上面的游戏元素的常量值。

理解了这个,就可以来分析下主角的移动逻辑处理了。

初始关卡的二维数组是这样:

对照常量,可以得出主角当前在第4行第4列(行列数从0开始),记manPositionRow=4,manPositionColumn=4,地图数组为map,方向是向下

现在我们只分析主角向下移动的逻辑处理:
 

if(主角当前位置向下一格是墙){

无法向下移动,直接return;

}

声明两个变量tempBox和tempMan,用于数组数据更新。

if(主角当前位置向下一格是‘箱子’或‘箱子在目标点上’){

 if(主角当前位置向下两格是‘目标点’或‘空地’){

  先保存快照,用作撤回操作。

   if(主角当前位置向下两格是‘目标点’吗){

     主角当前位置向下两格状态待会更新为 ‘箱子在目标点上’

    }else{

     主角当前位置向下两格状态待会更新为 ‘箱子 ’  

    }

   if(主角当前位置向下一格是‘箱子在目标点上’吗){

     主角当前位置向下一格状态待会更新为 ‘主角在目标点上此时方向向下’

    }else{

     主角当前位置向下一格状态待会更新为 ‘主角方向向下’

    }
    //移动操作,数据更新
    将主角当前位置恢复成‘空地’或‘目标点’
    主角当前位置向下两格状态更新
    主角当前位置向下一格状态更新
    主角所在行数+1

}

}else{
//主角当前位置向下一格是‘空地’或‘目标点’
 先保存快照,用作撤回操作。
  if(主角向下一格是空地吗){
      主角向下一格状态待会更新成‘主角方向向下’
  }else{
      主角向下一格状态待会更新成‘主角在目标点上此时方向向下’
  }
   //移动操作,数据更新
   将主角当前位置恢复成‘空地’或‘目标点’
   更新主角向下一格状态
   主角所在行数+1
}

对应代码如下,已经灰常灰常详细了。。。。

 

贴代码

(1)GameClient

窗体类,中间只是设置下窗口的大小,放置游戏面板GamePanel

package 推箱子重构版;

import java.awt.Color;

import javax.swing.JFrame;

/***
 * @author 墨染秦月
 * @date 2020年12月26日
 *
 * 描述:窗口类,用来放置游戏面板
 */
public class GameClient extends JFrame{

	public GameClient(){
		//设置窗口标题
		super("推箱子游戏带音乐版");
		//生成游戏面板对象
		GamePanel gamePanel = new GamePanel();
		gamePanel.setBackground(Color.white);
		//将游戏面板添加到窗口的容器中
		getContentPane().add(gamePanel);
		//点击右上角的x可以退出游戏
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		//设置可见性
		setVisible(true);
		//设置窗口不可动态调整
		setResizable(false);
		//设置窗口大小
		setSize(600,600);
		//设置窗口左上角坐标
		setLocation(300,20);		
		//播放音乐
		GameMusicUtil.play();
	}
	
	public static void main(String[] args) {
		//启动游戏
		new GameClient();
	}

}

(2)GameMusicUtil

音乐助手类,用来播放音乐

package 推箱子重构版;

import java.io.File;

import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;

/**
 * @author 墨染秦月
 * @date 2020年12月26日
 *
 * 描述:播放音乐工具类
 */
public class GameMusicUtil {

	private static String musicFile;
	private static Sequence seq;
	private static Sequencer midi;

	static {
		try {
			musicFile = new String("res/nor.mid");
			seq = MidiSystem.getSequence(new File(musicFile));
			midi = MidiSystem.getSequencer();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void play() {
		try {
			if(midi!=null){
				midi.open();
				midi.setSequence(seq);				
				midi.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
				midi.start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	
	
	public static void stop() {
		if(midi!=null){
			midi.stop();
			midi.close();			
		}
	}
}

(3)GameMapSet

专门存放关卡数组,返回指定关卡的数组数据。

package 推箱子重构版;

/**
 * @author 墨染秦月
 * @date 2020年12月26日
 *
 * 描述:存放游戏地图信息,返回指定关卡地图数据
 */
public class GameMapSet {

	// 存放各个游戏关卡的地图数组
	private static byte map[][][] = {
			{ { 0, 0, 1, 1, 1, 0, 0, 0 }, { 0, 0, 1, 4, 1, 0, 0, 0 }, { 0, 0, 1, 9, 1, 1, 1, 1 },
					{ 1, 1, 1, 2, 9, 2, 4, 1 }, { 1, 4, 9, 2, 5, 1, 1, 1 }, { 1, 1, 1, 1, 2, 1, 0, 0 },
					{ 0, 0, 0, 1, 4, 1, 0, 0 }, { 0, 0, 0, 1, 1, 1, 0, 0 } },
			{ { 1, 1, 1, 1, 1, 0, 0, 0, 0 }, { 1, 9, 9, 5, 1, 0, 0, 0, 0 }, { 1, 9, 2, 2, 1, 0, 1, 1, 1 },
					{ 1, 9, 2, 9, 1, 0, 1, 4, 1 }, { 1, 1, 1, 9, 1, 1, 1, 4, 1 }, { 0, 1, 1, 9, 9, 9, 9, 4, 1 },
					{ 0, 1, 9, 9, 9, 1, 9, 9, 1 }, { 0, 1, 9, 9, 9, 1, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 0, 0, 0 } },
			{ { 1, 1, 1, 1, 1, 0, 0, 0, 0 }, { 1, 9, 9, 9, 1, 1, 0, 0, 0 }, { 1, 9, 2, 9, 9, 1, 0, 0, 0 },
					{ 1, 1, 9, 2, 9, 1, 1, 1, 1 }, { 0, 1, 1, 1, 5, 4, 9, 9, 1 }, { 0, 0, 1, 9, 9, 4, 1, 9, 1 },
					{ 0, 0, 1, 9, 9, 9, 9, 9, 1 }, { 0, 0, 1, 1, 1, 1, 1, 1, 1 } },
			{ { 0, 0, 1, 1, 1, 1 }, { 0, 1, 1, 9, 9, 1 }, { 1, 1, 9, 2, 9, 1 }, { 1, 4, 2, 9, 5, 1 },
					{ 1, 2, 4, 9, 1, 1 }, { 1, 4, 9, 1, 1, 0 }, { 1, 1, 1, 1, 0, 0 } },

			{ { 1, 1, 1, 1, 1, 1 }, { 1, 9, 5, 9, 9, 1 }, { 1, 9, 2, 9, 9, 1 }, { 1, 2, 9, 1, 1, 1 },
					{ 1, 4, 9, 4, 1, 0 }, { 1, 1, 1, 1, 1, 0 } },
			{ { 0, 0, 0, 1, 1, 1, 1, 0, 0 }, { 0, 0, 1, 1, 9, 9, 1, 1, 0 }, { 1, 1, 1, 9, 5, 2, 4, 1, 1 },
					{ 1, 9, 9, 9, 9, 2, 4, 9, 1 }, { 1, 9, 9, 9, 1, 2, 4, 9, 1 }, { 1, 1, 1, 1, 1, 9, 1, 9, 1 },
					{ 0, 0, 0, 0, 1, 9, 9, 9, 1 }, { 0, 0, 0, 0, 1, 1, 1, 1, 1 } },

			{ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 19行
					{ 0, 0, 0, 0, 1, 9, 9, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
					{ 0, 0, 0, 0, 1, 2, 9, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
					{ 0, 0, 1, 1, 1, 9, 9, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
					{ 0, 0, 1, 9, 9, 2, 9, 2, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
					{ 1, 1, 1, 9, 1, 9, 1, 1, 9, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1 },
					{ 1, 9, 9, 9, 1, 9, 1, 1, 9, 1, 1, 1, 1, 1, 9, 9, 4, 4, 1 },
					{ 1, 9, 2, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 1 },
					{ 1, 1, 1, 1, 1, 9, 1, 1, 1, 9, 1, 5, 1, 1, 9, 9, 4, 4, 1 },
					{ 0, 0, 0, 0, 1, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
					{ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 } },
			{ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, // 14行
					{ 1, 4, 4, 9, 9, 1, 9, 9, 9, 9, 9, 1, 1, 1 }, { 1, 4, 4, 9, 9, 1, 9, 2, 9, 9, 2, 9, 9, 1 },
					{ 1, 4, 4, 9, 9, 1, 2, 1, 1, 1, 1, 9, 9, 1 }, { 1, 4, 4, 9, 9, 9, 9, 5, 9, 1, 1, 9, 9, 1 },
					{ 1, 4, 4, 9, 9, 1, 9, 1, 9, 9, 2, 9, 1, 1 }, { 1, 1, 1, 1, 1, 1, 9, 1, 1, 2, 9, 2, 9, 1 },
					{ 0, 0, 1, 9, 2, 9, 9, 2, 9, 2, 9, 2, 9, 1 }, { 0, 0, 1, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 1 },
					{ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } },
			{ { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, // 17行
					{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 9, 9, 9, 9, 9, 5, 1, 0 },
					{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 9, 2, 1, 2, 9, 1, 1, 0 },
					{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 9, 2, 9, 9, 2, 1, 0, 0 },
					{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 9, 2, 9, 1, 0, 0 },
					{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 2, 9, 1, 9, 1, 1, 1 },
					{ 1, 4, 4, 4, 4, 9, 9, 1, 1, 9, 2, 9, 9, 2, 9, 9, 1 },
					{ 1, 1, 4, 4, 4, 9, 9, 9, 9, 2, 9, 9, 2, 9, 9, 9, 1 },
					{ 1, 4, 4, 4, 4, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
					{ 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
			{ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, // 19行
					{ 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 9, 9, 1, 0, 0, 0, 0, 0 },
					{ 0, 0, 0, 1, 1, 1, 9, 5, 1, 1, 1, 2, 9, 1, 0, 0, 0, 0, 0 },
					{ 0, 0, 1, 1, 9, 9, 9, 9, 9, 9, 2, 9, 9, 1, 0, 0, 0, 0, 0 },
					{ 0, 1, 1, 9, 9, 2, 9, 2, 2, 1, 1, 9, 1, 1, 0, 0, 0, 0, 0 },
					{ 0, 1, 9, 9, 1, 2, 1, 1, 9, 9, 9, 9, 9, 1, 0, 0, 0, 0, 0 },
					{ 0, 1, 9, 1, 9, 2, 9, 2, 2, 9, 1, 9, 1, 1, 1, 0, 0, 0, 0 },
					{ 0, 1, 9, 9, 9, 2, 9, 1, 9, 9, 1, 9, 2, 9, 1, 1, 1, 1, 1 },
					{ 1, 1, 1, 1, 9, 9, 9, 9, 1, 9, 9, 2, 2, 9, 1, 9, 9, 9, 1 },
					{ 1, 1, 1, 1, 9, 1, 1, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1 },
					{ 1, 4, 9, 9, 9, 9, 1, 1, 1, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 },
					{ 1, 4, 4, 9, 4, 4, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 },
					{ 1, 4, 4, 4, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
					{ 1, 4, 4, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
					{ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }

			}

	};
	// 游戏关卡数目
	private static int count = map.length;

	// 返回指定关卡的地图数据克隆数组
	public static byte[][] getMap(int grade) {
		if (grade >= 0 && grade < count) {
			return realCloneArray(map[grade]);
		}
		return realCloneArray(map[0]);
	}
	
	//深拷贝二维数组
	private static byte[][] realCloneArray(byte[][] map){
		byte[][] cloneMap = new byte[map.length][map[0].length];
		for(int i=0;i<map.length;i++){
			cloneMap[i] = map[i].clone();
		}
		return cloneMap;
	}

	// 获取关卡数量
	public static int getGradeCount() {
		return count;
	}

}

(4)GamePanel

游戏面板类,主要负责游戏元素的显示,涉及到的逻辑大部分都转交给GameLogic来做。

 

package 推箱子重构版;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

/**
 * @author 墨染秦月
 * @date 2020年12月26日
 *
 * 描述:游戏面板,只管视图的显示
 */
public class GamePanel extends JPanel implements KeyListener {

	private GameLogic mLogic;
	private boolean isAcceptKey = true;
	private int width, height;// 屏幕大小
	private byte[][] map;
	private int marginLeft, marginTop;
	private int grade = 0;
	// 图片资源
	private Image[] images;
	private static final int DIRECTION_UP = 1, DIRECTION_DOWN = 2, DIRECTION_LEFT = 3, DIRECTION_RIGHT = 4;

	public GamePanel() {
		setSize(600, 600);
		// 屏幕宽度
		this.width = getWidth();
		// 屏幕高度
		this.height = getHeight();
		// 加载图片资源
		initImageResource();
		// 游戏逻辑类实例化
		mLogic = GameLogic.getInstance();
		// 初始化关卡
		initGame(grade);
		setFocusable(true);
		addKeyListener(this);
	}

	// 初始化图片资源
	private void initImageResource() {
		images = new Image[14];
		for (int i = 0; i <= 13; i++) {
			images[i] = Toolkit.getDefaultToolkit().getImage("res/pic" + i + ".png");
		}
	}

	private void initGame(int grade) {		
		// 设置关卡
		mLogic.setGrade(grade);
		map = mLogic.getMapData();
		// 左上角的左侧外边距
		marginLeft = (width - map[0].length * 30) / 2;
		// 左上角的上侧外边距
		marginTop = (height - map.length * 30) / 2;		 
		repaint();
	}

	public void paint(Graphics g) {
		//清空画布
		g.setColor(Color.white); 
		g.fillRect(0, 0, width, height);
		//绘制游戏元素
		for (int i = 0; i < map.length; i++)
			for (int j = 0; j < map[0].length; j++) {
				if (map[i][j] != 0) {
					g.drawImage(images[map[i][j]], marginLeft + j * 30, marginTop + i * 30, 30, 30, this);
				}
			}
		g.setColor(Color.RED);
		g.setFont(new Font("楷体_2312", Font.BOLD, 30));
		g.drawString("现在是第", 150, 140);
		g.drawString(String.valueOf(grade + 1), 310, 140);
		g.drawString("关", 360, 140);
	}

	//处理人物移动,每次人物移动后,需要获取最新地图数据,进行界面绘制。然后判断当前关卡是否已经通过
	private void move(int directionType) {
		switch (directionType) {
		case DIRECTION_UP:
			mLogic.moveUp();
			break;
		case DIRECTION_DOWN:
			mLogic.moveDown();
			break;
		case DIRECTION_LEFT:
			mLogic.moveLeft();
			break;
		case DIRECTION_RIGHT:
			mLogic.moveRight();
			break;
		}
		// 获取map数组
		map = mLogic.getMapData();
		// 重绘界面
		repaint();

		// 每次移动后,判断当前关卡是否已经过关
		if (mLogic.isFinished()) {
			isAcceptKey = false;
			if (grade == GameMapSet.getGradeCount()) {
				DisplayOkToast("恭喜通过最后一关");
			} else {
				String msg = "恭喜你通过第" + (grade + 1) + "关!!!\n是否要进入下一关?";
				int type = JOptionPane.YES_NO_OPTION;
				String title = "过关";
				int choice = 0;
				choice = JOptionPane.showConfirmDialog(this, msg, title, type);
				// 询问是否进入下一关
				if (choice == 1) {
					System.exit(0);
				} else {
					isAcceptKey = true;
					initGame(++grade);
				}
			}
		}

	}

	@Override
	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == KeyEvent.VK_UP) {
			move(DIRECTION_UP);
		}
		if (e.getKeyCode() == KeyEvent.VK_DOWN) {
			move(DIRECTION_DOWN);
		}
		if (e.getKeyCode() == KeyEvent.VK_LEFT) {
			move(DIRECTION_LEFT);
		}
		if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
			move(DIRECTION_RIGHT);
		}

		if (e.getKeyCode() == KeyEvent.VK_A) {// 上一关
			isAcceptKey = true;
			initGame(grade > 0 ? --grade : 0);
			return;
		}
		if (e.getKeyCode() == KeyEvent.VK_D) {// 下一关
			isAcceptKey = true;
		    initGame(grade < GameMapSet.getGradeCount() - 1 ? ++grade : GameMapSet.getGradeCount() - 1);
			return;
		}
		
		
		if (e.getKeyCode() == KeyEvent.VK_B) {// 撤回

			if (isAcceptKey) {
				boolean undoResult = mLogic.undo();
				// 撤销成功,重新获取map,并重新绘制
				if (undoResult) {
					map = mLogic.getMapData();
					repaint();
				} else {
					DisplayErrorToast("不能再撤销");
				}
			}

		}
	}

	public void DisplayOkToast(String str) {
		JOptionPane.showMessageDialog(this, str, "提示", JOptionPane.OK_OPTION);
	}

	public void DisplayErrorToast(String str) {
		JOptionPane.showMessageDialog(this, str, "提示", JOptionPane.ERROR_MESSAGE);
	}

	@Override
	public void keyReleased(KeyEvent e) {

	}

	@Override
	public void keyTyped(KeyEvent e) {

	}

}

GamePanel清晰很多了,按上下左右键,主角上下左右移动。按A上一关,按D下一关,按B撤回最近一次操作。

弹框分成两个方法来做了,DisplayOkToast()弹出带绿√的框,DisplayErrorToast()弹出带红X的框。

 

GamePanel实例化后,会做以下事情:

a.设置面板大小

b.加载图片资源

c.获取游戏逻辑类的实例做mLogic

d.初始化关卡为第一关

e.添加监听

 

initGame()会通过mLogic获取关卡数组,计算出左外边距和上外边距。

每次主角移动后,获取最新的地图数据,并重画界面。

画界面时,先清空画布,再进行绘画。

主角移动后,重绘完当前界面,会判断当前关卡是否过关,再做相应处理。

过关时先取消掉键位监听,下一关时,再恢复键位监听。

 

(5)GameLogic

游戏逻辑处理类,几乎所有的逻辑都交给它来实现。

package 推箱子重构版;

import java.util.Stack;

/**
 * @author 墨染秦月
 * @date 2020年12月26日
 *
 * 描述:游戏逻辑类,处理游戏中的各种逻辑
 */
public class GameLogic {

	// 主角所在行列数
	private int manPositionRow, manPositionColumn;
	// 地图的行列数
	private int mapRows, mapColumns;
	// 地图数组
	private byte[][] map;
	// 用来实现撤回上一步操作的栈
	private Stack<Dump> stack = new Stack<>();
	private static volatile GameLogic instance;
	private static final byte WALL = 1, BOX = 2, BOXONEND = 3, END = 4, MANDOWN = 5, MANLEFT = 6, MANRIGHT = 7,
			MANUP = 8, GRASS = 9, MANDOWNONEND = 10, MANLEFTONEND = 11, MANRIGHTONEND = 12, MANUPONEND = 13;

	private GameLogic() {
	}

	// 初始化参数
	private void initParams() {
		// 快照栈清空
		stack.clear();
		// 初始化地图行数
		mapRows = map.length;
		// 初始化地图列数
		mapColumns = map[0].length;
		// 获取当前地图中,主角在第几行第几列
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[0].length; j++) {
				if (map[i][j] == MANDOWN || map[i][j] == MANUP || map[i][j] == MANLEFT || map[i][j] == MANRIGHT) {
					manPositionRow = i;
					manPositionColumn = j;
					break;
				}
			}
		}

	}

	// 初始化地图数据
	private void initMap(int grade) {
		map = GameMapSet.getMap(grade);
	}

	//设置关卡,初始化地图数据,初始化行列数和主角位置
	public void setGrade(int grade) {
		initMap(grade);
		initParams();
	}

	//返回当前地图数据,用作界面绘制
	public byte[][] getMapData() {
		return realCloneArray(map);
	}

	// 打印地图细节,用于测试
	private void printMapDetails(byte[][] map) {
		System.out.println("***************************************");
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[0].length; j++) {
				System.out.print(map[i][j] + " ");
			}
			System.out.println();
		}
		System.out.println("***************************************");
	}

	// 判断玩家是否已经过关
	public boolean isFinished() {
		for (int i = 0; i < mapRows; i++)
			for (int j = 0; j < mapColumns; j++) {
				if (map[i][j] == END || map[i][j] == MANDOWNONEND || map[i][j] == MANUPONEND
						|| map[i][j] == MANLEFTONEND || map[i][j] == MANRIGHTONEND) {
					return false;
				}
			}
		return true;
	}

	// 返回当前人物是在过道上还是在终点上
	public byte isGrassOrEnd(byte man) {
		byte result = GRASS;
		if (man == MANLEFTONEND || man == MANRIGHTONEND || man == MANUPONEND || man == MANDOWNONEND) {
			result = END;
		}

		return result;
	}

	//快照类,用于撤销操作
	private class Dump {
		private int manPositionRow = 0;
		private int manPositionColumn = 0;
		private byte dumpMap[][];

		public Dump(int manPositionRow, int manPositionColumn, byte[][] map) {
			this.manPositionRow = manPositionRow;
			this.manPositionColumn = manPositionColumn;
			this.dumpMap = map;
		}

		public int getManPositionRow() {
			return manPositionRow;
		}

		public int getManPositionColumn() {
			return manPositionColumn;
		}

		public byte[][] getMap() {
			return dumpMap;
		}
	}

	//主角向上移动
	public void moveUp() {
		// 如果主角向上一格是墙,啥也不做
		if (map[manPositionRow - 1][manPositionColumn] == WALL)
			return;
		byte tempBox;
		byte tempMan;
		// 如果主角向上一格是箱子
		if (map[manPositionRow - 1][manPositionColumn] == BOX
				|| map[manPositionRow - 1][manPositionColumn] == BOXONEND) {
			// 如果主角向上第二格是过道或者终点
			if (map[manPositionRow - 2][manPositionColumn] == GRASS
					|| map[manPositionRow - 2][manPositionColumn] == END) {
				Dump dump = new Dump(manPositionRow, manPositionColumn,realCloneArray(map));
				// 保存当前状态
				stack.push(dump);
				// 如果主角上两格是终点,箱子状态变更为BOXONEND
				tempBox = map[manPositionRow - 2][manPositionColumn] == END ? BOXONEND : BOX;
				// 如果主角上一格的箱子在终点上,那么主角状态变更为MANUPONEND
				tempMan = map[manPositionRow - 1][manPositionColumn] == BOXONEND ? MANUPONEND : MANUP;
				// 如果主角当前在终点上,需要恢复当前节点状态为END
				map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
				map[manPositionRow - 2][manPositionColumn] = tempBox;
				map[manPositionRow - 1][manPositionColumn] = tempMan;
				// 主角向上移动一格
				manPositionRow--;
			}
		} else {
			// 如果主角向上一格是过道或者终点
			Dump dump = new Dump(manPositionRow, manPositionColumn,realCloneArray(map));
			// 保存当前状态
			stack.push(dump);
			// 如果主角上一格是终点,主角状态变更为MANUPONEND
			tempMan = map[manPositionRow-1][manPositionColumn] == END ? MANUPONEND : MANUP;
			// 如果主角当前在终点上,需要恢复当前节点状态为END
			map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
			map[manPositionRow - 1][manPositionColumn] = tempMan;	
			// 主角向上移动一格
			manPositionRow--;
		}

	}

	//主角向下移动。详细逻辑参考moveUp()
	public void moveDown() {
		if (map[manPositionRow + 1][manPositionColumn] == WALL)
			return;
		byte tempBox;
		byte tempMan;

		if (map[manPositionRow + 1][manPositionColumn] == BOX
				|| map[manPositionRow + 1][manPositionColumn] == BOXONEND) {
			if (map[manPositionRow + 2][manPositionColumn] == END
					|| map[manPositionRow + 2][manPositionColumn] == GRASS) {
				Dump dump = new Dump(manPositionRow, manPositionColumn, realCloneArray(map));
				stack.push(dump);
				tempBox = map[manPositionRow + 2][manPositionColumn] == END ? BOXONEND : BOX;
				tempMan = map[manPositionRow + 1][manPositionColumn] == BOXONEND ? MANDOWNONEND : MANDOWN;
				map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
				map[manPositionRow + 2][manPositionColumn] = tempBox;
				map[manPositionRow + 1][manPositionColumn] = tempMan;
				manPositionRow++;
			}
		} else {
			Dump dump = new Dump(manPositionRow, manPositionColumn,  realCloneArray(map));
			stack.push(dump);
			tempMan = map[manPositionRow + 1][manPositionColumn] == GRASS ? MANDOWN : MANDOWNONEND;
			map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
			map[manPositionRow + 1][manPositionColumn] = tempMan;
			manPositionRow++;
		}
	}

	//主角向左移动。详细逻辑参考moveUp()
	public void moveLeft() {
		if (map[manPositionRow][manPositionColumn - 1] == WALL)
			return;
		byte tempBox;
		byte tempMan;

		if (map[manPositionRow][manPositionColumn - 1] == BOX
				|| map[manPositionRow][manPositionColumn - 1] == BOXONEND) {
			if (map[manPositionRow][manPositionColumn - 2] == END
					|| map[manPositionRow][manPositionColumn - 2] == GRASS) {
				Dump dump = new Dump(manPositionRow, manPositionColumn, realCloneArray(map));
				stack.push(dump);
				tempBox = map[manPositionRow][manPositionColumn - 2] == END ? BOXONEND : BOX;
				tempMan = map[manPositionRow][manPositionColumn - 1] == BOXONEND ? MANLEFTONEND : MANLEFT;
				map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
				map[manPositionRow][manPositionColumn - 2] = tempBox;
				map[manPositionRow][manPositionColumn - 1] = tempMan;
				manPositionColumn--;
			}
		} else {
			Dump dump = new Dump(manPositionRow, manPositionColumn, realCloneArray(map));
			stack.push(dump);
			tempMan = map[manPositionRow][manPositionColumn - 1] == GRASS ? MANLEFT : MANLEFTONEND;
			map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
			map[manPositionRow][manPositionColumn - 1] = tempMan;
			manPositionColumn--;
		}
	}

	//主角向右移动。详细逻辑参考moveUp()
	public void moveRight() {
		if (map[manPositionRow][manPositionColumn + 1] == WALL)
			return;
		byte tempBox;
		byte tempMan;

		if (map[manPositionRow][manPositionColumn + 1] == BOX
				|| map[manPositionRow][manPositionColumn + 1] == BOXONEND) {
			if (map[manPositionRow][manPositionColumn + 2] == END
					|| map[manPositionRow][manPositionColumn + 2] == GRASS) {
				Dump dump = new Dump(manPositionRow, manPositionColumn, realCloneArray(map));
				stack.push(dump);
				tempBox = map[manPositionRow][manPositionColumn + 2] == END ? BOXONEND : BOX;
				tempMan = map[manPositionRow][manPositionColumn + 1] == BOXONEND ? MANRIGHTONEND : MANRIGHT;
				map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
				map[manPositionRow][manPositionColumn + 2] = tempBox;
				map[manPositionRow][manPositionColumn + 1] = tempMan;
				manPositionColumn++;
			}
		} else {
			Dump dump = new Dump(manPositionRow, manPositionColumn,  realCloneArray(map));
			stack.push(dump);
			tempMan = map[manPositionRow][manPositionColumn + 1] == GRASS ? MANRIGHT : MANRIGHTONEND;
			map[manPositionRow][manPositionColumn] = isGrassOrEnd(map[manPositionRow][manPositionColumn]);
			map[manPositionRow][manPositionColumn + 1] = tempMan;
			manPositionColumn++;
		}

	}
	
	//深拷贝二维数组
	private byte[][] realCloneArray(byte[][] map){
		byte[][] cloneMap = new byte[map.length][map[0].length];
		for(int i=0;i<map.length;i++){
			cloneMap[i] = map[i].clone();
		}
		return cloneMap;
	}
	
	//撤销上一步操作,撤销成功返回true,否则返回false
	public boolean undo() {
		if (stack.isEmpty()) {
			return false;
		}
		Dump dump = stack.pop();
		this.manPositionRow = dump.getManPositionRow();
		this.manPositionColumn = dump.getManPositionColumn();
		this.map = dump.getMap();
		return true;
	}

	//单例模式做游戏逻辑处理类
	public static GameLogic getInstance() {
		if (instance == null) {
			synchronized (GameLogic.class) {
				if (instance == null) {
					instance = new GameLogic();
				}
			}
		}
		return instance;
	}

}

它对GamePanel提供了一个setGrade(int grade)的方法,用来设置关卡,这个方法会调用initMap()和initParams()。

initMap()根据关卡初始化地图数组数据。

initParams()根据地图数组数据初始化地图的总行数和总列数,主角所在的行列数,并清空快照栈stack。

Dump是用来存储每一步的快照的,他会将主角所在的行列数和map存储到快照栈stack中,撤销操作时,直接从栈取出就可以用了。

GameLogic.getInstance()获取单例对象。

 

核心方法很明了:

moveUp()上移

moveDown()下移

moveLeft()左移

moveRight()右移

undo()撤销最近一次操作

isGrassOrEnd()判断主角当前踩在过道上还是终点上

isFinished()判断当前关卡是否过关

getMapData()返回当前地图数组数据给GamePanel,用来绘制界面

realCloneArray()深拷贝二维数组并返回

 

中间调试时踩了一些坑:

(1)代码重构过程中,运行界面绘制错乱,后面通过在paint()方法先清空当前画布得到了解决。

(2)改写之前的备份关卡代码时,为了省事,直接调用了二维数组的clone方法,没想到这个clone如果二维数组直接调用只是浅拷贝,后面重写了一个深拷贝的方法解决了问题。

 

总而言之,重构代码的感觉还是蛮爽的,推箱子重构版代码也会上传到网盘中,加油ヾ(◍°∇°◍)ノ゙。

  • 59
    点赞
  • 243
    收藏
    觉得还不错? 一键收藏
  • 27
    评论
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值