More Samples
{
A pentomino is an arrangement of five unit squares that are joined along their
edges. There are 12 possible shapes, which are depicted below. Traditionally, 
each piece is labelled by the letter that most accurately reflects its shape. 

  +-+      +-+-+-+     +-+     +-+                +-+-+      +-+-+  +-+-+
  | |      | | | |     | |     | |                | | |      | | |  | | |
  +-+      +-+-+-+   +-+-+-+   +-+-+     +-+ +-+  +-+-+    +-+-+-+  +-+-+
  | |        | |     | | | |   | | |     | | | |    | |    | | |    | | |
  +-+-+-+    +-+     +-+-+-+   +-+-+-+   +-+-+-+    +-+-+  +-+-+    +-+-+
  | | | |    | |       | |       | | |   | | | |    | | |    | |    | | 
  +-+-+-+    +-+       +-+       +-+-+   +-+-+-+    +-+-+    +-+    +-+
    V         T         X         W         U        Z        F      P
 
  +-+       +-+      +-+   +-+
  | |       | |      | |   | |
  +-+     +-+-+    +-+-+   +-+
  | |     | | |    | | |   | |
  +-+     +-+-+    +-+-+   +-+
  | |     | |        | |   | |
  +-+     +-+        +-+   +-+-+
  | |     | |        | |   | | |
  +-+     +-+        +-+   +-+-+
  | |                      
  +-+                      
   I       N          Y      L
  
Typical problem is to fit the 12 pentomino pieces into various shapes, mostly rectangles.
Each piece can be rotated and flipped. 
Possible rectangle shapes that fit all 60 squares can be 3x20, 4x15, 5x12, and 6x10. 
An example of a 6 x 10 solution is this:

    V V V T I I I I I W 
    L L V T T T N N W W 
    L F V T N N N W W Z 
    L F F F X U U Z Z Z  
    L Y F X X X U Z P P 
    Y Y Y Y X U U P P P 
 
Not counting rotations and reflections, the number of solutions for each pentomino
problem can be summarized as follows:

    6x10: 2339
    5x12: 1010
    4x15:  368
    3x10:    2

Other shapes are possible. The code below solves all rectangular problems, but it 
is trivial to modify it to solve pentomino layout for different shapes.
 
There are two possible approaches to tackle the pentomino problem: calculate until
all pieces are used or calculate until the whole board is covered. Covering the
whole board is much faster, as any impossible solution is detected much sooner.
Additionally, covering a rectangle is much faster when we start covering along
the smaller of the rectangle dimensions. Again, this is because using the shorter 
side first we detect impossible solutions much faster.

The solution are calculated by running the following queries:

    all Pentomino(10,6)     // all pentomino solutions for rectangle 10x6
    all Pentomino(12,5)     // all pentomino solutions for rectangle 12x5
    all Pentomino(15,4)     // all pentomino solutions for rectangle 15x4
    all Pentomino(20,3)     // all pentomino solutions for rectangle 20x3

Using the queries above, the calculation will take anything from one second to several
minutes, based on the rectangle and the speed of the CPU. 

Final note:

As mentioned before, calculating along the longer rectangle side first, i.e.
the query

      all Pentomino(6,10) 

will take much longer then the query

       all Pentomino(10,6)

In this case, the calculation can take several hours. 
Of course, the results will be the same (albeit not in the same order).

}         

Block = [0..]->[0..]->I

// In this program, we implement the pentomino pieces as rectangles, with some elements 
// "transparent", i.e. initialized as " " (space), the non-transparent elements are 
// initialzied based on the pentomino piece label.

BlockV :< Block = [["V"," "," "],
                   ["V"," "," "],
                   ["V","V","V"]]

BlockT :< Block = [["T","T","T"],
                   [" ","T"," "],
                   [" ","T"," "]]

BlockX :< Block = [[" ","X"," "],
                   ["X","X","X"],
                   [" ","X"," "]]

BlockW :< Block = [["W"," "," "],
                   ["W","W"," "],
                   [" ","W","W"]]

BlockU :< Block = [["U"," ","U"],
                   ["U","U","U"]]

BlockZ :< Block = [["Z","Z"," "],
                   [" ","Z"," "],
                   [" ","Z","Z"]]

BlockF :< Block = [[" ","F","F"],
                   ["F","F"," "],
                   [" ","F"," "]]

BlockP :< Block = [["P","P"],
                   ["P","P"],
                   ["P"," "]]

BlockI :< Block = [["I"],
                   ["I"],
                   ["I"],
                   ["I"],
                   ["I"]]

BlockN :< Block = [[" ","N"],
                   ["N","N"],
                   ["N"," "],
                   ["N"," "]]

BlockY :< Block = [[" ","Y"],
                   ["Y","Y"],
                   [" ","Y"],
                   [" ","Y"]]

BlockL :< Block = [["L"," "],
                   ["L"," "],
                   ["L"," "],
                   ["L","L"]]

local BlockEx = piece:Block,origin:I
local Board = [0..]->[0..]->I  
local AllPieces = [0..]->>[0..]->BlockEx
local Used = [0..]->I

pred Pentomino(rows:<[1..], columns:<I[1..]) iff
    Print('\n Solving Rectangular Pentomino Problem ',rows, ' x ',columns,'\n\n') &

    // Initialize the "board", i.e. the rectangle which we want to fill with
    // the pentomino pieces. We intialize all position as " " (no pieces placed yet) 

    board:.Board & Dupl(rows,Dupl(columns," "),board) & 

    // Each pentomino piece can have several orientations, we let the program 
    // to calculate them. 

    CreatePossibleShapes(BlockV,aBlockV) &
    CreatePossibleShapes(BlockT,aBlockT) &
    CreatePossibleShapes(BlockX,aBlockX) &
    CreatePossibleShapes(BlockW,aBlockW) &
    CreatePossibleShapes(BlockU,aBlockU) &
    CreatePossibleShapes(BlockI,aBlockI) &
    CreatePossibleShapes(BlockL,aBlockL) &
    CreatePossibleShapes(BlockZ,aBlockZ) &
    CreatePossibleShapes(BlockF,aBlockF) &
    CreatePossibleShapes(BlockP,aBlockP) &
    CreatePossibleShapes(BlockN,aBlockN) &
    CreatePossibleShapes(BlockY,aBlockY) &

    // Create an array of all arrays of all possible pentomino pieces orientations.
    // Note that it does not matter that the array elements may have different 
    // lengths.

    pieces :> AllPieces & pieces =
    [aBlockV,aBlockT,aBlockX,aBlockW,aBlockU,aBlockI,aBlockL,aBlockZ,aBlockF,aBlockP,aBlockN,aBlockY] &

    // We keep track of used pentomino pieces in "used". 
    // We initialize the map of used pentomino pieces to 0 (no piece used used)

    used :.Used & Dupl(Len(pieces),0,used) &

    // Having initialized the board, all possible pieces orientations and used pieces map
    // we can start the actual computation

    CoverAllBlocks(board,pieces,used) &

    // A solution was found, so print it

    PrintFormattedSolution(board,0)
  

// Cover the board as long as we can find any place on the board
// that is not covered yet by a pentomino.
local pred CoverAllBlocks(board:.Board,pieces:<AllPieces,used:.Used) iff
    if FindFreeRowCol(board,row,col,0) then
        PlaceOnePiece(board,pieces,used,row,col,0) & CoverAllBlocks(board,pieces,used)
    end

// Board position at row,col is unused. Place there a piece.
local pred PlaceOnePiece(board:.Board,pieces:<AllPieces,used:.Used,row:<I,col:<I,i:<I) iff
    p::I[0..] & p < Len(pieces) & used(p) = 0 & // get an unused piece index
    piece = pieces(p) &                         // get an unused piece
    rot::I[0..] & rot < Len(piece) &            // piece can have this many transformations
    CheckPosition(board,piece(rot),row,col,0) & // validate we can place this piece
    PlacePosition(board,piece(rot),row,col,0) & // place the piece on the board
    used(p) := 1                                // mark the piece as used

local pred PlacePosition(board:.Board,piece:<BlockEx,row:<I,col:<I,prow:<I)  iff
    if prow < Len(piece.piece) then
        PlaceOneRow(board,piece.piece,row,col-piece.origin,prow, 0) &
        PlacePosition(board,piece,row,col,prow+1) 
    end

local pred PlaceOneRow(board:.Board,piece :< Block, row:<I, col:<I, prow:<I, pcol:<I)  iff
    if pcol < Len(piece(0)) then
        if piece(prow,pcol) <> " " then
            board(row+prow,col+pcol) := piece(prow,pcol)
        end & PlaceOneRow(board,piece,row,col, prow,pcol+1) 
    end

local pred CheckPosition(board:.Board,piece :< BlockEx, row:<I, col:<I, prow:<I)  iff
    if prow < Len(piece.piece) then
        CheckOneRow(board,piece.piece,row,col-piece.origin,prow, 0) & 
        CheckPosition(board,piece,row,col, prow+1) 
    end

local pred CheckOneRow(board:.Board,piece :< Block, row:<I, col:<I, prow:<I, pcol:<I)  iff
    if pcol < Len(piece(0)) then
        if piece(prow,pcol) <> " " then
            board(row+prow,col+pcol) = " " 
        end & CheckOneRow(board,piece,row,col, prow,pcol+1) 
    end


// Find the first occurence of a free board square.
local proc FindFreeRowCol(board:<Board,row:>I, col:>I, i:<I) iff
    i < Len(board) &
    if FindFreeCol(board(i),col1,0) then 
        row = i & col = col1
    else 
        FindFreeRowCol(board,row,col,i+1) & true
    end
    
local proc FindFreeCol(board:<[0..]->I,col:>I,i:<I) iff
    i < Len(board) &
    if board(i) = " " then 
        col = i
    else
        FindFreeCol(board,col,i+1)
    end

// Print the solution in a more presentable way

local proc PrintFormattedSolution(board:<Board,row:<I) iff
    if row < Len(board) then 
        Print('\n') & PrintOneRow(board(row),0) & PrintFormattedSolution(board,row+1) 
    else
        Print('\n')
    end

local proc PrintOneRow(r:<[0..]->I,col:<I) iff
    if col < Len(r) then
        if r(col) < 10 then 
            Print(' ')
        end &
        Print(r(col):S,' ') & PrintOneRow(r,col+1)
    end

    
// In principle, we can place any pentomino piece in 4x2 different ways:
// each piece can be rotate 0,90,180 or 360 degrees and for each rotation
// we can flip the piece upside down (mirror image). Of course, this would 
// lead to many identical solutions, as various pentomino pieces exhibit various 
// degrees of symmetry. Instead of rotating and flipping the pentomino pieces 
// at run-time, we precalculate all possible orientation of a pentomino
// piece, at the same time avoiding identical shapes due to rotational/mirror
// symmetry.

// All the code bellow deals with calculating all possible shapes of a pentomino
// piece: rotations by 0, 90, 180, 270 degrees and all flipped (mirror) images 
// corresponding to all rotations. 
// Additionally, since our representation of a (irregular) pentomino piece is 
// a rectangle with some fields defined as "transparent" (initialized as " "), 
// for each piece orientation we also calculate the piece "origin", i.e. the first 
// "non-transparent" column.

// NOTE: in order not to generate solutions that are simple rotations or
// mirror reflection of another solution, we simply fix the "L" pentomino
// to include only two shapes: regular "L" and "L" rotated by 90 degrees.
// (Instead of "L this could be any piece that can have 4x2 different orientations) 

local proc CreatePossibleShapes(piece:<Block,aBlock:>[0..]->BlockEx) iff
    newpieces :. list BlockEx & newpieces := (piece,CalcOffset(piece)),Nil &

    // Special cas the "L" piece (see the note above)

    if piece = BlockL then 
        Rotate(piece,0,newpiece0) & 
        newpieces := newpiece0,newpieces
    else
        Flip(piece,fnewpiece) &
        if ~fnewpiece in newpieces then
            newpieces := fnewpiece,newpieces
        end & 

        Rotate(piece,0,newpiece0) & 
        if ~newpiece0 in newpieces then
            newpieces := newpiece0,newpieces
        end & 
        Flip(newpiece0.piece,fnewpiece0) &
        if ~fnewpiece0 in newpieces then
            newpieces := fnewpiece0,newpieces
        end & 
    
        Rotate(piece,1,newpiece1) & 
        if ~newpiece1 in newpieces then
            newpieces := newpiece1,newpieces
        end &
        Flip(newpiece1.piece,fnewpiece1) &
        if ~fnewpiece1 in newpieces then
            newpieces := fnewpiece1,newpieces
        end &
    
        Rotate(piece,2,newpiece2) & 
        if ~newpiece2 in newpieces then
            newpieces := newpiece2,newpieces
        end &
        Flip(newpiece2.piece,fnewpiece2) &
        if ~fnewpiece2 in newpieces then
            newpieces := fnewpiece2,newpieces
        end 
    end & aBlock = newpieces:[0..]->BlockEx 

// in each row reverse columns
local proc Flip(piece:<Block,fpiece:>BlockEx) iff
    fpiece0 :.[0..]->[0..]->I & Dupl(Len(piece),Dupl(Len(piece(0)),0),fpiece0) &
    ReverseColumns(piece,fpiece0,0) &
    fpiece = fpiece0,CalcOffset(fpiece0)           

local proc Rotate(piece:<Block,i:<I,newpiece:>BlockEx) iff
    if i = 0 then //rotate by 90 degrees, swap rows & columns
        rpiece0 :.[0..]->[0..]->I & Dupl(Len(piece(0)),Dupl(Len(piece),0),rpiece0) &
        rpiece00 :.[0..]->[0..]->I & Dupl(Len(piece(0)),Dupl(Len(piece),0),rpiece00) &
        Transpose(piece,rpiece0) &
        ReverseColumns(rpiece0,rpiece00,0) &
        newpiece = rpiece00,CalcOffset(rpiece00)
    elsif i = 1 then //180
        rpiece1 :.[0..]->[0..]->I & Dupl(Len(piece),Dupl(Len(piece(0)),0),rpiece1) &
        RtlReverse(piece,rpiece) & ReverseColumns(rpiece,rpiece1,0) & newpiece = rpiece1,CalcOffset(rpiece1)  
    else //i = 2, rotate by 270
        rpiece2 :.[0..]->[0..]->I & Dupl(Len(piece(0)),Dupl(Len(piece),0),rpiece2) &
        Transpose(piece,rpiece2) &
        RtlReverse(rpiece2,newpiece2) & newpiece = newpiece2,CalcOffset(newpiece2)
    end

local proc ReverseColumns(piece:<Block,rpiece:.Block, col:<I) iff
    if col < Len(piece) then
        RtlReverse(piece(col),rpiece(col)) &
        ReverseColumns(piece,rpiece,col+1)
    end

local proc Transpose(a :<Block, b:.Block) iff
    TransposeRows(a,b,0,Len(b))
    
local proc TransposeRows(a:<Block, b:.Block,r:<I, maxr:<I) iff
    if r < maxr then
        Len(b(r),Len(a)) & TransposeColumns(a,b,r,0,Len(a)) & 
        TransposeRows(a,b,r+1, maxr)
    end

local proc TransposeColumns(a:<Block,b:.Block,r:<I,c:<I,maxc:<I) iff
    if c < maxc then
        b(r,c) := a(c,r) & TransposeColumns(a,b,r,c+1,maxc)
    end

// Fo our program purposes, each pentomino piece is placed within a bounding rectangle.
// The "unused" portion of the rectangle is initialized as " " (space). 
// Here we simply find the first "non-transparent" element of the pentomino piece.
local proc CalcOffset(piece:<Block,offset:>I) iff
    x:.I & x := 0 & CalcOffsetX(piece(0),x,0) & offset = x

local proc CalcOffsetX(a:<[0..]->I,x:.I,i:<I) iff
    if i < Len(a) then
        if a(i) = " " then
            x := x+1 & CalcOffsetX(a,x,i+1)
        end
    end







This page was created by F1toHTML