using System;
using System.Drawing;
using System.Collections.Generic;
namespace Skyiv.Ben.PushBox.Common
{
/// <summary>
/// 工作环境
/// </summary>
sealed class Env : IDisposable
{
DataFile db; // 数据文件
ConfigFile cfg; // 配置文件
string errorMsg; // 错误信息
string debugMsg; // 调试信息
bool isReplay; // 是否正在回放
Action active; // 模式: 正常 新建 编辑 删除
byte pen; // 设计时的笔
Bitmap img; // 图形单元, 横向被均匀分为八份
Stack<Step> stack; // 历史路线, 用于后退功能
Size clientSize; // 工作区域尺寸(以像素为单位)
Size boxSize; // 图形元素尺寸(以像素为单位)
Point toPixel; // 将要到达的位置(以像素为单位)
Point worker; // 当前工人位置(以单元格为单位)
int pushSteps; // 推动着箱子走的步数
int levelOem; // 原来的关数,仅用于“菜单 -> 数据 -> 设计 -> 新建”放弃后恢复现场
public string ErrorMsg { get { return errorMsg; } }
public string DebugMsg { get { return debugMsg; } }
public string[] Groups { get { return cfg.Groups; } }
public int Group { get { return cfg.Group; } set { cfg.Group = value; } }
public int Level { get { return cfg.Levels[Group]; } set { cfg.Levels[Group] = value; } }
public int LeveLOem { get { return levelOem; } }
public int MaxLevel { get { return db.MaxLevel; } }
public string Steps { get { return cfg.Steps; } }
public Size LevelSize { get { return db.LevelSize; } }
public Size ClientSize { set { clientSize = value; } }
public Point ToPixel { set { toPixel = value; } }
public int MaxLevelSize { get { return cfg.MaxLevelSize; } set { cfg.MaxLevelSize = value; } }
public int StepDelay { get { return cfg.StepDelay; } set { cfg.StepDelay = value; } }
public int ReplayDelay { get { return cfg.ReplayDelay; } set { cfg.ReplayDelay = value; } }
public Action Active { get { return active; } set { active = value; } }
public byte Pen { get { return pen; } set { pen = value; } }
public bool HasError { get { return !string.IsNullOrEmpty(errorMsg); } }
public bool HasWorker { get { return db.HasWorker; } }
public bool CanUndo { get { return stack.Count != 0; } }
public bool CanReplay { get { return db.IsFinished && !CanUndo; } }
public bool IsSave { get { return cfg.IsSave; } set { cfg.IsSave = value; } }
public bool IsFinish { get { return db.Tasks == db.Boths; } }
public bool IsReplay { get { return isReplay; } set { isReplay = value; } }
public bool IsDesign { get { return active != Action.None; } }
public Env()
{
stack = new Stack<Step>();
cfg = new ConfigFile();
db = new DataFile();
Init();
}
/// <summary>
/// 状态栏信息
/// </summary>
public string StatusMessage
{
get
{
return HasError ? "请点击“菜单 -> 帮助 -> 错误信息”" : string.Format(
"{0} {1}/{2} {3} {4} {5} [{6}] {7}",
(active == Action.Create) ? ‘+‘ : (active == Action.Edit) ? ‘=‘ : isReplay ? "|/-\\"[stack.Count % 4] : ‘>‘,
Level + 1, MaxLevel, Pub.ToString(LevelSize),
IsDesign ? string.Format("{0}={1}", db.Boxs, db.Slots) : string.Format("{0}/{1}", db.Boths, db.Tasks),
IsDesign ? Block.GetPenName(pen) : string.Format("{0}({1})", stack.Count, pushSteps),
IsDesign ? (active == Action.Create ? "新建" : "编辑") : db.IsFinished ?
string.Format("{0}({1})", db.MovedSteps, db.PushedSteps) : string.Empty,
db.GroupName);
}
}
public void Dispose()
{
db.Dispose();
}
public void Init()
{
active = Action.None;
pen = Block.Land;
stack.Clear();
SetExceptionMessage(null);
}
void SetExceptionMessage(Exception ex)
{
errorMsg = Pub.GetMessage(ex, false);
debugMsg = Pub.GetMessage(ex, true);
}
/// <summary>
/// 计算当使用标准箱子尺寸时主窗体客户区的尺寸
/// </summary>
/// <param name="statusBarHeight">状态条的高度</param>
/// <returns>客户区的尺寸</returns>
public Size GetClientSize(int statusBarHeight)
{
int width = (Properties.Resources.PushBox24.Width / 8) * LevelSize.Width;
int height = Properties.Resources.PushBox24.Height * LevelSize.Height + statusBarHeight;
if (width < 240) width = 240;
if (height < 48) height = 48;
if (width > 1008) width = 1008;
if (height > 672) height = 672;
return new Size(width, height);
}
/// <summary>
/// 根据客户区尺寸,计算箱子的尺寸,并相应设定要显示的图形单元
/// </summary>
public void SetBoxInfo()
{
if (HasError) return;
if (LevelSize.IsEmpty) return;
int rX = clientSize.Width / LevelSize.Width;
int rY = clientSize.Height / LevelSize.Height;
int r = Math.Min(rX, rY);
if (r >= 24) img = Properties.Resources.PushBox24;
else if (r >= 20) img = Properties.Resources.PushBox20;
else if (r >= 16) img = Properties.Resources.PushBox16;
else img = Properties.Resources.PushBox12;
boxSize = new Size(img.Height, img.Width / 8);
}
/// <summary>
/// 装入配置文件
/// </summary>
public void LoadConfig()
{
if (HasError) return;
try
{
cfg.LoadConfig();
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 保存组信息到配置文件
/// </summary>
/// <param name="groups">组信息</param>
public void SaveConfig(string[] groups)
{
if (HasError) return;
try
{
cfg.SaveConfig(groups);
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 保存当前选项及当前走法到配置文件
/// </summary>
public void SaveConfig()
{
if (HasError) return;
try
{
cfg.SaveConfig(stack.ToArray());
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 装入当前组信息
/// </summary>
public void LoadGroup()
{
if (HasError) return;
try
{
db.LoadGroup(Groups[Group]);
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 装入当前关信息
/// </summary>
public void LoadLevel()
{
active = Action.None;
if (HasError) return;
try
{
db.LoadLevel(Level);
worker = db.Worker;
stack.Clear();
pushSteps = 0;
isReplay = false;
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 新建一关
/// </summary>
/// <param name="isCopy">是否复制当前关</param>
/// <param name="size">新建关的尺寸</param>
public void NewLevel(bool isCopy, Size size)
{
if (HasError) return;
try
{
levelOem = Level;
Level = MaxLevel;
db.NewLevel(isCopy, size);
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 给出通关步骤
/// </summary>
/// <returns>通关步骤</returns>
public string GetSteps()
{
string steps = "";
if (!HasError)
{
try
{
steps = db.GetSteps(Level);
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
return steps;
}
/// <summary>
/// 记录通关步骤
/// </summary>
public void Record()
{
if (HasError) return;
try
{
db.SaveLevel(Level, stack.ToArray(), pushSteps);
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 保存设计数据
/// </summary>
public void SaveDesign()
{
if (HasError) return;
try
{
db.SaveDesign(active == Action.Create, Level);
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 删除最后一关
/// </summary>
public void DeleteLastLevel()
{
if (HasError) return;
try
{
db.DeleteLastLevel(Level);
}
catch (Exception ex)
{
SetExceptionMessage(ex);
}
}
/// <summary>
/// 更新主窗体客户区
/// </summary>
/// <param name="dc">画布</param>
/// <param name="rectangle">要在其中绘画的矩形</param>
public void Draw(Graphics dc, Rectangle rectangle)
{
if (HasError) return;
Rectangle box = PixelToBox(rectangle);
Rectangle box2 = new Rectangle(box.Left, box.Top, box.Width + 1, box.Height + 1);
for (int i = 1; i <= LevelSize.Height; i++)
{
for (int j = 1; j <= LevelSize.Width; j++)
{
if (!box2.Contains(j, i)) continue;
DrawBox(dc, j, i);
}
}
}
/// <summary>
/// 绘制一个单元格
/// </summary>
/// <param name="dc">画布</param>
/// <param name="x">单元格的横坐标</param>
/// <param name="y">单元格的纵坐标</param>
void DrawBox(Graphics dc, int x, int y)
{
DrawBox(dc, db.Map[y, x], (x - 1) * boxSize.Width, (y - 1) * boxSize.Height);
}
/// <summary>
/// 绘制一个单元格
/// </summary>
/// <param name="dc">画布</param>
/// <param name="idx">单元格的类型: 地 槽 墙 砖 箱子 工人</param>
/// <param name="x">单元格的横坐标</param>
/// <param name="y">单元格的纵坐标</param>
void DrawBox(Graphics dc, int idx, int x, int y)
{
dc.DrawImage(img, x, y, new Rectangle(idx * boxSize.Width, 0, boxSize.Width, boxSize.Height), GraphicsUnit.Pixel);
}
/// <summary>
/// 将单元格换算为像素
/// </summary>
/// <param name="box">单元格矩形</param>
/// <returns>像素矩形</returns>
Rectangle BoxToPixel(Rectangle box)
{
return new Rectangle((box.Left - 1) * boxSize.Width, (box.Top - 1) * boxSize.Height,
(box.Width + 1) * boxSize.Width, (box.Height + 1) * boxSize.Height);
}
/// <summary>
/// 将像素换算为单元格
/// </summary>
/// <param name="pixel">像素矩形</param>
/// <returns>单元格矩形</returns>
Rectangle PixelToBox(Rectangle pixel)
{
int x0 = pixel.Left / boxSize.Width + 1;
int y0 = pixel.Top / boxSize.Height + 1;
int x1 = (pixel.Right - 1) / boxSize.Width + 1;
int y1 = (pixel.Bottom - 1) / boxSize.Height + 1;
return new Rectangle(x0, y0, x1 - x0, y1 - y0);
}
/// <summary>
/// 根据指定的对角顶点创建矩形
/// </summary>
/// <param name="a">顶点</param>
/// <param name="b">对角的顶点</param>
/// <returns>所需要的矩形</returns>
Rectangle GetRectangle(Point a, Point b)
{
return Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y));
}
/// <summary>
/// 设计模式下,当鼠标点击时要采取的动作
/// </summary>
/// <param name="invalid">输出:要重绘的区域</param>
/// <returns>是否发生动作</returns>
public bool Design(out Rectangle invalid)
{
invalid = Rectangle.Empty;
Point to;
if (!ValidClick(out to)) return false;
db.UpdateCounts(to.X, to.Y, false);
Block.Update(ref db.Map[to.Y, to.X], pen);
db.UpdateCounts(to.X, to.Y, true);
if (pen == Block.Man0 && HasWorker) pen = Block.Box0;
invalid = BoxToPixel(GetRectangle(to, to));
return true;
}
/// <summary>
/// 工人往指定方向前进一步(可能推着箱子)
/// </summary>
/// <param name="dir">前进的方向</param>
/// <param name="isStop">“撤销”时是否停留</param>
/// <param name="invalid">输出:要重绘的区域</param>
/// <returns>是否成功</returns>
public bool StepIt(Direction dir, bool isStop, out Rectangle invalid)
{
invalid = Rectangle.Empty;
if (HasError) return false;
if (Direction.None == dir) return false;
Point p1 = worker; // 工人前进方向一步的位置
Point p2 = worker; // 箱子前进方向一步的位置
switch (dir)
{
case Direction.East: p1.X++; p2.X += 2; break;
case Direction.South: p1.Y++; p2.Y += 2; break;
case Direction.West: p1.X--; p2.X -= 2; break;
case Direction.North: p1.Y--; p2.Y -= 2; break;
}
byte b1 = db.Map[p1.Y, p1.X]; // 工人前进方向一步位置上的东西
bool isBox = Block.IsBox(b1); // 是否推着箱子前进
if (!isBox && !Block.IsBlank(b1)) return false; // 如果没有推着箱子且前方不是空地则失败
if (isBox && !Block.IsBlank(db.Map[p2.Y, p2.X])) return false; // 如果推着箱子且箱子前方不是空地则失败
invalid = BoxToPixel(GetRectangle(worker, isBox ? p2 : p1)); // 要重绘的区域
stack.Push(new Step(dir, isBox, isStop)); // 记录走法步骤
Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置
Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入前方位置
if (isBox)
{
pushSteps++; // 更新推箱子步数
db.Boths += (db.Map[p2.Y, p2.X] - Block.Land) - (b1 - Block.Box0); // 更新已完成任务数
Block.BoxOut(ref db.Map[p1.Y, p1.X]); // 箱子离开当前位置
Block.BoxIn(ref db.Map[p2.Y, p2.X]); // 箱子进入前方位置
}
worker = p1; // 更新工人位置
return true; // 工人成功前进一步(可能推着条子)
}
/// <summary>
/// 工人后退一步(可能连带箱子一起后退)
/// </summary>
/// <param name="invalid">输出:要重绘的区域</param>
/// <returns>是否完成“撤消”</returns>
public bool Back(out Rectangle invalid)
{
invalid = Rectangle.Empty;
if (HasError) return true;
if (stack.Count == 0) return true;
Step step = stack.Pop(); // 当前步骤
Point p1 = worker; // 工人后退方向一步的位置
Point p2 = worker; // 箱子的当前位置
switch (step.Direct)
{
case Direction.East: p1.X--; p2.X++; break;
case Direction.South: p1.Y--; p2.Y++; break;
case Direction.West: p1.X++; p2.X--; break;
case Direction.North: p1.Y++; p2.Y--; break;
}
invalid = BoxToPixel(GetRectangle(p1, step.IsBox ? p2 : worker)); // 要重绘的区域
Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置
Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入后退方向一步的位置
if (step.IsBox)
{
Block.BoxOut(ref db.Map[p2.Y, p2.X]); // 箱子离开当前位置
Block.BoxIn(ref db.Map[worker.Y, worker.X]); // 箱子进入工人原来的位置
db.Boths += (db.Map[worker.Y, worker.X] - Block.Box0) - (db.Map[p2.Y, p2.X] - Block.Land); // 更新已完成任务数
pushSteps--; // 更新推箱子步数
}
worker = p1; // 更新工人位置
return step.IsStop; // 是否完成“撤消”
}
/// <summary>
/// 寻找一条将工人移动到鼠标点击的位置的路线
/// </summary>
/// <returns>移动的路线</returns>
public Queue<Direction> GetMoveInfo()
{
Point to;
if (!CanTo(out to)) return null;
return FindPath.Seek(db.Map, worker, to);
}
/// <summary>
/// 给出将箱子推动到鼠标点击的位置所需的信息
/// </summary>
/// <param name="dir">输出:工人移动的方向</param>
/// <returns>工人移动的步数</returns>
public int GetPushInfo(out Direction dir)
{
dir = Direction.None;
if (HasError) return 0;
Point to; // 目的地
if (!CanTo(out to)) return 0; // 无效的目的地
if (to.Y != worker.Y && to.X != worker.X) return 0; // 目的地和工人不在同一条直线上
int z0 = (to.Y == worker.Y) ? worker.X : worker.Y;
int z9 = (to.Y == worker.Y) ? to.X : to.Y;
if (to.Y == worker.Y) dir = (z9 > z0) ? Direction.East : Direction.West;
else dir = (z9 > z0) ? Direction.South : Direction.North;
int i0 = Math.Min(z9, z0);
int i9 = Math.Max(z9, z0);
int steps = i9 - i0; // 目的地和工人之间的距离
int boxs = 0;
for (int i = i0 + 1; i < i9; i++)
{
byte bi = (to.Y == worker.Y) ? db.Map[worker.Y, i] : db.Map[i, worker.X];
if (Block.IsBox(bi)) boxs++; // 计算工人和目的地之间的箱子的个数
else if (!Block.IsBlank(bi)) boxs += 2; // “墙”和“砖”折算为两个箱子
}
if (boxs > 1) return 0; // 最多只能推着一个箱子前进
return steps - boxs; // 工人移动的步数
}
/// <summary>
/// 检查鼠标点击位置是否可达, 并将像素换算为单元格
/// </summary>
/// <param name="to">输出:换算后的位置</param>
/// <returns>是否可达</returns>
bool CanTo(out Point to)
{
if (!ValidClick(out to)) return false;
if (!Block.IsMan(db.Map[worker.Y, worker.X])) throw new Exception("内部错误:工人的位置上不是工人");
if (!Block.IsBlank(db.Map[to.Y, to.X])) return false; // 目的地必须是“地”或“槽”
if (to.Y == worker.Y && to.X == worker.X) return false; // 目的地不能是工人当前的位置
return true; // 目的地可达
}
/// <summary>
/// 检查鼠标点击位置是否有效, 并将像素换算为单元格
/// </summary>
/// <param name="to">输出:换算后的位置</param>
/// <returns>是否有效位置</returns>
bool ValidClick(out Point to)
{
to = Point.Empty;
if (HasError) return false;
to.Y = toPixel.Y / boxSize.Height + 1;
to.X = toPixel.X / boxSize.Width + 1;
if (toPixel.X >= boxSize.Width * LevelSize.Width || toPixel.Y >= boxSize.Height * LevelSize.Height)
return false; // 目的地超出当前关的有效范围
return true; // 目的地有效
}
}
}
版权声明:本文为博主http://www.zuiniusn.com 原创文章,未经博主允许不得转载。
原文:http://blog.csdn.net/u013141940/article/details/47066445