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

今回は部品クラスを作ります。その前にペントミノを簡単な文字列で表現する方法について説明します(平面版ペントミノ2Dのときにも使いました)。


図の折れ線矢印は起点から上下左右に図形を一筆書きのようにたどります。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, 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, 0)
               Case "D", "d"  '手前
                  .MoveTo(.X, .Y + 1, 0)
               Case "L", "l"  '左
                  .MoveTo(.X - 1, .Y, 0)
               Case "U", "u"  '奥
                  .MoveTo(.X, .Y - 1, 0)
            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

・プロパティ:最小値 MinX, MinY, MinZ

Coordsの座標でそれぞれの最小値を返します。

ReadOnly Property MinX As Integer
   Get
      Return Coords.Min(Function(s) s.X)
   End Get
End Property

ReadOnly Property MinY As Integer
   Get
      Return Coords.Min(Function(s) s.Y)
   End Get
End Property

ReadOnly Property MinZ As Integer
   Get
      Return Coords.Min(Function(s) s.Z)
   End Get
End Property

・プロパティ:長さ LenX, LenY, LenZ

Coordsの座標でそれぞれの軸方向の長さを返します。

ReadOnly Property LenX As Integer
   Get
      Return Coords.Max(Function(s) s.X) - MinX + 1
   End Get
End Property

ReadOnly Property LenY As Integer
   Get
      Return Coords.Max(Function(s) s.Y) - MinY + 1
   End Get
End Property

ReadOnly Property LenZ As Integer
   Get
      Return Coords.Max(Function(s) s.Z) - MinZ + 1
   End Get
End Property

・プロパティ:重心 CentroidX, CentroidY, CentroidZ

Coordsの座標でそれぞれの軸方向の重心座標を返します。

Public Function CentroidX() As Single
   Return Coords.Sum(Function(c) c.X) / Size
End Function

Public Function CentroidY() As Single
   Return Coords.Sum(Function(c) c.Y) / Size
End Function

Public Function CentroidZ() As Single
   Return Coords.Sum(Function(c) c.Z) / Size
End Function

・複製(関数)

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

Public Function Clone() As Piece
   Dim newPiece As New Piece()
   For Each c In Coords
      newPiece.Coords.Add(c.Clone)
   Next
   Return newPiece
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")
MsgBox(If(p1 = p2, "True", "False"))   'Trueが表示されます

・座標の回転

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

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

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

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

・原点にシフト

部品を原点(0, 0, 0)にシフトする。

Public Sub ShiftToOrigin()

   'YZ平面に接地(X座標=0)させる
   Dim dX = Coords.Min(Function(p) p.X)
   For Each c In Coords
      c.MoveTo(c.X - dX, c.Y, c.Z)
   Next

   '上で接地した面をXZ平面に接地(Y座標=0)させる
   Dim dY = Coords.Where(Function(p) p.X = 0).ToList.Min(Function(p) p.Y)
   For Each c In Coords
      c.MoveTo(c.X, c.Y - dY, c.Z)
   Next

   '上で接地した面をXY平面に接地(Z座標=0)させる
   Dim dZ = Coords.Where(Function(p) p.X = 0 And p.Y = 0).ToList.Min(Function(p) p.Z)
   For Each c In Coords
      c.MoveTo(c.X, c.Y, c.Z - dZ)
   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

#Region "最小値: MinX, MinY, MinZ"

   ReadOnly Property MinX As Integer
      Get
         Return Coords.Min(Function(s) s.X)
      End Get
   End Property

   ReadOnly Property MinY As Integer
      Get
         Return Coords.Min(Function(s) s.Y)
      End Get
   End Property

   ReadOnly Property MinZ As Integer
      Get
         Return Coords.Min(Function(s) s.Z)
      End Get
   End Property

#End Region

#Region "長さ: LenX, LenY, LenZ"

   ReadOnly Property LenX As Integer
      Get
         Return Coords.Max(Function(s) s.X) - MinX + 1
      End Get
   End Property

   ReadOnly Property LenY As Integer
      Get
         Return Coords.Max(Function(s) s.Y) - MinY + 1
      End Get
   End Property

   ReadOnly Property LenZ As Integer
      Get
         Return Coords.Max(Function(s) s.Z) - MinZ + 1
      End Get
   End Property

#End Region

#Region "重心: CentroidX, CentroidY, CentroidZ"

   Public Function CentroidX() As Single
      Return Coords.Sum(Function(c) c.X) / Size
   End Function

   Public Function CentroidY() As Single
      Return Coords.Sum(Function(c) c.Y) / Size
   End Function

   Public Function CentroidZ() As Single
      Return Coords.Sum(Function(c) c.Z) / Size
   End Function

#End Region

   '** 初期化
   Sub New(Optional expression As String = "")
      Coords = New List(Of Coord)
      If expression <> "" Then
         With New Coord(0, 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, 0)
                  Case "D", "d"  '手前
                     .MoveTo(.X, .Y + 1, 0)
                  Case "L", "l"  '左
                     .MoveTo(.X - 1, .Y, 0)
                  Case "U", "u"  '奥
                     .MoveTo(.X, .Y - 1, 0)
               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 newPiece As New Piece()
      For Each c In Coords
         newPiece.Coords.Add(c.Clone)
      Next
      Return newPiece
   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

#Region "回転: Rotate"

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

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

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

#End Region

   '** ShiftToOrigin:座標(0,0,0)に部品をシフトする
   Public Sub ShiftToOrigin()

      'YZ平面に接地(X座標=0)させる
      Dim dX = Coords.Min(Function(p) p.X)
      For Each c In Coords
         c.MoveTo(c.X - dX, c.Y, c.Z)
      Next

      '上で接地した面をXZ平面に接地(Y座標=0)させる
      Dim dY = Coords.Where(Function(p) p.X = 0).ToList.Min(Function(p) p.Y)
      For Each c In Coords
         c.MoveTo(c.X, c.Y - dY, c.Z)
      Next

      '上で接地した面をXY平面に接地(Z座標=0)させる
      Dim dZ = Coords.Where(Function(p) p.X = 0 And p.Y = 0).ToList.Min(Function(p) p.Z)
      For Each c In Coords
         c.MoveTo(c.X, c.Y, c.Z - dZ)
      Next

   End Sub

End Class

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