基于 A*搜索算法迷宫游戏开发
1、项目概述
(1)项目目标和主要内容
<1>迷宫游戏是非常经典的游戏,在该题中要求随机生成一个迷宫,并求解迷宫;
<2>要求查找并理解迷宫生成的算法,并尝试用两种不同的算法来生成随机的迷宫。
<3>要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘 方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于 A算法实现,输出走迷宫的最优路径并显示。设计交互友好的游戏图形界面。
(2)项目的主要功能
<1>迷宫游戏可以锻炼我们对数据结构中图的更好的理解
<2>迷宫游戏可以通过键盘控制方向,输出走的方向。
2、编写程序的语言为Java。
3、A算法的原理
A* (A-Star)算法是bai一种静态路网du中求解最短路最有zhi效的直接dao搜索方法。注意是最有效的直接搜索算法。之后涌shu现了很多预处理算法(ALT,CH,HL等等),在线查询效率是A*算法的数千甚至上万倍。
公式表示为: f(n)=g(n)+h(n),其中 f(n) 是从初始点经由节点n到目标点的估价函数,g(n) 是在状态空间中从初始节点到n节点的实际代价,
h(n) 是从n到目标节点最佳路径的估计代价。保证找到最短路径(最优解的)条件,关键在于估价函数f(n)的选取:估价值h(n)<= n到目标节点的距离实际值,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到最优解。并且如果h(n)=d(n),即距离估计h(n)等于最短距离,那么搜索将严格沿着最短路径进行, 此时的搜索效率是最高的。如果 估价值>实际值,搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。
4、对于项目的想法
2.1项目总体框架
包括系统框架图或层次逻辑图,设计思想等。系统详细设计
【1】 模块划分图及描述
【2】 存储结构、内存分配
【3】 程序流程图及描述
2.2关键算法分析
算法 1:函数名GameClient
【1】 算法功能
图形界面的实现和优先级的判断
【2】 算法基本思想
继承父类JFrame,构造图形界面。
【3】 算法空间、时间复杂度分析
<1>算法空间复杂度分析:递归算法来说,一般都比较简短,算法本身所占用的存储空间较少,但运行时需要一个附加堆栈,从而占用较多的临时工作单元;若写成非递归算法,一般可能比较长,算法本身占用的存储空间较多,但运行时将可能需要较少的存储单元。
<2>算法时间复杂度分析:当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;反之,当追求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。另外,算法的所有性能之间都存在着或多或少的相互影响。因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。算法的时间复杂度和空间复杂度合称为算法的复杂度。
【4】 代码逻辑(可用伪代码描述)
public class GameClient extends JFrame{
public GameClient(){
this.getContentPane().add(new GamePanel(20, 40));
pack();
setTitle(“pic0”);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); }
public static void main(String[] args) {
new GameClient();
}
}
算法 2:函数名 GamePanel
【1】 算法功能
实现对JFrame方法的布置,键盘控制走的方向
【2】 算法基本思想
图,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
【3】 算法空间、时间复杂度分析
<1>算法空间复杂度分析:递归算法来说,一般都比较简短,算法本身所占用的存储空间较少,但运行时需要一个附加堆栈,从而占用较多的临时工作单元;若写成非递归算法,一般可能比较长,算法本身占用的存储空间较多,但运行时将可能需要较少的存储单元。
<2>算法时间复杂度分析:当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;反之,当追求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。另外,算法的所有性能之间都存在着或多或少的相互影响。因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。算法的时间复杂度和空间复杂度合称为算法的复杂度。
【4】 代码逻辑(可用伪代码描述)
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
public class GamePanel extends JPanel implements KeyListener{
int[][] map;
MapUtil mapUtil;
int row,col;
int leftX,leftY;
int manX,manY;
int direction;
int manIndex;
Image[] pics;
public static final int BLANK=-1,MAN=0,WALL=1,EXIT=2,UP=1,DOWN=2,LEFT=3,RIGHT=4;
public GamePanel(int row,int col){
this.row = row;
this.col = col;
leftX = 0;
leftY = 0;
manX = 1;
manY = 1;
pics = new Image[20];
mapUtil = new MapUtil(row, col);
map = mapUtil.getMap();
map[manX][manY] = MAN;
manIndex = 0;
direction = DOWN;
getPics();
setPreferredSize(new Dimension((col*2+1)20,(row2+1)*20));
addKeyListener(this);
setFocusable(true);
}
private void getPics() {
for(int i=0;i<20;i++){
pics[i] = Toolkit.getDefaultToolkit().getImage(“D:/Game/MazeGameEasy/pic”+i+".png");
}
}
public void paint(Graphics g){
g.clearRect(0, 0, getWidth(), getHeight());
for(int i=0;i<2*row+1;i++){
for(int j=0;j<2*col+1;j++){
if(map[i][j]==WALL){
g.drawImage(pics[0],leftX+20*j, leftY+20*i, 20, 20, this);
}else if(map[i][j]==MAN){
switch(direction){
case UP:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[3+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
case DOWN:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[7+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
case LEFT:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[11+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
case RIGHT:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[15+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
}
}else if(map[i][j]==BLANK){
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
}else{
g.drawImage(pics[2],leftX+20*j, leftY+20*i, 20, 20,this);
}
}
}
}
@Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_UP:
if(map[manX-1][manY]!=WALL){
if(map[manX-1][manY]==EXIT){
JOptionPane.showMessageDialog(this, “pic0”);
return ;
}
if(direction==UP){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=UP;
}
map[manX][manY] = BLANK;
manX -= 1;
map[manX][manY] = MAN;
}
;break;
case KeyEvent.VK_DOWN:
if(map[manX+1][manY]!=WALL){
if(map[manX+1][manY]==EXIT){
JOptionPane.showMessageDialog(this, "pic0");
return ;
}
if(direction==DOWN){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=DOWN;
}
map[manX][manY] = BLANK;
manX += 1;
map[manX][manY] = MAN;
}
;break;
case KeyEvent.VK_LEFT:
if(map[manX][manY-1]!=WALL){
if(map[manX][manY-1]==EXIT){
JOptionPane.showMessageDialog(this, "pic0");
return ;
}
if(direction==LEFT){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=LEFT;
}
map[manX][manY] = BLANK;
manY -= 1;
map[manX][manY] = MAN;
}
;break;
case KeyEvent.VK_RIGHT:
if(map[manX][manY+1]!=WALL){
if(map[manX][manY+1]==EXIT){
JOptionPane.showMessageDialog(this, "pic0");
return ;
}
if(direction==RIGHT){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=RIGHT;
}
map[manX][manY] = BLANK;
manY += 1;
map[manX][manY] = MAN;
}
;break;
}
repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
算法 3:函数名MapUtil
【1】 算法功能
进行测试
【2】 算法基本思想
遍历函数
【3】 算法空间、时间复杂度分析
<1>算法空间复杂度分析:简单
<2>算法时间复杂度分析:算法的所有性能之间都存在着或多或少的相互影响。因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。算法的时间复杂度和空间复杂度合称为算法的复杂度。
【4】 代码逻辑(可用伪代码描述)
public class MapUtil {
Union unionUtil;
ArrayList list;
ArrayList map;
int row;
int col;
public MapUtil(int row,int col){
this.row = row;
this.col = col;
unionUtil = new Union(row*col);
list = new ArrayList();
map = new ArrayList();
init();
}
public void init(){
for(int i=0;i<col-1;i++){
list.add(new Edge(i,i+1,(int) (Math.random()*100000)));
}
for(int i=0;i<row-1;i++){
list.add(new Edge(i*col,(i+1)*col,(int) (Math.random()*100000)));
}
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
list.add(new Edge(i*col+j-1,i*col+j,(int) (Math.random()*100000)));
list.add(new Edge((i-1)*col+j,i*col+j,(int) (Math.random()*100000)));
}
}
Collections.sort(list,new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
if(o1.value<o2.value)
return -1;
else
return 1;
}
});
int count = 0;
for(Edge edge:list){
int start = edge.start;
int end = edge.end;
if(!unionUtil.isConnected(start, end)){
unionUtil.union(start, end);
map.add(edge);
count++;
if(count==col*row-1)
break;
}
}
}
public int[][] getMap(){
int[][] array1 = new int[2row+1][2col+1];
for(int i=1;i<2row+1;i+=2){
for(int j=1;j<2col+1;j+=2){
array1[i][j] = -1;
}
}
for(int i=1;i<2*row+1;i+=2){
for(int j=2;j<2*col+1;j+=2){
array1[i][j] = 1;
}
}
for(int i=0;i<2*row+1;i+=2){
for(int j=1;j<2*col+1;j++){
array1[i][j] = 1;
}
}
for(int i=0;i<2*row+1;i++){
array1[i][0] = 1;
}
}
for(Edge edge:map){
int start = edge.start;
int end = edge.end;
if(Math.abs(start-end)==1){
int x = start/col;
int y = start%col;
array1[2*x+1][2*y+2] = -1;
}else{
int x = start/col;
int y = start%col;
array1[2*x+2][2*y+1] = -1;
}
}
for(int i=0;i<2*row+1;i++){
for(int j=0;j<2*col+1;j++){
if(array1[i][j]==-1)
System.out.print(" ");
else
System.out.print("#");
}
System.out.println();
}
array1[2*row][2*col-1] = 2;
return array1;
}
public void print(){
int index = 0;
for(Edge edge:map){
if(index%10==0)
System.out.println();
System.out.print("("+edge.start+","+edge.end+","+edge.value+")");
index ++;
}
}
}
3、程序运行结果分析
开始界面:
结束界面:
4、总结
1、项目的难点和关键点
在这个项目中,难点在于代码的编写,在编写代码过程中最难的还是优先级的完成,图论算法中由任意一点出发遍历整个连通图的其他所有顶点的遍历算法即可找到一条从迷宫入口到迷宫出口的路径。
2、项目的评价
图论算法中由任意一点出发遍历整个连通图的其他所有顶点的遍历算法即可找到一条从迷宫入口到迷宫出口的路径。
3、心得体会
在这次的简易计算机的实现过程中,使我对Java有了更深的理解。
5、实验代码
package maze;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class MapUtil {
Union unionUtil;
ArrayList list;
ArrayList map;
int row;
int col;
public MapUtil(int row,int col){
this.row = row;
this.col = col;
unionUtil = new Union(row*col);
list = new ArrayList();
map = new ArrayList();
init();
}
public void init(){
for(int i=0;i<col-1;i++){
list.add(new Edge(i,i+1,(int) (Math.random()*100000)));
}
for(int i=0;i<row-1;i++){
list.add(new Edge(i*col,(i+1)*col,(int) (Math.random()*100000)));
}
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
list.add(new Edge(i*col+j-1,i*col+j,(int) (Math.random()*100000)));
list.add(new Edge((i-1)*col+j,i*col+j,(int) (Math.random()*100000)));
}
}
Collections.sort(list,new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
if(o1.value<o2.value)
return -1;
else
return 1;
}
});
int count = 0;
for(Edge edge:list){
int start = edge.start;
int end = edge.end;
if(!unionUtil.isConnected(start, end)){
unionUtil.union(start, end);
map.add(edge);
count++;
if(count==col*row-1)
break;
}
}
}
public int[][] getMap(){
int[][] array1 = new int[2row+1][2col+1];
for(int i=1;i<2*row+1;i+=2){
for(int j=1;j<2*col+1;j+=2){
array1[i][j] = -1;
}
}
for(int i=1;i<2*row+1;i+=2){
for(int j=2;j<2*col+1;j+=2){
array1[i][j] = 1;
}
}
for(int i=0;i<2*row+1;i+=2){
for(int j=1;j<2*col+1;j++){
array1[i][j] = 1;
}
}
for(int i=0;i<2*row+1;i++){
array1[i][0] = 1;
}
for(Edge edge:map){
int start = edge.start;
int end = edge.end;
if(Math.abs(start-end)==1){
int x = start/col;
int y = start%col;
array1[2*x+1][2*y+2] = -1;
}else{
int x = start/col;
int y = start%col;
array1[2*x+2][2*y+1] = -1;
}
}
for(int i=0;i<2*row+1;i++){
for(int j=0;j<2*col+1;j++){
if(array1[i][j]==-1)
System.out.print(" ");
else
System.out.print("#");
}
System.out.println();
}
array1[2*row][2*col-1] = 2;
return array1;
}
public void print(){
int index = 0;
for(Edge edge:map){
if(index%10==0)
System.out.println();
System.out.print("("+edge.start+","+edge.end+","+edge.value+")");
index ++;
}
}
}
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
public class GamePanel extends JPanel implements KeyListener{
int[][] map;
MapUtil mapUtil;
int row,col;
int leftX,leftY;
int manX,manY;
int direction;
int manIndex;
Image[] pics;
public static final int BLANK=-1,MAN=0,WALL=1,EXIT=2,UP=1,DOWN=2,LEFT=3,RIGHT=4;
public GamePanel(int row,int col){
this.row = row;
this.col = col;
leftX = 0;
leftY = 0;
manX = 1;
manY = 1;
pics = new Image[20];
mapUtil = new MapUtil(row, col);
map = mapUtil.getMap();
map[manX][manY] = MAN;
manIndex = 0;
direction = DOWN;
getPics();
setPreferredSize(new Dimension((col*2+1)20,(row2+1)*20));
addKeyListener(this);
setFocusable(true);
}
private void getPics() {
for(int i=0;i<20;i++){
pics[i] = Toolkit.getDefaultToolkit().getImage(“D:/Game/MazeGameEasy/pic”+i+".png");
}
}
public void paint(Graphics g){
g.clearRect(0, 0, getWidth(), getHeight());
for(int i=0;i<2*row+1;i++){
for(int j=0;j<2*col+1;j++){
if(map[i][j]==WALL){
g.drawImage(pics[0],leftX+20*j, leftY+20*i, 20, 20, this);
}else if(map[i][j]==MAN){
switch(direction){
case UP:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[3+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
case DOWN:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[7+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
case LEFT:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[11+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
case RIGHT:
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
g.drawImage(pics[15+manIndex], leftX+20*j, leftY+20*i, 20, 20, this);
break;
}
}else if(map[i][j]==BLANK){
g.drawImage(pics[1],leftX+20*j, leftY+20*i, 20, 20,this);
}else{
g.drawImage(pics[2],leftX+20*j, leftY+20*i, 20, 20,this);
}
}
}
}
@Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_UP:
if(map[manX-1][manY]!=WALL){
if(map[manX-1][manY]==EXIT){
JOptionPane.showMessageDialog(this, “pic0”);
return ;
}
if(direction==UP){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=UP;
}
map[manX][manY] = BLANK;
manX -= 1;
map[manX][manY] = MAN;
}
;break;
case KeyEvent.VK_DOWN:
if(map[manX+1][manY]!=WALL){
if(map[manX+1][manY]==EXIT){
JOptionPane.showMessageDialog(this, "pic0");
return ;
}
if(direction==DOWN){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=DOWN;
}
map[manX][manY] = BLANK;
manX += 1;
map[manX][manY] = MAN;
}
;break;
case KeyEvent.VK_LEFT:
if(map[manX][manY-1]!=WALL){
if(map[manX][manY-1]==EXIT){
JOptionPane.showMessageDialog(this, "pic0");
return ;
}
if(direction==LEFT){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=LEFT;
}
map[manX][manY] = BLANK;
manY -= 1;
map[manX][manY] = MAN;
}
;break;
case KeyEvent.VK_RIGHT:
if(map[manX][manY+1]!=WALL){
if(map[manX][manY+1]==EXIT){
JOptionPane.showMessageDialog(this, "pic0");
return ;
}
if(direction==RIGHT){
manIndex=manIndex+1==4?0:++manIndex;
}else{
manIndex=0;
direction=RIGHT;
}
map[manX][manY] = BLANK;
manY += 1;
map[manX][manY] = MAN;
}
;break;
}
repaint();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
public class Union {
int count;
int[] parent;
int[] rank;
public Union(int count){
this.count = count;
parent = new int[count];
rank = new int[count];
for(int i=0;i<count;i++){
parent[i] = i;
rank[i] = 1;
}
}
public int find(int p){
while(parent[p]!=p){
p = parent[p];
parent[p] = parent[parent[p]];
}
return parent[p];
}
public boolean isConnected(int p,int q){
return find§==find(q);
}
public void union(int p,int q){
int pRoot = find§;
int qRoot = find(q);
if(pRoot == qRoot){
return ;
}
if(rank[pRoot]<rank[qRoot]){
parent[pRoot] = qRoot;
}else if(rank[pRoot]>rank[qRoot]){
parent[qRoot] = pRoot;
}else{
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
public class Edge{
int start;
int end;
int value;
public Edge(){
}
public Edge(int start,int end,int value){
this.start = start;
this.end = end;
this.value = value;
}
}
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class GameClient extends JFrame{
public GameClient(){
this.getContentPane().add(new GamePanel(20, 40));
pack();
setTitle(“pic0”);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new GameClient();
}
}