这是“
使用 C# 开发智能手机软件:推箱子”系列文章的第十一篇。在这篇文章中,介绍 Common/Env.cs 源程序文件。这个源程序文件中包含表示“工作环境”的密封类 Env 。也就是说,主程序中重要的变量都封装在这个类中,作为整个程序的“工作环境”。她还起着桥梁作用,其中两个字段:
正是我们以前介绍过的管理数据文件的密封类 DataFile 和管理配置文件的密封类 ConfigFile 的实例。密封类 Env 中的不少属性和方法是通过这两个字段调用其各自的属性和方法。
下面对密封类 Env 中的一些方法作点说明:
GetClientSize 方法用来计算当使用标准箱子尺寸时主窗体客户区的尺寸。该方法仅当程序运行在计算机上时才会被调用,使主窗体的尺寸根据当前关的尺寸自动调整。程序运行在智能手机时是不会被调用的,因为在智能手机上本程序并不改变主窗体的尺寸。
SetBoxInfo 方法的作用是根据客户区尺寸计算箱子的尺寸,并相应设定要显示的图形单元。图形单元共有 24x24、20x20、16x16 和 12x12 四种尺寸,如下所示:
如果使用 12x12 的图形单元还不能在客户区完整显示地图的话,可能这一关的游戏就无法玩了。
Draw 方法用来更新主窗体客户区,也就是在主窗体客户区画出本关的地图,并根据玩家的动作随时更新地图。
Design 方法实现在设计模式下,当鼠标点击时要采取的动作。
StepIt 方法实现工人往指定方向前进一步(可能推着箱子)。
Back 方法实现工人后退一步(可能连带箱子一起后退)。
GetMoveInfo 方法寻找一条将工人移动到鼠标点击的位置的路线。她调用我们以前介绍过的静态类 FindPath 的 Seek 方法来寻找最短路线。
GetPushInfo 方法给出将箱子推动到鼠标点击的位置所需的信息。
到此为止,Common 目录下所有源程序文件都介绍完了,这些源程序文件中包含的类是实现整个程序功能的基础。在随后的文章中将介绍 Windows 目录下的源程序文件,她们包含的类实现整个程序的用户界面。
下面就是密封类 Env 的源程序代码,虽然稍微长了一点,但是里面的注释比较详细,应该不难理解。
1
using System;
2
using System.Drawing;
3
using System.Collections.Generic;
4
5
namespace Skyiv.Ben.PushBox.Common
6

{
7
/**//// <summary>
8
/// 工作环境
9
/// </summary>
10
sealed class Env : IDisposable
11
{
12
DataFile db; // 数据文件
13
ConfigFile cfg; // 配置文件
14
string errorMsg; // 错误信息
15
string debugMsg; // 调试信息
16
bool isReplay; // 是否正在回放
17
Action active; // 模式: 正常 新建 编辑 删除
18
byte pen; // 设计时的笔
19
Bitmap img; // 图形单元, 横向被均匀分为八份
20
Stack<Step> stack; // 历史路线, 用于后退功能
21
Size clientSize; // 工作区域尺寸(以像素为单位)
22
Size boxSize; // 图形元素尺寸(以像素为单位)
23
Point toPixel; // 将要到达的位置(以像素为单位)
24
Point worker; // 当前工人位置(以单元格为单位)
25
int pushSteps; // 推动着箱子走的步数
26
int levelOem; // 原来的关数,仅用于“菜单 -> 数据 -> 设计 -> 新建”放弃后恢复现场
27
28
public string ErrorMsg
{ get
{ return errorMsg; } }
29
public string DebugMsg
{ get
{ return debugMsg; } }
30
public string[] Groups
{ get
{ return cfg.Groups; } }
31
public int Group
{ get
{ return cfg.Group; } set
{ cfg.Group = value; } }
32
public int Level
{ get
{ return cfg.Levels[Group]; } set
{ cfg.Levels[Group] = value; } }
33
public int LeveLOem
{ get
{ return levelOem; } }
34
public int MaxLevel
{ get
{ return db.MaxLevel; } }
35
public string Steps
{ get
{ return cfg.Steps; } }
36
public Size LevelSize
{ get
{ return db.LevelSize; } }
37
public Size ClientSize
{ set
{ clientSize = value; } }
38
public Point ToPixel
{ set
{ toPixel = value; } }
39
public int MaxLevelSize
{ get
{ return cfg.MaxLevelSize; } set
{ cfg.MaxLevelSize = value; } }
40
public int StepDelay
{ get
{ return cfg.StepDelay; } set
{ cfg.StepDelay = value; } }
41
public int ReplayDelay
{ get
{ return cfg.ReplayDelay; } set
{ cfg.ReplayDelay = value; } }
42
public Action Active
{ get
{ return active; } set
{ active = value; } }
43
public byte Pen
{ get
{ return pen; } set
{ pen = value; } }
44
public bool HasError
{ get
{ return !string.IsNullOrEmpty(errorMsg); } }
45
public bool HasWorker
{ get
{ return db.HasWorker; } }
46
public bool CanUndo
{ get
{ return stack.Count != 0; } }
47
public bool CanReplay
{ get
{ return db.IsFinished && !CanUndo; } }
48
public bool IsSave
{ get
{ return cfg.IsSave; } set
{ cfg.IsSave = value; } }
49
public bool IsFinish
{ get
{ return db.Tasks == db.Boths; } }
50
public bool IsReplay
{ get
{ return isReplay; } set
{ isReplay = value; } }
51
public bool IsDesign
{ get
{ return active != Action.None; } }
52
53
public Env()
54
{
55
stack = new Stack<Step>();
56
cfg = new ConfigFile();
57
db = new DataFile();
58
Init();
59
}
60
61
/**//// <summary>
62
/// 状态栏信息
63
/// </summary>
64
public string StatusMessage
65
{
66
get
67
{
68
return HasError ? "请点击“菜单 -> 帮助 -> 错误信息”" : string.Format(
69
"{0} {1}/{2} {3} {4} {5} [{6}] {7}",
70
(active == Action.Create) ? '+' : (active == Action.Edit) ? '=' : isReplay ? "|/-\\"[stack.Count % 4] : '>',
71
Level + 1, MaxLevel, Pub.ToString(LevelSize),
72
IsDesign ? string.Format("{0}={1}", db.Boxs, db.Slots) : string.Format("{0}/{1}", db.Boths, db.Tasks),
73
IsDesign ? Block.GetPenName(pen) : string.Format("{0}({1})", stack.Count, pushSteps),
74
IsDesign ? (active == Action.Create ? "新建" : "编辑") : db.IsFinished ?
75
string.Format("{0}({1})", db.MovedSteps, db.PushedSteps) : string.Empty,
76
db.GroupName);
77
}
78
}
79
80
public void Dispose()
81
{
82
db.Dispose();
83
}
84
85
public void Init()
86
{
87
active = Action.None;
88
pen = Block.Land;
89
stack.Clear();
90
SetExceptionMessage(null);
91
}
92
93
void SetExceptionMessage(Exception ex)
94
{
95
errorMsg = Pub.GetMessage(ex, false);
96
debugMsg = Pub.GetMessage(ex, true);
97
}
98
99
/**//// <summary>
100
/// 计算当使用标准箱子尺寸时主窗体客户区的尺寸
101
/// </summary>
102
/// <param name="statusBarHeight">状态条的高度</param>
103
/// <returns>客户区的尺寸</returns>
104
public Size GetClientSize(int statusBarHeight)
105
{
106
int width = (Properties.Resources.PushBox24.Width / 8) * LevelSize.Width;
107
int height = Properties.Resources.PushBox24.Height * LevelSize.Height + statusBarHeight;
108
if (width < 240) width = 240;
109
if (height < 48) height = 48;
110
if (width > 1008) width = 1008;
111
if (height > 672) height = 672;
112
return new Size(width, height);
113
}
114
115
/**//// <summary>
116
/// 根据客户区尺寸,计算箱子的尺寸,并相应设定要显示的图形单元
117
/// </summary>
118
public void SetBoxInfo()
119
{
120
if (HasError) return;
121
if (LevelSize.IsEmpty) return;
122
int rX = clientSize.Width / LevelSize.Width;
123
int rY = clientSize.Height / LevelSize.Height;
124
int r = Math.Min(rX, rY);
125
if (r >= 24) img = Properties.Resources.PushBox24;
126
else if (r >= 20) img = Properties.Resources.PushBox20;
127
else if (r >= 16) img = Properties.Resources.PushBox16;
128
else img = Properties.Resources.PushBox12;
129
boxSize = new Size(img.Height, img.Width / 8);
130
}
131
132
/**//// <summary>
133
/// 装入配置文件
134
/// </summary>
135
public void LoadConfig()
136
{
137
if (HasError) return;
138
try
139
{
140
cfg.LoadConfig();
141
}
142
catch (Exception ex)
143
{
144
SetExceptionMessage(ex);
145
}
146
}
147
148
/**//// <summary>
149
/// 保存组信息到配置文件
150
/// </summary>
151
/// <param name="groups">组信息</param>
152
public void SaveConfig(string[] groups)
153
![]()