在上篇文章“使用 C# 開發智能手機軟件:推箱子(四)”中,我對Common/FindPath.cs 源程序文件進行了介紹。在這篇文章中介紹經過改進後的 Common/FindPath.cs 源程序文件。也就是說,已經實現了“使用 C# 開發智能手機軟件:推箱子(四)”的第二個評論中的想法,將地圖 ushort[,] map 改為 byte[,] map 了。下面就是改進後的 FindPath 類:
以下是引用片段:
1 using System;
2 using System.Drawing;
3 using System.Collections.Generic;
4
5 namespace Skyiv.Ben.PushBox.Common
6 {
7 ///
8 /// 尋找最短路線
9 ///
10 static class FindPath
11 {
12 static Size[] offsets = { new Size(0, 1), new Size(1, 0), new Size(0, -1), new Size(-1, 0) };
13 static Direction[] directions = { Direction.South, Direction.East, Direction.North, Direction.West };
14
15 ///
16 /// 尋找最短路線
17 ///
18 /// 地圖
19 /// 出發點
20 /// 目的地
21 /// 最短路線
22 public static Queue Seek(byte[,] map, Point from, Point to)
23 {
24 Queue moveQueue = new Queue(); // 路線
25 int value; // 與離目的地距離相關的一個量,變化規律: => 2 => 1 => 3 => 2 => 1 => 3 => 2 => 1
26 if (Seek(map, to, out value)) // 找到了一條路線
27 {
28 Point here = from; // 出發點(即工人的位置)
29 Point nbr = new Point(); // 四周的鄰居
30 for (value = (value + 1) % 3 + 1; here != to; value = (value + 1) % 3 + 1) // 逐步走向目的地
31 {
32 for (int i = 0; i < offsets.Length; i++)
33 {
34 nbr = Fcl.Add(here, offsets[i]); // 開始尋找四周的鄰居
35 if (Block.Value(map[nbr.Y, nbr.X]) == value) // 就往這個方向走
36 {
37 moveQueue.Enqueue(directions[i]); // 路線向目的地延伸一步
38 break;
39 }
40 }
41 here = nbr; // 繼續前進
42 }
43 }
44 Block.CleanAllMark(map); // 清除所有標志,恢復現場
45 return moveQueue; // 所尋找的路線,如果無法到達目的地則為該路線的長度為零
46 }
47
48 ///
49 /// 尋找最短路線,使用廣度優先搜索
50 ///
51 /// 地圖
52 /// 目的地
53 /// 輸出:搜索完成時標記的值
54 /// 是否成功
55 static bool Seek(byte[,] map, Point to, out int value)
56 {
57 Queue q = new Queue();
58 Block.Mark(ref map[to.Y, to.X], 1); // 從目的地開始往回尋找出發點,目的地標記為1
59 Point nbr = Point.Empty; // 四周的鄰居
60 for (; ; )
61 {
62 value = Block.Value(map[to.Y, to.X]) % 3 + 1; // 與離目的地距離相關的一個量,用作標記,變化規律:
63 for (int i = 0; i < offsets.Length; i++) // 1 => 2 => 3 => 1 => 2 => 3 => 1 => 2 => 3 =>
64 {
65 nbr = Fcl.Add(to, offsets[i]); // 開始尋找四周的鄰居
66 if (Block.IsMan(map[nbr.Y, nbr.X])) break; // 到達出發點(即工人的位置)
67 if (Block.IsBlank(map[nbr.Y, nbr.X])) // 可以走的路
68 {
69 Block.Mark(ref map[nbr.Y, nbr.X], value); // 標記,防止以後再走這條路
70 q.Enqueue(nbr); // 加入隊列,等待以後繼續尋找
71 }
72 }
73 if (Block.IsMan(map[nbr.Y, nbr.X])) break; // 到達出發點
74 if (q.Count == 0) return false; // 無法到達出發點
75 to = q.Dequeue(); // 出隊,繼續尋找,這是廣度優先搜索,因為前面已經把四周能夠走的路全部加入隊列中了.
76 }
77 return true; // 找到一條路線
78 }
79 }
80 }
上面的源程序已經對搜索算法作了很好的注釋。我們還是來看兩幅反映算法運行時地圖上各標記值的圖片吧:
圖中,帶圓圈的紅色的數字“1”是“目的地”,也就是算法開始的地方,因為該算法是從目的地開始往回尋找出發點。在改進後的算法中,標記值始終是在“1、2、3”這三個數中循環,而不是象以前一樣一直增大。在圖中,算法按“紅、黃、綠、藍、粉紅、青”的順序從目的地往外搜索,直到遇到“工人”而返回成功,或者填滿能夠到達的空地而返回失敗。
算法經過這次改進,搜索的距離就不象原來一樣受限於 8192 步。而且也將地圖所占用的內存空間減少到原來的二分之一。
這次改進,除了仔細重寫了 FindPath 類以外,程序其余地方只是將所有的“ushort”替換為“byte”就行了,因為本程序只在涉及地圖的地方使用過“ushort”。