×

[PR]この広告は3ヶ月以上更新がないため表示されています。
ホームページを更新後24時間以内に表示されなくなります。

協力最短詰めオセロ

問題

黒と白のプレイヤーが二人で協力して、番面の石を黒か白一色にする最短の手順を求めてください。 石を裏返すルールはオセロのルールをそのまま採用します。 


最短の解をひとつだけ求めるプログラムを作ってくださいという問題です。ここでは、解を探索するのに幅優先のアルゴリズムを採用しています。こうすることで、最短手を見つけた時点で探索を終わらせることができます。

これまでのこのサイトで解いてきたパズルは、プレイヤーはひとりのものがほとんどだったのですが、このパズルはプレイヤーが2人いるので、 その制御にちょっと手こずりました。
具体的に何をやっているかは、コードに書いてあるコメントを読んでいただければと思います。

以下に実行時のスクリーンショットを載せます。

初期状態

解が求まり、手を再現しているところ。右側の数値はその手数を示しています。

解が見つかり、すべての石が白になったところ

解が求まると、自動で石が反転していきます。その動きを載せられないのが残念。
実行してみると僕のPCで解が求まるまでに約50秒でした。

WPFアプリケーションとして作成しています。なお、ここでも例により Boardクラス、BoardCanvasクラスを利用していますが、当プログラムにおいては過剰実装ですが汎用性を考えて作成したもので、「ここ」に掲載した Sliverlight用のものを WPFに書き換えたものです。。
この2つのクラスは、ライフゲームのプログラムでも利用しています。

以下に、C#のコードとXAMLを示します

■MainWindow.cs
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using Gushwell.Etude;

namespace CooperatePerfectOthello {

    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        private OthBoard board = new OthBoard();
        private BoardCanvas bc;

        // 初期化
        private void canvas1_Loaded(object sender, RoutedEventArgs e) {
            bc = new BoardCanvas(canvas1, board);
            bc.DrawRuledLines(BoardType.Chess);
            board.Initialize();
        }

        // 思考開始
        private void button1_Click(object sender, RoutedEventArgs e) {
            progressBar1.Visibility = System.Windows.Visibility.Visible;
            textBlock2.Visibility = System.Windows.Visibility.Visible;
            BackgroundWorker bw = new BackgroundWorker();
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);
            bw.RunWorkerAsync();
            bc.Synchronize = false;
        }

        // バックグラウンドでSolveメソッドを呼び出す
        void bw_DoWork(object sender, DoWorkEventArgs e) {
            Solver sol = new Solver();
            Location[] moves = sol.Solve(board);
            e.Result = moves;
        }

        // 解が見つかった。
        void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            progressBar1.Visibility = System.Windows.Visibility.Collapsed;
            textBlock2.Visibility = System.Windows.Visibility.Collapsed;
            string s = "";
            foreach (var loc in e.Result as Location[])
                s += string.Format("{0}\n", loc);
            textBlock1.Text = s;
            bc.Synchronize = true;
            Replay(e.Result as Location[]);
        }

        // これ以降は、手を再現する処理

        private void Replay(Location[] moves) {
            Thread th = new Thread(_Replay);
            th.Start(moves);
        }

        private void _Replay(object param) {
            Location[] moves = param as Location[];
            bc.UpdateInterval = TimeSpan.FromSeconds(0.3);
            foreach (var loc in moves) {
                board.Put(board.Turn, board.ToIndex(loc));
                System.Threading.Thread.Sleep(800);
            }
        }
    }
}


■MainWindow.xaml
<Window x:Class="CooperatePerfectOthello.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="429.11">
    <Grid>
        <Canvas Height="200" HorizontalAlignment="Left" Margin="13,16,0,0" Name="canvas1" 
                VerticalAlignment="Top" Width="200" Loaded="canvas1_Loaded" />
        <Button Content="Solve" Height="29" HorizontalAlignment="Left" Margin="224,16,0,0" 
                Name="button1" VerticalAlignment="Top" Width="165" Click="button1_Click" />
        <ScrollViewer Height="205" HorizontalAlignment="Left" Margin="224,60,0,0" Name="scrollViewer1" 
                      VerticalAlignment="Top" Width="185">
            <TextBlock Name="textBlock1"  />
        </ScrollViewer>
        <TextBlock Height="23" HorizontalAlignment="Stretch" Margin="10,10,10,11" Name="textBlock2" 
                   Text="探索中です。" VerticalAlignment="Bottom"  
                   Width="185" Visibility="Collapsed" />
        <ProgressBar Height="26" HorizontalAlignment="Stretch" Margin="10" Name="progressBar1" 
                     VerticalAlignment="Bottom" Width="396" IsIndeterminate="True" IsEnabled="False" 
                     Visibility="Collapsed" />

    </Grid>
</Window>


■Solver.cs
using System.Collections.Generic;
using System.Linq;
using Gushwell.Etude;

namespace CooperatePerfectOthello {
    public class Solver {
        // 幅優先探索で調べる
        public Location[] Solve(OthBoard b) {
            // 最初はどこに打っても同じなので、ひとつに固定。
            int p = 43;
            b.Put(b.Turn, p);

            Queue<OthBoard> queu = new Queue<OthBoard>();
            queu.Enqueue(new OthBoard(b));
            OthBoard current = b;
            while (queu.Count != 0) {
                //キューの先頭からノード currentNode を取り出す
                current = queu.Dequeue();
                if ((current.StoneCount(Pieces.Black) == 0) ||
                     (current.StoneCount(Pieces.White) == 0)) {
                     // 解がひとつ見つかれば終了。その手順を返す。
                     return current.GetMoves();
                }
                foreach (var pos in current.PutablePlaces(current.Turn).ToList()) {
                    OthBoard next = new OthBoard(current);
                    next.Put(next.Turn, pos);
                    queu.Enqueue(next);
                }
            }
            return null;
        }
    }
}


■OthBoard.cs
using System.Collections.Generic;
using System.Linq;
using Gushwell.Etude;

// Boardから継承し、特有の機能を追加

namespace CooperatePerfectOthello {
    public class OthBoard : Board {
        public IPiece Turn { get; set; }
        private List<int> Moves = new List<int>();

        // コンストラクタ
        public OthBoard()
            : base(8, 8) {
            Turn = Pieces.Black;
        }

        // コンストラクタ (Cloneと同じ用途)
        public OthBoard(OthBoard board)
            : base(board) {
            Turn = board.Turn;
            Moves = board.Moves.ToList();
        }

        // 初期化
        public void Initialize() {
            this[ToIndex(4, 4)] = Pieces.White;
            this[ToIndex(5, 5)] = Pieces.White;
            this[ToIndex(4, 5)] = Pieces.Black;
            this[ToIndex(5, 4)] = Pieces.Black;
        }

        // 相手の石
        public IPiece Opponent(IPiece stone) {
            return stone == Pieces.White ? Pieces.Black : Pieces.White;
        }

        // 石の数をカウント
        public int StoneCount(IPiece stone) {
            return this.GetValidLocations().Count(loc => this[loc] == stone);
        }

        // 8つの方向を列挙 (このBoardは、番兵用に一回り大きなサイズとなっている)
        public IEnumerable<int> Directions() {
            return new int[] { -11, -10, -9, 1, 11, 10, 9, -1 };
        }

        // 置ける場所を列挙する
        public IEnumerable<int> PutablePlaces(IPiece stone) {
            return this.GetVacantIndexes().Where(index => CanPut(stone, index));
        }

        // 石を置けるか
        public bool CanPut(IPiece stone, int place) {
            if (this[place] != Pieces.Empty)
                return false;
            return Directions().Any(d => CanReverse(stone, place, d));
        }

        // direction方向の石をひっくり返せるか
        public bool CanReverse(IPiece stone, int place, int direction) {
            IPiece opponent = Opponent(stone);
            int np = place + direction;
            while (this[np] == opponent)
                np += direction;
            return (this[np] == stone && np != place + direction);
        }

        // direction方向にstoneと同じ色の石があるか
        public bool FindStone(IPiece stone, int place, int direction) {
            IPiece opponent = Opponent(stone);
            int np = place + direction;
            while (this[np] == opponent)
                np += direction;
            return (this[np] == stone);
        }

        // direction方向の石をひっくり返えす 
        public void Reverse(IPiece stone, int place, int direction) {
            if (!FindStone(stone, place, direction))
                return;
            IPiece opponent = Opponent(stone);
            int np = (int)(place + direction);
            while (this[np] == opponent) {
                this[np] = stone;
                np += direction;
            }
        }

        // stoneをplace位置に置く (必ず置けることを前提としている)
        public void Put(IPiece stone, int place) {
            this[place] = stone;
            IPiece opponent = Opponent(stone);
            foreach (int direction in Directions()) {
                Reverse(stone, place, direction);
            }
            Turn = opponent;
            if (!PutablePlaces(Turn).Any())
                Turn = stone;
            Moves.Add(place);
        }

        // 現時点での手順を返す。
        public Location[] GetMoves() {
            return Moves.Select(n => this.ToLocation(n)).ToArray();
        }
    }

}


■Board.cs
using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows.Media;

// WindowsFormsで利用する場合は、System.Windows.Media の代わりに、
// System.Drawing を using する。

namespace Gushwell.Etude {
    // Changeイベントで使われる EventArgs
    public class BoardChangedEventArgs : EventArgs {
        public Location Location { get; internal set; }
        public IPiece Piece { get; internal set; }
    }

    // 盤データクラス
    public class Board {
        // 駒が配置される1次元配列 (周辺には番兵が置かれる)
        private IPiece[] _pieces;

        // 番兵以外の有効な位置(1次元のインデックス)が格納される
        private readonly int[] _validIndexes;

        // 盤の行(縦方向)数
        public int YSize { get; private set; }
        // 盤のカラム(横方向)数
        public int XSize { get; private set; }

        // _pieces配列に変更があるとChangeイベントが発生する。
        public event EventHandler<BoardChangedEventArgs> Changed;

        // コンストラクタ
        public Board(int xsize, int ysize) {
            this.YSize = xsize;
            this.XSize = ysize;
            // 盤データの初期化 (周りは番兵(Guard)をセットしておく)
            _pieces = new IPiece[(xsize + 2) * (ysize + 2)];
            for (int i = 0; i < _pieces.Length; i++) {
                if (IsOnBoard(ToLocation(i)))    // この時点でIsOnBoard(int index) は利用できない
                    _pieces[i] = Pieces.Empty;
                else
                    _pieces[i] = Pieces.Guard;
            }
            // 毎回求めるのは無駄なので最初に求めておく
            _validIndexes = Enumerable.Range(0, _pieces.Length)
                                     .Where(ix => _pieces[ix] == Pieces.Empty)
                                     .ToArray();

        }

        // コンストラクタ (Cloneとしても利用できる)
        public Board(Board board) {
            this.YSize = board.YSize;
            this.XSize = board.XSize;
            this._validIndexes = board._validIndexes.ToArray();
            this._pieces = board._pieces.ToArray();
        }

        // イベント発行 
        protected void OnChanged(Location loc, IPiece piece) {
            if (Changed != null) {
                var args = new BoardChangedEventArgs {
                    Location = loc,
                    Piece = piece
                };
                Changed(this, args);
            }
        }

        // (x,y) から、_pieceへのインデックスを求める
        public int ToIndex(int x, int y) {
            return x + y * (XSize + 2);
        }

        // Location から _pieceのIndexを求める
        public int ToIndex(Location loc) {
            return ToIndex(loc.X, loc.Y);
        }

        // IndexからLocationを求める
        public Location ToLocation(int index) {
            return new Location(index % (XSize + 2), index / (YSize + 2));
        }

        // 本来のボード上の位置かどうかを調べる
        protected bool IsOnBoard(Location loc) {
            int x = loc.X;
            int y = loc.Y;
            return ((1 <= x && x <= XSize) &&
                   (1 <= y && y <= YSize));
        }

        // 本来のボード上の位置(index)かどうかを調べる
        protected bool IsOnBoard(int index) {
            if (0 <= index && index < _pieces.Length)
                return this[index] != Pieces.Guard;
            return false;
        }

        // Pieceを置く_piecesの要素を変更するのはこのメソッドだけ(コンストラクタは除く)。
        // override可 
        protected virtual void PutPiece(int index, IPiece piece) {
            if (IsOnBoard(index)) {
                _pieces[index] = piece;
                OnChanged(ToLocation(index), piece);
            } else {
                throw new ArgumentOutOfRangeException();
            }
        }

        // インデクサ (x,y)の位置の要素へアクセスする
        public IPiece this[int index] {
            get { return _pieces[index]; }
            set { PutPiece(index, value); }
        }

        // インデクサ (x,y)の位置の要素へアクセスする
        public IPiece this[int x, int y] {
            get { return this[ToIndex(x, y)]; }
            set { this[ToIndex(x, y)] = value; }
        }

        // インデクサ locの位置の要素へアクセスする
        public IPiece this[Location loc] {
            get { return this[loc.X, loc.Y]; }
            set { this[loc.X, loc.Y] = value; }
        }

        // 全てのPieceをクリアする
        public virtual void ClearAll() {
            foreach (var ix in GetOccupiedIndexes())
                ClearPiece(ToLocation(ix));
        }

        // x,yの位置をクリアする
        public virtual void ClearPiece(Location loc) {
            this[loc.X, loc.Y] = Pieces.Empty;
        }

        // EmptyPiece 以外の全てのPieceを列挙する
        public IEnumerable<IPiece> GetAllPieces() {
            return _validIndexes.Select(i => this[i]).Where(p => p != Pieces.Empty);
        }

        // 指定したIPieceが置いてあるLocationを列挙する
        public IEnumerable<Location> GetLocations(IPiece piece) {
            Type type = piece.GetType();
            return GetValidLocations().Where(loc => this[loc].GetType() == type);
        }

        // 指定したIPieceがおいてあるIndexを列挙する
        public IEnumerable<int> GetIndexes(IPiece piece) {
            Type type = piece.GetType();
            return _validIndexes.Where(index => this[index].GetType() == type);
        }

        // 番兵部分を除いた有効なLocationを列挙する
        public IEnumerable<Location> GetValidLocations() {
            return _validIndexes.Select(ix => ToLocation(ix));
        }

        // 番兵部分を除いた有効なIndexを列挙する
        public IEnumerable<int> GetValidIndexes() {
            return _validIndexes;
        }

        // 駒が置かれているLocationを列挙する
        public IEnumerable<Location> GetOccupiedLocations() {
            return GetOccupiedIndexes().Select(index => ToLocation(index));
        }

        // 駒が置かれているLocationを列挙する
        public IEnumerable<int> GetOccupiedIndexes() {
            return _validIndexes.Where(index => this[index] != Pieces.Empty);
        }

        // 何もおかれていないLocationを列挙する
        public IEnumerable<Location> GetVacantLocations() {
            return _validIndexes.Select(index => ToLocation(index));
        }

        // 何もおかれていないIndexを列挙する
        public IEnumerable<int> GetVacantIndexes() {
            return GetIndexes(Pieces.Empty);
        }

        // 指定した方向の位置を番兵が見つかるまで取得する。
        public IEnumerable<int> GetSeriesIndexes(int index, int direction) {
            for (int pos = index; this[pos] != Pieces.Guard; pos += direction)
                yield return pos;
        }

        // 上方向
        public int UpDirection {
            get { return -(this.XSize + 2); }
        }

        // 下方向
        public int DownDirection {
            get { return (this.XSize + 2); }
        }

        // 左方向
        public int LeftDirection {
            get { return -1; }
        }

        // 右方向
        public int RightDirection {
            get { return 1; }
        }

        // 右上の方向
        public int UpperRightDirection {
            get { return UpDirection + 1; }
        }

        // 左上の方向
        public int UpperLeftDirection {
            get { return UpDirection - 1; }
        }

        // 右下の方向
        public int LowerRightDirection {
            get { return DownDirection + 1; }
        }

        // 左下の方向
        public int LowerLeftDirection {
            get { return DownDirection - 1; }
        }
    }

    // 駒を示すマーカーインターフェース(marker interface)
    public interface IPiece {
    }

    // 色を持つ駒
    public interface IColorPiece : IPiece {
        Color Color { get; }
    }

    // 良く利用する駒たち
    public static class Pieces {
        public static readonly IPiece Black = new BlackPiece();
        public static readonly IPiece White = new WhitePiece();
        public static readonly IPiece Empty = new EmptyPiece();
        public static readonly IPiece Guard = new GuardPiece();
    }

    // 黒石
    public struct BlackPiece : IColorPiece {
        public Color Color {
            get { return Color.FromArgb(255, 128, 128, 128); }
        }
    }

    // 白石
    public struct WhitePiece : IColorPiece {
        public Color Color {
            get { return Color.FromArgb(255, 255, 255, 255); }
        }
    }

    // 番兵用:他と区別するためだけなので中身は何でも良い (マーカーオブジェクト)
    public struct GuardPiece : IPiece {
    }

    // 何もおかれていないことを示す:いわゆる NullObject  (マーカーオブジェクト)
    public struct EmptyPiece : IPiece {
    }

    // ボード上の位置を示す
    public class Location {
        public int X { get; set; }
        public int Y { get; set; }
        public Location(int x, int y) {
            X = x;
            Y = y;
        }
        public override string ToString() {
            return string.Format("({0},{1}) ", X, Y);
        }
    }
}


■BoardCanvas.cs
using System;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Gushwell.Etude {
    // 罫線の種類
    public enum BoardType {
        Go,
        Chess
    }

    // Boardおよび駒(PIece)の表示を担当する
    // BoardオブジェクトからChangeイベントを受け取ると、変更されたセルのPieceを描画する。
    // その他Board/Pieceを描画するための各種メソッドを用意。
    // PanelにUiElementを動的に追加削除することで描画を行っている。
    public class BoardCanvas {
        protected double CellWidth { get; private set; }        // ひとつのCellの幅
        protected double CellHeight { get; private set; }       // ひとつのCellの高さ
        protected int YSize { get; private set; }            // 縦方向のCellの数
        protected int XSize { get; private set; }            // 横方向のCellの数
        protected BoardType BoardType { get; private set; }     // 罫線の種類 (碁盤かチェス盤か)
        protected Panel Panel { get; private set; }             // 対象となる Panelオブジェクト
        protected Board Board { get; private set; }             // 対象となる Boardオブジェクト

        // コンストラクタ
        public BoardCanvas(Panel panel, Board board) {
            this.Board = board;
            this.Panel = panel;
            YSize = board.YSize;
            XSize = board.XSize;

            CellWidth = (panel.ActualWidth - 1) / XSize;
            CellHeight = (panel.ActualHeight - 1) / YSize;
            foreach (var loc in board.GetValidLocations()) {
                UpdatePiece(loc, board[loc]);
            }
            this.Board.Changed += new EventHandler<BoardChangedEventArgs>(board_Changed);
            _synchronize = true;
        }

        private bool _synchronize;

        // Boardオブジェクトと同期するか否か (初期値:同期する)
        public bool Synchronize {
            get { return _synchronize; }
            set {
                if (value == true) {
                    if (!_synchronize) {
                        this.Board.Changed += new EventHandler<BoardChangedEventArgs>(board_Changed);
                        _synchronize = true;
                    }
                } else {
                    if (_synchronize) {
                        this.Board.Changed -= new EventHandler<BoardChangedEventArgs>(board_Changed);
                        _synchronize = false;
                    }
                }
            }
        }

        public void ChangeBoard(Board board) {
            this.Board = board;
        }


        // 罫線を引く
        public void DrawRuledLines(BoardType linetype) {
            this.BoardType = linetype;
            int startx = (linetype == BoardType.Chess)
                            ? 0
                            : (int)(CellHeight / 2);
            for (double i = startx; i <= Panel.ActualHeight; i += CellHeight) {
                DrawLine(0, i, Panel.ActualWidth, i);
            }
            int starty = (linetype == BoardType.Chess)
                            ? 0
                            : (int)(CellWidth / 2);
            for (double i = starty; i <= Panel.ActualWidth; i += CellWidth) {
                DrawLine(i, 0, i, Panel.ActualHeight);
            }
        }

        // 線を引く
        protected void DrawLine(double x1, double y1, double x2, double y2) {
            Line line = new Line();
            line.X1 = x1;
            line.Y1 = y1;
            line.X2 = x2;
            line.Y2 = y2;
            line.Stroke = new SolidColorBrush(Colors.LightGray);
            Panel.Children.Add(line);
        }

        // 円オブジェクトを生成する (Pieceのデフォルト表示を担当)
        public Ellipse CreateEllipse(Location loc, Color color) {
            Ellipse eli = new Ellipse();
            eli.Name = PieceName(loc);
            eli.Height = CellWidth * 0.85;
            eli.Width = CellHeight * 0.85;
            eli.Fill = new SolidColorBrush(color);
            eli.Stroke = new SolidColorBrush(Colors.DarkGray);
            Point pt = ToPoint(loc);
            Canvas.SetLeft(eli, pt.X + CellWidth / 2 - eli.Width / 2);
            Canvas.SetTop(eli, pt.Y + CellHeight / 2 - eli.Height / 2);
            return eli;
        }

        // Pieceオブジェクトの名前を生成する
        public string PieceName(Location loc) {
            return string.Format("x{0}y{1}", loc.X, loc.Y);
        }

        // 矩形を描く
        public void DrawRectangle(Point p1, Point p2, Color color) {
            this.Panel.Children.Add(CreateRectangle(p1, p2, color));
        }

        // 矩形を消去する
        public void EraseRectangles(Point p1, Point p2) {
            var name = RectangleName(p1);
            Rectangle obj = Panel.FindName(name) as Rectangle;
            if (obj != null) {
                Panel.Children.Remove(obj as UIElement);
            }
        }

        // 四角形を生成する
        public Rectangle CreateRectangle(Point p1, Point p2, Color color) {
            var rect = new Rectangle();
            rect.Name = RectangleName(p1);
            rect.Stroke = new SolidColorBrush(color);
            double x1 = Math.Min(p1.X, p2.X);
            double y1 = Math.Min(p1.Y, p2.Y);
            double x2 = Math.Max(p1.X, p2.X);
            double y2 = Math.Max(p1.Y, p2.Y);
            rect.Margin = new Thickness(x1, y1, x2, y2);
            rect.Width = x2 - x1;
            rect.Height = y2 - y1;
            return rect;
        }

        // 矩形の名前を得る
        protected string RectangleName(Point p1) {
            return string.Format("r{0}{1}", (int)p1.X, (int)p1.Y);
        }

        // Boardの内容に沿って、Pieceを描画しなおす
        // UIスレッドとは別スレッドで動作していた場合を考慮。
        public void Invalidate() {
            Panel.Dispatcher.BeginInvoke(new Action(() => {
                foreach (var loc in Board.GetValidLocations()) {
                    this.RemovePiece(loc);
                }
                foreach (var loc in Board.GetValidLocations()) {
                    IPiece piece = Board[loc];
                    UpdatePiece(loc, piece);
                }
            }));
        }

        // UIスレッドとは別スレッドで動作させている時だけ意味を持つ。
        public TimeSpan UpdateInterval { get; set; }

        // Boardオブジェクトが変更されたときに呼び出されるイベントハンドラ
        // UIスレッドとは別スレッドで動作していた場合を考慮。
        private void board_Changed(object sender, BoardChangedEventArgs e) {
            Thread.Sleep(UpdateInterval);
            Panel.Dispatcher.BeginInvoke(new Action(() => {
                UpdatePiece(e.Location, e.Piece);
            }));
        }

        // Pieceの描画を更新する
        public virtual void UpdatePiece(Location loc, IPiece piece) {
            if (piece == null || piece is EmptyPiece) {
                RemovePiece(loc);
            } else {
                var name = PieceName(loc);
                object obj = Panel.FindName(name);
                if (obj != null) {
                    Panel.Children.Remove(obj as UIElement);
                }
                if (piece is EmptyPiece || piece is GuardPiece)
                    return;
                DrawPiece(loc, piece);
            }
        }

        // Pieceを描く。 デフォルト実装は、IColorPieceのみに対応。
        // 他のPiece型は、独自に当メソッドをoverrideする必要がある。
        // なお、overrideした DrawPiece内では、DrawRectangleメソッドは利用できない。
        public virtual void DrawPiece(Location loc, IPiece piece) {
            var colorPiece = piece as IColorPiece;
            if (colorPiece != null)
                Panel.Children.Add(CreateEllipse(loc, colorPiece.Color));
        }

        // 指定した位置のPieceを取り除く
        public void RemovePiece(Location loc) {
            var name = PieceName(loc);
            object obj = Panel.FindName(name);
            if (obj != null) {
                Panel.Children.Remove(obj as UIElement);
            }
        }

        // Locationからグラフィックの座標であるPointへ変換
        public Point ToPoint(Location loc) {
            return new Point {
                X = CellWidth * (loc.X - 1),
                Y = CellHeight * (loc.Y - 1)
            };
        }

        // グラフィックの座標であるPointからLocationへ変換
        public Location ToLocation(Point pt) {
            var x = pt.X;
            var y = pt.Y;
            int a = Math.Max(0, (int)(x / CellWidth));
            if (a >= XSize)
                a = XSize - 1;
            int b = Math.Max(0, (int)(y / CellHeight));
            if (b >= YSize)
                b = YSize - 1;
            return new Location(a + 1, b + 1);
        }
    }
}