银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

概述

在上一篇随笔“【算法】从推箱子的解答步骤还原关卡地图”中,我给出一个控制台应用程序,将 LURD 数据转换为 XSB 数据。为了方便使用,我编写了一个 ASP.NET 网页实现从推箱子的解答步骤还原关卡地图:Sokoban: Lurd to Xsb,如下所示:

源程序代码

首先是 lurd2xsb.aspx 源程序文件:

01:  <%@ Page Language="C#" inherits="Skyiv.Ben.Web.Lurd2xsbPage" %>
02:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
03:  <html xmlns="http://www.w3.org/1999/xhtml" >
04:  <head runat="server">
05:    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
06:    <title>Sokoban: Lurd to Xsb</title>
07:  </head>
08:  <body>
09:    <form id="form1" runat="server">
10:    <asp:Button Text="Home" OnClick="BtnUriHome_Click" runat="server" />
11:    <asp:Button Text="Start" OnClick="BtnSubmit_Click" runat="server" />
12:    <a href="http://sokoban.ws/xsb_lurd.php" target="_blank">Introduction to XSB and LURD</a>
13:    <a href="http://sokoban.ws/sokoplayer/SokoPlayer_HTML5.php" target="_blank">SokoPlayer HTML5</a>
14:    <a href="http://sokoban.ws/utility/SokoEditor.php" target="_blank">SokoEditor HTML5</a>
15:    <a href="http://www.cnblogs.com/skyivben/archive/2011/07/03/2096801.html" target="_blank">Algorithm</a>
16:    <hr />
17:    <div>
18:    <table><tbody><tr/><td>
19:    <asp:TextBox Runat="Server" Id="tbxLurd" MaxLength="102400" TextMode="MultiLine" Columns="50" Rows="11"
20:      Text="rrurruulllulldRRRRlllldlluRRurrddLLrDururrurrdLddlldlLuululldRRuurrdLLrrrRurrdLLrddlldlluUruRRlldlluRRlddddlluRdrU"/>
21:    <br />
22:    <asp:TextBox Runat="Server" Id="tbxMoves" ReadOnly="True" Columns="57" BackColor="#CFECEC"/></td><td>
23:    <asp:TextBox Runat="Server" Id="tbxXsb" ReadOnly="True" TextMode="MultiLine" Columns="60" Rows="12" Wrap="False" BackColor="#CFECEC"/>
24:    </td></tr></tbody></table>
25:    <br />
26:    <img src="xsbbitmap.aspx" /><hr />
27:    I would like to thank <a href="http://borgar.net/s/2009/07/sokoban/" target="_blank">Borgar 
28:    &#222;orsteinsson</a> for his kind permission using the elegant skin designed by him.
29:    </div>
30:    </form>
31:  </body>
32:  </html>

注意上述源程序文件中第 26 行使用了 HTML 的 <img /> 标签,而没有使用 ASP.NET 的 <asp:Image /> 控件。这是因为后者只能通过其 ImageUri 属性来显示图像。而我们的图像是根据 Xsb 数据动态生成的,这就不符合要求了。相应的 lurd2xsb.aspx.cs 源程序文件如下所示:

01:  using System;
02:  using System.IO;
03:  using System.Web.UI;
04:  using System.Web.UI.WebControls;
05:  using Skyiv.Ben.Game;
06:  
07:  namespace Skyiv.Ben.Web
08:  {
09:    public class Lurd2xsbPage : Page
10:    {
11:      protected TextBox tbxLurd;
12:      protected TextBox tbxMoves;
13:      protected TextBox tbxXsb;
14:  
15:      public void BtnUriHome_Click(object sender, EventArgs e)
16:      {
17:        Response.Redirect("~/");
18:      }
19:  
20:      public void BtnSubmit_Click(object sender, EventArgs e)
21:      {
22:        try
23:        {
24:          var runer = new Lurd2Xsb();
25:          var xsb = runer.GetXsbFromLurd(tbxLurd.Text);
26:          tbxMoves.Text = string.Format("{0:N0} moves, {1:N0} pushes", runer.Moves, runer.Pushes);
27:          tbxXsb.Text = xsb;
28:          Session["map"] = Xsb2Bitmap.GetMap(xsb);
29:        }
30:        catch (Exception ex)
31:        {
32:          tbxXsb.Text = "Error: " + ex.Message;
33:        }
34:      }
35:    }
36:  }

上述源程序代码的第 28 行将表示关卡地图的二维的字节数组存放到 Session 中,以便后面动态生成关卡地图的图像时使用。上述源程序第 24 行中使用到的 Skyiv.Ben.Game.Lurd2Xsb 类位于 lurd2xsb.cs 源程序文件中:

001:  using System;
002:  using System.Text;
003:  using System.Drawing;
004:  
005:  namespace Skyiv.Ben.Game
006:  {
007:    public sealed class Lurd2Xsb
008:    {
009:      public int Moves { get { return Steps + Pushes; } }
010:      public int Pushes { get; private set; }
011:      public int Steps { get; private set; }
012:      void SetBrick(byte[,] map, int x, int y) { map[x, y] = 8; }
013:      void SetFloor(byte[,] map, Point pt) { map[pt.X, pt.Y] |= 1; }
014:      void SetGoal(byte[,] map, Point pt) { map[pt.X, pt.Y] |= 2; }
015:      void SetBox(byte[,] map, Point pt) { map[pt.X, pt.Y] |= 4; }
016:      void UnsetBox(byte[,] map, Point pt) { map[pt.X, pt.Y] &= 0xFB; }
017:      bool HasBox(byte[,] map, int x, int y) { return (map[x, y] & 4) != 0; }
018:      bool IsGoal(byte[,] map, int x, int y) { return (map[x, y] & 2) != 0; }
019:      bool IsFloor(byte[,] map, int x, int y) { return (map[x, y] & 1) != 0; }
020:      bool IsBrick(byte[,] map, int x, int y) { return map[x, y] == 8; }
021:      bool IsWall(byte[,] map, int x, int y) { return map[x, y] == 0; }
022:      bool IsBrickOrWall(byte[,] map, int x, int y) { return IsBrick(map, x, y) || IsWall(map, x, y); }
023:      
024:      void MarkBrick(byte[,] map, int x, int y)
025:      {
026:        if (!IsWall(map, x, y)) return;
027:        if (!IsBrickOrWall(map, x - 1, y - 1)) return;
028:        if (!IsBrickOrWall(map, x - 1, y + 1)) return;
029:        if (!IsBrickOrWall(map, x + 1, y - 1)) return;
030:        if (!IsBrickOrWall(map, x + 1, y + 1)) return;
031:        if (!IsBrickOrWall(map, x - 1, y)) return;
032:        if (!IsBrickOrWall(map, x + 1, y)) return;
033:        if (!IsBrickOrWall(map, x, y - 1)) return;
034:        if (!IsBrickOrWall(map, x, y + 1)) return;
035:        SetBrick(map, x, y);
036:      }
037:      
038:      int GetX(byte[,] map, int y0, int y1, int x, int x2)
039:      {
040:        for (var y = y0; y < y1; y++)
041:          if (!IsBrick(map, x, y)) return x;
042:        return x2;
043:      }
044:  
045:      int GetY(byte[,] map, int x0, int x1, int y, int y2)
046:      {
047:        for (var x = x0; x < x1; x++)
048:          if (!IsBrick(map, x, y)) return y;
049:        return y2;
050:      }
051:  
052:      Rectangle GetRectangle(byte[,] map)
053:      {
054:        int x0 = 1, y0 = 1, x1 = map.GetLength(0) - 2, y1 = map.GetLength(1) - 2;
055:        x0 = GetX(map, y0, y1, x0, x0 + 1);
056:        x1 = GetX(map, y0, y1, x1, x1 - 1); 
057:        y0 = GetY(map, x0, x1, y0, y0 + 1);
058:        y1 = GetY(map, x0, x1, y1, y1 - 1);
059:        return Rectangle.FromLTRB(x0, y0, x1, y1);
060:      }
061:  
062:      string GetXsb(byte[,] map, Point man)
063:      {
064:        var rect = GetRectangle(map);
065:        for (var y = rect.Top; y <= rect.Bottom; y++)
066:          for (var x = rect.Left; x <= rect.Right; x++)
067:            MarkBrick(map, x, y);
068:        rect = GetRectangle(map);
069:        var xsb = new StringBuilder();
070:        for (var y = rect.Top; y <= rect.Bottom; y++)
071:        {
072:          for (var x = rect.Left; x <= rect.Right; x++)
073:          {
074:            if (IsGoal(map, x, y))
075:            {
076:              if (man.X == x && man.Y == y) xsb.Append('+');
077:              else if (HasBox(map, x, y)) xsb.Append('*');
078:              else xsb.Append('.');
079:            }
080:            else if (IsFloor(map, x, y))
081:            {
082:              if (man.X == x && man.Y == y) xsb.Append('@');
083:              else if (HasBox(map, x, y)) xsb.Append('$');
084:              else xsb.Append('-');
085:            }
086:            else if (IsBrick(map, x, y)) xsb.Append('_');
087:            else xsb.Append('#');
088:          }
089:          xsb.AppendLine();
090:        }
091:        return xsb.ToString();
092:      }
093:  
094:      void LeftOrRight(byte[,] map, ref Point man, int step)
095:      {
096:        man.X += step;
097:        if (HasBox(map, man.X, man.Y)) UnsetBox(map, man);
098:        else SetGoal(map, man);
099:        man.X -= step;
100:        SetBox(map, man);
101:        man.X -= step;
102:        SetFloor(map, man);
103:      }
104:      
105:      void UpOrDown(byte[,] map, ref Point man, int step)
106:      {
107:        man.Y += step;
108:        if (HasBox(map, man.X, man.Y)) UnsetBox(map, man);
109:        else SetGoal(map, man);
110:        man.Y -= step;
111:        SetBox(map, man);
112:        man.Y -= step;
113:        SetFloor(map, man);
114:      }
115:      
116:      byte[,] GetMap(string lurd, Size size, ref Point man)
117:      {
118:        var map = new byte[size.Width, size.Height];
119:        SetFloor(map, man);
120:        for (var i = lurd.Length - 1; i >= 0; i--)
121:        {
122:          switch (lurd[i])
123:          {
124:            case 'l': man.X++; SetFloor(map, man); Steps++; break;
125:            case 'r': man.X--; SetFloor(map, man); Steps++; break;
126:            case 'u': man.Y++; SetFloor(map, man); Steps++; break;
127:            case 'd': man.Y--; SetFloor(map, man); Steps++; break;
128:            case 'L': LeftOrRight(map, ref man, -1); Pushes++; break;
129:            case 'R': LeftOrRight(map, ref man, 1); Pushes++; break;
130:            case 'U': UpOrDown(map, ref man, -1); Pushes++; break;
131:            case 'D': UpOrDown(map, ref man, 1); Pushes++; break;
132:          }
133:        }
134:        return map;
135:      }
136:      
137:      Rectangle GetBoundary(string lurd)
138:      {
139:        var boundary = new Rectangle(0, 0, 1, 1);
140:        var man = new Point();
141:        for (var i = lurd.Length - 1; i >= 0; i--)
142:        {
143:          switch (lurd[i])
144:          {
145:            case 'l': case 'L': man.X++; if (boundary.Right <= man.X) boundary.Width++; break;
146:            case 'r': case 'R': man.X--; if (boundary.Left > man.X) { boundary.Width++; boundary.X--; } break;
147:            case 'u': case 'U': man.Y++; if (boundary.Bottom <= man.Y) boundary.Height++; break;
148:            case 'd': case 'D': man.Y--; if (boundary.Top > man.Y) { boundary.Height++; boundary.Y--; } break;
149:          }
150:        }
151:        return boundary;
152:      }
153:      
154:      public string GetXsbFromLurd(string lurd)
155:      {
156:        var boundary = GetBoundary(lurd);
157:        var size = new Size(boundary.Width + 6, boundary.Height + 6);
158:        var man = new Point(3 - boundary.X, 3 - boundary.Y);
159:        var map = GetMap(lurd, size, ref man);
160:        return GetXsb(map, man);
161:      }
162:    }
163:  }

这个源程序文件已经在上一篇随笔“【算法】从推箱子的解答步骤还原关卡地图”中介绍过了。下面是 xsb2bitmap.cs 源程序文件:

01:  using System;
02:  using System.Drawing;
03:  using System.Reflection;
04:  
05:  namespace Skyiv.Ben.Game
06:  {
07:    public sealed class Xsb2Bitmap
08:    {
09:      static Bitmap img8;
10:      static Size boxSize;
11:      byte[,] map;
12:      
13:      static Xsb2Bitmap()
14:      {
15:        img8 = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("PushBox24.png"));
16:        boxSize = new Size(img8.Width / 8, img8.Height);
17:      }
18:      
19:      public static byte[,] GetMap(string xsb)
20:      {
21:        var lines = xsb.Split(default(Char[]), StringSplitOptions.RemoveEmptyEntries);
22:        var map = new byte[lines[0].Length, lines.Length];
23:        for (var y = 0; y < map.GetLength(1); y++)
24:          for (var x = 0; x < map.GetLength(0); x++)
25:            map[x, y] = GetByte(lines[y][x]);
26:        return map;
27:      }
28:      
29:      static byte GetByte(char c)
30:      {
31:        switch (c)
32:        {
33:          case ' ':
34:          case '-': return 0;
35:          case '.': return 1;
36:          case '#': return 2;
37:          case '_': return 3;
38:          case '$': return 4;
39:          case '*': return 5;
40:          case '@': return 6;
41:          case '+': return 7;
42:        }
43:        throw new Exception("invalid XSB char:[" + c + "]");
44:      }
45:      
46:      public Xsb2Bitmap(byte[,] map)
47:      {
48:        this.map = map;
49:      }
50:      
51:      public Bitmap GetBitmap()
52:      {
53:        if (map == null) return new Bitmap(1, 1);
54:        var img = new Bitmap(boxSize.Width * map.GetLength(0), boxSize.Height * map.GetLength(1));
55:        var graphics = Graphics.FromImage(img);
56:        for (var y = 0; y < map.GetLength(1); y++)
57:          for (var x = 0; x < map.GetLength(0); x++)
58:            DrawBox(graphics, map[x, y], x * boxSize.Width, y * boxSize.Height);
59:        return img;
60:      }
61:      
62:      void DrawBox(Graphics graphics, int idx, int x, int y)
63:      {
64:        graphics.DrawImage(img8, x, y, new Rectangle(idx * boxSize.Width, 0, boxSize.Width, boxSize.Height), GraphicsUnit.Pixel);
65:      }    
66:    }
67:  }

上述源程序有两个用途:

  • 静态的 GetMap 方法将 Xsb 数据(文件格式)转换为二维字节数组
  • GetBitmap 方法从上述二维字节数组动态生成表示关卡地图的图片

下面就是用于动态生成图像的 xsbbitmap.aspx 源程序文件了:

01:  <%@ Page Language="C#" inherits="Skyiv.Ben.Web.XsbBitmapPage" %>
02:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
03:  <html xmlns="http://www.w3.org/1999/xhtml">
04:  <head runat="server">
05:      <title>Xsb Bitmap</title>
06:  </head>
07:  <body>
08:      <form id="form1" runat="server">
09:      </form>
10:  </body>
11:  </html>

对应的 xsbbitmap.aspx.cs 源程序文件如下所示:

01:  using System;
02:  using System.IO;
03:  using System.Drawing.Imaging;
04:  using System.Web.UI;
05:  using System.Web.UI.WebControls;
06:  using Skyiv.Ben.Game;
07:  
08:  namespace Skyiv.Ben.Web
09:  {
10:    public class XsbBitmapPage : Page
11:    {
12:      public void Page_Load(object sender, EventArgs e)
13:      {
14:        CreateImage((byte[,])Session["map"]);
15:      }
16:      
17:      void CreateImage(byte[,] map)
18:      {
19:        Session["map"] = null;
20:        var img = new Xsb2Bitmap(map).GetBitmap();
21:        try
22:        {
23:          var ms = new MemoryStream();
24:          img.Save(ms, ImageFormat.Png);
25:          Response.ClearContent();
26:          Response.ContentType = "image/Png";
27:          Response.BinaryWrite(ms.ToArray());        
28:        }
29:        finally
30:        {
31:          img.Dispose();
32:          Response.End();
33:        }
34:      }
35:    }
36:  }

上述源程序第 14 行从前面赋值的 Session 取得表示关卡地图的二维字节数组,然后在第 20 行调用 Xsb2Bitmap 类的 GetBitmap 方法得到一个 Bitmap 图像,接着就是通过 Page  类的 Response 属性(类型为 HttpResponse 类)调用 BinaryWrite 方法将图像数据写入 HTTP 输出流。

源代码下载

本文中的所有源代码可以到 https://bitbucket.org/ben.skyiv/aspspider/downloads 页面下载。
也可以使用 hg clone https://bitbucket.org/ben.skyiv/aspspider 命令下载。
关于 hg ,请参阅 Mercurial 备忘录

posted on 2011-07-10 12:47  银河  阅读(10658)  评论(4编辑  收藏  举报