MENU

【C#】ペントミノ2Dパズルを解く(3)部品クラスを作る

今回は部品クラスを作ります。その前にペントミノを簡単な文字列で表現する方法について説明します。


図の折れ線矢印は起点から上下左右に図形を一筆書きのようにたどります。1マスの移動を アルファベット1文字("U", "D", "L", "R")で表します。それぞれ "Up", "Down", "Left", "Right" の頭文字です。移動後部品を外れるときは小文字を使います。
こうすると図の部品 "F" は "RDRdL" という文字列で表現できます(表し方は一意ではありません)。他の部品 "L", "T", "X" もそれぞれ "DDDR", "RRdLD", "DLdRrU" と表せます。初期化ではこの文字列を引数として受けとり、座標のコレクションに変換します。

・クラス宣言

部品クラスの名前は "Piece" です。

internal class Piece

・プロパティ:Coords

座標クラス(Coord)のコレクションです。ペントミノでは5個の コレクションとなります。

public readonly List<Coord> Coords;

・初期化

冒頭で述べたとおり部品内の起点からの移動を文字列にしたものを引数にします。引数は省略するとインスタンスのみ取得します。

public Piece(string expression = "")
{
    Coords = new(); 
    if(expression != "")
    {
        Coord p = new Coord(0,0);
        Coords.Add(p with { });      //起点を登録

        foreach (char c in expression)
        {
            string s = c.ToString();
            switch (s)
            {
                case "R":
                case "r":
                    p.MoveTo(p.X + 1, p.Y);
                    break;
                case "D":
                case "d":
                    p.MoveTo(p.X , p.Y + 1);
                    break;
                case "L":
                case "l":
                    p.MoveTo(p.X - 1, p.Y);
                    break;
                case "U":
                case "u":
                    p.MoveTo(p.X, p.Y-1);
                    break;
                default:
                    break;
            }
            //大文字のときはリストに加える
            if ("RDLU".Contains(s))
            {
                Coords.Add(p with { });
            }
        }
    }
}

//=====(例)=====
Piece piece = new Piece("RDRdL");      //部品"F"を作成します

・プロパティ:Size  

部品の大きさ(Coordsの要素数=正方形の個数)を返します。ペントミノの場合は 5 。

public int Size()
{
    return Coords.Count;   
}

・複製(関数)

座標クラスで述べたようにレコードクラスにすると複製(clone)関数や演算子(==)定義が不要になるのですが、このPieceクラスの場合プロパティ(Coords)が参照型なのでレコードクラスとして定義できません。VBのときと同じように複製関数は自作することにします。

public Piece Clone()
{
    Piece piece = new Piece();    //引数なしでインスタンスのみを作成
    foreach (Coord c in Coords)
    {
        piece.Coords.Add(c with { });
    }
    return piece;
}

演算子定義

レコードクラスではないのでプロパティの一致の判定用に演算子("==", "!=")を自前で定義する必要があります。Coords の要素の順序は問わない仕様です(GetHashCode関数の説明は省略します)。

public static bool operator ==(Piece? p1, Piece? p2)
{
    if (p1 is null || p2 is null || p1.Coords is null || p2.Coords is null)
    {
        return false;
    }

    if (p1.Coords.Count != p2.Coords.Count)
    {
        return false;
    }
    else
    {
        foreach (Coord c1 in p1.Coords)
        {
            bool f = false;
            foreach (Coord c2 in p2.Coords)
            {
                if (c1 == c2)
                {
                    f = true;
                    break;
                }
            }
            if (f == false)
            {
                return false;
            }
        }
    }
    return true;
}

public static bool operator !=(Piece? p1, Piece? p2)
{
    return !(p1 == p2);
}

public override int GetHashCode()
{
    int hash = 0;
    foreach (Coord c in Coords)
    {
        hash ^= HashCode.Combine(c.X, c.Y);
    }
    return hash;
}

//=====(例)=====
Piece p1 = new Piece("RDRdL");
Piece p2 = new Piece("RDDrU");
if (p1==p2)
{
   MessageBox.Show("True")   //Trueが表示されます
}
else
{
   MessageBox.Show("False");
}

・座標の回転

反時計回りで90度単位で回転させます。引数には90度単位の度数を指定します。

public void AntiClockWiseRotate(int ct)   //ct:1=90度,2=180度,・・・
{
    foreach (Coord c in Coords)
    {
        c.AntiClockWiseRotate(ct);
    }
}

・反転

Y軸に対して折り返します。

public void HorizontalReverse()
{
    foreach (Coord c in Coords)
    {
        c.HorizontalReverse();
    }
}

・原点にシフト

部品の左端の最上段が(0,0)になるようにシフトする。

public void ShiftToOrigin()
{
    // 左端をY軸に接地させる
    int dx = Coords.Min(s => s.X);
    foreach(Coord c in Coords)
    {
        c.MoveTo(c.X - dx, c.Y);
    }
    // 接地した辺の上端を座標(0,0)にシフト
    int dY = Coords.Where(s => s.X == 0).ToList().Min(s => s.Y);
    foreach(Coord c in Coords)
    {
        c.MoveTo(c.X, c.Y - dY);
    }
}


以上が「部品クラス」です。クラス全体のソースは以下のとおりです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ペントミノソルバー2D
{
    internal class Piece
    {
        //****************************************
        //*          Piece  (部品クラス) 
        //****************************************
        public readonly List<Coord> Coords;       // 座標

        public int Size()
        {
            return Coords.Count;   
        }

        public int Left()
        {
            return Coords.Min(s => s.X);
        }

        public int Top()
        {
            return Coords.Min(s => s.Y); 
        }

        public int Width()
        {
            return Coords.Max(s => s.X) - Left() + 1;
        }

        public int Height()
        {
            return Coords.Max(s => s.Y) - Top() + 1;
        }

        // 初期化
        public Piece(string expression = "")
        {
            Coords = new(); 
            if(expression != "")
            {
                Coord p = new Coord(0,0);
                Coords.Add(p with { });

                foreach (char c in expression)
                {
                    string s = c.ToString();
                    switch (s)
                    {
                        case "R":
                        case "r":
                            p.MoveTo(p.X + 1, p.Y);
                            break;
                        case "D":
                        case "d":
                            p.MoveTo(p.X , p.Y + 1);
                            break;
                        case "L":
                        case "l":
                            p.MoveTo(p.X - 1, p.Y);
                            break;
                        case "U":
                        case "u":
                            p.MoveTo(p.X, p.Y-1);
                            break;
                        default:
                            break;
                    }
                    if ("RDLU".Contains(s))
                    {
                        Coords.Add(p with { });
                    }
                }
            }
        }

        // 複製
        public Piece Clone()
        {
            Piece piece = new Piece();
            foreach (Coord c in Coords)
            {
                piece.Coords.Add(c with { });
            }
            return piece;
        }

        // 演算子定義(==)
        public static bool operator ==(Piece? p1, Piece? p2)
        {
            if (p1 is null || p2 is null || p1.Coords is null || p2.Coords is null)
            {
                return false;
            }

            if (p1.Coords.Count != p2.Coords.Count)
            {
                return false;
            }
            else
            {
                foreach (Coord c1 in p1.Coords)
                {
                    bool f = false;
                    foreach (Coord c2 in p2.Coords)
                    {
                        if (c1 == c2)
                        {
                            f = true;
                            break;
                        }
                    }
                    if (f == false)
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        public static bool operator !=(Piece? p1, Piece? p2)
        {
            return !(p1 == p2);
        }

        public override int GetHashCode()
        {
            int hash = 0;
            foreach (Coord c in Coords)
            {
                hash ^= HashCode.Combine(c.X, c.Y);
            }
            return hash;
        }

        // 反時計回りに90度回転する
        public void AntiClockWiseRotate(int ct)   //ct:1=90度,2=180度,・・・
        {
            foreach (Coord c in Coords)
            {
                c.AntiClockWiseRotate(ct);
            }
        }

        // y軸に対して反転する
        public void HorizontalReverse()
        {
            foreach (Coord c in Coords)
            {
                c.HorizontalReverse();
            }
        }

        // ShiftToOrigin:左端の最上段が(0,0)になるようにシフトする
        public void ShiftToOrigin()
        {
            // 左端をY軸に接地させる
            int dx = Coords.Min(s => s.X);
            foreach(Coord c in Coords)
            {
                c.MoveTo(c.X - dx, c.Y);
            }
            // 接地した辺の上端を座標(0,0)にシフト
            int dY = Coords.Where(s => s.X == 0).ToList().Min(s => s.Y);
            foreach(Coord c in Coords)
            {
                c.MoveTo(c.X, c.Y - dY);
            }
        }

    }
}

次回は「棚クラス」です。