C#で将棋盤を描画してみようその1 ～枠線の描画～

今日からC#で将棋盤を描画するプログラムを作っていきたいと思います。

# その1 フォームの作成

取り敢えず、将棋盤を描画するフォームを作成しましょう。作成したフォームのPaintイベントに将棋盤を描画するコードを書いていくことになりますが、今は未だ空です。

```csharp
    //プログラム
    public static class Program
    {
        //ここからプログラムの実行が始まる
        [STAThread]
        public static void Main()
        {
            //将棋盤を描画するためのフォームを作成する
            using (Form form = new Form())
            {
                //描画を実行しなければならない時
                //将棋盤を描画する
                form.Paint += (sender, e) =>
                {
                };

                //アプリケーションの実行を開始する
                Application.Run(form);
            }
        }
    }
```

# その2 描画内容

将棋盤を描画するのに最低限必要なパラメータには何があるでしょうか？
将棋盤には9行9列計81個の枡があります。まずはこれらの枡の大きさが必要です。それぞれの枡の大きさは同一ということにしましょう（それぞれの枡の大きさが異なる将棋盤というのはまずないであろうと考えられる）。大きさは高さと幅から成ります。高さも幅も1以上でなければなりません。
更に、それぞれの枡は境界に線を引くことによって識別されます。この線の太さや色が必要です。線も縦線が10個横線が10個必要となりますが、それぞれの線の太さや色は同一ということにしましょう（それぞれの線の太さや色が異なる将棋盤というのはまずないであろうと考えられる）。太さは0以上でなければなりません（線を引かない場合も考えられなくはない）。

描画内容を表すクラスを作成します。

将棋盤は9行9列なのですが、一応行数と列数は可変にしておきます。行数と列数は1以上でなければなりません。

```csharp
    //描画内容
    public class Content
    {
        //行数
        public int NumOfRows { get; set; }
        //列数
        public int NumOfColumns { get; set; }

        //枡の幅
        public int Width { get; set; }
        //枡の高さ
        public int Height { get; set; }

        //線の太さ
        public int Thickness { get; set; }
        //線の色
        public Color Color { get; set; }

        //妥当な行数か？
        public static bool IsValidNumOfRows(int numOfRows)
        {
            return numOfRows > 0;
        }

        //妥当な列数か？
        public static bool IsValidNumOfColumns(int numOfColumns)
        {
            return numOfColumns > 0;
        }

        //妥当な枡の高さか？
        public static bool IsValidHeight(int height)
        {
            return height > 0;
        }

        //妥当な枡の幅か？
        public static bool IsValidWidth(int width)
        {
            return width > 0;
        }

        //妥当な線の太さか？
        public static bool IsValidThickness(int thickness)
        {
            return thickness >= 0;
        }
    }
```

# その3 描画環境

描画を行うには何が必要でしょうか？
今回はフォームに描画を行う訳ですが、フォームに描画を行う手段が必要です。Windows Formsの場合、Graphicsクラスです。
更に、描画を行う際にはフォームのどの範囲に描画を行うべきかを知る必要があります。

このような、描画を行うための環境（文脈）を保持するクラスを作成します。

その前に、範囲を表すためのクラスを作成します。

```csharp
    //範囲（二次元（矩形））
    public class Range2d<T>
    {
        public Range2d(T _x, T _y, T _width, T _height)
        {
            X = _x;
            Y = _y;
            Width = _width;
            Height = _height;
        }

        //左上のX座標
        public T X { get; private set; }
        //左上のY座標
        public T Y { get; private set; }
        //幅
        public T Width { get; private set; }
        //高さ
        public T Height { get; private set; }
    }
```

描画環境クラスを作成します。

```csharp
    //描画環境
    public class Context
    {
        //描画手段
        public Graphics G { get; set; }
        //描画範囲
        public Range2d<int> Range { get; set; }
    }
```

# その4 既定の描画内容

既定の描画内容を作成するCreateDefaultContent関数を作成します。

```csharp
        //既定の描画内容を作成する
        private static Content CreateDefaultContent()
        {
            //既定の行数
            const int numOfRows = 9;
            //既定の列数
            const int numOfColumns = 9;

            //既定の枡の幅
            const int width = 40;
            //既定の枡の高さ
            const int height = 40;
            //既定の線の太さ
            const int thickness = 1;
            //既定の線の色の赤成分
            const int red = 0;
            //既定の線の色の緑成分
            const int green = 0;
            //既定の線の色の青成分
            const int blue = 0;

            //既定の線の色
            Color color = Color.FromArgb(red, green, blue);

            //既定の描画内容を作成する
            Content content = new Content();
            //既定の描画内容を設定する
            content.NumOfRows = numOfRows;
            content.NumOfColumns = numOfColumns;
            content.Width = width;
            content.Height = height;
            content.Thickness = thickness;
            content.Color = color;

            //既定の描画内容を返す
            return content;
        }
```

# その5 描画処理

描画処理を実行するDraw関数を作成します。

```csharp
        //描画を実行する
        private static void Draw(Context context, Content content)
        {
            //描画手段
            Graphics g = context.G;
            //描画範囲
            Range2d<int> range = context.Range;

            //描画範囲外に描画しようとしても描画されないようにする
            g.SetClip(new Rectangle(range.X, range.Y, range.Width, range.Height));

            //枡の境界の線を描画しなければならない場合
            if (content.Thickness != 0 && content.Color != Color.Empty && content.Color != Color.Transparent)
            {
                //枡と枡の境界の全体の幅
                int width = content.Width * content.NumOfColumns + content.Thickness * (content.NumOfColumns + 1);
                //枡と枡の境界の全体の高さ
                int height = content.Height * content.NumOfRows + content.Thickness * (content.NumOfRows + 1);

                //線を描画するための単色のブラシを作成する
                using (SolidBrush brush = new SolidBrush(content.Color))
                {
                    //縦線を描画する
                    for (int i = 0, x = range.X; i <= content.NumOfColumns; i++, x += content.Thickness + content.Width)
                        g.FillRectangle(brush, x, range.Y, content.Thickness, height);
                    //横線を描画する
                    for (int i = 0, y = range.Y; i <= content.NumOfRows; i++, y += content.Thickness + content.Height)
                        g.FillRectangle(brush, range.X, y, width, content.Thickness);
                }
            }
        }
```

# その6 描画の実行

実際に描画が行われるようにMain関数を変更します。フォームの大きさを取り敢えず適当に設定しました。

```csharp
        //ここからプログラムの実行が始まる
        [STAThread]
        public static void Main()
        {
            //フォーム名
            const string title = "クレアたん将棋";
            //フォームの幅
            const int width = 800;
            //フォームの高さ
            const int height = 600;

            //将棋盤を描画するためのフォームを作成する
            using (Form form = new Form())
            {
                //フォーム名を設定する
                form.Text = title;
                //フォームの大きさを設定する
                form.Width = width;
                form.Height = height;
                //描画を実行しなければならない時
                //将棋盤を描画する
                form.Paint += (sender, e) =>
                {
                    //描画環境を作成する
                    Context context = new Context();
                    context.G = e.Graphics;
                    //描画範囲はフォームのクライアント領域の全面
                    context.Range = new Range2d<int>(0, 0, form.ClientSize.Width, form.ClientSize.Height);

                    //描画内容を作成する
                    Content content = CreateDefaultContent();

                    //描画を実行する
                    Draw(context, content);
                };

                //アプリケーションの実行を開始する
                Application.Run(form);
            }
        }
```

# 実行結果

![実行結果1](https://qiita-image-store.s3.amazonaws.com/0/10969/194a4df0-80e2-1b78-517f-a773bd3638cf.png)

描画内容を適当に変更してみます。

![実行結果2](https://qiita-image-store.s3.amazonaws.com/0/10969/dc7c6c77-f1b9-32a6-e341-f17acdab33df.png)