168 lines
5.6 KiB
Python
168 lines
5.6 KiB
Python
# Given the position of the pegs, we solve the puzzle
|
|
|
|
from board import Board, generate_orientations
|
|
from typing import Tuple, List
|
|
import pieces
|
|
import sys
|
|
import numpy as np
|
|
import colorama
|
|
import hashlib
|
|
|
|
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 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)
|
|
|
|
return solutions
|
|
|
|
|
|
def colorful_solution(xPin, yPin, zPin) -> str:
|
|
"""
|
|
Return a colorful representation of the solution.
|
|
"""
|
|
|
|
if not (solutions := solve(xPin, yPin, zPin)):
|
|
return print("No solution found.")
|
|
|
|
|
|
for i, solution in enumerate(solutions):
|
|
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"
|
|
print(f"Solution {i+1}")
|
|
print(result)
|
|
print("--------------------")
|
|
print(f"Total solutions: {len(solutions)}")
|
|
|
|
def check_rotations(Ax, Ay, Bx, By, Cx, Cy, checked):
|
|
"""
|
|
Check if any rotation of these three pins was already in `checked`.
|
|
If yes, return True; if not, add them and return False.
|
|
We assume a 5x5 board with valid row/col in [0..4].
|
|
"""
|
|
|
|
def rotate_90(r, c):
|
|
# Rotate (r, c) by 90 degrees on a 5x5 board => (c, 4 - r)
|
|
return (c, 4 - r)
|
|
|
|
# Start with the 3 pin positions
|
|
base_pins = [(Ax, Ay), (Bx, By), (Cx, Cy)]
|
|
# Sort them so (pin1, pin2, pin3) is in a canonical order
|
|
|
|
# Generate all 4 rotations
|
|
rotations = []
|
|
current = base_pins
|
|
for _ in range(4):
|
|
# Rotate each pin in `current` by 90 degrees
|
|
current = [rotate_90(r, c) for (r, c) in current]
|
|
# Sort again so we have a canonical ordering
|
|
rotations.append(tuple(current))
|
|
|
|
# Check if any rotation is already in `checked`
|
|
for pins_tuple in rotations:
|
|
if pins_tuple in checked:
|
|
return True
|
|
|
|
# If none were found, add *all* rotations to `checked`
|
|
# so we never re-check or re-add them in the future.
|
|
for pins_tuple in rotations:
|
|
checked.add(pins_tuple)
|
|
|
|
return False
|
|
|
|
|
|
def try_all_pegs_naive():
|
|
from tqdm import tqdm
|
|
all_solutions = []
|
|
# Solution entry {x: (row, col), y: (row, col), z: (row, col), solution_count: int}
|
|
# 5^4 = 625 iterations
|
|
# 3^6 = 729 iterations
|
|
# 5^6 = 15625 iterations
|
|
checked = set()
|
|
for xPinA in tqdm(range(5)):
|
|
for yPinA in (range(5)):
|
|
for xPinB in range(5):
|
|
for yPinB in range(5):
|
|
for xPinC in (range(5)):
|
|
for yPinC in range(5):
|
|
if (xPinA, yPinA) == (xPinB, yPinB) or (xPinA, yPinA) == (xPinC, yPinC) or (xPinB, yPinB) == (xPinC, yPinC):
|
|
continue
|
|
|
|
if check_rotations(xPinA, yPinA, xPinB, yPinB, xPinC, yPinC, checked):
|
|
continue
|
|
|
|
solutions = solve((xPinA, yPinA), (xPinB, yPinB), (xPinC, yPinC))
|
|
import sys
|
|
if solutions:
|
|
print({
|
|
'x': (xPinA, yPinA),
|
|
'y': (xPinB, yPinB),
|
|
'z': (xPinC, yPinC),
|
|
'solution_count': len(solutions)
|
|
}, file=sys.stdout, flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
colorful_solution((0, 0), (4, 4), (4, 0))
|
|
# try_all_pegs_naive() |