Solved!
This commit is contained in:
parent
615290521b
commit
a8a362c277
105
iq_mini_4489/README.md
Normal file
105
iq_mini_4489/README.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src=https://hopdowody.pl/wp-content/uploads/2023/03/SMART-GAMES-IQ-MINI-Kod-producenta-5414301524489.jpg />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
|
||||||
|
### Game
|
||||||
|
|
||||||
|
<!-- | | c1 | c2 | c3 | c4 | c5 |
|
||||||
|
| :----: | :-: | :-: | :-: | :-: | --- |
|
||||||
|
| **r1** | x | x | o | y | y |
|
||||||
|
| **r2** | x | x | x | x | y |
|
||||||
|
| **r3** | o | x | z | z | y |
|
||||||
|
| **r4** | z | x | z | o | y |
|
||||||
|
| **r5** | z | z | z | y | y | -->
|
||||||
|
<!-- html table -->
|
||||||
|
<style>
|
||||||
|
.z {
|
||||||
|
background-color: purple;
|
||||||
|
}
|
||||||
|
.x {
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
.y {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<table >
|
||||||
|
<tr>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td>o</td>
|
||||||
|
<td class="y">y</td>
|
||||||
|
<td class="y">y</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td class="y">y</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>o</td>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td class="z">z</td>
|
||||||
|
<td class="z">z</td>
|
||||||
|
<td class="y">y</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="z">z</td>
|
||||||
|
<td class="x">x</td>
|
||||||
|
<td class="z">z</td>
|
||||||
|
<td>o</td>
|
||||||
|
<td class="y">y</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="z">z</td>
|
||||||
|
<td class="z">z</td>
|
||||||
|
<td class="z">z</td>
|
||||||
|
<td class="y">y</td>
|
||||||
|
<td class="y">y</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
It's a 5x5 grid of holes, connected as shown in the diagram. Each colored path contains one peg which can be moved around to any other square with the same color. The point of the game is to fit the 6 uniquely shaped pieces in the puzzle such that all of them fit (and subsequently fill up the entire matrix).
|
||||||
|
|
||||||
|
### Pieces
|
||||||
|
Very similar to tetris:
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
The pieces are as follows:
|
||||||
|
|
||||||
|
#### 3-in-a-row
|
||||||
|
Effectively 2 different ways to place it. It's just 3 consecutive squares.
|
||||||
|
|
||||||
|
#### L shape
|
||||||
|
There are 4 different ways to place it. It's a 3-in-a-row with an extra square attached to one of the ends.
|
||||||
|
You can also use the third dimension and flip it, leading to 8 different ways to place it.
|
||||||
|
|
||||||
|
#### T shape
|
||||||
|
There are 4 different ways to place it. It's a 3-in-a-row with an extra square attached to the middle.
|
||||||
|
|
||||||
|
#### Square
|
||||||
|
1 way to place it. It's a 2x2 square.
|
||||||
|
|
||||||
|
#### Smaller L shape
|
||||||
|
There are 4 different ways to place it. It's a 2-in-a-row with an extra square attached to one of the ends.
|
||||||
|
|
||||||
|
#### 2 lines offset by 1
|
||||||
|
4 different ways.
|
||||||
|
|
||||||
|
## Optimization
|
||||||
|
- [ ] Bitmasking
|
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))
|
Loading…
x
Reference in New Issue
Block a user