DS课设【坦克大战最短路】
还是决定写点东西简单记录下这次编码。
一、想法
还没放假的时候只想着用C#实现,算法图论方面觉得图论方向会靠谱些,但一直没有什么好点子。C#以前也没学过,自信来源于MFC的学习经历(以前也是用它做了C语言课设)。C#应该是没有MFC那么复杂的,心想看几天应该就可以上手一些小东西了,事实证明也如此。
寒假时间相对以前更长,也并不着急做课设。开始一段是刷题+学习Kinect+顺带了解Kinect,后来在刷题过程中遇到这题,还蛮有意思的,当即就写了个“坦克大战最短路简单设计”:
坦克大战最短路 算法:优先队列+广度优先搜索 用户自定义地图 1. 设置地图大小起点和终点 设置弹药 2. 设置铁墙、土墙、河水 3. 模式一:用户自己走 模式二:自动给出最短路 对比 坦克方法: 1. 调整方向 2. 开枪 事件: 判断是否达到 是否可以走 是否打到墙(子弹能可以穿过河) 游戏是否结束 人性化: 背景音乐 子弹效果 保存地图 当前最短 爆炸效果
二、实现
当时也没有十足把握能实现它,搁置了几天。期间看了一些关于C#项目开发的教学视频,也是跟着例程做了下,心里有底了,首先实现UI是没有问题的。
然后就有了下面这个详细设计文档:
一、界面 1、地图 大小 12*12 每个方格 边长60个像素 左上角为原点(0,0) 2、对象 坦克(Tank) 砖块(Brick) 1次打破 钢墙(Steel) 2次打破 河水(River) 子弹可以穿过,坦克不可以 星星(Star) 子弹(Bullet) 3、菜单 添加对象 手动模式 自动模式 4、显示 当前消耗 帮助 二、类设计 1.游戏父类 GameObject 属性: x,y,width,height,image 方法: 构造函数(初始化) Draw(); GetRectangle(); 2.墙壁父类 WallFather:GameObject 属性: +life 方法: 构造函数(初始化) 重写Draw(); IsOver(); 3.River&Star父类 RSFather:GameObject 方法: 构造函数(初始化) 重写Draw(); 4.Bullet :GameObject 属性: +power 方法: 构造函数(初始化) 重写Draw(); 5.Tank:GameObject 属性: 方法: 重写Draw(g); Move(); Fire(); 6.SingleObject 方法: GetSingle(); Draw(); Check(); 三、事件响应
于是照着这个,一个一个类的实现了。在这个过程中,颇有面向对象的意思在里面,以前写MFC程序的时候感触没这么明显。
1.新建一个VC++ win32 DLL 2.添加头文件"" 在头文件中加入以下代码: #ifndef DLL_EXPORT #define DECLDIR __declspec(dllimport) #else #define DECLDIR __declspec(dllexport) #endif 3.添加源程序,添加以下代码: #define DLL_EXPORT //先定义宏 #include "Dll.h"//这个头文件必须在#define DLL_EXPORT后面 4.编写自己需要的函数: extern "C" { DECLDIR 返回值 函数名(参数...) { } } 5.添加必要的头文件,不需要main函数,编译即可 6.建立一个C#工程,将生成的Dll文件(在Debug目录下)扔到C#工程Bin 目录下。 7.在C#中添加如下代码: (1). 命名空间 using System.Runtime.InteropServices; (2).声明 [DllImport("CppDll.dll", SetLastError = true)] private static extern 返回值 函数名(参数...); 8.然后这个函数就可以直接在C#工程中使用了
这期间,特别要注意的是C++和C#之间数据类型的对应关系!! 这里着实费了一番功夫。
附上C++与C#数据类型对照表:
C++ C# ===================================== WORD ushort DWORD uint UCHAR int/byte 大部分情况都可以使用int代替,而如果需要严格对齐的话则应该用bytebyte UCHAR* string/IntPtr unsigned char* [MarshalAs(UnmanagedType.LPArray)]byte[]/?(Intptr) char* string LPCTSTR string LPTSTR [MarshalAs(UnmanagedType.LPTStr)] string long int ulong uint Handle IntPtr HWND IntPtr void* IntPtr int int int* ref int *int IntPtr unsigned int uint COLORREF uint
这个很实用。
然后我把以前的代码做了简单修改就可以了,不过只能是把最短的路径长度算出来,还没有记录路径的功能。
我C++的代码中是直接调用STL提供的“优先队列”实现的BFS,这样记录路径稍微麻烦点~ 后来是想着在结点专门开个变量记录路径。最后代码被我改成了这个样子[变量名什么的不大规范]:
#include<iostream> #include<algorithm> #include<string> #include<cstring> #include<queue> #define N 15 #define Max_Len 100 #define DLL_EXPORT //先定义宏 #include "Dll.h"//这个头文件必须在#define DLL_EXPORT后面 using namespace std; int n, m, sx, sy, ex, ey, visit[N][N]; int dir[][2] = { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } }; char chess[N][N]; enum TankAcationType { //Move MoveStep, //Fire Move FireOneWall, //Fire Fire Move FireTwoWall, //Dir->Up Move ChangeTheDirToUp, //Dir->Up Fire ChangeToUpFireOne, //Dir->Up Fire Fire ChangeToUpFireTwo, //Dir->Down Fire ChangeToDownFireOne, //Dir->Down Move ChangeTheDirToDown, //Dir->Down Fire Fire ChangeToDownFireTwo, //Dir->Left Move ChangeTheDirToLeft, //Dir->Left Fire ChangeToLeftFireOne, //Dir->Left Fire Fire ChangeToLeftFireTwo, //Dir->Right Move ChangeTheDirToRight, //Dir->Right Fire ChangeToRightFireOne, //Dir->Right Fire Fire ChangeToRightFireTwo, None } TmpType; TankAcationType TmpPath[Max_Len]; struct Node{ int x, y, s,d; int step; TankAcationType Path[Max_Len]; friend bool operator <(Node a, Node b){ return a.s>b.s; } }; bool ok(int x, int y){ if (x >= 0 && x<m&&y >= 0 && y<n&&chess[x][y] != 'R'&&chess[x][y] != '#') return true; return false; } extern "C" { DECLDIR int bfs( char * Map,TankAcationType * ActionPath,int &Count){ m = 15; n = 15; for (int i = 0; i < 15; i++) for (int j = 0; j < 15; j++) chess[i][j] = '#'; for (int i = 0; i < 12;i++) for (int j = 0; j < 11; j++) chess[i][j] = '.'; for (int i = 0; i<n; i++) for (int j = 0; j < m; j++){ if (Map[i*m + j] == '#'&&chess[i][j] == '.')continue; chess[i][j] = Map[i*m + j]; } for (int i = 0; i<m; i++) for (int j = 0; j<n; j++){ if (chess[i][j] == 'T'){ sx = i; sy = j; chess[i][j] = 'R'; } else if (chess[i][j] == 'X'){ ex = i; ey = j; chess[i][j] = '.'; } } priority_queue<Node> q; memset(visit, -1, sizeof(visit)); visit[sx][sy] = 0; Node head = { sx, sy, 0 ,1,0}; head.d = 1; q.push(head); while (!q.empty()) { Node f = q.top(); q.pop(); if (f.x == ex&&f.y == ey){ for (int i = 0; i < f.step; i++) ActionPath[i] = f.Path[i]; Count = f.step; return f.s; } for (int i = 0; i<4; i++){ int dx = f.x + dir[i][0], dy = f.y + dir[i][1]; if (ok(dx, dy) && visit[dx][dy]){ visit[dx][dy] = 0; int temp = 0; if (chess[dx][dy] == 'S'){ temp = 3; if (f.d==i) TmpType = FireTwoWall; else { temp++; switch (i) { case 0: TmpType = ChangeToDownFireTwo; break; case 1: TmpType = ChangeToUpFireTwo; break; case 2: TmpType = ChangeToRightFireTwo; break; case 3: TmpType = ChangeToLeftFireTwo; break; } } } else if (chess[dx][dy] == 'B'){ temp = 2; if(f.d==i)TmpType = FireOneWall; else { temp++; switch (i) { case 0: TmpType = ChangeToDownFireOne; break; case 1: TmpType = ChangeToUpFireOne; break; case 2: TmpType = ChangeToRightFireOne; break; case 3: TmpType = ChangeToLeftFireOne; break; } } } else if (chess[dx][dy] == '.') { temp = 1; if(f.d==i)TmpType = MoveStep; else { temp++; switch (i) { case 0: TmpType = ChangeTheDirToDown; break; case 1: TmpType = ChangeTheDirToUp; break; case 2: TmpType = ChangeTheDirToRight; break; case 3: TmpType = ChangeTheDirToLeft; break; } } } for (int k = 0; k < f.step; k++) TmpPath[k] = f.Path[k]; TmpPath[f.step] = TmpType; Node tmp = { dx, dy, f.s + temp}; for (int k = 0; k <= f.step; k++) tmp.Path[k] = TmpPath[k]; tmp.step = f.step + 1; tmp.d = i; q.push(tmp); } } } return -1; } }
70行变200行,吓尿【一些代码有点傻,懒得改了】
C#显示路径那段代码是这个样子的:
#region AutoRun 方法 public static void AutoRun() { for (int i = 0; i <TotalStep; i++) { switch (GameObject.AcationPath[i]) { case TankAcationType.ChangeTheDirToUp: MessageBox.Show("向上"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Up; MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeTheDirToDown: MessageBox.Show("向下"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Down; MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeTheDirToLeft: MessageBox.Show("向左"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Left; MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeTheDirToRight: MessageBox.Show("向右"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Right; MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.FireOneWall: MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.FireTwoWall: MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.MoveStep: MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToDownFireOne: MessageBox.Show("向下"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Down; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToDownFireTwo: MessageBox.Show("向下"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Down; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToLeftFireOne: MessageBox.Show("向左"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Left; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToLeftFireTwo: MessageBox.Show("向左"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Left; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToRightFireOne: MessageBox.Show("向右"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Right; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToRightFireTwo: MessageBox.Show("向右"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Right; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToUpFireOne: MessageBox.Show("向上"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Up; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; case TankAcationType.ChangeToUpFireTwo: MessageBox.Show("向上"); GameObject.Cost++; SingleObject.GetObject().T.Dir = Direction.Up; MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("开火"); SingleObject.GetObject().T.Fire(); MessageBox.Show("向前"); SingleObject.GetObject().T.Move(); break; } } } #endregion
这个过程中出现了好几个隐蔽的bug,大大小小——有些东西根本无法依靠debug找出来。。。。
这些东西完成之后就剩下一些细节了【“新游戏”启动界面 什么的】 之前想过的“导入地图”等功能现在还不想动了【应该可以利用文件操作实现吧】,开学再慢慢研究*_*
三、类图[在新标签页打开查看大图]
、
四、效果
【转载请注明出处】
作者:MummyDing
出处:http://blog.csdn.net/mummyding/article/details/43965923
2015年2月27日