MENU

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

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


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

・クラス宣言

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

Public Class Piece

・プロパティ:Coords

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

ReadOnly Property Coords As List(Of Coord)

・初期化

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

Sub New(Optional expression As String = "")
   Coords = New List(Of Coord)
   If expression <> "" Then
      With New Coord(0, 0)
         Coords.Add(.Clone)
         For Each c As Char In expression
            Dim s = CStr(c)
            Select Case s
               Case "R", "r"  '右
                  .MoveTo(.X + 1, .Y)
               Case "D", "d"  '下
                  .MoveTo(.X, .Y + 1)
               Case "L", "l"  '左
                  .MoveTo(.X - 1, .Y)
               Case "U", "u"  '上
                  .MoveTo(.X, .Y - 1)
            End Select
            '大文字のときはリストに加える
            If "RDLU".Contains(s) Then
               Coords.Add(.Clone)
            End If
         Next
      End With
   End If
End Sub

'=====(例)=====
Dim piece As New Piece("RDRdL")      '部品"F"を作成します

・プロパティ:Size  

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

ReadOnly Property Size As Integer
   Get
      Return Coords.Count
   End Get
End Property

・複製(関数)

座標クラスのときと同様に、同じプロパティを持つインスタンスをもう一つ作ります。

Public Function Clone() As Piece
   Dim piece As New Piece()
   For Each c In Coords
      piece.Coords.Add(c.Clone)
   Next
   Return piece
End Function

演算子定義

プロパティの一致の判定用に演算子("=", "<>")を定義します。Coords の要素の順序は不一致でも構わない仕様です。

Public Shared Operator =(p1 As Piece, p2 As Piece) As Boolean
   If p1.Coords.Count <> p2.Coords.Count Then
      Return False
   Else
      For Each c1 In p1.Coords
         Dim f = False
         For Each c2 In p2.Coords
            If c1 = c2 Then
               f = True
               Exit For
            End If
         Next
         If f = False Then
            Return False
         End If
      Next
   End If
   Return True
End Operator

Public Shared Operator <>(p1 As Piece, p2 As Piece) As Boolean
   Return Not (p1 = p2)
End Operator

'=====(例)=====
Dim p1 As New Piece("RDRdL")
Dim p2 As New Piece("RDDrU")
If p1=p2 Then
   MsgBox("True")   'Trueが表示されます
Else
   MsgBox("False")
End If

・座標の回転

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

Public Sub AntiClockWiseRotate(ct As Integer)   'ct:1=90度,2=180度,・・・
   For Each c In Coords
      c.AntiClockWiseRotate(ct)
   Next
End Sub

・反転

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

Public Sub HorizontalReverse()
   For Each c In Coords
      c.HorizontalReverse()
   Next
End Sub

・原点にシフト

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

Public Sub ShiftToOrigin()
   '左端をY軸に接地させる
   Dim dX = Coords.Min(Function(s) s.X)
   For Each c In Coords
      With c
         .MoveTo(.X - dX, .Y)
      End With
   Next
   '接地した辺の上端を座標(0,0)にシフト
   Dim dY = Coords.Where(Function(s) s.X = 0).ToList.Min(Function(s) s.Y)
   For Each c In Coords
      With c
         .MoveTo(.X, .Y - dY)
      End With
   Next
End Sub


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

Public Class Piece
   '**********************************************
   '*             Piece  (部品クラス) 
   '**********************************************
   ReadOnly Property Coords As List(Of Coord)    '座標

   ReadOnly Property Size As Integer
      Get
         Return Coords.Count
      End Get
   End Property

   '** 初期化
   Sub New(Optional expression As String = "")
      Coords = New List(Of Coord)
      If expression <> "" Then
         With New Coord(0, 0)
            Coords.Add(.Clone)
            For Each c As Char In expression
               Dim s = CStr(c)
               Select Case s
                  Case "R", "r"  '右
                     .MoveTo(.X + 1, .Y)
                  Case "D", "d"  '下
                     .MoveTo(.X, .Y + 1)
                  Case "L", "l"  '左
                     .MoveTo(.X - 1, .Y)
                  Case "U", "u"  '上
                     .MoveTo(.X, .Y - 1)
               End Select
               '大文字のときはリストに加える
               If "RDLU".Contains(s) Then
                  Coords.Add(.Clone)
               End If
            Next
         End With
      End If
   End Sub

   '** 複製
   Public Function Clone() As Piece
      Dim piece As New Piece()
      For Each c In Coords
         piece.Coords.Add(c.Clone)
      Next
      Return piece
   End Function

   '** 演算子定義(=)
   Public Shared Operator =(p1 As Piece, p2 As Piece) As Boolean
      If p1.Coords.Count <> p2.Coords.Count Then
         Return False
      Else
         For Each c1 In p1.Coords
            Dim f = False
            For Each c2 In p2.Coords
               If c1 = c2 Then
                  f = True
                  Exit For
               End If
            Next
            If f = False Then
               Return False
            End If
         Next
      End If
      Return True
   End Operator

   Public Shared Operator <>(p1 As Piece, p2 As Piece) As Boolean
      Return Not (p1 = p2)
   End Operator

   '** 反時計回りに90度回転する
   Public Sub AntiClockWiseRotate(ct As Integer)   'ct:1=90度,2=180度,・・・
      For Each c In Coords
         c.AntiClockWiseRotate(ct)
      Next
   End Sub

   '** y軸に対して反転する
   Public Sub HorizontalReverse()
      For Each c In Coords
         c.HorizontalReverse()
      Next
   End Sub

   '** ShiftToOrigin:左端の最上段が(0,0)になるようにシフトする
   Public Sub ShiftToOrigin()
      '左端をY軸に接地させる
      Dim dX = Coords.Min(Function(s) s.X)
      For Each c In Coords
         With c
            .MoveTo(.X - dX, .Y)
         End With
      Next
      '接地した辺の上端を座標(0,0)にシフト
      Dim dY = Coords.Where(Function(s) s.X = 0).ToList.Min(Function(s) s.Y)
      For Each c In Coords
         With c
            .MoveTo(.X, .Y - dY)
         End With
      Next
   End Sub

End Class

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