Solved!
This commit is contained in:
BIN
iq_mini_4489/src/__pycache__/board.cpython-312.pyc
Normal file
BIN
iq_mini_4489/src/__pycache__/board.cpython-312.pyc
Normal file
Binary file not shown.
BIN
iq_mini_4489/src/__pycache__/pieces.cpython-312.pyc
Normal file
BIN
iq_mini_4489/src/__pycache__/pieces.cpython-312.pyc
Normal file
Binary file not shown.
77
iq_mini_4489/src/board.py
Normal file
77
iq_mini_4489/src/board.py
Normal file
@ -0,0 +1,77 @@
|
||||
import numpy as np
|
||||
from typing import Tuple, List
|
||||
import pieces
|
||||
import colorama
|
||||
|
||||
|
||||
def generate_orientations(piece: np.ndarray) -> List[np.ndarray]:
|
||||
"""
|
||||
Generate all unique orientations of a piece.
|
||||
"""
|
||||
orientations = []
|
||||
|
||||
# Rotations
|
||||
current = piece.copy()
|
||||
for _ in range(4):
|
||||
current = np.rot90(current)
|
||||
if not any(np.array_equal(current, o) for o in orientations):
|
||||
orientations.append(current.copy())
|
||||
|
||||
# Flip + rotations
|
||||
flipped = np.flip(piece, axis=0)
|
||||
for _ in range(4):
|
||||
flipped = np.rot90(flipped)
|
||||
if not any(np.array_equal(flipped, o) for o in orientations):
|
||||
orientations.append(flipped.copy())
|
||||
|
||||
return orientations
|
||||
|
||||
|
||||
class Board:
|
||||
def __init__(self, size: Tuple[int, int] = (5, 5)):
|
||||
self.rows, self.cols = size
|
||||
self.grid = np.zeros((self.rows, self.cols), dtype=int)
|
||||
|
||||
def in_bounds(self, r: int, c: int) -> bool:
|
||||
return 0 <= r < self.rows and 0 <= c < self.cols
|
||||
|
||||
def placeable(self, shape: np.ndarray, top_left: Tuple[int,int]) -> bool:
|
||||
"""
|
||||
check if valid
|
||||
"""
|
||||
(r_offset, c_offset) = top_left
|
||||
shape_rows, shape_cols = shape.shape
|
||||
|
||||
for i in range(shape_rows):
|
||||
for j in range(shape_cols):
|
||||
if shape[i, j] == 1:
|
||||
r = r_offset + i
|
||||
c = c_offset + j
|
||||
if not self.in_bounds(r, c) or self.grid[r, c] != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def place(self, shape: np.ndarray, top_left: Tuple[int,int], piece_id: int) -> None:
|
||||
"""
|
||||
place the piece
|
||||
"""
|
||||
(r_offset, c_offset) = top_left
|
||||
shape_rows, shape_cols = shape.shape
|
||||
for i in range(shape_rows):
|
||||
for j in range(shape_cols):
|
||||
if shape[i, j] == 1:
|
||||
self.grid[r_offset + i, c_offset + j] = piece_id
|
||||
|
||||
def remove(self, shape: np.ndarray, top_left: Tuple[int,int]) -> None:
|
||||
(r_offset, c_offset) = top_left
|
||||
shape_rows, shape_cols = shape.shape
|
||||
for i in range(shape_rows):
|
||||
for j in range(shape_cols):
|
||||
if shape[i, j] == 1:
|
||||
self.grid[r_offset + i, c_offset + j] = 0
|
||||
|
||||
def is_full(self) -> bool:
|
||||
return np.all(self.grid != 0)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.grid)
|
96
iq_mini_4489/src/main.py
Normal file
96
iq_mini_4489/src/main.py
Normal file
@ -0,0 +1,96 @@
|
||||
from board import Board, generate_orientations
|
||||
from typing import Tuple, List
|
||||
import pieces
|
||||
import numpy as np
|
||||
import colorama
|
||||
|
||||
def backtrack(board: Board, pieces_list: List[str], piece_index: int,
|
||||
orientations: dict, solutions: List[np.ndarray]) -> None:
|
||||
"""
|
||||
Backtracking to place each piece in the board in all possible ways.
|
||||
"""
|
||||
|
||||
# if full, check if solution
|
||||
if piece_index == len(pieces_list):
|
||||
if board.is_full():
|
||||
solutions.append(board.grid.copy())
|
||||
return
|
||||
|
||||
piece_name = pieces_list[piece_index]
|
||||
all_orientations = orientations[piece_name]
|
||||
|
||||
for orientation in all_orientations:
|
||||
rows, cols = orientation.shape
|
||||
for row in range(board.rows - rows + 1):
|
||||
for col in range(board.cols - cols + 1):
|
||||
if board.placeable(orientation, (row, col)):
|
||||
board.place(orientation, (row, col), piece_index + 1)
|
||||
backtrack(board, pieces_list, piece_index + 1, orientations, solutions)
|
||||
board.remove(orientation, (row, col))
|
||||
|
||||
|
||||
def colorful_solution(solution: np.ndarray) -> str:
|
||||
"""
|
||||
Return a colorful representation of the solution.
|
||||
"""
|
||||
rows, cols = solution.shape
|
||||
result = ""
|
||||
piece_id_to_color = {
|
||||
-1: colorama.Back.BLACK,
|
||||
1: colorama.Back.GREEN,
|
||||
2: colorama.Back.BLUE,
|
||||
3: colorama.Back.YELLOW,
|
||||
4: colorama.Back.MAGENTA,
|
||||
5: colorama.Back.CYAN,
|
||||
6: colorama.Back.RED,
|
||||
}
|
||||
for r in range(rows):
|
||||
for c in range(cols):
|
||||
piece_id = solution[r, c]
|
||||
if piece_id == 0:
|
||||
result += colorama.Back.LIGHTWHITE_EX + " "
|
||||
else:
|
||||
result += piece_id_to_color[piece_id] + " "
|
||||
|
||||
result += colorama.Back.RESET
|
||||
result += "\n"
|
||||
return result
|
||||
|
||||
def solve(xPin: Tuple[int, int], yPin: Tuple[int, int], zPin: Tuple[int, int]):
|
||||
"""
|
||||
solve the puzzle, avoiding the pins at the specified positions.
|
||||
|
||||
Args:
|
||||
xPin (Tuple[int, int]): row, col of the 'x' pin
|
||||
yPin (Tuple[int, int]): row, col of the 'y' pin
|
||||
zPin (Tuple[int, int]): row, col of the 'z' pin
|
||||
"""
|
||||
|
||||
board = Board((5, 5))
|
||||
|
||||
pinned_positions = [xPin, yPin, zPin]
|
||||
for (pr, pc) in pinned_positions:
|
||||
board.grid[pr, pc] = -1
|
||||
|
||||
orientations_map = {}
|
||||
for piece_name, piece_matrix in pieces.all_pieces.items():
|
||||
orientations_map[piece_name] = generate_orientations(piece_matrix)
|
||||
piece_names = list(pieces.all_pieces.keys())
|
||||
solutions = []
|
||||
backtrack(board, piece_names, 0, orientations_map, solutions)
|
||||
if solutions:
|
||||
print(f"Found {len(solutions)} solution(s).")
|
||||
for idx, sol in enumerate(solutions, start=1):
|
||||
print(f"Solution #{idx}:")
|
||||
print(colorful_solution(sol))
|
||||
print("------------------")
|
||||
print(f"Total: {len(solutions)} solution{"s" if len(solutions) else ""}.")
|
||||
return
|
||||
|
||||
print("No solution found.")
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
solve((0, 2), (2, 0), (3, 3))
|
99
iq_mini_4489/src/pieces.py
Normal file
99
iq_mini_4489/src/pieces.py
Normal file
@ -0,0 +1,99 @@
|
||||
import numpy as np
|
||||
|
||||
three_in_a_row = np.array([
|
||||
[1, 1, 1]
|
||||
])
|
||||
|
||||
l_shape = np.array([
|
||||
[1, 0],
|
||||
[1, 0],
|
||||
[1, 1]
|
||||
])
|
||||
|
||||
t_shape = np.array([
|
||||
[1, 1, 1],
|
||||
[0, 1, 0],
|
||||
])
|
||||
|
||||
square = np.array([
|
||||
[1, 1],
|
||||
[1, 1]
|
||||
])
|
||||
|
||||
smaller_l_shape = np.array([
|
||||
[1, 0],
|
||||
[1, 1]
|
||||
])
|
||||
|
||||
tetris_z = np.array([
|
||||
[1, 1, 0],
|
||||
[0, 1, 1]
|
||||
])
|
||||
|
||||
all_pieces = {
|
||||
"Consecutive 3s": three_in_a_row,
|
||||
"L": l_shape,
|
||||
"T": t_shape,
|
||||
"Square": square,
|
||||
"Smaller L": smaller_l_shape,
|
||||
"Z": tetris_z
|
||||
}
|
||||
|
||||
def sanity_check():
|
||||
"""Check all possible ways to position the pieces(including 3d rotation)"""
|
||||
for piece_name, piece in all_pieces.items():
|
||||
all_placements = []
|
||||
|
||||
# Check original rotations
|
||||
for _ in range(4):
|
||||
piece = np.rot90(piece)
|
||||
if piece.tolist() not in all_placements:
|
||||
all_placements.append(piece.tolist())
|
||||
|
||||
# Check flipped rotations
|
||||
flipped_piece = np.flip(piece, axis=0)
|
||||
for _ in range(4):
|
||||
flipped_piece = np.rot90(flipped_piece)
|
||||
if flipped_piece.tolist() not in all_placements:
|
||||
all_placements.append(flipped_piece.tolist())
|
||||
|
||||
print(f"Piece: {piece_name}")
|
||||
print(f"Distinct Placements (including flips and rotations): {len(all_placements)}")
|
||||
print("--------------------")
|
||||
|
||||
def count_combinations_on_matrix(matrix_size):
|
||||
"""Count all possible combinations of pieces on a matrix of given size."""
|
||||
matrix = np.zeros(matrix_size, dtype=int)
|
||||
total_combinations = 0
|
||||
|
||||
for piece_name, piece in all_pieces.items():
|
||||
piece_rows, piece_cols = piece.shape
|
||||
placements = []
|
||||
|
||||
# Generate all distinct placements of the piece
|
||||
for _ in range(4):
|
||||
piece = np.rot90(piece)
|
||||
if piece.tolist() not in placements:
|
||||
placements.append(piece.tolist())
|
||||
|
||||
flipped_piece = np.flip(piece, axis=0)
|
||||
for _ in range(4):
|
||||
flipped_piece = np.rot90(flipped_piece)
|
||||
if flipped_piece.tolist() not in placements:
|
||||
placements.append(flipped_piece.tolist())
|
||||
|
||||
# Count valid placements on the matrix
|
||||
for placement in placements:
|
||||
rows, cols = len(placement), len(placement[0])
|
||||
for i in range(matrix_size[0] - rows + 1):
|
||||
for j in range(matrix_size[1] - cols + 1):
|
||||
sub_matrix = matrix[i:i + rows, j:j + cols]
|
||||
if not sub_matrix.any(): # Check if the space is empty
|
||||
total_combinations += 1
|
||||
|
||||
print(f"Total combinations on {matrix_size[0]}x{matrix_size[1]} matrix: {total_combinations}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sanity_check()
|
||||
count_combinations_on_matrix((5, 5))
|
Reference in New Issue
Block a user