1. 前言
这个游戏的经典程度就不用说了吧,相信大家都玩过,而且在学习之初说不定也都还写过呢,
这个实际上写了已经很久了,所以可能注释, 代码风格等信息可能不是那么的好看
设计的思路 : 因为当时在写这个程序的时候, 并没有了解A* 算法, dijkstra算法[看过, 但是没有用过], 所以当时使用的是dfs, 深 + 盲 搜, 如果条件稍差, 则可能遍历整个图, 所以寻路的效果就不是那么好了, 请见谅
路径的绘制 也比较重要, 涉及多线程
这个游戏 主要是挪用了推箱子的界面, 素材等等
元素介绍 :
角色 : 即我们控制的人物
地板 : 地板砖, 人物可在上面行走
墙 : 障碍物, 会挡住我们角色的去向
终点 : 我们角色的终点站
游戏画面 :
2. 基本的数据结构介绍
这里 基本上是挪用了推箱子的所有数据结构, 这里着重介绍几种修改的数据结构
2.1 Map : 整个地图的model, 控制着图形的显示
/**
* file name : Map.java
* created at : 9:20:56 PM Apr 13, 2015
* created by 970655147
*/
package com.hx.maze;
// Map整个程序的主Model
public class Map {
// 地图的模型, 加载完成的的地图元素
private int[][] map;
private Element[][] eles;
// 角色, 游戏面板, 所有的箱子数, 已经推到正确位置的箱子数, 当前关卡数
private Hero hero;
private MyPanel gamePanel;
private int allBoxNums;
private int finishedBoxNums;
private int stageNum;
private LinkedList<Node> turns;
private LinkedList<Node> path;
private int updateDirections;
private int[] preferencesDirections;
// 初始化
// 如果没有显示 指定stageNum 默认设置为0
public Map (int stageNum) throws IOException {
this(new FileInputStream(new File(System.getProperty("user.dir"), getMapPathByStage(stageNum))) );
this.stageNum = stageNum;
}
public Map (InputStream inputStream) throws IOException {
this(FileParser.parse(inputStream) );
stageNum = 0;
turns = new LinkedList<Node>();
path = new LinkedList<Node>();
}
public Map() {
}
public Map (int[][] map) {
this.map = map;
refreshEles();
stageNum = 0;
preferencesDirections = Constants.ALL_DIRECTIONS;
updateDirections = 16 - 1;
}
// 获取地图文件相对路径
public static String getMapPathByStage(int stageNum2) {
return "src/com/hx/maze/map01.txt";
}
// getter & setter
// 省略 ...
// 设置关卡 先获取逻辑地图, 在实际加载地图所有的元素 更新数据等等
public void setStage(int stageNum) {
try {
this.map = (FileParser.parse(new FileInputStream(new File(System.getProperty("user.dir"), getMapPathByStage(stageNum)))));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
refreshEles();
this.stageNum = stageNum;
}
// 依据map 加载所有的元素 统计所有的箱子数 清零已经完成的箱子数 重新绘制panel
public void refreshEles() {
eles = new Element[map.length][map.length];
for(int i=0; i<map.length; i++) {
for(int j=0; j<map[0].length; j++) {
eles[i][j] = getEleForVal(i, j, getVal(i, j));
}
}
eles[Constants.END_POINT_ROW][Constants.END_POINT_COL] = getEleForVal(Constants.END_POINT_ROW, Constants.END_POINT_COL, Constants.BOX_RIGHT);
allBoxNums = countAllBoxNums(map);
finishedBoxNums = 0;
// Log.log(allBoxNums, finishedBoxNums);
if(gamePanel != null) {
gamePanel.repaint();
}
}
// 统计箱子数[箱子目的图标 + 已经放置正确的箱子图标]
private int countAllBoxNums(int[][] map) {
int allBoxNums = 0;
for(int i=0; i<map.length; i++) {
for(int j=0; j<map[0].length; j++) {
if(map[i][j] == Constants.BOX_RIGHT || map[i][j] == Constants.BOX_RIGHT_POS) {
allBoxNums ++;
}
}
}
return allBoxNums;
}
// 根据坐标以及value 返回相应的元素
private Element getEleForVal(int i, int j, int val) {
// 因为i 表示纵向的偏移
// j表示横向的偏移
if(val == 3 || val == 9) {
PushableElement pe = new PushableElement(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
pe.setMap(this);
return pe;
} else if(val == 2 || val == 4) {
return new MovableElement(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
} else if(val >= 5 && val < 9) {
hero = new Hero(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
hero.setMap(this);
return hero;
}
return new Element(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
}
// 检查是否游戏当前关卡结束
public void checkIsSuccess() {
if(isSucc()) {
JOptionPane.showMessageDialog(gamePanel, "congratulations...");
gamePanel.nextStage();
}
}
// 判断 是否符合成功的条件
public boolean isSucc() {
if(hero.getX() == (Constants.END_POINT_ROW) && hero.getY() == (Constants.END_POINT_COL)) {
return true;
}
return false;
}
// 判断 node结点是否符合成功的条件
private boolean isSucc(Node node, Point dst) {
if(node.row == (dst.x) && node.col == (dst.y) ) {
return true;
}
return false;
}
public void parse() {
path.clear();
parse(hero.getX(), hero.getY(), Constants.END_POINT_ROW, Constants.END_POINT_COL, path);
optimize(path);
Log.log(path);
}
// 解析是否 可到达终点
// 获取当前的角色位置 解析其课走的方向
// 然后 试探的向一个方向[左>下>右>上]走 然后在循环判定该位置, 并继续走
// 感觉 这样好傻,,
// 后来 更新了一下, 更新了选择方向的优先级 没updateDirections次 更新一次
private void parse(int srcRow, int srcCol, int dstRow, int dstCol, LinkedList<Node> path) {
Point dst = new Point(dstRow, dstCol);
preferencesDirections = Tools.getPreferenceDirections(new Point(srcRow, srcCol), dst );
turns.clear();
int cnt = 0;
Node node = new Node(srcRow, srcCol );
setDirectionsForNode(node);
turns.push(node);
path.add(node);
// 原来Arrays.copyOf居然是浅拷贝..?? --2015.05.20
// int[][] newMap = Arrays.copyOf(map, map.length);
int[][] copyOfMap = copyMap();
Node tmp = null;
setBlock(map, node);
while((tmp = turns.getFirst() ) != null) {
if((cnt & updateDirections) == updateDirections) {
preferencesDirections = Tools.getPreferenceDirections(new Point(tmp.row, tmp.col), dst );
}
int direction = -1;
removeAfter(path, tmp);
if((direction = tmp.nextDirection() ) != Constants.NULL) {
tmp = new Node(tmp);
tmp.move(direction);
setBlock(map, tmp);
setDirectionsForNode(tmp);
// 擦 为什么没有打印出(0, 0)...
// 看清楚 我上面的tmp是从turns中取得的值, 所以对其move会对turns中的数据修改... 所以,没有(0, 0)...
turns.push(tmp);
path.add(tmp);
if(isSucc(tmp, dst)) {
break;
}
} else {
Node n = turns.pop();
if(turns.size() == 0) {
break;
}
}
cnt ++;
}
if(turns.size() == 0) {
// JOptionPane.showMessageDialog(null, "can't got it...");
}
// 恢复map
this.map = copyOfMap;
}
// 优化那些正方向上相关的点
// 两个迭代器 一个迭代器从头开始遍历 一个从后开始遍历
// 如果两个结点在正方向上 则尝试从第一个结点走到第二个结点
// 如果所用的步数少于之前path中所用过的步数 则更新这两个节点之间的路径 continue Optimize;
// 因为 更改了path 所以需要从新获取迭代器 遍历
private void optimize(LinkedList<Node> path) {
LinkedList<Node> tmp = new LinkedList<Node>();
Iterator<Node> it01 = null;
int idx01 = 0, idx02 = 0;
optimize:
while(true ) {
for(it01 = path.iterator(), idx01 = 0; it01.hasNext(); ) {
Node node01 = it01.next();
idx02 = path.size() - 1;
ListIterator<Node> it02 = path.listIterator(path.size());
while(it02.hasPrevious() ) {
Node node02 = it02.previous();
int direction = Tools.getDirection(node01.row, node01.col, node02.row, node02.col);
if(Tools.isRightDirection(direction)) {
tmp.clear();
parse(node01.row, node01.col, node02.row, node02.col, tmp);
if(tmp.size() < (idx02 - idx01) ) {
while(idx02 >= idx01) {
Log.log(path.remove(idx02--) );
}
path.addAll(idx01, tmp);
continue optimize;
}
}
idx02 --;
}
idx01 ++;
}
break;
}
}
// 复制一个int[][] 备份
private int[][] copyMap() {
int[][] newMap = new int[map.length][map[0].length];
for(int row=0; row<map.length; row++) {
for(int col=0; col<map[0].length; col++) {
newMap[row][col] = map[row][col];
}
}
return newMap;
}
// 移除tmp元素之后的元素
private void removeAfter(LinkedList<Node> path2, Node tmp) {
Node node = new Node(4, 18);
int idx = path2.size();
// get方法的node方法优化 头尾操作比较快
// 我去 之前的时候 我这里path2 居然写的path, 所以优化的时候 出现了一个奇怪的问题, 很多两个结点之间可以直接到达... --2015.05.22
while(idx >0 && !tmp.eq(path2.get(--idx)) ) ;
++ idx;
if(idx > 0) {
while(path2.size() > idx) {
path2.removeLast();
}
}
}
// 根据path 获取路径的Point[]表示
public Point[] getPath() {
Point[] res = new Point[path.size()];
Iterator<Node> it = path.iterator();
int idx = 0;
while(it.hasNext()) {
Node node = it.next();
res[idx++] = new Point(getXByCol(node.col), getYByRow(node.row) );
}
return res;
}
// 通过col的数字 获取x方向的坐标
private int getXByCol(int col) {
return Constants.HEIGHT/2 + col*Constants.HEIGHT;
}
// 通过row的数字 获取y方向的坐标
private int getYByRow(int row) {
return Constants.WIDTH/2 + row*Constants.WIDTH;
}
private void setBlock(int[][] newMap, Node node) {
newMap[node.row][node.col] = Constants.BLOCK;
}
// 获取一个node可以移动的方向
private void setDirectionsForNode(Node node) {
int[] dirs = new int[]{Constants.NULL, Constants.NULL, Constants.NULL, Constants.NULL };
int idx = 0;
int[] directions = preferencesDirections;
for(int i=0; i<directions.length; i++) {
if(node.canMove(directions[i])) {
dirs[idx++] = directions[i];
}
}
node.setDirs(dirs);
node.setIdx(idx - 1);
}
// 一个Node
public class Node {
// 横坐标, 纵坐标, 可移动的方向, 下一个方向的索引
int row;
int col;
int[] dirs;
int dirIdx;
// 初始化
public Node() {
}
public Node(int row, int col) {
this.row = row;
this.col = col;
}
public Node(Node dst) {
this.row = dst.row;
this.col = dst.col;
this.dirs = dst.dirs;
this.dirIdx = dst.dirIdx;
}
// 相等判定
public boolean eq(Node other) {
return row == other.row && col == other.col;
}
// getter & setter
// 省略 ...
// 该方向是否可移动
public boolean canMove(int direction) {
boolean canMove = false;
switch(direction) {
case Constants.UP:
canMove = isMovable(row-1, col);
break;
case Constants.RIGHT:
canMove = isMovable(row, col+1);
break;
case Constants.DOWN:
canMove = isMovable(row+1, col);
break;
case Constants.LEFT:
canMove = isMovable(row, col-1);
break;
}
return canMove;
}
// 向direction方向移动 更新坐标
public void move(int direction) {
switch(direction) {
case Constants.UP:
up();
break;
case Constants.RIGHT:
right();
break;
case Constants.DOWN:
down();
break;
case Constants.LEFT:
left();
break;
}
}
// 各个方向的移动
private void up() {
move(row - 1, col);
}
private void right() {
move(row, col + 1);
}
private void down() {
move(row + 1, col);
}
private void left() {
move(row, col - 1);
}
// 移动的工具方法
private void move(int x, int y) {
this.row = x;
this.col = y;
}
// row, col是否可移动
private boolean isMovable(int row, int col) {
if(Tools.isOutBounds(row, col)) {
return false;
}
return map[row][col] != Constants.BLOCK;
}
// 获取下一个方向
public int nextDirection() {
if(dirIdx >= 0) {
return dirs[dirIdx--];
}
return -1;
}
// For Debug
public String toString() {
return "row : " + row + ", col : " + col;
}
}
}
- 2 MyPanel $DrawLineRunnable : 绘制路径的线程
// 绘制路径的Runnable
class DrawLineRunnable implements Runnable {
// 途经的各个点, 以及需要的时间, 以及是否值正向绘制
Point[] path;
int ms;
boolean isPositive;
// 初始化
public DrawLineRunnable() {
}
public DrawLineRunnable(Point[] path, boolean isPositive, int ms) {
this.path = path;
this.ms = ms;
this.isPositive = isPositive;
}
// 绘制path的轨迹 清理path的轨迹
public void run() {
Graphics g = MyPanel.this.getGraphics();
if(isPositive) {
// 画出轨迹
for(int i=0; i<path.length-1; i++) {
move(path[i], path[i+1], g);
}
// 清理轨迹
for(int i=0; i<path.length-1; i++) {
clear(path[i], path[i+1], g);
}
} else {
for(int i=path.length-1; i>0; i--) {
move(path[i], path[i-1], g);
}
for(int i=path.length-1; i>0; i--) {
clear(path[i], path[i-1], g);
}
}
}
// 绘制src到dst的路径
public void move(Point src, Point dst, Graphics g) {
draw(src, dst, g, BORDER_COLOR);
}
// 清理src到dst的路径
public void clear(Point src, Point dst, Graphics g) {
int num = ms * 3;
int upInc = (dst.y - src.y) / num;
int rightInc = (dst.x - src.x) / num;
Point tmp = new Point(src);
for(int i=0; i<num; i++) {
g.setColor(BG);
g.drawLine(tmp.x, tmp.y, tmp.x+rightInc, tmp.y+upInc);
tmp.x += rightInc; tmp.y += upInc;
}
}
// assistMethod
public void draw(Point src, Point dst, Graphics g, Color color){
int num = ms * 3;
int upInc = (dst.y - src.y) / num;
int rightInc = (dst.x - src.x) / num;
int xOffset = 10;
int yOffset = 10;
Point tmp = new Point(src);
int direction = Tools.getDirection(src.y, src.x, dst.y, dst.x);
if(direction <= 3) {
for(int i=0; i<num; i++) {
g.setColor(BG);
drawArrow(tmp, xOffset, yOffset, direction, g);
g.setColor(color);
g.drawLine(tmp.x, tmp.y, tmp.x+rightInc, tmp.y+upInc);
tmp.x += rightInc; tmp.y += upInc;
drawArrow(tmp, xOffset, yOffset, direction, g);
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
g.setColor(BG);
drawArrow(tmp, xOffset, yOffset, direction, g);
}
}
// 绘制箭头 -> 中的 ">"
// 先获取上面点, 下面点的坐标, 在绘制tmp到两个点的直线, 形成">"
public void drawArrow(Point tmp, int xOffset, int yOffset, int direction, Graphics g) {
Point dstUp = new Point(tmp), dstDown = new Point(tmp);
switch(direction ) {
case Tools.LEFT:
dstUp.x += xOffset; dstUp.y -= yOffset;
dstDown.x += xOffset; dstDown.y += yOffset;
break;
case Tools.UP:
dstUp.x -= xOffset; dstUp.y += yOffset;
dstDown.x += xOffset; dstDown.y += yOffset;
break;
case Tools.RIGHT:
dstUp.x -= xOffset; dstUp.y -= yOffset;
dstDown.x -= xOffset; dstDown.y += yOffset;
break;
case Tools.DOWN:
dstUp.x -= xOffset; dstUp.y -= yOffset;
dstDown.x += xOffset; dstDown.y -= yOffset;
break;
default:
throw new RuntimeException("have no this direction....");
}
g.drawLine(tmp.x, tmp.y, dstUp.x, dstUp.y);
g.drawLine(tmp.x, tmp.y, dstDown.x, dstDown.y);
}
}
3 下载链接 [包含图片, 源码] :
运行时截图 :
注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!