这是“使用 C# 开发智能手机软件:推箱子”系列文章的第十篇。在这篇文章中,介绍 Common/DataFile.cs 源程序文件。这个源程序文件中包含密封类 DataFile,用来管理数据文件。

    上图是数据文件 konka.bxb 的结构图。该数据文件大小为 297 字节,包含三个关卡,各个关卡的大小分别为:“8x7”、“8x7”和“9x5”。内容如下:
    1. 文件头(32字节,图中的青色部分)。首先是保留的四个字节。然后是一个字节的数据文件版本号(目前为“2”)。接着是三个字节的标志(内容为“BOX”)。接着是十六字节的组名(编码为“GB2312”,本数据文件中为“康佳”)。接着是总关数(Int32,四个字节,因为本组共有三个关卡,所以内容为“3”)。最后是第一关起始地址位置(Int32,四个字节,本数据文件中的内容为“0x11D”)。
    2. 以关数据头(32字节,图中的绿色部分)开始的各关数据。首先是一个字节的关数据头开始标志(“@”)。然后是一个字节的标志(最低位:0:未通关,1:已通关)。接着是通关总步数(Int32,四个字节)。接着是通关推箱子步数(Int32,四个字节)。接着是十四个字节的保留字段。接着是本关的宽度(Int32,四个字节)。接着是本关的高度(Int32,四个字节)。最后是“本关宽度x本关高度”个字节的关数据,也就是说地图中每个单元格占一个字节,取值范围是“0”到“7”,分别表示:0:地,1:槽,2:墙,3:砖,4:箱子放在地上,5:箱子放在槽上,6:工人站在地上,7:工人站在槽上。注意,每一个关卡必须刚好有一个工人。
    3. 数据文件的最后是各关起始地址列表(“总关数x4”个字节,图中的黄色部分)。每关的起始地址均为四个字节(Int32),所以共有“总关数x4”个字节。
    密封类 DataFile 的源代码中有详细的注释,很容易看懂。
    1. InitMap 方法用来初始化地图。地图的大小是“(关的高度+2) x (关的宽度+2)”,这是为了在地图四周砌上围墙,以免搜索算法越出地图边界。
    2. DeleteLevel 方法用来删除指定的关。注意,删除关时并不删除关数据,只是将该关的起始地址从各关地址列表中删除,然后将文件缩短四个字节(因为各关地址列表在数据文件的最后)。这样数据文件中就可能包含不需要的冗余数据。通过“菜单 -> 数据 -> 转换”,先“导出”,然后“导入”,可以消除冗余数据。

  1 using System;
  2 using System.IO;
  3 using System.Drawing;
  4 using System.Collections.Generic;
  5 using System.Windows.Forms;
  6 
  7 namespace Skyiv.Ben.PushBox.Common
  8 {
  9   // data/<group>.bxb 文件格式
 10   // 保留 ver(2) BOX 组名- 总关数 第1关起始地址位置
 11   // 0--3 4----- 5-7 8--23 24--27 28-------------31
 12   //
 13   // @ Flag 总步数 推箱子步数 保留- wide- high- data
 14   // 0 1--- 2----5 6--------9 10-23 24-27 28-31 32..
 15   // Flag: 最低位: 0:未通关 1:已通关
 16   //
 17   // 第1关起始地址 第2关起始地址 . 最后一关起始地址
 18   // 0-----------3 4-----------7 . (文件最后四字节)
 19   //
 20   // steps/<group><level>.bxs 文件格式见 Step.cs
 21   // 其中<level>为关数(1起始),最少四位,不足前补零
 22   //
 23   // text/<group>.bxa 文件格式
 24   // 0 - land             SPACE
 25   // 1 + slot             .
 26   // 2 # wall             #
 27   // 3 % brick            N/A
 28   // 4 x box on land      $
 29   // 5 X box on slot      *
 30   // 6 ( man on land      @
 31   // 7 ) man on slot      +  .XSB 文件格式
 32   // 第一行如果以!开头的话, 则为组名(不能超过16个字符)
 33   // 以:开头的行为通关步骤, 格式同(.bxs)文件
 34   // 以'开头的行为注释, 完全忽略
 35   // 各关之间必须以空行分隔
 36 
 37   /// <summary>
 38   /// 管理数据文件: *.bxb  *.bxa  *.bxs
 39   /// </summary>
 40   sealed class DataFile : IDisposable
 41   {
 42     const byte DataVersion = 2;       // 数据文件(.bxb)的版本
 43     const byte LevelFlag = (byte)'@'; // 数据文件(.bxb)的关标志
 44     const char RemChar = '\'';        // 文本文件(.bxa)的注释
 45     const char StepsChar = ':';       // 文本文件(.bxa)的通关步骤
 46 
 47     FileStream fs;    // 数据文件基础流
 48     BinaryReader br;  // 数据文件读取器
 49     BinaryWriter bw;  // 数据文件写入器
 50     string groupName; // 当前组名称
 51     int[] addrs;      // 各关起始地址列表,最后一项为第1关起始地址位置
 52     byte[,] map;      // 当前关地图
 53     int maxLevel;     // 总关数
 54     Size levelSize;   // 当前关尺寸(以单元格为单位)
 55     Point worker;     // 当前工人位置(以单元格为单位)
 56     int mans;         // 工人数
 57     int boxs;         // 箱子数
 58     int slots;        // 槽数
 59     int tasks;        // 总任务数
 60     int boths;        // 已完成任务数
 61     bool isFinished;  // 是否曾经通关
 62     int movedSteps;   // 通关的总步数
 63     int pushedSteps;  // 通关的推箱子步数
 64     string fileName { get { return Path.GetFileNameWithoutExtension(fs.Name); } } // 数据文件主名
 65 
 66     public string GroupName { get { return groupName; } }
 67     public int MaxLevel { get { return maxLevel; } }
 68     public byte[,] Map { get { return map; } }
 69     public Size LevelSize { get { return levelSize; } }
 70     public bool IsFinished { get { return isFinished; } }
 71     public int MovedSteps { get { return movedSteps; } }
 72     public int PushedSteps { get { return pushedSteps; } }
 73     public Point Worker { get { return worker; } }
 74     public bool HasWorker { get { return mans != 0; } }
 75     public int Boxs { get { return boxs; } }
 76     public int Slots { get { return slots; } }
 77     public int Tasks { get { return tasks; } }
 78     public int Boths { get { return boths; } set { boths = value; } }
 79 
 80     /// <summary>
 81     /// 装入组数据
 82     /// </summary>
 83     /// <param name="name">组文件名</param>
 84     public void LoadGroup(string name)
 85     {
 86       Dispose();
 87       fs = new FileStream(Path.Combine(Pub.DataDirectory, name + Pub.DataExtName), FileMode.Open);
 88       br = new BinaryReader(fs, Pub.Encode);
 89       bw = new BinaryWriter(fs, Pub.Encode);
 90       br.ReadInt32(); // 保留
 91       if (br.ReadByte() != DataVersion) throw new Exception("数据文件版本错");
 92       byte[] bs = br.ReadBytes(3); // 数据文件标志:BOX
 93       for (int i = 0; i < bs.Length; i++) if (bs[i] != "BOX"[i]) throw new Exception("数据文件标志错");
 94       bs = br.ReadBytes(16); // 组名
 95       for (int i = 0; i < bs.Length; i++) if (bs[i] == 0) bs[i] = 32;
 96       groupName = Pub.Encode.GetString(bs, 0, bs.Length).Trim();
 97       if (groupName.Length == 0) groupName = fileName; // 如果数据文件中组名为空,则用数据文件主名代替
 98       maxLevel = br.ReadInt32(); // 总关数
 99       int addrPos = br.ReadInt32(); // 第1关起始地址位置
100       br.BaseStream.Seek(addrPos, SeekOrigin.Begin);
101       addrs = new int[maxLevel + 1]; // 各关起始地址列表,最后一项为第1关起始地址位置
102       for (int i = 0; i < maxLevel; i++) addrs[i] = br.ReadInt32();
103       addrs[maxLevel] = addrPos; // 第1关起始地址位置
104       if (addrPos + 4 * maxLevel != br.BaseStream.Length) throw new Exception("数据文件地址表必须位于数据最后");
105     }
106 
107     /// <summary>
108     /// 装入关数据
109     /// </summary>
110     /// <param name="level">关数</param>
111     public void LoadLevel(int level)
112     {
113       LoadLevelHead(level);
114       InitMap();
115       for (int i = 1; i <= levelSize.Height; i++)
116       {
117         for (int j = 1; j <= levelSize.Width; j++)
118         {
119           map[i, j] = br.ReadByte();
120           UpdateCounts(j, i, true);
121         }
122       }
123       if (mans != 1) throw new Exception("读取关数据失败:必须刚好有一个工人");
124       tasks = Math.Min(boxs, slots);
125     }
126 
127     /// <summary>
128     /// 新建一关
129     /// </summary>
130     /// <param name="isCopy">是否复制当前关</param>
131     /// <param name="size">新建关的尺寸</param>
132     public void NewLevel(bool isCopy, Size size)
133     {
134       Size levelSizeOem = levelSize;
135       byte[,] mapOem = isCopy ? (byte[,])map.Clone() : null;
136       levelSize = size;
137       InitMap();
138       for (int i = 1; i <= levelSize.Height; i++)
139       {
140         for (int j = 1; j <= levelSize.Width; j++)
141         {
142           map[i, j] = (isCopy && i <= levelSizeOem.Height && j <= levelSizeOem.Width) ? mapOem[i, j] : Block.Land;
143           UpdateCounts(j, i, true);
144         }
145       }
146       if (mans != 1 && mans != 0) throw new Exception("不能超过一个工人");
147       tasks = Math.Min(boxs, slots);
148     }
149 
150     /// <summary>
151     /// 初始化地图
152     /// </summary>
153     private void InitMap()
154     {
155       map = new byte[levelSize.Height + 2, levelSize.Width + 2];
156       for (int i = 0; i <= levelSize.Height + 1; i++) map[i, 0] = map[i, levelSize.Width + 1] = Block.Wall;
157       for (int j = 0; j <= levelSize.Width + 1; j++) map[0, j] = map[levelSize.Height + 1, j] = Block.Wall;
158       mans = boxs = slots = boths = 0;
159     }
160 
161     /// <summary>
162     /// 根据地图项目更新统计信息
163     /// </summary>
164     /// <param name="x">当前位置横坐标</param>
165     /// <param name="y">当前位置纵坐标</param>
166     /// <param name="isAdd">加或减</param>
167     public void UpdateCounts(int x, int y, bool isAdd)
168     {
169       int sign = isAdd ? 1 : -1;
170       if (Block.IsBox(map[y, x])) boxs += sign;
171       if (Block.IsSlot(map[y, x])) slots += sign;
172       if (Block.Box1 == map[y, x]) boths += sign;
173       if (Block.IsMan(map[y, x]))
174       {
175         mans += sign;
176         worker = isAdd ? new Point(x, y) : Point.Empty;
177       }
178     }
179 
180     /// <summary>
181     /// 装入关数据头
182     /// </summary>
183     /// <param name="level">关数</param>
184     void LoadLevelHead(int level)
185     {
186       if (level > maxLevel - 1) throw new Exception(string.Format("当前关数({0})不能大于总关数({1})", level + 1, maxLevel));
187       br.BaseStream.Seek(addrs[level], SeekOrigin.Begin);
188       if (br.ReadByte() != LevelFlag) throw new Exception("关数据标志错");
189       isFinished = (br.ReadByte() & 1) == 1; // 是否曾经通关
190       movedSteps = br.ReadInt32(); // 通关的总步数
191       pushedSteps = br.ReadInt32(); // 通关的推箱子步数
192       br.ReadBytes(14); // 保留
193       levelSize.Width = br.ReadInt32();
194       levelSize.Height = br.ReadInt32();
195     }
196 
197     /// <summary>
198     /// 更新当前关数据
199     /// </summary>
200     /// <param name="level">关数</param>
201     /// <param name="steps">通关步骤</param>
202     /// <param name="pushs">推箱子步数</param>
203     public void SaveLevel(int level, Step[] steps, int pushs)
204     {
205       SaveLevelHead(level, steps.Length, pushs);
206       SaveLevelSteps(level, Pub.ToString(steps));
207       LoadLevelHead(level);
208     }
209 
210     /// <summary>
211     /// 更新当前关头数据
212     /// </summary>
213     /// <param name="level">关数</param>
214     /// <param name="moves">通关步数</param>
215     /// <param name="pushs">推箱子步数</param>
216     void SaveLevelHead(int level, int moves, int pushs)
217     {
218       if (level > maxLevel - 1) throw new Exception("关数太大");
219       bw.BaseStream.Seek(addrs[level] + 1, SeekOrigin.Begin);
220       bw.Write((byte)1); // 是否曾经通关
221       bw.Write(moves); // 通关的总步数
222       bw.Write(pushs); // 通关的推箱子步数
223     }
224 
225     /// <summary>
226     /// 保存通关步骤
227     /// </summary>
228     /// <param name="level">关数</param>
229     /// <param name="steps">通关步骤</param>
230     void SaveLevelSteps(int level, string steps)
231     {
232       if (!Directory.Exists(Pub.StepsDirectory)) Directory.CreateDirectory(Pub.StepsDirectory);
233       Fcl.WriteAllText(GetStepsFileName(fileName, level), steps);
234     }
235 
236     /// <summary>
237     /// 给出通关步骤
238     /// </summary>
239     /// <param name="level">关数</param>
240     /// <returns>通关步骤</returns>
241     public string GetSteps(int level)
242     {
243       return GetSteps(fileName, level);
244     }
245 
246     string GetSteps(string name, int level)
247     {
248       return Fcl.ReadAllText(GetStepsFileName(name, level));
249     }
250 
251     string GetStepsFileName(string name, int level)
252     {
253       return Path.Combine(Pub.StepsDirectory, name + (level + 1).ToString("D4") + Pub.StepsExtName);
254     }
255 
256     /// <summary>
257     ///  删除通关步骤文件
258     /// </summary>
259     /// <param name="level">关数</param>
260     private void DeleteStepsFile(int level)
261     {
262       // 虽然 File.Delete(): 删除指定的文件。如果指定的文件不存在,则不引发异常。 
263       // 但是: 如果指定的路径无效,还是会引发 DirectoryNotFoundException 异常。
264       // 所以需要先用 File.Exists() 判断一下文件是否存在
265       string name = GetStepsFileName(fileName, level);
266       if (File.Exists(name)) File.Delete(name);
267     }
268 
269     /// <summary>
270     /// 保存设计数据
271     /// </summary>
272     /// <param name="isNew">是否新建</param>
273     /// <param name="level">要保存的关数</param>
274     public void SaveDesign(bool isNew, int level)
275     {
276       if (isNew && level != maxLevel) throw new Exception("新建的关必须在最后一关之后");
277       bw.BaseStream.Seek(addrs[level], SeekOrigin.Begin);
278       WriteLevel(level, string.Empty); // 如果不是新建,则关尺寸不能比原来的大
279       if (isNew)
280       {
281         Fcl.Resize(ref addrs, addrs.Length + 1);
282         addrs[++maxLevel] = (int)bw.BaseStream.Position;
283         WriteAddrs();
284       }
285       DeleteStepsFile(level); // 删除通关步骤文件
286     }
287 
288     /// <summary>
289     /// 删除最后一关
290     /// </summary>
291     /// <param name="level">关数(必须是最后一关)</param>
292     public void DeleteLastLevel(int level)
293     {
294       if (level != maxLevel - 1) throw new Exception("要删除的关必须是最后一关");
295       DeleteLevel(level);
296       DeleteStepsFile(level); // 删除通关步骤文件,如果被删除的关不是最后一关,以后各关的通关步骤文件就不对了
297     }
298 
299     /// <summary>
300     /// 删除指定的关
301     /// </summary>
302     /// <param name="level">关数</param>
303     void DeleteLevel(int level)
304     {
305       for (int i = level + 1; i <= maxLevel; i++) addrs[i - 1] = addrs[i]; // 之后的关起始地址前移
306       --maxLevel; // 更新总关数
307       WriteAddrs();
308     }
309 
310     /// <summary>
311     /// 更新各关起始地址列表及总关数和第1关起始地址位置
312     /// </summary>
313     private void WriteAddrs()
314     {
315       bw.Seek(addrs[maxLevel], SeekOrigin.Begin);
316       for (int i = 0; i < maxLevel; i++) bw.Write(addrs[i]); // 各关起始地址
317       bw.BaseStream.SetLength(bw.BaseStream.Position); // 关起始地址列表位于数据文件最后, 用于删除关的情况
318       bw.Seek(24, SeekOrigin.Begin);
319       bw.Write(maxLevel); // 总关数
320       bw.Write(addrs[maxLevel]); // 第1关起始地址位置
321     }
322 
323     /// <summary>
324     /// 更新组名
325     /// </summary>
326     void WriteGroupName()
327     {
328       byte[] bs = new byte[16];
329       byte[] bn = Pub.Encode.GetBytes(groupName);
330       for (int i = 0; i < bs.Length && i < bn.Length; i++) bs[i] = bn[i];
331       for (int i = bn.Length; i < bs.Length; i++) bs[i] = 32;
332       bw.Seek(8, SeekOrigin.Begin);
333       bw.Write(bs); // 组名
334     }
335 
336     /// <summary>
337     /// 写关数据和通关步骤
338     /// 注意:调用本函数前必须定位到数据文件的正确位置
339     /// </summary>
340     /// <param name="level">关数</param>
341     /// <param name="steps">通关步骤</param>
342     /// <returns>本关的统计信息</returns>
343     string WriteLevel(int level, string steps)
344     {
345       bw.Write(LevelFlag); // 关标志
346       bw.Write((byte)(string.IsNullOrEmpty(steps) ? 0 : 1)); // 标志:是否已通关
347       bw.Write(steps.Length); // 总步数
348       bw.Write(GetPushSteps(steps)); // 推箱子步数
349       bw.Write(new byte[14]); // 保留
350       bw.Write(levelSize.Width); // 当前关宽度
351       bw.Write(levelSize.Height); // 当前关高度
352       mans = slots = boxs = 0;
353       int lands = 0, walls = 0, bricks = 0;
354       for (int i = 1; i <= levelSize.Height; i++)
355       {
356         for (int j = 1; j <= levelSize.Width; j++)
357         {
358           bw.Write(map[i, j]);
359           switch (map[i, j])
360           {
361             case Block.Land: lands++; break;
362             case Block.Slot: slots++; break;
363             case Block.Wall: walls++; break;
364             case Block.Brick: bricks++; break;
365             case Block.Box0: lands++; boxs++; break;
366             case Block.Box1: slots++; boxs++; break;
367             case Block.Man0: lands++; mans++; break;
368             case Block.Man1: slots++; mans++; break;
369           }
370         }
371       }
372       if (mans != 1) ErrorExit(true, level + 1, "必须刚好有一个工人");
373       if (!string.IsNullOrEmpty(steps)) SaveLevelSteps(level, steps);
374       return string.Format("{1}: {2} {3} {4} {5} {6} {7} {8}{0}",
375         Fcl.NewLine, level + 1, Pub.ToString(levelSize), walls, bricks, lands, slots, boxs, steps.Length);
376     }
377 
378     /// <summary>
379     /// 根据通关步骤给出推箱子步数
380     /// </summary>
381     /// <param name="steps">通关步骤</param>
382     /// <returns>推箱子步数</returns>
383     int GetPushSteps(string steps)
384     {
385       int n = 0;
386       foreach (char c in steps) if (((Step)c).IsBox) n++;
387       return n;
388     }
389 
390     /// <summary>
391     /// 数据导入
392     /// </summary>
393     /// <param name="name">数据文件主名</param>
394     /// <param name="maxLevelSize">最大关尺寸</param>
395     /// <param name="tbxMsg">显示相关信息的文本框</param>
396     public void Import(string name, int maxLevelSize, TextBox tbxMsg)
397     {
398       try
399       {
400         tbxMsg.Text = string.Format("{1} => {2}{0}", Fcl.NewLine, name + Pub.TextExtName, name + Pub.DataExtName);
401         if (!Directory.Exists(Pub.DataDirectory)) Directory.CreateDirectory(Pub.DataDirectory);
402         using (StreamReader sr = new StreamReader(Path.Combine(Pub.TextDirectory, name + Pub.TextExtName), Pub.Encode))
403         {
404           Dispose();
405           fs = new FileStream(Path.Combine(Pub.DataDirectory, name + Pub.DataExtName), FileMode.Create, FileAccess.Write);
406           bw = new BinaryWriter(fs, Pub.Encode);
407           byte[] buf = new byte[32];
408           buf[4] = DataVersion;
409           buf[5] = (byte)'B';
410           buf[6] = (byte)'O';
411           buf[7] = (byte)'X';
412           bw.Write(buf);
413           map = new byte[maxLevelSize + 2, maxLevelSize + 2];
414           List<int> addrList = new List<int>(); // 各关起始地址列表,最后一项为第1关起始地址位置
415           addrList.Add((int)bw.BaseStream.Position); // 第1关起始地址
416           groupName = name; // 组名
417           int level = 0;
418           levelSize = Size.Empty;
419           string steps = ""; // 通关步骤
420           bool isFirst = true;
421           for (int line = 1; ; line++)
422           {
423             string s = sr.ReadLine();
424             if (s != null) s = s.Trim();
425             if (line == 1 && s != null && s.Length > 0 && s[0] == '!')
426             {
427               groupName = s.Substring(1).Trim();
428               tbxMsg.Text += "组名: [" + groupName + "]" + Fcl.NewLine;
429               continue;
430             }
431             if (isFirst)
432             {
433               isFirst = false;
434               tbxMsg.Text += "#: 宽x高 墙 砖 地 槽 箱 通关步数" + Fcl.NewLine;
435             }
436             if ((s == null || s.Length == 0) && levelSize != Size.Empty)
437             {
438               tbxMsg.Text += WriteLevel(level, steps);
439               addrList.Add((int)bw.BaseStream.Position); // 下一关起始地址
440               level++;
441               levelSize = Size.Empty;
442               steps = "";
443             }
444             if (s == null) break;
445             if (s.Length == 0 || s[0] == RemChar) continue;
446             if (s[0] == StepsChar)
447             {
448               steps = s.Substring(1).Trim(); // 通关步骤
449               continue;
450             }
451             levelSize.Height++;
452             if (levelSize.Height == 1) levelSize.Width = s.Length;
453             else if (levelSize.Width != s.Length) ErrorExit(false, line, "宽度不齐");
454             if (levelSize.Width > maxLevelSize) ErrorExit(false, line, GetMessage("宽度太大", true));
455             if (levelSize.Height > maxLevelSize) ErrorExit(false, line, GetMessage("高度太大", true));
456             for (int i = 0; i < levelSize.Width; i++)
457               if (!Block.IsBlock(map[levelSize.Height, i + 1] = Block.GetByte(s[i])))
458                 ErrorExit(false, line, "非法字符:[" + s[i] + "]");
459           }
460           addrs = addrList.ToArray();
461           maxLevel = level;
462           WriteAddrs();
463           WriteGroupName();
464         }
465       }
466       catch (OutOfMemoryException ex)
467       {
468         throw new Exception(GetMessage("内存不足", false), ex);
469       }
470       finally
471       {
472         Dispose();
473       }
474       tbxMsg.Text += "导入完成";
475     }
476 
477     string GetMessage(string msg1, bool isIncrease)
478     {
479       return msg1 + ",请在“菜单 -> 选项”对话框中" + (isIncrease ? "增加" : "减少") +"“最大关尺寸”";
480     }
481 
482     /// <summary>
483     /// 数据导出
484     /// </summary>
485     /// <param name="name">数据文件主名</param>
486     /// <param name="tbxMsg">显示相关信息的文本框</param>
487     public void Export(string name, TextBox tbxMsg)
488     {
489       try
490       {
491         tbxMsg.Text = string.Format("{1} => {2}{0}", Fcl.NewLine, name + Pub.DataExtName, name + Pub.TextExtName);
492         LoadGroup(name);
493         if (!Directory.Exists(Pub.TextDirectory)) Directory.CreateDirectory(Pub.TextDirectory);
494         using (StreamWriter sw = new StreamWriter(
495           Path.Combine(Pub.TextDirectory, name + Pub.TextExtName), false, Pub.Encode))
496         {
497           sw.WriteLine("! {0}", groupName);
498           tbxMsg.Text += "组名: [" + groupName + "]" + Fcl.NewLine;
499           tbxMsg.Text += "#: 宽x高 总任务数 通关步数" + Fcl.NewLine;
500           for (int level = 0; level < maxLevel; level++)
501           {
502             LoadLevel(level);
503             sw.WriteLine("{0}[{1}]", RemChar, level + 1); // 注释:第几关
504             for (int y = 0; y < levelSize.Height; y++)
505             {
506               for (int x = 0; x < levelSize.Width; x++) sw.Write(Block.GetChar(map[y + 1, x + 1]));
507               sw.WriteLine();
508             }
509             string steps = GetSteps(name, level); // 通关步骤
510             if (!string.IsNullOrEmpty(steps)) sw.WriteLine(StepsChar + steps);
511             sw.WriteLine();
512             tbxMsg.Text += string.Format("{1}: {2} {3} {4}{0}",
513               Fcl.NewLine, level + 1, Pub.ToString(levelSize), tasks, steps.Length);
514           }
515         }
516       }
517       finally
518       {
519         Dispose();
520       }
521       tbxMsg.Text += "导出完成";
522     }
523 
524     void ErrorExit(bool isLevel, int idx, string msg)
525     {
526       throw new Exception(string.Format("错误:第{0}{1}:{2}", idx, isLevel ? "关" : "行", msg));
527     }
528 
529     public void Dispose()
530     {
531       if (br != null) br.Close();
532       if (bw != null) bw.Close();
533       if (fs != null) fs.Close();
534       br = null;
535       bw = null;
536       fs = null;
537     }
538   }
539 }
540 

上一篇:使用 C# 开发智能手机软件:推箱子(九)
下一篇:使用 C# 开发智能手机软件:推箱子(十一)
返回目录

使用 C# 开发智能手机软件:推箱子(十)相关推荐

  1. 使用 C# 开发智能手机软件:推箱子(十二)

    这是"使用 C# 开发智能手机软件:推箱子"系列文章的第十二篇.在这篇文章中,介绍 Window/AboutDlg.cs 源程序文件. 这个源程序文件包括 AboutDlg 类,该 ...

  2. 使用 C# 开发智能手机软件:推箱子(二十)

    这是"使用 C# 开发智能手机软件:推箱子" 系列文章的第二十篇.在这篇文章中,介绍 Window/DesignDlg.cs 源程序文件.这个源程序文件包含 DesignDlg 类 ...

  3. 使用 C# 开发智能手机软件:推箱子(二十三)

    这是"使用 C# 开发智能手机软件:推箱子" 系列文章的第二十三篇.在这篇文章中,介绍 Window/MainForm.Common.cs 源程序文件.这个源程序文件是 MainF ...

  4. 使用 C# 开发智能手机软件:推箱子(四)

    这是" 使用 C# 开发智能手机软件:推箱子 "系列文章的第四篇.在这篇文章中,介绍 Common/FindPath.cs 源程序文件. using System; using S ...

  5. 使用 C# 开发智能手机软件:推箱子(十一)

    这是" 使用 C# 开发智能手机软件:推箱子 "系列文章的第十一篇.在这篇文章中,介绍 Common/Env.cs 源程序文件.这个源程序文件中包含表示"工作环境&quo ...

  6. python 推箱子实验开发报告_推箱子实验报告.doc

    推箱子实验报告 青岛大学软件技术学院 游戏制作实践实训 题目名称 推箱子游戏 姓 名 丁帅帅 专 业 数字媒体艺术 班 级 3班 指导教师 解新峰 2014 年 1 月 16 日 目 录 1 引言3 ...

  7. 一文教你使用java开发一款推箱子游戏

    导读:社会在进步,人们生活质量也在日益提高.高强度的压力也接踵而来.社会中急需出现新的有效方式来缓解人们的压力.此次设计符合了社会需求,Java推箱子游戏可以让人们在闲暇之余,体验游戏的乐趣.具有操作 ...

  8. 一步一步教你开发《松鼠推箱子》手机游戏

    这类游戏大家肯定都玩过,一个很有趣味性的小游戏.操作简单,具有一定的逻辑性.很适合无聊的时候消遣时间.:) 首先简单介绍下手机游戏的一般性开发过程.首先需要策划出一个游戏方案,也就是要给出一个游戏的整 ...

  9. [html5游戏开发]经典的推箱子

    开言: lufylegend.js引擎已经更新到1.6以上了,虽然我陆陆续续发布了一些教程,也提供了一些简单的游戏示例,但是一直以来也没有制作几款完整的作品来,实在也是自己一个人时间太有限了,接下来的 ...

  10. HTML5 游戏开发实战 | 推箱子

    经典的推箱子是一个来自日本的古老游戏,目的是在训练玩家的逻辑思考能力.在一个狭小的仓库中,要求把木箱放到指定的位置,稍不小心就会出现箱子无法移动或者通道被堵住的情况,所以需要巧妙地利用有限的空间和通道 ...

最新文章

  1. php读取屏幕大小,jQuery 获取屏幕尺寸
  2. [MySQL实践] 实践记录
  3. Hyperledger Fabric 1.0 实战开发系列 第四课 搭建node.js服务器
  4. thinkphp session mysql_ThinkPHP实现将SESSION存入MYSQL的方法
  5. html函数splice,js数组的常用函数(slice()和splice())和js引用的三种方法总结—2019年1月16日...
  6. 论文浅尝 | GNN with Generated Parameters for Relation Extraction
  7. Laravel 学习路线【4】控制器
  8. [Flex]Flex编程注意之自动获取焦点、监听全局键盘事件
  9. YOLO: Real-Time Object Detection 遇到的问题
  10. oracle ebs工单入库,ORACLE-EBS_库存功能点操作模块.doc
  11. [FAQ21153]MT6761/MT6762/MT6765平台搭配LPDDR4 注意事项
  12. 神经网络之输出层设计
  13. Qt QPushButton水晶按钮样式例子
  14. Mysql索引结构全维度比较
  15. 淘宝大数据产品解析之淘宝数据魔方技术架构(1)
  16. frps port unavailable
  17. win10 安全模式开机
  18. YBT高效进阶 6.2.5 余数之和
  19. 80、54、84坐标系七参数转换算法及Java代码
  20. echarts的X轴文字倾斜显示、竖向显示、上下错开显示

热门文章

  1. ArcGIS 从大区域提取小区域的行政区shp并用于裁剪其他数据
  2. 遥感动态监测实验(以福州为例)
  3. 如何制作频数折线图(详细)
  4. Python类中的__init__,__del__和__call__方法
  5. 进程控制(PCB,进程ID,进程状态,fork函数,文件共享)
  6. 4种方案,帮你解决Maven创建项目过慢问题
  7. 【独家】一种手机上实现屏幕录制成gif的方案
  8. 西南林业大学计算机考研,西南林业大学考研难吗
  9. 打开pdf文件提示文件过大_如何把pdf文件进行分割?拆分pdf文件的方法分享
  10. python列表操作详解_Python --列表(List)详解