迷路の生成とその解
パラドックス大全 - 世にも不思議な逆説パズル
著:ウィリアム・パウンドストーン 訳:松浦俊介
という本を読了しました。冒頭の水槽の脳の話から最後まで楽しく読めました。
その中で、NP完全と迷宮の話がでてきまして、大変興味をそそられました。
プログラミングにおける、迷路のアルゴリズムの原理については、
一応たしなむ程度には知っていましたが、実際に書いたことはなかったので、
試しに書いてみることにしました。どうせなら、ちょっとは見た目にも懲りたいなー、と思って作っていたら、
結果的に見た目の部分のプログラミングの方がメインっぽくなってしまいました(本末転倒)。
まだリファクタリングが不十分だったり、クラス設計でポカやってたり、
やり残し感がかなりありますが、なんか飽きちゃった(笑)ので、このあたりで一端ストップ。
恥ずかしげもなく、今回書いたC#で迷路なコードをあげておきます。
迷路のインターフェイス
まずは迷路のインターフェイスを定義してみましょう。ひとくちに迷路のインターフェイスと言っても、いろいろと考えられますが、
今回定義する迷路のインターフェイスのコンセプトとしては、
「穴掘り法」や「壁延ばし法」などのさまざまな迷路生成のアルゴリズムを、
後で入れ替え可能にできるようにするイメージで。
いわゆるGoFのデザパタでいうところのStrategyパターンというやつです。
今回はこんな感じにしてみました。
using System; using System.Collections.Generic; using System.Drawing; namespace ClassLibrary1 { /// <summary> /// 迷路のインターフェイス /// </summary> public interface IMaze { /// <summary> /// 迷路の解が完了したことを通知 /// </summary> event EventHandler MazeSolveComplete; /// <summary> /// スタック /// </summary> Stack<Cell> CellStack { get; set; } /// <summary> /// Cells /// </summary> Cell[][] Cells { get; set; } /// <summary> /// カレントセル /// </summary> Cell CurrentCell { get; set; } /// <summary> /// 迷路を初期化します。 /// </summary> void Initialize(Size dimension); /// <summary> /// 迷路を生成します。 /// </summary> void Generate(); /// <summary> /// 迷路を解きます。 /// </summary> void Solve(); /// <summary> /// 迷路を描画します。 /// </summary> /// <param name="g"></param> void Draw(Graphics g); /// <summary> /// リセットします。 /// </summary> void Reset(); } }
迷路を描画する升目を表すCellクラス
迷路の自動生成と、解を求めるプログラムを表現するのであれば、別にConsoleApplicationへWrite()するだけの単純なプログラムでも良いのですが、
今回はそれなりに見た目にもこだわりたいということで、
迷路の升目をグラフィカルに描画することを考慮したうえでCellクラスを作ります。
なんかいろいろと、ツッ込みどころ満載ですがご容赦ください。
using System; using System.Drawing; using System.ComponentModel; namespace ClassLibrary1 { /// <summary> /// Cell /// </summary> public class Cell { public static readonly Random _random = new Random((int)DateTime.Now.Ticks); private static int _cellSize = 5; [DefaultValue(5)] public static int CellSize { get { return _cellSize; } set { _cellSize = value; } } public const int Padding = 10; /// <summary> /// 壁の色 /// </summary> private static Color _wallColor = Color.Black; [DefaultValue(typeof(Color),"Black")] public static Color WallColor { get { return _wallColor; } set { _wallColor = value; } } /// <summary> /// Cellの色 /// </summary> private static Color _cellColor = Color.Yellow; [DefaultValue(typeof(Color), "Yellow")] public static Color CellColor { get { return _cellColor; } set { _cellColor = value; } } /// <summary> /// 答えの色 /// </summary> private static Color _solveColor = Color.Pink; [DefaultValue(typeof(Color), "Pink")] public static Color SolveColor { get { return _solveColor; } set { _solveColor = value; } } /// <summary> /// 周りの壁 /// </summary> public int[] Walls = new int[4] { 1, 1, 1, 1 }; [DefaultValue(typeof(CellState), "CellState.Init")] public enum CellState { /// <summary> /// 初期 /// </summary> Init = 0, /// <summary> /// フリー /// </summary> Free = 1, /// <summary> /// 行き止まり /// </summary> Wall = 2 } public Cell() {} public int Row { get; set; } public int Column { get; set; } public CellState State { get; set; } public bool HasAllWalls() { for (int i = 0; i < 4; i++) if (Walls[i] == 0) return false; return true; } /// <summary> /// セルの周りの壁を全部ぬっ壊します。 /// </summary> /// <param name="wall"></param> public void DestroyWall(int wall) { Walls[wall] = 0; } /// <summary> /// 壁をぬっ壊します。 /// </summary> /// <param name="theCell"></param> public void DestroyWall(Cell cell) { //隣接している壁を探す int dw = GetAdjacentWall(cell); Walls[dw] = 0; //次のセルの逆の壁もぬっこわす int oppositeWall = (dw + 2) % 4; cell.Walls[oppositeWall] = 0; } /// <summary> /// 隣接しているセル方向の壁を取得します。 /// </summary> /// <param name="cell"></param> /// <returns></returns> public int GetAdjacentWall(Cell cell) { if (cell.Row == Row) { if (cell.Column < Column) return 0; return 2; } if (cell.Row < Row) return 1; return 3; } /// <summary> /// ランダムに壁を選択します。 /// </summary> /// <returns></returns> public int SelectRandomWall() { int nextWall = _random.Next(0, 3); while ( (Walls[nextWall] == 0) || ((Row == 0) && (nextWall == 0)) || ((Row == Excavation.Dimension.Height - 1) && (nextWall == 2)) || ((Column == 0) && (nextWall == 1)) || ((Column == Excavation.Dimension.Width - 1) && (nextWall == 3)) ) { nextWall = _random.Next(0, 3); } return nextWall; } /// <summary> /// Cellを描画します。 /// </summary> /// <param name="g"></param> public void Draw(Graphics g) { g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver; using (var wallPen = new Pen(WallColor)) using (var cellPen = new Pen(CellColor)) { if (State == CellState.Free) { using (var solvePen = new Pen(SolveColor)) { g.FillRectangle(solvePen.Brush, Row * CellSize + Padding + 1, Column * CellSize + Padding + 1, CellSize - 1, CellSize - 1); if (Walls[0] == 0) g.DrawLine(solvePen, Row * CellSize + Padding, Column * CellSize + Padding, (Row + 1) * CellSize + Padding, Column * CellSize + +Padding); if (Walls[1] == 0) g.DrawLine(solvePen, Row * CellSize + Padding, Column * CellSize + Padding, Row * CellSize + Padding, (Column + 1) * CellSize + Padding); if (Walls[2] == 0) g.DrawLine(solvePen, Row * CellSize + Padding, (Column + 1) * CellSize + Padding, (Row + 1) * CellSize + Padding, (Column + 1) * CellSize + Padding); if (Walls[3] == 0) g.DrawLine(solvePen, (Row + 1) * CellSize + Padding, Column * CellSize + Padding, (Row + 1) * CellSize + Padding, (Column + 1) * CellSize + Padding); } } if (State != CellState.Free) g.FillRectangle(cellPen.Brush, Row * CellSize + Padding + 1, Column * CellSize + Padding + 1, CellSize, CellSize); if (Walls[0] == 1) g.DrawLine(wallPen, Row * CellSize + Padding, Column * CellSize + Padding, (Row + 1) * CellSize + Padding, Column * CellSize + +Padding); if (Walls[1] == 1) g.DrawLine(wallPen, Row * CellSize + Padding, Column * CellSize + Padding, Row * CellSize + Padding, (Column + 1) * CellSize + Padding); if (Walls[2] == 1) g.DrawLine(wallPen, Row * CellSize + Padding, (Column + 1) * CellSize + Padding, (Row + 1) * CellSize + Padding, (Column + 1) * CellSize + Padding); if (Walls[3] == 1) g.DrawLine(wallPen, (Row + 1) * CellSize + Padding, Column * CellSize + Padding, (Row + 1) * CellSize + Padding, (Column + 1) * CellSize + Padding); } } } }
IMazeインターフェイスを実装する、穴掘り法な迷路
穴掘り法を用いた迷路の自動生成の実装です。
迷路の解を求めるアルゴリズムは、総当りでの塗りつぶし法を採用しています。
効率的なアルゴリズムではないので、迷路が巨大になると、それに比例してかなりの時間を要します。
using System; using System.ComponentModel; using System.Collections.Generic; using System.Drawing; namespace ClassLibrary1 { /// <summary> /// 穴掘り法の迷路 /// </summary> public sealed class Excavation : IMaze { public Stack<Cell> CellStack { get; set;} public Excavation() { this.Initialize(Dimension); } /// <summary> /// Solveの終了を通知します。 /// </summary> public event EventHandler MazeSolveComplete; /// <summary> /// 現在のセル /// </summary> public Cell CurrentCell { get; set; } /// <summary> /// Cells /// </summary> public Cell[][] Cells { get; set; } private static Size _dimension= new Size(50, 50); [DefaultValue(typeof(Size))] public static Size Dimension { get { return _dimension; } set { _dimension = value; } } [EditorBrowsable(EditorBrowsableState.Never)] public bool ShouldSerializeDimension() { if (_dimension.Width != 50) return true; if (_dimension.Height != 50) return true; return false; } [EditorBrowsable(EditorBrowsableState.Never)] public void ResetDimension() { _dimension = new Size(50, 50); } /// <summary> /// 隣接する壁を持つセルのリストを取得 /// </summary> /// <param name="cell"></param> /// <returns></returns> private List<Cell> GetNeighborsWithWalls(Cell cell) { List<Cell> Neighbors = new List<Cell>(); //int count = 0; for (int countRow = -1; countRow <= 1; countRow++) for (int countCol = -1; countCol <= 1; countCol++) { if ( (cell.Row + countRow < Excavation.Dimension.Height) && (cell.Column + countCol < Excavation.Dimension.Width) && (cell.Row + countRow >= 0) && (cell.Column + countCol >= 0) && ((countCol == 0) || (countRow == 0)) && (countRow != countCol)) { if (Cells[cell.Row+countRow][cell.Column+countCol].HasAllWalls()) Neighbors.Add( Cells[cell.Row+countRow][cell.Column+countCol]); } } return Neighbors; } /// <summary> /// 迷路の初期化 /// </summary> public void Initialize(Size dimension) { this.CellStack = this.CellStack ?? new Stack<Cell>(); this.Cells = new Cell[dimension.Height][]; var totalCells = dimension.Height * dimension.Width; for (int i = 0; i < dimension.Height; i++) { this.Cells[i] = new Cell[dimension.Width]; for (int j = 0; j < dimension.Width; j++) this.Cells[i][j] = new Cell() { Row = i, Column = j }; } this.CurrentCell = this.Cells[0][0]; this.CellStack.Clear(); } /// <summary> /// 迷路を生成します。 /// /// 穴掘り法 /// </summary> public void Generate() { for (int i = 1; i < Dimension.Width * Dimension.Height;) { var adjacentCells = GetNeighborsWithWalls(CurrentCell); if (adjacentCells.Count > 0) { //ランダムにセル1つを選択し、それとカレントセルの間の壁をぬっ壊す int randomCell = Cell._random.Next(0, adjacentCells.Count); var cell = (Cell)adjacentCells[randomCell]; CurrentCell.DestroyWall(cell); CellStack.Push(CurrentCell); CurrentCell = cell; i++; continue; } CurrentCell = CellStack.Pop(); } Cells[0][0].Walls[1] = 0; Cells[Excavation.Dimension.Height - 1][Excavation.Dimension.Width - 1].Walls[3] = 0; } /// <summary> /// 迷路を解きます。 /// /// すべての行き止まりを塗り潰せば、結果的に正解が浮かび上がる方式で解を得る /// </summary> public void Solve() { bool search = true; while (search) { search = false; foreach (var cell in GetDeadEnd()) { cell.State = Cell.CellState.Wall; search = true; } }; MazeSolveComplete(this, EventArgs.Empty); } /// <summary> /// 行き止まりのセルのリストを取得します。 /// </summary> /// <returns></returns> private IEnumerable<Cell> GetDeadEnd() { for (int i = 0; i < Excavation.Dimension.Height; i++) { for (int j = 0; j < Excavation.Dimension.Width; j++) { if (Cells[i][j].State == Cell.CellState.Free) if (!IsFree(i,j)) yield return Cells[i][j]; } } } /// <summary> /// Cellがフリーか否かを取得します。 /// </summary> /// <param name="r"></param> /// <param name="c"></param> /// <returns></returns> private bool IsFree( int r, int c ) { int walls = 0; if( Cells[r][c].Walls[0] == 1 ) walls ++; else if( c > 0 && ((int)Cells[r][c - 1].State > (int)Cell.CellState.Free )) walls++; if( Cells[r][c].Walls[1] == 1 ) walls ++; else if( r > 0 && ((int)Cells[r - 1][c].State > (int)Cell.CellState.Free )) walls++; if( Cells[r][c].Walls[2] == 1 ) walls ++; else if (c < Excavation.Dimension.Width - 1 && ((int)Cells[r][c + 1].State > (int)Cell.CellState.Free)) walls++; if( Cells[r][c].Walls[3] == 1 ) walls ++; else if (r < Excavation.Dimension.Height - 1 && ((int)Cells[r + 1][c].State > (int)Cell.CellState.Free)) walls++; return (walls < 3); } /// <summary> /// 迷路を描画します。 /// </summary> /// <param name="g"></param> public void Draw(Graphics g) { for (int i = 0; i < Excavation.Dimension.Height; i++) for (int j = 0; j < Excavation.Dimension.Width; j++) Cells[i][j].Draw(g); } /// <summary> /// 迷路のCellをリセットします。 /// </summary> public void Reset() { for (int i = 0; i < Excavation.Dimension.Height; i++) for (int j = 0; j < Excavation.Dimension.Width; j++) Cells[i][j].State = Cell.CellState.Free; } } }
ToolStripNumericUpDown
ToolStripにNumericUpDownを表示するためのもの
using System; using System.Drawing; using System.Windows.Forms; namespace ClassLibrary1 { /// <summary> /// ToolStripNumericUpDown /// </summary> [ToolboxBitmap(typeof(NumericUpDown), "NumericUpDown")] public sealed class ToolStripNumericUpDown : ToolStripControlHost { #region コンストラクタ /// <summary> /// コンストラクタ /// </summary> public ToolStripNumericUpDown() : base(new NumericUpDown() { TextAlign = HorizontalAlignment.Right , Maximum = 128, Minimum = 2}) {} public ToolStripNumericUpDown(string name) : base(new NumericUpDown() { TextAlign = HorizontalAlignment.Right , Maximum = 128, Minimum = 2}, name) {} #endregion #region プロパティ /// <summary> /// ホストしているNumericUpDownコントロール /// </summary> public NumericUpDown NumericUpDown { get { return (NumericUpDown)Control; } } /// <summary> /// ホストしているコントロールのValueの値を取得または設定します。 /// </summary> public decimal Value { get { return NumericUpDown.Value; } set { NumericUpDown.Value = value; } } /// <summary> /// ホストしているコントロールのTextの値を取得または設定します。 /// </summary> public string NumericUpDownText { get { return NumericUpDown.Text; } set { NumericUpDown.Text = value; } } #endregion #region イベント /// <summary> /// Valueの値が変化した /// </summary> public event EventHandler ValueChanged; /// <summary> /// ValueChangedイベント発生 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void NumericUpDown_OnValueChanged(object sender, EventArgs e) { if (ValueChanged != null) { ValueChanged(this, e); } } #endregion #region オーバーライド /// <summary> /// ホストしているNumericUpDownのイベントをサブスクライブします。 /// </summary> /// <param name="control"></param> protected override void OnSubscribeControlEvents(System.Windows.Forms.Control control) { base.OnSubscribeControlEvents(control); NumericUpDown chkControl = (NumericUpDown)control; chkControl.ValueChanged += new EventHandler(NumericUpDown_OnValueChanged); } /// <summary> /// ホストしているNumericUpDownのイベントをアンサブスクライブします。 /// </summary> /// <param name="control"></param> protected override void OnUnsubscribeControlEvents(System.Windows.Forms.Control control) { base.OnUnsubscribeControlEvents(control); NumericUpDown chkControl = (NumericUpDown)control; chkControl.ValueChanged -= new EventHandler(NumericUpDown_OnValueChanged); } #endregion } }
ColorComboBox
オーナードローで色分けコンボボックス
using System.Collections.Generic; using System.Linq; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace ClassLibrary1 { public sealed class SplitColorComboBox : ComboBox { private const int MarginItemToColorBox = 2; private const int MarginColorBoxToString = 1; private const int MarginItemToString = MarginItemToColorBox + MarginColorBoxToString; /// <summary> /// コンストラクタ /// </summary> public SplitColorComboBox() { this.DropDownStyle = ComboBoxStyle.DropDownList; this.DrawMode = DrawMode.OwnerDrawFixed; this.ItemHeight = this.Font.Height + MarginItemToString * 2; this.DropDownWidth = 180; InitColorCombo(); this.SelectedIndex = 0; } private bool _displayColorName = true; /// <summary> /// 色名を表示するか否かを取得または設定します。 /// </summary> [DefaultValue(true)] public bool DisplayColorName { get { return _displayColorName; } set { _displayColorName = value; } } private Dictionary<string, int> _dicColor = new Dictionary<string, int>(); /// <summary> /// 現在選択中の色を取得または設定します。 /// </summary> public Color CurrentColor { get { return Color.FromName(this.Items[SelectedIndex].ToString()); } set { SelectedIndex = _dicColor[value.Name]; } } /// <summary> /// コンボボックスにKnownColor列挙体に存在する色名を設定します。 /// </summary> private void InitColorCombo() { foreach (var Item in System.Enum.GetNames(typeof(KnownColor)).Select( (x,i) => new {Value = x, Index = i })) { this.Items.Add(Item.Value); _dicColor.Add(Item.Value,Item.Index); } } /// <summary> /// OnDrawItem /// </summary> /// <param name="e"></param> protected override void OnDrawItem(DrawItemEventArgs e) { if (e.Index == -1) return; var itemColor = Color.FromName(this.Items[e.Index].ToString()); var backColor = itemColor; Rectangle rect = new Rectangle(MarginItemToColorBox , MarginItemToColorBox , e.Bounds.Width - MarginItemToColorBox * 2 , e.Bounds.Height - MarginItemToColorBox * 2); PointF colorNameLocation = new PointF(rect.Left + MarginColorBoxToString, rect.Top + MarginColorBoxToString); using (var bmp = new Bitmap(e.Bounds.Width, e.Bounds.Height)) using (var g = Graphics.FromImage(bmp)) using (var foreBrush = new SolidBrush(GetForeColor(itemColor))) { DrawFrameBox(g, Color.Black, backColor, rect); if (DisplayColorName) { if (e.State == (DrawItemState.Focus | DrawItemState.Selected) || e.State == DrawItemState.Selected) g.DrawString(itemColor.Name, this.Font, foreBrush, colorNameLocation); } e.Graphics.DrawImage(bmp, e.Bounds); } } /// <summary> /// 枠付きの塗り潰しボックスを描画します。 /// </summary> /// <param name="g"></param> /// <param name="frameColor"></param> /// <param name="backColor"></param> /// <param name="rect"></param> private void DrawFrameBox(Graphics g, Color frameColor, Color backColor, Rectangle rect) { using (var backBrush = new SolidBrush(backColor)) using (var framePen = new Pen(frameColor)) { g.FillRectangle(backBrush, rect); g.DrawRectangle(framePen, rect); } } /// <summary> /// 項目の色の明るさに応じて、色名を表示する文字色を取得します。 /// </summary> /// <param name="itemColor"></param> /// <returns></returns> private Color GetForeColor(Color itemColor) { if (itemColor.GetBrightness() >= 0.35) return Color.Black; return Color.White; } } }
ToolStripSplitColorComboBox
ToolStripにSplitColorComboBoxを表示するためのもの
using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace ClassLibrary1 { /// <summary> /// ToolStripSplitColorComboBox /// </summary> public class ToolStripSplitColorComboBox : ToolStripControlHost { #region コンストラクタ /// <summary> /// コンストラクタ /// </summary> public ToolStripSplitColorComboBox() : base(new ColorComboBox()) {} public ToolStripSplitColorComboBox(string name) : base(new ColorComboBox(), name) {} #endregion #region プロパティ /// <summary> /// ホストしているColorComboBoxコントロール /// </summary> public ColorComboBox ColorComboBox { get { return (ColorComboBox)Control; } } /// <summary> /// ホストしているコントロールのCurrentColorの値を取得します。 /// </summary> public Color CurrentColor { get { return ColorComboBox.CurrentColor; } set { ColorComboBox.CurrentColor = value; } } #endregion #region イベント /// <summary> /// SelectedIndexの値が変化した /// </summary> [Category("動作")] public event EventHandler SelectedIndexChanged; /// <summary> /// ValueChangedイベント発生 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ColorComboBox_OnSelectedIndexChanged(object sender, EventArgs e) { if (SelectedIndexChanged != null) SelectedIndexChanged(this, e); } #endregion #region オーバーライド /// <summary> /// ホストしているColorComboBoxのイベントをサブスクライブします。 /// </summary> /// <param name="control"></param> protected override void OnSubscribeControlEvents(System.Windows.Forms.Control control) { base.OnSubscribeControlEvents(control); ColorComboBox chkControl = (ColorComboBox)control; chkControl.SelectedIndexChanged += new EventHandler(ColorComboBox_OnSelectedIndexChanged); } /// <summary> /// ホストしているColorComboBoxのイベントをアンサブスクライブします。 /// </summary> /// <param name="control"></param> protected override void OnUnsubscribeControlEvents(System.Windows.Forms.Control control) { base.OnUnsubscribeControlEvents(control); ColorComboBox chkControl = (ColorComboBox)control; chkControl.SelectedIndexChanged -= new EventHandler(ColorComboBox_OnSelectedIndexChanged); } #endregion } }
迷路の描画をスクロール可能にするための対応
迷路の描画についてスクロールしても描画が崩れないようにするために、
面倒くさいけど、スクロール対応Panelを作ります。
それもこれも標準のPanelが空気読めなさすぎによるためのものです。
using System.Drawing; using System.Windows.Forms; namespace ClassLibrary1 { /// <summary> /// ScrollPanle /// </summary> [ToolboxBitmap(typeof(Panel), "Panel")] public sealed class ScrollPanle : Panel { public new event PaintEventHandler Paint; private readonly DrawControl _drawctrl = null; public ScrollPanle() { _drawctrl = new DrawControl(this) { Size = new System.Drawing.Size(this.Width, this.Height) }; Controls.Add(_drawctrl); AutoScroll = true; } public Size DrawScrollControlSize { get { return _drawctrl.Size; } set { _drawctrl.Size = value; } } private sealed class DrawControl : Control { private ScrollPanle _parent; public DrawControl(ScrollPanle parent) { _parent = parent; SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (_parent.DesignMode) return; var g = e.Graphics; g.FillRectangle(new Pen(_parent.BackColor).Brush, ClientRectangle); _parent.Paint(this, e); } } } }
カーソルチェンジャー
本来の使い方ではないIDisposableの使い方。
まぁ、本来の使い方ではないとはいえ、ありがちな実装ですね。
using System; using System.Windows.Forms; namespace ClassLibrary1 { /// <summary> /// カーソルチェンジャー /// </summary> public sealed class CursorChanger : IDisposable { private Control control; private Cursor oldCursor; /// <summary> /// コンストラクタ /// </summary> /// <param name="control">カーソル適用対象</param> /// <param name="newCursor">新しいカーソル</param> public CursorChanger(Control control, Cursor newCursor) { this.control = control; this.oldCursor = this.control.Cursor; FindTopParent(control).Cursor = newCursor; } /// <summary> /// デストラクタ /// </summary> ~CursorChanger() { Dispose(false); } #region IDisposableメンバ private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void Dispose(bool disposing) { if (!disposed) { if (disposing) { FindTopParent(control).Cursor = oldCursor; } disposed = true; } } #endregion /// <summary> /// 対象コントロールについて、TopParentを取得します。 /// </summary> /// <param name="control">対象コントロール</param> /// <returns>TopParent</returns> private Control FindTopParent(Control control) { var parent = control.Parent; if (parent == null) return control; return (FindTopParent(parent)); } } }
迷路のフォームの実装
最後にFormの実装です。
いままで作ってきたクラスをごにょごにょすれば完成です。
using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Threading; using System.Windows.Forms; using ClassLibrary1; namespace WindowsFormsApplication1 { public partial class frmMaze : Form { private IMaze _maze = new Excavation(); public frmMaze() { InitializeComponent(); this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); this.tssccmbCellColor.CurrentColor = Cell.CellColor; this.tssccmbWallColor.CurrentColor = Cell.WallColor; this.tssccmbSolveColor.CurrentColor = Cell.SolveColor; SetEvent(); using (var cur = new CursorChanger(this,Cursors.WaitCursor)) { _maze.Generate(); _maze.MazeSolveComplete += new EventHandler(MazeComplete); } SetFormSize(); } /// <summary> /// イベント設定 /// </summary> private void SetEvent() { this.pnlMaze.Paint += (sender, e) => this.pnlMaze_Paint(sender, e); this.tssccmbCellColor.SelectedIndexChanged += (sender, e) => tssccmbCellColor_SelectedIndexChanged(sender, e); this.tssccmbWallColor.SelectedIndexChanged += (sender, e) => tssccmbWallColor_SelectedIndexChanged(sender, e); this.tssccmbSolveColor.SelectedIndexChanged += (sender, e) => tssccmbSolveColor_SelectedIndexChanged(sender, e); this.tsnrcCellSize.ValueChanged += (sender, e) => tsnrcCellSize_ValueChanged(sender, e); } private void SetFormSize() { var header = this.toolStripTop.Height; var footer = this.toolStripBottom.Height; SetBounds(this.Left , this.Top , Excavation.Dimension.Height * Cell.CellSize + Cell.Padding * 3 , Excavation.Dimension.Width * Cell.CellSize + Cell.Padding * 3 + header + footer + 30); this.pnlMaze.DrawScrollControlSize = new Size(this.pnlMaze.Width, this.pnlMaze.Height); } private void pnlMaze_Paint(object sender, PaintEventArgs e) { _maze.Draw(e.Graphics); } private delegate void Action(); private void MazeComplete(object sender, EventArgs e) { if (this.IsRestrictedWindow) { this.Refresh(); this.Cursor = Cursors.Default; return; } this.Invoke(new Action(() => { this.Refresh(); this.Cursor = Cursors.Default; })); } private void tsbtnGenerate_Click(object sender, EventArgs e) { var col = Convert.ToInt32(this.tsnrcCol.Value); var row = Convert.ToInt32(this.tsnrcRow.Value); Excavation.Dimension = new Size(col, row); Cell.CellSize = Convert.ToInt32(this.tsnrcCellSize.Value); _maze.Initialize(Excavation.Dimension); _maze.Generate(); SetFormSize(); Invalidate(true); } private void tsbtnSolve_Click(object sender, EventArgs e) { _maze.Reset(); this.Cursor = Cursors.WaitCursor; new Thread(new ThreadStart(_maze.Solve)).Start(); } private void tssccmbCellColor_SelectedIndexChanged(object sender, EventArgs e) { Cell.CellColor = this.tssccmbCellColor.CurrentColor; this.pnlMaze.Invalidate(true); } private void tssccmbWallColor_SelectedIndexChanged(object sender, EventArgs e) { Cell.WallColor = this.tssccmbWallColor.CurrentColor; this.pnlMaze.Invalidate(true); } private void tssccmbSolveColor_SelectedIndexChanged(object sender, EventArgs e) { Cell.SolveColor = this.tssccmbSolveColor.CurrentColor; this.pnlMaze.Invalidate(true); } private void tsnrcCellSize_ValueChanged(object sender, EventArgs e) { Cell.CellSize = (int)this.tsnrcCellSize.Value; this.pnlMaze.Invalidate(true); SetFormSize(); } } }
という感じです。お疲れ様でした。
迷路の解法部分を抽象化したり、迷路の生成部分と解法をプラグイン化したりとか
いろいろとやり残しがあるので、興味があるかたは改造したりして遊んでみてください。
自分も気が向いたら、また遊んでみます。