This commit is contained in:
Boyan 2024-12-24 23:04:34 +02:00
parent 615290521b
commit a8a362c277
8 changed files with 377 additions and 0 deletions

105
iq_mini_4489/README.md Normal file
View 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:
![Tetris](https://static.vecteezy.com/system/resources/previews/009/102/301/original/set-of-colorful-blocks-for-tetris-game-illustration-vector.jpg)
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

Binary file not shown.

Binary file not shown.

77
iq_mini_4489/src/board.py Normal file
View 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
View 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))

View 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))