WPF オセロ

出張で無事死んでいました...
記事いっぱい書いていかないと...

オセロって使うもの自体は少ないんだけど、満たす要件だったりロジックが複雑だったりするのでわりと頭の体操によくやったりします。(書くことないのでこれを書きます。)

オセロの説明は言うまでもないと思うので省略

###オセロの要件

- 8×8の緑に黒線のボードで行う。
- ターンがあり、●と〇が交互に打っていく。
- すでに石があるところに石はおけず、またひっくり返せない所には石はおけない。
- 自分の色で相手の色を挟んだらひっくり返す。
- 最後に多い方の勝ち。
- 途中で打てなくなった場合Passをする。
- 途中で石がなくなった場合その時点で負け

###実装

まずは、xaml側から。
こちらは8×8の緑に黒線のボードを用意してあげて、Passボタンと現在のターン表示と一応黒と白の現在の数を用意してあげましょう。

```xml:MainWindow.xaml
<Window x:Class="osero_wpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:osero_wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="600">
    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Width" Value="50"></Setter>
            <Setter Property="Height" Value="50"></Setter>
            <Setter Property="Background" Value="Green"></Setter>
            <Setter Property="BorderBrush" Value="Black"></Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Name="Board">
            <StackPanel Orientation="Horizontal" Tag="1">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Tag="2">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Tag="3">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Tag="4">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Tag="5">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Tag="6">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Tag="7">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Tag="8">
                <Button Content="" Click="Button_Click" Tag="1"></Button>
                <Button Content="" Click="Button_Click" Tag="2"></Button>
                <Button Content="" Click="Button_Click" Tag="3"></Button>
                <Button Content="" Click="Button_Click" Tag="4"></Button>
                <Button Content="" Click="Button_Click" Tag="5"></Button>
                <Button Content="" Click="Button_Click" Tag="6"></Button>
                <Button Content="" Click="Button_Click" Tag="7"></Button>
                <Button Content="" Click="Button_Click" Tag="8"></Button>
            </StackPanel>
        </StackPanel>
        <StackPanel Orientation="Vertical" VerticalAlignment="Top" HorizontalAlignment="Left">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="黒:"></TextBlock>
                <TextBlock Text="" Name="ViewBlackCount"></TextBlock>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="白:"></TextBlock>
                <TextBlock Text="" Name="ViewWhiteCount"></TextBlock>
            </StackPanel>
        </StackPanel>
        <StackPanel  VerticalAlignment="Center" HorizontalAlignment="Left" Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="" Name="TurnText"></TextBlock>
                <TextBlock Text="のターンです。"></TextBlock>
            </StackPanel>
            <Button Content="Pass" Click="Button_Click_1" Background="AliceBlue"></Button>
        </StackPanel>
    </Grid>
</Window>
```

コード側でやってあげたほうがよかったかもしれないですが、面倒なのでxaml側でばーっとやっちゃいました。
コード側

```cs:MainWindow.xaml.cs
public partial class MainWindow : Window
{
    private bool IsBlackTurn = true;
    private int[,] BoardInfo = new int[8, 8];
    public MainWindow()
    {
        InitializeComponent();
        Init();
    }
}
```

ターンの状態と、ボードの情報をMainWindowクラスで保持。ボードの情報に関してはint型で保持し、
1は黒、0はなし、-1は白とする。

InitではBoardInfoにすべて0を入れてから、4,4に黒、4,5に白、5,4に白、5,5に黒を入れてやる。

```cs:MainWindow.xaml.cs
private void Init()
{
    for (var i = 0; i < 8; i++)
    {
        for (var j = 0; j < 8; j++)
        {
            BoardInfo[i, j] = 0;
        }
    }

    SetBoardInfo(4, 4, 1);
    SetBoardInfo(4, 5, -1);
    SetBoardInfo(5, 4, -1);
    SetBoardInfo(5, 5, 1);
    ReflectBoardInfoToXaml();
}
```

SetBoardInfoは4,4,1であれば[4,4]に黒を入れてやる、の意。
わざわざメソッド化をしてあげる理由は配列が0から始まるが、見かけ上は1からはじまるため。
(ここは別にTag付けを0から始めればよかったか)
ReflectBoardInfoToXamlメソッドはBoardInfoの情報をxaml側に反映させてあげるメソッド。

```cs:MainWindow.xaml.cs
private void ReflectBoardInfoToXaml()
{
    int row = 0;
    int col = 0;
    int BlackCount = 0;
    int WhiteCount = 0;
    int NoCount = 0;
    foreach (var Children in Board.Children)
    {
        var Panel = Children as StackPanel;
        foreach (var But in Panel.Children)
        {
            var Butt = But as Button;
            Butt.Content = ConvertIntInfoToStringInfo(BoardInfo[row, col]);
            if (BoardInfo[row, col] == 1) BlackCount++;
            else if (BoardInfo[row, col] == -1) WhiteCount++;
            else NoCount++;
            col++;
        }
        col = 0;
        row++;
    }
    if (BlackCount == 0)
    {
        MessageBox.Show("白の勝ち");
        Init();
    }
    else if(WhiteCount == 0)
    {
        MessageBox.Show("黒の勝ち");
        Init();
    }
    else if(NoCount == 0)
    {
        if(WhiteCount < BlackCount)
        {
            MessageBox.Show("黒の勝ち");
            Init();
        }
        else if(WhiteCount > BlackCount)
        {
            MessageBox.Show("白の勝ち");
            Init();
        }
        else
        {
            MessageBox.Show("引き分け");
            Init();
        }
    }
    ViewBlackCount.Text = BlackCount.ToString();
    ViewWhiteCount.Text = WhiteCount.ToString();
    TurnText.Text = IsBlackTurn ? "黒" : "白";
}
```

基本的に最初のforeachの終わりまでが反映作業。
foreachのところのChildrenに関しては子要素を取得。
なので、全体のStackPanelの子要素を取得して、8枚のStackPanelを取り出す。
その後、各StackPanelからButtonを取り出してそこのContentに情報を入れていく。
その後の勝ち負けの処理を入れるのはforeachですべての情報を回してるここでやると便利なため。

オセロのボタンをクリックしたときの処理。

```cs:MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    var button = sender as Button;
    var stackPanel = button.Parent as StackPanel;

    int Col = int.Parse(button.Tag.ToString());
    int Row = int.Parse(stackPanel.Tag.ToString());

    if(BoardInfo[Row - 1, Col - 1] != 0)
    {
        MessageBox.Show("そこにはすでに置かれています。");
        return;
    }

    var IsTurnChange = CheckValidBoardInfo(Row, Col);
    if (!IsTurnChange)
    {
        MessageBox.Show("そこには置けません。");
        return;
    }

    SetBoardInfo(Row, Col, ConvertTurnToIntInfo(IsBlackTurn));
    ReflectBoardInfoToXaml();
    IsBlackTurn = !IsBlackTurn;
}
```

CheckValidBoardInfoは挟めることができたらtrueを返し、できなければfalseを返す。
まぁなので名前としてはTryAndReverseみたいなの方が妥当だろうか。
そして、無事ひっくり返すことができたあとに、Row,Colに自分の石を置き、ターンを変える。

そしてオセロを実装する上で一番の面倒なところが石を挟んだらひっくり返す処理だろう。
石を挟むという言葉もなかなか抽象的である。なので、より具体的な言葉で言ってあげるならば

座標(x, y)がzであり、(x + a * 1, y + b * 1)から(x + a * (n - 1), y + b * (n - 1))が-zで(x + a * n, y + b * n)がzである場合(x + a * 1, y + b * 1)から(x + a * (n - 1), y + b * (n - 1))をzに変更する。(0 <= x <= 8, 0 <= y <= 8, a,b は1,0,-1のいずれか。2 <= n <= 7)

という言葉になる。
まぁ簡単に言えば方向と挟む数と挟む回数の順番でfor文を回してやり適宜チェックを行ってやればよい。
それをコードで表すと

```cs:MainWindow.xaml.cs
private bool CheckValidBoardInfo(int Row, int Col)
{
    int Info = ConvertTurnToIntInfo(IsBlackTurn);
    int RowDirection;
    int ColDirection;
    var IsTurnChange = false;
    //方向
    for(var i = 1; i <= 8; i++)
    {
        //石の数
        for(var j = 7; j >= 2; j--)
        {
            (RowDirection, ColDirection) = GetDirection(i);
            //ひとつでも変更があればturnChangeFlgをtrueにする。
            if (!CheckRangeValid(Row + j * RowDirection, Col + j * ColDirection) ||
                !CheckReverse(Row, Col, Info, i, j)) continue;
            else IsTurnChange = true;
            //チェックが通ったものの場合j - 1個ひっくり返す。
            for(var k = 1; k < j; k++)
            {
                SetBoardInfo(Row + k * RowDirection, Col + k * ColDirection, Info);
            }
        }
    }

    return IsTurnChange;
}
```

という風になる。
全体のコードを示すと

```cs:MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;

namespace osero_wpf
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private bool IsBlackTurn = true;
        private int[,] BoardInfo = new int[8, 8];
        public MainWindow()
        {
            InitializeComponent();
            Init();
        }
        /// <summary>
        /// 初期化
        /// </summary>
        private void Init()
        {
            for (var i = 0; i < 8; i++)
            {
                for (var j = 0; j < 8; j++)
                {
                    BoardInfo[i, j] = 0;
                }
            }

            SetBoardInfo(4, 4, 1);
            SetBoardInfo(4, 5, -1);
            SetBoardInfo(5, 4, -1);
            SetBoardInfo(5, 5, 1);
            ReflectBoardInfoToXaml();
        }

        /// <summary>
        /// オセロの諸々の処理実行
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var button = sender as Button;
            var stackPanel = button.Parent as StackPanel;

            int Col = int.Parse(button.Tag.ToString());
            int Row = int.Parse(stackPanel.Tag.ToString());

            if(BoardInfo[Row - 1, Col - 1] != 0)
            {
                MessageBox.Show("そこにはすでに置かれています。");
                return;
            }

            var IsTurnChange = CheckValidBoardInfo(Row, Col);
            if (!IsTurnChange)
            {
                MessageBox.Show("そこには置けません。");
                return;
            }

            SetBoardInfo(Row, Col, ConvertTurnToIntInfo(IsBlackTurn));
            ReflectBoardInfoToXaml();
            IsBlackTurn = !IsBlackTurn;
        }

        private bool CheckValidBoardInfo(int Row, int Col)
        {
            int Info = ConvertTurnToIntInfo(IsBlackTurn);
            int RowDirection;
            int ColDirection;
            var IsTurnChange = false;
            //方向
            for(var i = 1; i <= 8; i++)
            {
                //石の数
                for(var j = 7; j >= 2; j--)
                {
                    (RowDirection, ColDirection) = GetDirection(i);
                    //ひとつでも変更があればturnChangeFlgをtrueにする。
                    if (!CheckRangeValid(Row + j * RowDirection, Col + j * ColDirection) ||
                        !CheckReverse(Row, Col, Info, i, j)) continue;
                    else IsTurnChange = true;
                    //チェックが通ったものの場合j - 1個ひっくり返す。
                    for(var k = 1; k < j; k++)
                    {
                        SetBoardInfo(Row + k * RowDirection, Col + k * ColDirection, Info);
                    }
                }
            }

            return IsTurnChange;
        }
        /// <summary>
        /// 配列の範囲をチェック
        /// </summary>
        /// <param name="RowLimit">Row</param>
        /// <param name="ColLimit">Col</param>
        /// <returns>配列の範囲を越えなければtrue超えればfalse</returns>
        private bool CheckRangeValid(int RowLimit = 0, int ColLimit = 0)
        {
            RowLimit--;
            ColLimit--;
            return RowLimit < 8 && ColLimit < 8 && 0 <= RowLimit && 0 <= ColLimit;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Row"></param>
        /// <param name="Col"></param>
        /// <param name="Info">現在のtrunをintで表したもの</param>
        /// <param name="Direction">チェックする方向</param>
        /// <param name="Length">チェックの範囲</param>
        /// <returns>ひっくり返すことができればtrueそうでなければfalse</returns>
        private bool CheckReverse(int Row, int Col, int Info, int Direction, int Length)
        {
            int RowDirection;
            int ColDirection;
            (RowDirection, ColDirection) = GetDirection(Direction);

            var IsOneReverse = false;
            for(var i = 1; i < Length; i++)
            {
                IsOneReverse = BoardInfo[Row + RowDirection * i - 1, Col + ColDirection * i - 1] == -Info;
                if (!IsOneReverse) return false;
            }

            return BoardInfo[Row + RowDirection * Length - 1, Col + ColDirection * Length - 1] == Info;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="Direction">方向</param>
        /// <returns>方向をRow,Colの二つに分ける</returns>
        private (int, int) GetDirection(int Direction)
        {
            int RowDirection;
            int ColDirection;
            if (Direction == 1)
            {
                RowDirection = 0;
                ColDirection = 1;
            }
            else if (Direction == 2)
            {
                RowDirection = 1;
                ColDirection = 1;
            }
            else if (Direction == 3)
            {
                RowDirection = 1;
                ColDirection = 0;
            }
            else if (Direction == 4)
            {
                RowDirection = 1;
                ColDirection = -1;
            }
            else if (Direction == 5)
            {
                RowDirection = 0;
                ColDirection = -1;
            }
            else if (Direction == 6)
            {
                RowDirection = -1;
                ColDirection = -1;
            }
            else if (Direction == 7)
            {
                RowDirection = -1;
                ColDirection = 0;
            }
            else
            {
                RowDirection = -1;
                ColDirection = 1;
            }

            return (RowDirection, ColDirection);
        }
        /// <summary>
        /// boolのターン情報をintに変換(intにしといたほうがいいかも？)
        /// </summary>
        /// <param name="Turn">現在のターン黒か白か</param>
        /// <returns></returns>
        private int ConvertTurnToIntInfo(bool Turn)
        {
            if (Turn)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }

        /// <summary>
        /// 黒か白かをintの情報からstringに変換
        /// </summary>
        /// <param name="info">1ならば黒,-1ならば白,0ならばなし</param>
        /// <returns></returns>
        private string ConvertIntInfoToStringInfo(int info)
        {
            if(info == 1)
            {
                return "●";
            }
            else if(info == -1)
            {
                return "〇";
            }
            else
            {
                return "";
            }
        }
        /// <summary>
        /// BoardInfoにsetするとき-1のずれが発生するのでメソッド化する。
        /// </summary>
        /// <param name="row">縦</param>
        /// <param name="col">横</param>
        /// <param name="info">黒か白かを1か-1かで表したもの</param>
        private void SetBoardInfo(int row, int col, int info)
        {
            BoardInfo[row - 1, col - 1] = info;
        }
        /// <summary>
        /// ボードの情報をxamlに反映
        /// </summary>
        private void ReflectBoardInfoToXaml()
        {
            int row = 0;
            int col = 0;
            int BlackCount = 0;
            int WhiteCount = 0;
            int NoCount = 0;
            foreach (var Children in Board.Children)
            {
                var Panel = Children as StackPanel;
                foreach (var But in Panel.Children)
                {
                    var Butt = But as Button;
                    Butt.Content = ConvertIntInfoToStringInfo(BoardInfo[row, col]);
                    if (BoardInfo[row, col] == 1) BlackCount++;
                    else if (BoardInfo[row, col] == -1) WhiteCount++;
                    else NoCount++;
                    col++;
                }
                col = 0;
                row++;
            }
            if (BlackCount == 0)
            {
                MessageBox.Show("白の勝ち");
                Init();
            }
            else if(WhiteCount == 0)
            {
                MessageBox.Show("黒の勝ち");
                Init();
            }
            else if(NoCount == 0)
            {
                if(WhiteCount < BlackCount)
                {
                    MessageBox.Show("黒の勝ち");
                    Init();
                }
                else if(WhiteCount > BlackCount)
                {
                    MessageBox.Show("白の勝ち");
                    Init();
                }
                else
                {
                    MessageBox.Show("引き分け");
                    Init();
                }
            }
            ViewBlackCount.Text = BlackCount.ToString();
            ViewWhiteCount.Text = WhiteCount.ToString();
            TurnText.Text = IsBlackTurn ? "黒" : "白";
        }
        /// <summary>
        /// 置けない時にパスするためのボタン。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            IsBlackTurn = !IsBlackTurn;
        }
    }
}
```

という感じになる。