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

本文介绍了使用C#开发智能手机软件的系列文章之十一篇的内容,重点讲解了Common/Env.cs源程序文件,其中包含了实现程序功能的基础类。文章详细解释了Env类的方法,如GetClientSize、SetBoxInfo等,并探讨了如何管理数据文件和配置文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2007年10月09日 11:28:00
这是" 使用 C# 开发智能手机软件:推箱子"系列文章的第十一篇。在这篇文章中,介绍 Common/Env.cs 源程序文件。这个源程序文件中包含表示"工作环境"的密封类 Env 。也就是说,主程序中重要的变量都封装在这个类中,作为整个程序的"工作环境"。她还起着桥梁作用,其中两个字段:

DataFile db; // 数据文件
ConfigFile cfg; // 配置文件

正是我们以前介绍过的管理数据文件的密封类 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


Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=1816536


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值