这是“
使用 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 方法用来删除指定的关。注意,删除关时并不删除关数据,只是将该关的起始地址从各关地址列表中删除,然后将文件缩短四个字节(因为各关地址列表在数据文件 的最后)。这样数据文件中就可能包含不需要的冗余数据。通过“菜单 -> 数据 -> 转换”,先“导出”,然后“导入”,可以消除冗余数据。
上图是数据文件 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
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