旧文链接: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如果二维数组直接调用只是浅拷贝,后面重写了一个深拷贝的方法解决了问题。
总而言之,重构代码的感觉还是蛮爽的,推箱子重构版代码也会上传到网盘中,加油ヾ(◍°∇°◍)ノ゙。