📄 datafile.cs
字号:
using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Skyiv.Ben.PushBox.Common
{
// data/<group>.bxb 文件格式
// 保留 ver(2) BOX 组名- 总关数 第1关起始地址位置
// 0--3 4----- 5-7 8--23 24--27 28-------------31
//
// @ Flag 总步数 推箱子步数 保留- wide- high- data...
// 0 1--- 2----5 6--------9 10-23 24-27 28-31 32.....
// Flag: 最低位: 0:未通关 1:已通关
//
// 第1关起始地址 第2关起始地址 .... 最后一关起始地址
// 0-----------3 4-----------7 .... (文件最后四字节)
//
// steps/<group><level>.bxs 文件格式见 Step.cs
// 其中<level>为关数(1起始),最少四位,不足前补零
//
// text/<group>.bxa 文件格式
// 0 - land SPACE
// 1 + slot .
// 2 # wall #
// 3 % brick N/A
// 4 x box on land $
// 5 X box on slot *
// 6 ( man on land @
// 7 ) man on slot + .XSB 文件格式
// 第一行如果以!开头的话, 则为组名(不能超过16个字符)
// 以:开头的行为通关步骤, 格式同(.bxs)文件
// 以'开头的行为注释, 完全忽略
// 各关之间必须以空行分隔
/// <summary>
/// 管理数据文件: *.bxb *.bxa *.bxs
/// </summary>
sealed class DataFile : IDisposable
{
const byte DataVersion = 2; // 数据文件(.bxb)的版本
const byte LevelFlag = (byte)'@'; // 数据文件(.bxb)的关标志
const char RemChar = '\''; // 文本文件(.bxa)的注释
const char StepsChar = ':'; // 文本文件(.bxa)的通关步骤
FileStream fs; // 数据文件基础流
BinaryReader br; // 数据文件读取器
BinaryWriter bw; // 数据文件写入器
string groupName; // 当前组名称
int[] addrs; // 各关起始地址列表,最后一项为第1关起始地址位置
byte[,] map; // 当前关地图
int maxLevel; // 总关数
Size levelSize; // 当前关尺寸(以单元格为单位)
Point worker; // 当前工人位置(以单元格为单位)
int mans; // 工人数
int boxs; // 箱子数
int slots; // 槽数
int tasks; // 总任务数
int boths; // 已完成任务数
bool isFinished; // 是否曾经通关
int movedSteps; // 通关的总步数
int pushedSteps; // 通关的推箱子步数
string fileName { get { return Path.GetFileNameWithoutExtension(fs.Name); } } // 数据文件主名
public string GroupName { get { return groupName; } }
public int MaxLevel { get { return maxLevel; } }
public byte[,] Map { get { return map; } }
public Size LevelSize { get { return levelSize; } }
public bool IsFinished { get { return isFinished; } }
public int MovedSteps { get { return movedSteps; } }
public int PushedSteps { get { return pushedSteps; } }
public Point Worker { get { return worker; } }
public bool HasWorker { get { return mans != 0; } }
public int Boxs { get { return boxs; } }
public int Slots { get { return slots; } }
public int Tasks { get { return tasks; } }
public int Boths { get { return boths; } set { boths = value; } }
/// <summary>
/// 装入组数据
/// </summary>
/// <param name="name">组文件名</param>
public void LoadGroup(string name)
{
Dispose();
fs = new FileStream(Path.Combine(Pub.DataDirectory, name + Pub.DataExtName), FileMode.Open);
br = new BinaryReader(fs, Pub.Encode);
bw = new BinaryWriter(fs, Pub.Encode);
br.ReadInt32(); // 保留
if (br.ReadByte() != DataVersion) throw new Exception("数据文件版本错");
byte[] bs = br.ReadBytes(3); // 数据文件标志:BOX
for (int i = 0; i < bs.Length; i++) if (bs[i] != "BOX"[i]) throw new Exception("数据文件标志错");
bs = br.ReadBytes(16); // 组名
for (int i = 0; i < bs.Length; i++) if (bs[i] == 0) bs[i] = 32;
groupName = Pub.Encode.GetString(bs, 0, bs.Length).Trim();
if (groupName.Length == 0) groupName = fileName; // 如果数据文件中组名为空,则用数据文件主名代替
maxLevel = br.ReadInt32(); // 总关数
int addrPos = br.ReadInt32(); // 第1关起始地址位置
br.BaseStream.Seek(addrPos, SeekOrigin.Begin);
addrs = new int[maxLevel + 1]; // 各关起始地址列表,最后一项为第1关起始地址位置
for (int i = 0; i < maxLevel; i++) addrs[i] = br.ReadInt32();
addrs[maxLevel] = addrPos; // 第1关起始地址位置
if (addrPos + 4 * maxLevel != br.BaseStream.Length) throw new Exception("数据文件地址表必须位于数据最后");
}
/// <summary>
/// 装入关数据
/// </summary>
/// <param name="level">关数</param>
public void LoadLevel(int level)
{
LoadLevelHead(level);
InitMap();
for (int i = 1; i <= levelSize.Height; i++)
{
for (int j = 1; j <= levelSize.Width; j++)
{
map[i, j] = br.ReadByte();
UpdateCounts(j, i, true);
}
}
if (mans != 1) throw new Exception("读取关数据失败:必须刚好有一个工人");
tasks = Math.Min(boxs, slots);
}
/// <summary>
/// 新建一关
/// </summary>
/// <param name="isCopy">是否复制当前关</param>
/// <param name="size">新建关的尺寸</param>
public void NewLevel(bool isCopy, Size size)
{
Size levelSizeOem = levelSize;
byte[,] mapOem = isCopy ? (byte[,])map.Clone() : null;
levelSize = size;
InitMap();
for (int i = 1; i <= levelSize.Height; i++)
{
for (int j = 1; j <= levelSize.Width; j++)
{
map[i, j] = (isCopy && i <= levelSizeOem.Height && j <= levelSizeOem.Width) ? mapOem[i, j] : Block.Land;
UpdateCounts(j, i, true);
}
}
if (mans != 1 && mans != 0) throw new Exception("不能超过一个工人");
tasks = Math.Min(boxs, slots);
}
/// <summary>
/// 初始化地图
/// </summary>
private void InitMap()
{
map = new byte[levelSize.Height + 2, levelSize.Width + 2];
for (int i = 0; i <= levelSize.Height + 1; i++) map[i, 0] = map[i, levelSize.Width + 1] = Block.Wall;
for (int j = 0; j <= levelSize.Width + 1; j++) map[0, j] = map[levelSize.Height + 1, j] = Block.Wall;
mans = boxs = slots = boths = 0;
}
/// <summary>
/// 根据地图项目更新统计信息
/// </summary>
/// <param name="x">当前位置横坐标</param>
/// <param name="y">当前位置纵坐标</param>
/// <param name="isAdd">加或减</param>
public void UpdateCounts(int x, int y, bool isAdd)
{
int sign = isAdd ? 1 : -1;
if (Block.IsBox(map[y, x])) boxs += sign;
if (Block.IsSlot(map[y, x])) slots += sign;
if (Block.Box1 == map[y, x]) boths += sign;
if (Block.IsMan(map[y, x]))
{
mans += sign;
worker = isAdd ? new Point(x, y) : Point.Empty;
}
}
/// <summary>
/// 装入关数据头
/// </summary>
/// <param name="level">关数</param>
void LoadLevelHead(int level)
{
if (level > maxLevel - 1) throw new Exception(string.Format("当前关数({0})不能大于总关数({1})", level + 1, maxLevel));
br.BaseStream.Seek(addrs[level], SeekOrigin.Begin);
if (br.ReadByte() != LevelFlag) throw new Exception("关数据标志错");
isFinished = (br.ReadByte() & 1) == 1; // 是否曾经通关
movedSteps = br.ReadInt32(); // 通关的总步数
pushedSteps = br.ReadInt32(); // 通关的推箱子步数
br.ReadBytes(14); // 保留
levelSize.Width = br.ReadInt32();
levelSize.Height = br.ReadInt32();
}
/// <summary>
/// 更新当前关数据
/// </summary>
/// <param name="level">关数</param>
/// <param name="steps">通关步骤</param>
/// <param name="pushs">推箱子步数</param>
public void SaveLevel(int level, Step[] steps, int pushs)
{
SaveLevelHead(level, steps.Length, pushs);
SaveLevelSteps(level, Pub.ToString(steps));
LoadLevelHead(level);
}
/// <summary>
/// 更新当前关头数据
/// </summary>
/// <param name="level">关数</param>
/// <param name="moves">通关步数</param>
/// <param name="pushs">推箱子步数</param>
void SaveLevelHead(int level, int moves, int pushs)
{
if (level > maxLevel - 1) throw new Exception("关数太大");
bw.BaseStream.Seek(addrs[level] + 1, SeekOrigin.Begin);
bw.Write((byte)1); // 是否曾经通关
bw.Write(moves); // 通关的总步数
bw.Write(pushs); // 通关的推箱子步数
}
/// <summary>
/// 保存通关步骤
/// </summary>
/// <param name="level">关数</param>
/// <param name="steps">通关步骤</param>
void SaveLevelSteps(int level, string steps)
{
if (!Directory.Exists(Pub.StepsDirectory)) Directory.CreateDirectory(Pub.StepsDirectory);
Fcl.WriteAllText(GetStepsFileName(fileName, level), steps);
}
/// <summary>
/// 给出通关步骤
/// </summary>
/// <param name="level">关数</param>
/// <returns>通关步骤</returns>
public string GetSteps(int level)
{
return GetSteps(fileName, level);
}
string GetSteps(string name, int level)
{
return Fcl.ReadAllText(GetStepsFileName(name, level));
}
string GetStepsFileName(string name, int level)
{
return Path.Combine(Pub.StepsDirectory, name + (level + 1).ToString("D4") + Pub.StepsExtName);
}
/// <summary>
/// 删除通关步骤文件
/// </summary>
/// <param name="level">关数</param>
private void DeleteStepsFile(int level)
{
// 虽然 File.Delete(): 删除指定的文件。如果指定的文件不存在,则不引发异常。
// 但是: 如果指定的路径无效,还是会引发 DirectoryNotFoundException 异常。
// 所以需要先用 File.Exists() 判断一下文件是否存在
string name = GetStepsFileName(fileName, level);
if (File.Exists(name)) File.Delete(name);
}
/// <summary>
/// 保存设计数据
/// </summary>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -