本文主要是介绍使用 C# 开发智能手机软件:推箱子(十一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
这是“ 使用 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
{
154
if (HasError) return;
155
try
156
{
157
cfg.SaveConfig(groups);
158
}
159
catch (Exception ex)
160
{
161
SetExceptionMessage(ex);
162
}
163
}
164
165
/// <summary>
166
/// 保存当前选项及当前走法到配置文件
167
/// </summary>
168
public void SaveConfig()
169
{
170
if (HasError) return;
171
try
172
{
173
cfg.SaveConfig(stack.ToArray());
174
}
175
catch (Exception ex)
176
{
177
SetExceptionMessage(ex);
178
}
179
}
180
181
/// <summary>
182
/// 装入当前组信息
183
/// </summary>
184
public void LoadGroup()
185
{
186
if (HasError) return;
187
try
188
{
189
db.LoadGroup(Groups[Group]);
190
}
191
catch (Exception ex)
192
{
193
SetExceptionMessage(ex);
194
}
195
}
196
197
/// <summary>
198
/// 装入当前关信息
199
/// </summary>
200
public void LoadLevel()
201
{
202
active = Action.None;
203
if (HasError) return;
204
try
205
{
206
db.LoadLevel(Level);
207
worker = db.Worker;
208
stack.Clear();
209
pushSteps = 0;
210
isReplay = false;
211
}
212
catch (Exception ex)
213
{
214
SetExceptionMessage(ex);
215
}
216
}
217
218
/// <summary>
219
/// 新建一关
220
/// </summary>
221
/// <param name="isCopy">是否复制当前关</param>
222
/// <param name="size">新建关的尺寸</param>
223
public void NewLevel(bool isCopy, Size size)
224
{
225
if (HasError) return;
226
try
227
{
228
levelOem = Level;
229
Level = MaxLevel;
230
db.NewLevel(isCopy, size);
231
}
232
catch (Exception ex)
233
{
234
SetExceptionMessage(ex);
235
}
236
}
237
238
/// <summary>
239
/// 给出通关步骤
240
/// </summary>
241
/// <returns>通关步骤</returns>
242
public string GetSteps()
243
{
244
string steps = "";
245
if (!HasError)
246
{
247
try
248
{
249
steps = db.GetSteps(Level);
250
}
251
catch (Exception ex)
252
{
253
SetExceptionMessage(ex);
254
}
255
}
256
return steps;
257
}
258
259
/// <summary>
260
/// 记录通关步骤
261
/// </summary>
262
public void Record()
263
{
264
if (HasError) return;
265
try
266
{
267
db.SaveLevel(Level, stack.ToArray(), pushSteps);
268
}
269
catch (Exception ex)
270
{
271
SetExceptionMessage(ex);
272
}
273
}
274
275
/// <summary>
276
/// 保存设计数据
277
/// </summary>
278
public void SaveDesign()
279
{
280
if (HasError) return;
281
try
282
{
283
db.SaveDesign(active == Action.Create, Level);
284
}
285
catch (Exception ex)
286
{
287
SetExceptionMessage(ex);
288
}
289
}
290
291
/// <summary>
292
/// 删除最后一关
293
/// </summary>
294
public void DeleteLastLevel()
295
{
296
if (HasError) return;
297
try
298
{
299
db.DeleteLastLevel(Level);
300
}
301
catch (Exception ex)
302
{
303
SetExceptionMessage(ex);
304
}
305
}
306
307
/// <summary>
308
/// 更新主窗体客户区
309
/// </summary>
310
/// <param name="dc">画布</param>
311
/// <param name="rectangle">要在其中绘画的矩形</param>
312
public void Draw(Graphics dc, Rectangle rectangle)
313
{
314
if (HasError) return;
315
Rectangle box = PixelToBox(rectangle);
316
Rectangle box2 = new Rectangle(box.Left, box.Top, box.Width + 1, box.Height + 1);
317
for (int i = 1; i <= LevelSize.Height; i++)
318
{
319
for (int j = 1; j <= LevelSize.Width; j++)
320
{
321
if (!box2.Contains(j, i)) continue;
322
DrawBox(dc, j, i);
323
}
324
}
325
}
326
327
/// <summary>
328
/// 绘制一个单元格
329
/// </summary>
330
/// <param name="dc">画布</param>
331
/// <param name="x">单元格的横坐标</param>
332
/// <param name="y">单元格的纵坐标</param>
333
void DrawBox(Graphics dc, int x, int y)
334
{
335
DrawBox(dc, db.Map[y, x], (x - 1) * boxSize.Width, (y - 1) * boxSize.Height);
336
}
337
338
/// <summary>
339
/// 绘制一个单元格
340
/// </summary>
341
/// <param name="dc">画布</param>
342
/// <param name="idx">单元格的类型: 地 槽 墙 砖 箱子 工人</param>
343
/// <param name="x">单元格的横坐标</param>
344
/// <param name="y">单元格的纵坐标</param>
345
void DrawBox(Graphics dc, int idx, int x, int y)
346
{
347
dc.DrawImage(img, x, y, new Rectangle(idx * boxSize.Width, 0, boxSize.Width, boxSize.Height), GraphicsUnit.Pixel);
348
}
349
350
/// <summary>
351
/// 将单元格换算为像素
352
/// </summary>
353
/// <param name="box">单元格矩形</param>
354
/// <returns>像素矩形</returns>
355
Rectangle BoxToPixel(Rectangle box)
356
{
357
return new Rectangle((box.Left - 1) * boxSize.Width, (box.Top - 1) * boxSize.Height,
358
(box.Width + 1) * boxSize.Width, (box.Height + 1) * boxSize.Height);
359
}
360
361
/// <summary>
362
/// 将像素换算为单元格
363
/// </summary>
364
/// <param name="pixel">像素矩形</param>
365
/// <returns>单元格矩形</returns>
366
Rectangle PixelToBox(Rectangle pixel)
367
{
368
int x0 = pixel.Left / boxSize.Width + 1;
369
int y0 = pixel.Top / boxSize.Height + 1;
370
int x1 = (pixel.Right - 1) / boxSize.Width + 1;
371
int y1 = (pixel.Bottom - 1) / boxSize.Height + 1;
372
return new Rectangle(x0, y0, x1 - x0, y1 - y0);
373
}
374
375
/// <summary>
376
/// 根据指定的对角顶点创建矩形
377
/// </summary>
378
/// <param name="a">顶点</param>
379
/// <param name="b">对角的顶点</param>
380
/// <returns>所需要的矩形</returns>
381
Rectangle GetRectangle(Point a, Point b)
382
{
383
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));
384
}
385
386
/// <summary>
387
/// 设计模式下,当鼠标点击时要采取的动作
388
/// </summary>
389
/// <param name="invalid">输出:要重绘的区域</param>
390
/// <returns>是否发生动作</returns>
391
public bool Design(out Rectangle invalid)
392
{
393
invalid = Rectangle.Empty;
394
Point to;
395
if (!ValidClick(out to)) return false;
396
db.UpdateCounts(to.X, to.Y, false);
397
Block.Update(ref db.Map[to.Y, to.X], pen);
398
db.UpdateCounts(to.X, to.Y, true);
399
if (pen == Block.Man0 && HasWorker) pen = Block.Box0;
400
invalid = BoxToPixel(GetRectangle(to, to));
401
return true;
402
}
403
404
/// <summary>
405
/// 工人往指定方向前进一步(可能推着箱子)
406
/// </summary>
407
/// <param name="dir">前进的方向</param>
408
/// <param name="isStop">“撤销”时是否停留</param>
409
/// <param name="invalid">输出:要重绘的区域</param>
410
/// <returns>是否成功</returns>
411
public bool StepIt(Direction dir, bool isStop, out Rectangle invalid)
412
{
413
invalid = Rectangle.Empty;
414
if (HasError) return false;
415
if (Direction.None == dir) return false;
416
Point p1 = worker; // 工人前进方向一步的位置
417
Point p2 = worker; // 箱子前进方向一步的位置
418
switch (dir)
419
{
420
case Direction.East: p1.X++; p2.X += 2; break;
421
case Direction.South: p1.Y++; p2.Y += 2; break;
422
case Direction.West: p1.X--; p2.X -= 2; break;
423
case Direction.North: p1.Y--; p2.Y -= 2; break;
424
}
425
byte b1 = db.Map[p1.Y, p1.X]; // 工人前进方向一步位置上的东西
426
bool isBox = Block.IsBox(b1); // 是否推着箱子前进
427
if (!isBox && !Block.IsBlank(b1)) return false; // 如果没有推着箱子且前方不是空地则失败
428
if (isBox && !Block.IsBlank(db.Map[p2.Y, p2.X])) return false; // 如果推着箱子且箱子前方不是空地则失败
429
invalid = BoxToPixel(GetRectangle(worker, isBox ? p2 : p1)); // 要重绘的区域
430
stack.Push(new Step(dir, isBox, isStop)); // 记录走法步骤
431
Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置
432
Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入前方位置
433
if (isBox)
434
{
435
pushSteps++; // 更新推箱子步数
436
db.Boths += (db.Map[p2.Y, p2.X] - Block.Land) - (b1 - Block.Box0); // 更新已完成任务数
437
Block.BoxOut(ref db.Map[p1.Y, p1.X]); // 箱子离开当前位置
438
Block.BoxIn(ref db.Map[p2.Y, p2.X]); // 箱子进入前方位置
439
}
440
worker = p1; // 更新工人位置
441
return true; // 工人成功前进一步(可能推着条子)
442
}
443
444
/// <summary>
445
/// 工人后退一步(可能连带箱子一起后退)
446
/// </summary>
447
/// <param name="invalid">输出:要重绘的区域</param>
448
/// <returns>是否完成“撤消”</returns>
449
public bool Back(out Rectangle invalid)
450
{
451
invalid = Rectangle.Empty;
452
if (HasError) return true;
453
if (stack.Count == 0) return true;
454
Step step = stack.Pop(); // 当前步骤
455
Point p1 = worker; // 工人后退方向一步的位置
456
Point p2 = worker; // 箱子的当前位置
457
switch (step.Direct)
458
{
459
case Direction.East: p1.X--; p2.X++; break;
460
case Direction.South: p1.Y--; p2.Y++; break;
461
case Direction.West: p1.X++; p2.X--; break;
462
case Direction.North: p1.Y++; p2.Y--; break;
463
}
464
invalid = BoxToPixel(GetRectangle(p1, step.IsBox ? p2 : worker)); // 要重绘的区域
465
Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置
466
Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入后退方向一步的位置
467
if (step.IsBox)
468
{
469
Block.BoxOut(ref db.Map[p2.Y, p2.X]); // 箱子离开当前位置
470
Block.BoxIn(ref db.Map[worker.Y, worker.X]); // 箱子进入工人原来的位置
471
db.Boths += (db.Map[worker.Y, worker.X] - Block.Box0) - (db.Map[p2.Y, p2.X] - Block.Land); // 更新已完成任务数
472
pushSteps--; // 更新推箱子步数
473
}
474
worker = p1; // 更新工人位置
475
return step.IsStop; // 是否完成“撤消”
476
}
477
478
/// <summary>
479
/// 寻找一条将工人移动到鼠标点击的位置的路线
480
/// </summary>
481
/// <returns>移动的路线</returns>
482
public Queue<Direction> GetMoveInfo()
483
{
484
Point to;
485
if (!CanTo(out to)) return null;
486
return FindPath.Seek(db.Map, worker, to);
487
}
488
489
/// <summary>
490
/// 给出将箱子推动到鼠标点击的位置所需的信息
491
/// </summary>
492
/// <param name="dir">输出:工人移动的方向</param>
493
/// <returns>工人移动的步数</returns>
494
public int GetPushInfo(out Direction dir)
495
{
496
dir = Direction.None;
497
if (HasError) return 0;
498
Point to; // 目的地
499
if (!CanTo(out to)) return 0; // 无效的目的地
500
if (to.Y != worker.Y && to.X != worker.X) return 0; // 目的地和工人不在同一条直线上
501
int z0 = (to.Y == worker.Y) ? worker.X : worker.Y;
502
int z9 = (to.Y == worker.Y) ? to.X : to.Y;
503
if (to.Y == worker.Y) dir = (z9 > z0) ? Direction.East : Direction.West;
504
else dir = (z9 > z0) ? Direction.South : Direction.North;
505
int i0 = Math.Min(z9, z0);
506
int i9 = Math.Max(z9, z0);
507
int steps = i9 - i0; // 目的地和工人之间的距离
508
int boxs = 0;
509
for (int i = i0 + 1; i < i9; i++)
510
{
511
byte bi = (to.Y == worker.Y) ? db.Map[worker.Y, i] : db.Map[i, worker.X];
512
if (Block.IsBox(bi)) boxs++; // 计算工人和目的地之间的箱子的个数
513
else if (!Block.IsBlank(bi)) boxs += 2; // “墙”和“砖”折算为两个箱子
514
}
515
if (boxs > 1) return 0; // 最多只能推着一个箱子前进
516
return steps - boxs; // 工人移动的步数
517
}
518
519
/// <summary>
520
/// 检查鼠标点击位置是否可达, 并将像素换算为单元格
521
/// </summary>
522
/// <param name="to">输出:换算后的位置</param>
523
/// <returns>是否可达</returns>
524
bool CanTo(out Point to)
525
{
526
if (!ValidClick(out to)) return false;
527
if (!Block.IsMan(db.Map[worker.Y, worker.X])) throw new Exception("内部错误:工人的位置上不是工人");
528
if (!Block.IsBlank(db.Map[to.Y, to.X])) return false; // 目的地必须是“地”或“槽”
529
if (to.Y == worker.Y && to.X == worker.X) return false; // 目的地不能是工人当前的位置
530
return true; // 目的地可达
531
}
532
533
/// <summary>
534
/// 检查鼠标点击位置是否有效, 并将像素换算为单元格
535
/// </summary>
536
/// <param name="to">输出:换算后的位置</param>
537
/// <returns>是否有效位置</returns>
538
bool ValidClick(out Point to)
539
{
540
to = Point.Empty;
541
if (HasError) return false;
542
to.Y = toPixel.Y / boxSize.Height + 1;
543
to.X = toPixel.X / boxSize.Width + 1;
544
if (toPixel.X >= boxSize.Width * LevelSize.Width || toPixel.Y >= boxSize.Height * LevelSize.Height)
545
return false; // 目的地超出当前关的有效范围
546
return true; // 目的地有效
547
}
548
}
549
}
550
using System;2
using System.Drawing;3
using System.Collections.Generic;4

5
namespace Skyiv.Ben.PushBox.Common6
{7
/// <summary>8
/// 工作环境9
/// </summary>10
sealed class Env : IDisposable11
{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 StatusMessage65
{66
get67
{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
try139
{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
{154
if (HasError) return;155
try156
{157
cfg.SaveConfig(groups);158
}159
catch (Exception ex)160
{161
SetExceptionMessage(ex);162
}163
}164

165
/// <summary>166
/// 保存当前选项及当前走法到配置文件167
/// </summary>168
public void SaveConfig()169
{170
if (HasError) return;171
try172
{173
cfg.SaveConfig(stack.ToArray());174
}175
catch (Exception ex)176
{177
SetExceptionMessage(ex);178
}179
}180

181
/// <summary>182
/// 装入当前组信息183
/// </summary>184
public void LoadGroup()185
{186
if (HasError) return;187
try188
{189
db.LoadGroup(Groups[Group]);190
}191
catch (Exception ex)192
{193
SetExceptionMessage(ex);194
}195
}196

197
/// <summary>198
/// 装入当前关信息199
/// </summary>200
public void LoadLevel()201
{202
active = Action.None;203
if (HasError) return;204
try205
{206
db.LoadLevel(Level);207
worker = db.Worker;208
stack.Clear();209
pushSteps = 0;210
isReplay = false;211
}212
catch (Exception ex)213
{214
SetExceptionMessage(ex);215
}216
}217

218
/// <summary>219
/// 新建一关220
/// </summary>221
/// <param name="isCopy">是否复制当前关</param>222
/// <param name="size">新建关的尺寸</param>223
public void NewLevel(bool isCopy, Size size)224
{225
if (HasError) return;226
try227
{228
levelOem = Level;229
Level = MaxLevel;230
db.NewLevel(isCopy, size);231
}232
catch (Exception ex)233
{234
SetExceptionMessage(ex);235
}236
}237

238
/// <summary>239
/// 给出通关步骤240
/// </summary>241
/// <returns>通关步骤</returns>242
public string GetSteps()243
{244
string steps = "";245
if (!HasError)246
{247
try248
{249
steps = db.GetSteps(Level);250
}251
catch (Exception ex)252
{253
SetExceptionMessage(ex);254
}255
}256
return steps;257
}258

259
/// <summary>260
/// 记录通关步骤261
/// </summary>262
public void Record()263
{264
if (HasError) return;265
try266
{267
db.SaveLevel(Level, stack.ToArray(), pushSteps);268
}269
catch (Exception ex)270
{271
SetExceptionMessage(ex);272
}273
}274

275
/// <summary>276
/// 保存设计数据277
/// </summary>278
public void SaveDesign()279
{280
if (HasError) return;281
try282
{283
db.SaveDesign(active == Action.Create, Level);284
}285
catch (Exception ex)286
{287
SetExceptionMessage(ex);288
}289
}290

291
/// <summary>292
/// 删除最后一关293
/// </summary>294
public void DeleteLastLevel()295
{296
if (HasError) return;297
try298
{299
db.DeleteLastLevel(Level);300
}301
catch (Exception ex)302
{303
SetExceptionMessage(ex);304
}305
}306

307
/// <summary>308
/// 更新主窗体客户区309
/// </summary>310
/// <param name="dc">画布</param>311
/// <param name="rectangle">要在其中绘画的矩形</param>312
public void Draw(Graphics dc, Rectangle rectangle)313
{314
if (HasError) return;315
Rectangle box = PixelToBox(rectangle);316
Rectangle box2 = new Rectangle(box.Left, box.Top, box.Width + 1, box.Height + 1);317
for (int i = 1; i <= LevelSize.Height; i++)318
{319
for (int j = 1; j <= LevelSize.Width; j++)320
{321
if (!box2.Contains(j, i)) continue;322
DrawBox(dc, j, i);323
}324
}325
}326

327
/// <summary>328
/// 绘制一个单元格329
/// </summary>330
/// <param name="dc">画布</param>331
/// <param name="x">单元格的横坐标</param>332
/// <param name="y">单元格的纵坐标</param>333
void DrawBox(Graphics dc, int x, int y)334
{335
DrawBox(dc, db.Map[y, x], (x - 1) * boxSize.Width, (y - 1) * boxSize.Height);336
}337

338
/// <summary>339
/// 绘制一个单元格340
/// </summary>341
/// <param name="dc">画布</param>342
/// <param name="idx">单元格的类型: 地 槽 墙 砖 箱子 工人</param>343
/// <param name="x">单元格的横坐标</param>344
/// <param name="y">单元格的纵坐标</param>345
void DrawBox(Graphics dc, int idx, int x, int y)346
{347
dc.DrawImage(img, x, y, new Rectangle(idx * boxSize.Width, 0, boxSize.Width, boxSize.Height), GraphicsUnit.Pixel);348
}349

350
/// <summary>351
/// 将单元格换算为像素352
/// </summary>353
/// <param name="box">单元格矩形</param>354
/// <returns>像素矩形</returns>355
Rectangle BoxToPixel(Rectangle box)356
{357
return new Rectangle((box.Left - 1) * boxSize.Width, (box.Top - 1) * boxSize.Height,358
(box.Width + 1) * boxSize.Width, (box.Height + 1) * boxSize.Height);359
}360

361
/// <summary>362
/// 将像素换算为单元格363
/// </summary>364
/// <param name="pixel">像素矩形</param>365
/// <returns>单元格矩形</returns>366
Rectangle PixelToBox(Rectangle pixel)367
{368
int x0 = pixel.Left / boxSize.Width + 1;369
int y0 = pixel.Top / boxSize.Height + 1;370
int x1 = (pixel.Right - 1) / boxSize.Width + 1;371
int y1 = (pixel.Bottom - 1) / boxSize.Height + 1;372
return new Rectangle(x0, y0, x1 - x0, y1 - y0);373
}374

375
/// <summary>376
/// 根据指定的对角顶点创建矩形377
/// </summary>378
/// <param name="a">顶点</param>379
/// <param name="b">对角的顶点</param>380
/// <returns>所需要的矩形</returns>381
Rectangle GetRectangle(Point a, Point b)382
{383
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));384
}385

386
/// <summary>387
/// 设计模式下,当鼠标点击时要采取的动作388
/// </summary>389
/// <param name="invalid">输出:要重绘的区域</param>390
/// <returns>是否发生动作</returns>391
public bool Design(out Rectangle invalid)392
{393
invalid = Rectangle.Empty;394
Point to;395
if (!ValidClick(out to)) return false;396
db.UpdateCounts(to.X, to.Y, false);397
Block.Update(ref db.Map[to.Y, to.X], pen);398
db.UpdateCounts(to.X, to.Y, true);399
if (pen == Block.Man0 && HasWorker) pen = Block.Box0;400
invalid = BoxToPixel(GetRectangle(to, to));401
return true;402
}403

404
/// <summary>405
/// 工人往指定方向前进一步(可能推着箱子)406
/// </summary>407
/// <param name="dir">前进的方向</param>408
/// <param name="isStop">“撤销”时是否停留</param>409
/// <param name="invalid">输出:要重绘的区域</param>410
/// <returns>是否成功</returns>411
public bool StepIt(Direction dir, bool isStop, out Rectangle invalid)412
{413
invalid = Rectangle.Empty;414
if (HasError) return false;415
if (Direction.None == dir) return false;416
Point p1 = worker; // 工人前进方向一步的位置417
Point p2 = worker; // 箱子前进方向一步的位置418
switch (dir)419
{420
case Direction.East: p1.X++; p2.X += 2; break;421
case Direction.South: p1.Y++; p2.Y += 2; break;422
case Direction.West: p1.X--; p2.X -= 2; break;423
case Direction.North: p1.Y--; p2.Y -= 2; break;424
}425
byte b1 = db.Map[p1.Y, p1.X]; // 工人前进方向一步位置上的东西426
bool isBox = Block.IsBox(b1); // 是否推着箱子前进427
if (!isBox && !Block.IsBlank(b1)) return false; // 如果没有推着箱子且前方不是空地则失败428
if (isBox && !Block.IsBlank(db.Map[p2.Y, p2.X])) return false; // 如果推着箱子且箱子前方不是空地则失败429
invalid = BoxToPixel(GetRectangle(worker, isBox ? p2 : p1)); // 要重绘的区域430
stack.Push(new Step(dir, isBox, isStop)); // 记录走法步骤431
Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置432
Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入前方位置433
if (isBox)434
{435
pushSteps++; // 更新推箱子步数436
db.Boths += (db.Map[p2.Y, p2.X] - Block.Land) - (b1 - Block.Box0); // 更新已完成任务数437
Block.BoxOut(ref db.Map[p1.Y, p1.X]); // 箱子离开当前位置438
Block.BoxIn(ref db.Map[p2.Y, p2.X]); // 箱子进入前方位置439
}440
worker = p1; // 更新工人位置441
return true; // 工人成功前进一步(可能推着条子)442
}443

444
/// <summary>445
/// 工人后退一步(可能连带箱子一起后退)446
/// </summary>447
/// <param name="invalid">输出:要重绘的区域</param>448
/// <returns>是否完成“撤消”</returns>449
public bool Back(out Rectangle invalid)450
{451
invalid = Rectangle.Empty;452
if (HasError) return true;453
if (stack.Count == 0) return true;454
Step step = stack.Pop(); // 当前步骤455
Point p1 = worker; // 工人后退方向一步的位置456
Point p2 = worker; // 箱子的当前位置457
switch (step.Direct)458
{459
case Direction.East: p1.X--; p2.X++; break;460
case Direction.South: p1.Y--; p2.Y++; break;461
case Direction.West: p1.X++; p2.X--; break;462
case Direction.North: p1.Y++; p2.Y--; break;463
}464
invalid = BoxToPixel(GetRectangle(p1, step.IsBox ? p2 : worker)); // 要重绘的区域465
Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置466
Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入后退方向一步的位置467
if (step.IsBox)468
{469
Block.BoxOut(ref db.Map[p2.Y, p2.X]); // 箱子离开当前位置470
Block.BoxIn(ref db.Map[worker.Y, worker.X]); // 箱子进入工人原来的位置471
db.Boths += (db.Map[worker.Y, worker.X] - Block.Box0) - (db.Map[p2.Y, p2.X] - Block.Land); // 更新已完成任务数472
pushSteps--; // 更新推箱子步数473
}474
worker = p1; // 更新工人位置475
return step.IsStop; // 是否完成“撤消”476
}477

478
/// <summary>479
/// 寻找一条将工人移动到鼠标点击的位置的路线480
/// </summary>481
/// <returns>移动的路线</returns>482
public Queue<Direction> GetMoveInfo()483
{484
Point to;485
if (!CanTo(out to)) return null;486
return FindPath.Seek(db.Map, worker, to);487
}488

489
/// <summary>490
/// 给出将箱子推动到鼠标点击的位置所需的信息491
/// </summary>492
/// <param name="dir">输出:工人移动的方向</param>493
/// <returns>工人移动的步数</returns>494
public int GetPushInfo(out Direction dir)495
{496
dir = Direction.None;497
if (HasError) return 0;498
Point to; // 目的地499
if (!CanTo(out to)) return 0; // 无效的目的地500
if (to.Y != worker.Y && to.X != worker.X) return 0; // 目的地和工人不在同一条直线上501
int z0 = (to.Y == worker.Y) ? worker.X : worker.Y;502
int z9 = (to.Y == worker.Y) ? to.X : to.Y;503
if (to.Y == worker.Y) dir = (z9 > z0) ? Direction.East : Direction.West;504
else dir = (z9 > z0) ? Direction.South : Direction.North;505
int i0 = Math.Min(z9, z0);506
int i9 = Math.Max(z9, z0);507
int steps = i9 - i0; // 目的地和工人之间的距离508
int boxs = 0;509
for (int i = i0 + 1; i < i9; i++)510
{511
byte bi = (to.Y == worker.Y) ? db.Map[worker.Y, i] : db.Map[i, worker.X];512
if (Block.IsBox(bi)) boxs++; // 计算工人和目的地之间的箱子的个数513
else if (!Block.IsBlank(bi)) boxs += 2; // “墙”和“砖”折算为两个箱子514
}515
if (boxs > 1) return 0; // 最多只能推着一个箱子前进516
return steps - boxs; // 工人移动的步数517
}518

519
/// <summary>520
/// 检查鼠标点击位置是否可达, 并将像素换算为单元格521
/// </summary>522
/// <param name="to">输出:换算后的位置</param>523
/// <returns>是否可达</returns>524
bool CanTo(out Point to)525
{526
if (!ValidClick(out to)) return false;527
if (!Block.IsMan(db.Map[worker.Y, worker.X])) throw new Exception("内部错误:工人的位置上不是工人");528
if (!Block.IsBlank(db.Map[to.Y, to.X])) return false; // 目的地必须是“地”或“槽”529
if (to.Y == worker.Y && to.X == worker.X) return false; // 目的地不能是工人当前的位置530
return true; // 目的地可达531
}532

533
/// <summary>534
/// 检查鼠标点击位置是否有效, 并将像素换算为单元格535
/// </summary>536
/// <param name="to">输出:换算后的位置</param>537
/// <returns>是否有效位置</returns>538
bool ValidClick(out Point to)539
{540
to = Point.Empty;541
if (HasError) return false;542
to.Y = toPixel.Y / boxSize.Height + 1;543
to.X = toPixel.X / boxSize.Width + 1;544
if (toPixel.X >= boxSize.Width * LevelSize.Width || toPixel.Y >= boxSize.Height * LevelSize.Height)545
return false; // 目的地超出当前关的有效范围546
return true; // 目的地有效547
}548
}549
} 550
这篇关于使用 C# 开发智能手机软件:推箱子(十一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!