解决方案资源管理器・PushBox ®蝇巫固ム故H解决方案“PushBox”(T个项目フPushBoxみ Properties引用Common图 Block, cs 二名 ConfigFile. cs :q DataFile.cs 用 Env. cs@ Fcl.cs些 FindPath. cs 些 Pub. cs 叫 Step.csImagesif] PushBox. ico* PushBoxl2. bmp“ PushBoxl6. bmp カイ PushBox20. bmp S PushBox24. bmp q WindowAboutDlg. csConfi gDlg. csDesi gnDlg. csErrorMsgDlg. csMainForm. Common, cs最近,使用C#开发了一款智能软件:推箱子先介绍ー下这款软件的特点:1 .可以在智能上运行,也可以在计算机上运行2 .退出程序时可保护现场,下次再运行自动恢复到原来的状态3 .玩家通关后可以使用、、衆像”功能保存通关步骤,以便将来''回放"4 .可以自由设计关卡,批量进行数据导出和导入如下闇的''解决方案资源管理器"所示,该程序的源程序主要分布在 "Window"和''Common"两个文件夹中。
其中''Window"文件夹存放的是程序 主窗体和各个对话框的源代码而''Common"文件夹存放的是公用的源代码, 包括各种数据结构,寻找最短路线的算法,读写配置文件和数据文件等我将在随后的文章中详细介绍各个源程序文件I MainForm. csI MainForm. Design, I MainForm. Replay. I OptionDlg. csコ SelectGroupDlg. ) _J SelectLevelDlg. । n TopicDlg. csコ TranDlg. cs changes, txt Program, cs PushBox. ico readme, txt..CSCSCS解决方案资源管理器メ索引珞‘莫于一推箱子 二マベkskyiv (R)推箱子版本 2.0 (Build: 2007-08-17 11:29:48)(C) Skyiv Studio (ben) skyivben® 推箱子是一款绿色软件,使用C4开发,基 于 Microsoft .NET Compact Framework 2.0 〇运行环境为使用Microsoft Windows Mobile 6.0的智能,也可以在安装有 Microsoft .NET Framework 2.0 运行库的计 算机上运行。
版本茶扰对了,推箱子程序的下载地址为:僧关干ー推箱子版本W0’操作系统标识:Win32NT操作系统版本:Microsoft Windows NT 5. 1.2600 Service Pack 2 公共语言运行库版本:2.〇. 50727.832屏幕分辨率:1024x768程序位置:C:/bin/PushBox/PushBox. exeヽ 7/100 10x8 3/5 67(14) [102(26)!游戏世 菜单・へ♦▲▼・回放数据 ►帮助 ,退出七ー321 U联想菜单=へ+ ▲▼«回放下帮助17推箱子状态栏各项依次为:>:正常I:回放+:新建=:编揖当前关数/总关数当前关宽度X高度已完成/总任务数或箱子=槽数 总步数(推箱子步数)或画笔[通关的总步数(通关的推箱子步数)] 当前组名称正常模式下菜单栏按钮依次为: 二:重玩へ:撤消+:后退 ▲:前关▼后关・:录像注意:当工人能够推着箱子到达目的地时, 总是优先推着箱子.如果需要绕过箱子,请 免往易边移あー木.入门港阶高级专家4’选美プ推駐一ご”《ヌ确定 取消可选的关数是从1到100请输入要玩的关数133( 圧0 0新建注意:只熊删除最后一关 〇编辑32确认删除第5723 关 徳删除最大关尺寸0保护现场错转损-推箱子 =“イタ**编辑]丨 导出 丨[导入konkakonka.bxb => konka.bxa组名:[康佳]#:宽x高总任务数通关步数1: 8x7 4 492: 8x7 4 03: 9x5 2 84: 12x10 14 283导出完就ゴ“セ][保存 丨]放弃konka!康佳II]####+xx-##-X4 式—— ###,正在编辑,不能退出。
请先“ 保存"或"放弃"LBBBBEEEEDRLCCCDDEEQOBRNDCCD DETJNEQGNCCCCBBEESIMQ国#####28/100 12x7 5=6槽[编揖]联想菜单保存放弃箱槽墙鶴地在上篇文章”使用C#开发智能软件:推箱子(一)'’中,我对推箱子程序作了总体介绍这 次,我先介绍Common/Fcl.cs源程序文件1 using System;2 using System.IO;3 using System.Drawing;45 namespace Skyiv.Ben.PushBox.Common6{7 /// 8 II! 这里是.NET Framework 支持,而.NET Compact Framework 不支持的东东9 III 10 static class Fcl11 {12 〃/ 13 〃/获取为此环境定义的换行字符串一 Environment14 m 15 public static string NewLine { get { return "\r\n"; } }1617 III 18 〃/打开一个文本文件,将文件的所有行读入•个字符串,然后关闭该文件。
File19 HI 20 HI 要写入的文件v/param>〃/ III
WinCE 操作系统为Windows CE.PlatformID.Unix并不被.NETCF所支持这实在是一件很奇怪的事,既然.NETCF都 支持 PlatformID 的 Win32NT、Win32s、Win32Windows、WinCE 成员,为什么就不能 支持Unix成员呢?这样,这个程序将来要移植到Linux操作系统时就有些小麻烦了要知道,这在主窗体的代码中用以下一段代码来实现在智能上禁用、、前端显示’’功能public partial class MainForm : Form
1 namespace Skyiv. Ben. Push Box. Common2{3 III 4 ///基本单元格:地槽墙砖箱子工人5 HI 6 static class Block78910111213141516171819202122232425262728293031323334353637383940public const byte Land = 0; /Z 地public const byte Slot = 1; /Z 槽public const byte Wall = 2; 〃墻public const byte Brick = 3; /Z砖:等同于墙,一般放在墙的外围public const byte BoxO = 4; // 箱子放在地 hpublic const byte Boxl = 5; // 箱子放在槽上public const byte ManO = 6; /Z 工人站在地上public const byte Mani = 7; /Z 工人站在槽上const string mask = "-+#%xX()”; 〃(・.bxa)文件用,依次代表以上各项public static string GetPenName(byte block)(return ”地槽墙砖箱箱人人”[block & 0x07].ToString();}public static char GetChar(ushort block)(return mask[block & 0x07];}public static byte GetByte(char block){return (byte)mask.IndexOf(block);?public static bool IsOk(ushort block){return block <= Mani;}public static void CleanAIIMark(ushort[z] bb)41424344454647484950515253545556575859606162636465666768697071727374for (int i = 0; i < bb.GetLength(O); i++)for (int j = 0; j < bb.GetLength(l); j+ + )bb[i, j] &= 0x07;}public static void Mark(ref ushort block, int value)block |= (ushort)(value << 3);}public static int Value(ushort block)return block >> 3;)public static void Update(ref ushort block, byte pen)if (IsSlot(block) && pen == Block.ManO) pen = Block.Mani;if (IsSlot(block) && pen == Block.BoxO) pen = Block.Boxl; block = pen;}public static void Manln(ref ushort block){block += (ManO - Land);}public static void ManOut(ref ushort block){block -= (ManO - Land);}public static void Boxln(ref ushort block)75 block += (BoxO - Land);76 }7778 public static void BoxOut(ref ushort block)79 {80 block -= (BoxO - Land);81 }8283 public static bool IsSlot(ushort block)84 {85 return block == Slot 11 block == Boxl 11 block == Mani;86 }8788 public static bool IsBlank(ushort block)89 (90 return block == Land 11 block == Slot;91 }9293 public static bool IsBox(ushort block)94 {95 return block == BoxO 11 block == Boxl;96 }9798 public static bool IsMan(ushort block)99 {100 return block == ManO 11 block == Mani;101 }102 }103 }104静态类Block用来表示基本单元格:空地、槽(箱子最终要存放的目的地)、墙、砖(在本程 序中等同于“墻,,,一•般放在墙的外围,使图形看起来漂亮些)、箱子、工人。
其中、、箱子”和、、工人〃 都可以位于、、空地〃或、、槽〃上,所以总共有八种状态,用〇到7表示,总共只需要三个二进位, 可以放入一个字节中在数据文件(・・bxb)中,每个基本单元格就是用ー个字节储存的,这在以 后介绍的Common/DataFile.cs源程序文件中会看到但是为什么静态类Block的大多数方法的参数都是ushort类型呢?这是为了寻找工人最短移动路线算法的需要,看了下ー篇介 绍Common/FindPath.cs源程序文件的文章就会明白了这个类还是比较简单的,现简要说明如下:GetPenName方法返回在设计关卡时所用画笔的名称Update方法用来在设计关卡时更新地图中的基本单元格GetChar方法返回将数据文件(data/*.bxb)导出为文本文件(text/*.bxa)所用的字符GetByte方法返回将文本文件(text/*.bxa)导入为数据文件(data/*.bxb)所用的字节IsOk方法判断表示基本单元格的字节是否合法,也用在数据导入时Mark方法在寻找工人最短移动路线算法中用来标记已经搜索过的基本单元格 CleanAIIMark方法在上述算法结束时用来清除地图中的所有基本单元格的标记。
Value方法返回上述算法搜索过程中所作的标记Manin、ManOut, Boxin, BoxOut方法用来更新推箱子过程中地图各基本单元格的状态IsSlot, IsBlank. IsBox, IsMan方法用来判断各基本单元格的类型这是、、使用C#开发智能软件:推箱子’’系列文章的第四篇在这篇文章中,介绍Common/FindPath.cs 源程序文件using System;using System.Drawing;using System.Collections.Generic;namespace Skyiv. Ben. Push Box.Common{/// //Z寻找最短路线/// static class FindPathIll寻找最短路线III III < pa ram name="map”> 地图 v/param>III < pa ram name="from”> 出发点 v/param>III < pa ram name=”to”> 目的地 v/param>III vreturns〉最短路线v/retums>public static Queue Seek(ushort[,] map, Point from, Point to){Queue moveQueue = new Queue(); /Z 路线int value; /Z离目的地距离if (Seek(map, to, out value)) /Z 找到了 •条路线{Point here = from; /Z出发点(即工人的位置)Point nbr = new Point(); /Z 四周的邻居for (value—; value > 0; value—) /Z 逐步走向目的地{for (int i = 0; i < offsets.Length; i++){nbr = Fcl.Add(here, offsets[i]); /Z 开始寻找四周的邻居if (Block.Value(map[nbr.Y, nbr.X]) == value) /Z 就往这个方向走{moveQueue.Enqueue(directions[i]); /Z 路线向目的地延伸,歩 break;}}here = nbr; /Z继续前进?}Block.CleanAIIMark(map); 〃清除所有标志,恢复现场return moveQueue; /Z所寻找的路线,如果无法到达目的地则为该路线的长度为零 }III III寻找最短路线,使用广度优先搜索Ill III < pa ram name="map”> 地图 v/param>III < pa ram name="to”> 目的地 v/param>III < pa ram name="value”> 输出:路线的长度(加 l)II!〈returns〉是否成功〈/returns〉static bool Seek(ushort[z] map, Point to, out int value)Queue q = new Queue();Block.Mark(ref map[to.Y, to.X], 1); /Z从目的地开始往回寻找出发点,目的地标记为 1Point nbr = Point.Empty; /Z 四周的邻居for (;;)
她采用一种广度优先搜索算法,使用循环,没有使用递归,而且地图上已经搜索过的路线决不再走第二遍 该算法分两个阶段进行:首先是寻找并标记最短路线,由该类的第二个Seek方法实现,这个 私有的方法返回一个布尔值表明是否成功然后,如果在第一阶段中找到了一条路线,则根据第 ー阶段所做的标记生成最短路线并将该路线返回给调用者我们来看儿个实例:▲ ▼・回放;5 ggggggll 12 1314I 8 q to~~tt~~1- 土,-1□1 x|▲ ▼ 回.放131415> 6/6 12x12 0/3 0(0) [47 (10)]康佳10/100 8x7 0/5 0(0) [38 03)]游戏世―/14 15在该算法中,是从要到达的目的地开始往回寻找出发点首先,将目的地标记为!,然后查 看周围的四个邻居(按南、东、北、西的顺序)是否是、、空白”(即、、地”和、、槽”,使用Block.IsBlank 方法来判断),如是,则表明这是可以走的路,将其作上标记(使用Blockmark方法,标记的 数值等于离开目的地的距离加一),然后加入队列这有两个作用,首先,标记过的单元格将不 再被认为是可以走的路,防止重复搜索。
其次,在第二阶段中要根据标记的值来生成最短路线 如果发现周围的邻居中有一个是工人(用Block.IsMan方法来判断),说明到达出发点,则立即 结束搜索,退出循环,返回成功否则,就检査队列是否为空,如果为空,则说明无法到达出发 点,返回失败如果不为空,则出队,从这一点继续开始搜索如此一直循环这个算法是广度优先的,如上面的两个图所示,该算法是按标记的值从小到大进行遍历的, 而该标记的值表示的是离开目的地的距离加…第二个阶段,如果在第一阶段返回失败,则返回一条空的路线(长度为零)给调用者否则, 从出发点(即工人的位置)开始,查看周围的四个邻居(按南、东、北、西的顺序),如果其标记的 值(使用Block.Value方法来获得)为到目的地的距离加一(至少可以找到ー个,可能有多个, 可以任取ー个,程序中使用第一个),就往这个方向走如此一直循环,直到到达目的地然后 返回这条路线给调用者从这里可以看出,为什么地闇(即ushortし]map)要使用ushort而不使用byte„因为在 该算法需要在地图中作标记,而且标记的值还必须是到目的地的距离加一(如果只须判断目的地 是否可达,而不要求给出到达目的地的具体路线,则在算法中标记的值可全部都为1,这样用 byte就足够了)。
地图中总共有八种类型的单元格,需要用三个二进位表示而byte只有ハ 个二进位,那么,只剩下五个二进位,25=32,也就是说,目的地在工人32步以外该算法就无 能为カ了而ushort有十六个二进位,减去三个,还有十三个二进位,213=8192,这应该 足够了让我们看看下图吧:这是一个9x9的地闇,共有81个单元格,其中49个是空地,假设目的在地图的右上角(标 记为1的地方),则工人需要48步才能到达目的地根据计算,如果是NxN (这里N是奇数) 的地闇,工人在最坏的情况下需要(N2 - 1)/2 + N -1步(走最短路线)才能到达冃的地这就 是说,在127x127的地图上,工人最多只需要8190步就可以到达目的地,这刚好在我们算 法的范围之内如果地闇再大,我们的算法就可能(只是可能,因为在大地图上一般情况下并不 会出现超过8192步的最短路线)无能为カ了这是"使用C»开发智能软件:推箱子'’系列文章的第五篇在这篇文章中,介绍经过改进 后的Common/FindPath.cs源程序文件也就是说,已经实现了、、使用C・开发智能软 件:推箱子(四)"的第二个评论中的想法,将地图ushort[,] map改为byte[,] map 了。
下 面就是改进后的FindPath类:1 using System;2 using System.Drawing;3 using System.Collections.Generic;45 namespace Skyiv.Ben.PushBox.Common6{7 /// 8 〃/寻找最短路线9 HI 10 static class FindPath11 (12 static Size[] offsets = { new Size(0z 1), new Size(l, 〇), new Size(〇,-1), ne w Size(-1, 0) };13 static Direction[] directions = { Direction.South, Direction.East, Direction.N orth, Direction.West };1415 /// 16 III寻找最短路线17 /// 18 /// 出发点v/param>20 /// 地图v/param>52 /// 目的地v/param>53 /// 10 〃/公共的字段和方法11 III 12 static class Pub13 {14 public const int OverY = 4; 〃允许在屏幕(Y)方向超过的像素数15 public const int DefaultMaxLevelSize = 32; // 缺省的最大关尺寸(宽度和高度)16 public const int DefaultStepDelay = 100; /Z 缺省移动时间间隔(や杪)17 public const int DefaultReplayDelay = 300; // 缺省回放时间间隔(毫秒)18 public const int MaxDelay = 1000; // 允许的最大时间间隔(毫秒)19 public readonly static string ConfigFileName = Path.Combine(baseDirectory ,"PushBox.cfg"); //配置文件全路径名20 public readonly static Encoding Encode = Encoding.GetEncoding("GB2312H) ;// Windows Mobile 6.0 不支持 GB1803021 static string baseDirectory { get { return Path.GetDirectoryName(Pub.Code Bases); } }/Z本程序所在的目录2223 static Assembly Assembly { get { return Assembly.GetExecutingAssembly( );}?24 static AssemblyName AssemblyName { get { return Pub.Assembly.GetNam e(); } ?25 public static Version Version { get { return Pub.AssemblyName.Version; }} /Z本程序的版本26 public static string TextDi rectory { get { return Path. Com bi ne( baseDirector y, "text"); } }27 public static string DataDirectory { get { return Path.Combine(baseDirector y, “dataり;} }28 public static string StepsDirectory { get { return Path.Combine(baseDirecto ryz "steps"); } }29 public const string TextExtName = ".bxa"; // 文本文件扩展名30 public const string DataExtName = ".bxb"; /Z 数据文件扩展名31 public const string StepsExtName = ".bxs"; /Z 通关步骤文件扩展名3233 III 34 HI本程序的全路径名35 HI 36 public static string CodeBases37 (38 get39 {40 string CodeBase = Pub.AssemblyName.CodeBase;41 string uri = "file:///";42 if (CodeBase.StartsWith(uri)) CodeBase = CodeBase.Substring(uri.Length);43 return CodeBase;44 }45 }4647 III 48 HI给出指定尺寸的显示字符串,格式为:宽x高49 III 50 III 51 III〈returns,指定尺寸的显示字符串〈/returns,52 public static string ToString(Size size)53 {54 return size.Width + "x" + size.Height;5657 III 58 III将走法步骤转换为字符串59 HI 60 III 指定的版本〈/param>75 HI〈returns〉指定版本的信息〈/returns〉76 public static string GetVersionBuildString(Version version)77 {double days = version.Build + 2 * version.Revision / ((double)TimeSpan. TicksPerDay / TimeSpan.TicksPerSecond);79 return string.Format("{0} (Build: {1})", version.ToString(2)z (new DateTi me(2000z 1, l)).AddDays(days).ToString(Hyyyy-MM-dd HH:mm:ss"));80 }8182 /// 83 〃/给出指定异常的信息,包含其内含异常的信息84 HI 85 ///
ConfigFileName只读字段返回配置文件(PushBox.cfg)的全路径名TextDirectory只读属性返回文木文件(・.bxa)所在目录(text)的绝对路径Data Di rectory只读属性返回数据文件(・.bxb)所在冃录(data)的绝对路径StepsDirectory只读属性返回通关步骤文件(・.bxs)所在目衆(steps)的绝对路径注意,Windows CE操作系统不具有当前目录功能,Directory.GetcurrentDirectory方 法在,NET Compact Framework中可用,但是当前并不支持,调用该方法会抛出ー个 NotSupportedException异常在Windows CE下编程,所有的文件名都是从智能的 根目录算起的,所以在Pub静态类给出了以上全路径名我提供下载的zip文件中包括以下内容:PushBox.exe 推箱子程序PushBox.cfg 配置文件data/*.bxb 数据文件steps/*, bxs 通关步骤其实只有PushBox.exe就完全可以玩推箱子游戏了,只不过所有的关卡都要自己设计运行时如下所示:然后,点击、、菜单ー> 数据ー〉配置”:再点击、、菜单ー> 数据ー> 转换”:程序就会自动生成所需的配置文件和数据文件,画面就正常了:然后就可以点击'、菜单ー> 数据ー>设计”,随心所欲地设计关卡了。
GetVersionBuildString方法给出指定。