Puzzle

There is a particular type of puzzle where the board is a four by four grid consisting of squares that are either land ('L'), water ('W'), or occupied ('O'), and puzzle pieces consist of squares that are either missing (None), are empty (' '), contain a fish ('F'), or contain a bear ('B').

The six puzzle pieces are as follows:

pieces = [
    [
        [ 'F', 'F' ]
    ],
    [
        [ 'F', 'B' ]
    ],
    [
        [ None, ' ' ],
        [ ' ', 'B' ]
    ],
    [
        [ 'F', ' ' ],
        [ 'B', None ]
    ],
    [
        [ 'F', None ],
        [ ' ', 'B' ]
    ],
    [
        [ ' ', 'B' ],
        [ 'F', None ]
    ]
]

Notice that two pieces consist of two squares each, and the remaining four pieces consist of three squares each. These six pieces together consist of 16 squares, exactly enough to fully cover the board.

Here is an example board:

board1 = [
    [ 'L', 'L', 'W', 'L' ],
    [ 'O', 'W', 'L', 'O' ],
    [ 'W', 'L', 'W', 'W' ],
    [ 'W', 'W', 'W', 'L' ]
]

The rules for filling a board with pieces are as follows:

There is one way to fill the example board:

We encode this solution into a Python value as follows:

solution = [
   [ 0, 3, 0 ], # Position of first piece: Rotation index 0, 3 rows from the top, 0 columns from the left
   [ 0, 3, 2 ], # Position of second piece: Rotation index 0, 3 rows from the top, 2 columns from the left
   [ 3, 0, 2 ], # Position of third piece: Rotation index 3 (270 degrees clockwise), 0 rows from the top, 2 columns from the left
   [ 3, 1, 0 ], # Position of fourth piece: Rotation index 3 (270 degrees clockwise), 1 row from the top, 0 columns from the left
   [ 2, 0, 0 ], # Position of fifth piece: Rotation index 2 (180 degrees clockwise), 0 rows from the top, 0 columns from the left
   [ 3, 1, 2 ]  # Position of sixth piece: Rotation index 3 (270 degrees clockwise), 1 row from the top, 2 columns from the left
]

Examples:

>>> piece_rotation([['F', 'B']])
[['F'], ['B']]
>>> piece_rotation([['F'], ['B']])
[['B', 'F']]
>>> piece_rotation([['F', ' '], ['B', None]])
[['B', 'F'], [None, ' ']]
>>> piece_rotations([['F', 'B']])
[[['F', 'B']], [['F'], ['B']], [['B', 'F']], [['B'], ['F']]]
>>> piece_fits(board1, [['F', 'B']], [[True, True, True, True], [True, False, False, True], [False, True, False, True], [True, True, False, False]], 3, 2)
True
>>> piece_fits(board1, [['B', 'F']], [[True, True, True, True], [True, False, False, True], [False, True, False, True], [True, True, False, False]], 3, 2)
False
>>> piece_fits(board1, [['F', 'B']], [[True, True, True, True], [True, False, False, True], [False, True, False, True], [True, True, False, True]], 3, 2)
False
>>> piece_fits(board1, [['B', 'F']], [[False, False, False, False], [False, False, False, False], [False, False, False, False], [False, False, False, False]], 1, 0)
False
>>> piece_fits(board1, [[' ', None], ['F', 'B']], [[True, True, True, True], [False, True, True, True], [False, False, True, True], [True, True, True, True]], 1, 0)
True
>>> piece_fits(board1, [[' ', None], ['F', 'B']], [[False, False, False, False], [True, False, False, False], [False, False, False, False], [False, False, False, False]], 1, 0)
False
>>> piece_fits(board1, [['B', 'F'], [None, ' ']], [[False, False, False, False], [False, False, False, False], [False, False, False, False], [False, False, False, False]], 1, 0)
False
>>> matrix = [[True, True, True, True], [False, False, False, True], [False, False, True, True], [True, True, False, False]]
>>> fill_matrix(matrix, [[None, ' '], [' ', 'B']], 1, 0)
>>> matrix
[[True, True, True, True], [False, True, False, True], [True, True, True, True], [True, True, False, False]]
>>> solutions_for_piece(board1, [[False, False, False, False], [False, False, False, False], [False, False, False, False], [False, False, False, False]], [['F', 'F']])
[[0, 2, 2], [0, 3, 0], [0, 3, 1], [1, 2, 0], [1, 2, 2], [2, 2, 2], [2, 3, 0], [2, 3, 1], [3, 2, 0], [3, 2, 2]]
>>> solutions_for_piece(board1, [[False, False, False, False], [False, False, False, False], [False, False, True, False], [True, False, False, False]], [['F', 'F']])
[[0, 3, 1], [2, 3, 1]]
>>> solutions_for_piece(board1, [[True, True, False, False], [True, True, False, False], [True, True, False, False], [True, True, True, True]], [['F', ' '], [None, 'B']])
[[2, 1, 2]]
>>> solutions(board1)
[[[0, 3, 0], [0, 3, 2], [3, 0, 2], [3, 1, 0], [2, 0, 0], [3, 1, 2]], [[2, 3, 0], [0, 3, 2], [3, 0, 2], [3, 1, 0], [2, 0, 0], [3, 1, 2]]]
>>> board2 = [
    ['L', 'O', 'L', 'W'],
    ['L', 'L', 'L', 'L'],
    ['W', 'L', 'W', 'L'],
    ['W', 'W', 'O', 'W']
]
>>> solutions(board2)
[[[0, 3, 0], [2, 0, 2], [2, 0, 0], [2, 2, 2], [3, 1, 0], [0, 1, 2]], [[2, 3, 0], [2, 0, 2], [2, 0, 0], [2, 2, 2], [3, 1, 0], [0, 1, 2]]]