• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

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

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
    这是“使用 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 != 1throw 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 != 0throw 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>

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C++编程命名规范发布时间:2022-07-13
下一篇:
C#开发WPF/Silverlight动画及游戏系列教程发布时间:2022-07-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap