From a8a362c2779524fc08483b5733f2b117f6042319 Mon Sep 17 00:00:00 2001 From: Boyan Date: Tue, 24 Dec 2024 23:04:34 +0200 Subject: [PATCH] Solved! --- iq_mini_4489/README.md | 105 ++++++++++++++++++ .../src/__pycache__/board.cpython-312.pyc | Bin 0 -> 4868 bytes .../src/__pycache__/pieces.cpython-312.pyc | Bin 0 -> 3924 bytes iq_mini_4489/src/board.py | 77 +++++++++++++ iq_mini_4489/src/main.py | 96 ++++++++++++++++ iq_mini_4489/src/pieces.py | 99 +++++++++++++++++ README.md => othello/README.md | 0 {src => othello/src}/game.py | 0 8 files changed, 377 insertions(+) create mode 100644 iq_mini_4489/README.md create mode 100644 iq_mini_4489/src/__pycache__/board.cpython-312.pyc create mode 100644 iq_mini_4489/src/__pycache__/pieces.cpython-312.pyc create mode 100644 iq_mini_4489/src/board.py create mode 100644 iq_mini_4489/src/main.py create mode 100644 iq_mini_4489/src/pieces.py rename README.md => othello/README.md (100%) rename {src => othello/src}/game.py (100%) diff --git a/iq_mini_4489/README.md b/iq_mini_4489/README.md new file mode 100644 index 0000000..004ff30 --- /dev/null +++ b/iq_mini_4489/README.md @@ -0,0 +1,105 @@ +

+ +

+ +## Description + + +### Game + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +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 \ No newline at end of file diff --git a/iq_mini_4489/src/__pycache__/board.cpython-312.pyc b/iq_mini_4489/src/__pycache__/board.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00c46eff64cfc47ff93ea34eaa39671aa2a56cf3 GIT binary patch literal 4868 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 literal 0 HcmV?d00001 diff --git a/iq_mini_4489/src/board.py b/iq_mini_4489/src/board.py new file mode 100644 index 0000000..1af2617 --- /dev/null +++ b/iq_mini_4489/src/board.py @@ -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) diff --git a/iq_mini_4489/src/main.py b/iq_mini_4489/src/main.py new file mode 100644 index 0000000..dff506f --- /dev/null +++ b/iq_mini_4489/src/main.py @@ -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)) \ No newline at end of file diff --git a/iq_mini_4489/src/pieces.py b/iq_mini_4489/src/pieces.py new file mode 100644 index 0000000..cfa5d48 --- /dev/null +++ b/iq_mini_4489/src/pieces.py @@ -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)) \ No newline at end of file diff --git a/README.md b/othello/README.md similarity index 100% rename from README.md rename to othello/README.md diff --git a/src/game.py b/othello/src/game.py similarity index 100% rename from src/game.py rename to othello/src/game.py
xxoyy
xxxxy
oxzzy
zxzoy
zzzyy