182 lines
5.4 KiB
Python
Executable File
182 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import sys
|
|
from time import sleep
|
|
|
|
# PADDING = int(os.popen('tput cols').read()) // 3 # '// 2 - 6' for exact middle for n=4
|
|
PADDING = 5
|
|
DEBUG = False
|
|
ITER = -1
|
|
|
|
def get_board_dim(board: list) -> int:
|
|
return int(len(board) ** 0.5)
|
|
|
|
def get_row(board: list, pos: int) -> list:
|
|
board_dim = get_board_dim(board)
|
|
row_start = pos // board_dim * board_dim
|
|
row = board[
|
|
row_start
|
|
:
|
|
row_start + board_dim
|
|
:
|
|
1
|
|
]
|
|
return row
|
|
|
|
def get_col(board: list, pos: int) -> list:
|
|
board_dim = get_board_dim(board)
|
|
column_start = pos % board_dim
|
|
column = board[
|
|
column_start
|
|
:
|
|
board_dim**2 - (board_dim - column_start) + 1
|
|
:
|
|
board_dim
|
|
]
|
|
return column
|
|
|
|
def get_candidates(board: list, next_candidate_index: int) -> int:
|
|
all_candidates = set(range(1 + get_board_dim(board)))
|
|
row = get_row(board, next_candidate_index)
|
|
column = get_col(board, next_candidate_index)
|
|
|
|
for candidate in all_candidates.copy():
|
|
if candidate in row:
|
|
all_candidates.discard(candidate)
|
|
continue
|
|
if candidate in column:
|
|
all_candidates.discard(candidate)
|
|
continue
|
|
return all_candidates
|
|
|
|
def get_constraints(board: list, border: list, pos: int) -> list:
|
|
board_dim = get_board_dim(board)
|
|
col_idx = pos // board_dim
|
|
row_idx = pos % board_dim
|
|
colup = border[row_idx]
|
|
coldown = border[row_idx + board_dim]
|
|
rowleft = border[col_idx + board_dim * 2]
|
|
rowright = border[col_idx + board_dim * 3]
|
|
constraints = [colup, coldown, rowleft, rowright]
|
|
return constraints
|
|
|
|
def check_constraint(slice: list, constraint: int) -> bool:
|
|
global ITER
|
|
|
|
constraint = int(constraint)
|
|
peak = 0
|
|
visible_towers = 0
|
|
|
|
if slice[0] == 0:
|
|
return True
|
|
|
|
board_dim = len(slice)
|
|
# print()
|
|
for i, tower in enumerate(slice):
|
|
if tower > peak:
|
|
peak = tower
|
|
visible_towers += 1
|
|
# print(f'{i=} {tower=} {peak=} {visible_towers=} {constraint=} {ITER=}')
|
|
if visible_towers > constraint:
|
|
return False
|
|
if constraint + tower > board_dim + 1 + i:
|
|
return False
|
|
if 0 not in slice:
|
|
return visible_towers == constraint
|
|
else:
|
|
return True
|
|
|
|
def is_valid_state(board: list, border: list, next_candidate_index: int) -> bool:
|
|
global ITER
|
|
|
|
row = get_row(board, next_candidate_index)
|
|
column = get_col(board, next_candidate_index)
|
|
constraints = get_constraints(board, border, next_candidate_index)
|
|
ITER += 1
|
|
if ITER % 100000 == 0:
|
|
os.system('clear')
|
|
print_board(board, border)
|
|
print(f'{ITER=}')
|
|
satisfies_constraints = all([
|
|
check_constraint(column, constraints[0]),
|
|
check_constraint(row, constraints[2]),
|
|
check_constraint(column[::-1], constraints[1]),
|
|
check_constraint(row[::-1], constraints[3]),
|
|
])
|
|
return satisfies_constraints
|
|
|
|
def print_board(board: list, border: list) -> None:
|
|
padding = ' ' * PADDING
|
|
board_dim = get_board_dim(board)
|
|
print(end=padding + ' ')
|
|
print(' '.join(border[:board_dim]))
|
|
for i in range(board_dim):
|
|
print(end=padding + border[i + board_dim * 2] + '|')
|
|
for j in range(board_dim):
|
|
print(board[j + i * board_dim], end=' ')
|
|
print(end='\b|')
|
|
print(end=border[i + board_dim * 3])
|
|
print(end='\b\n')
|
|
print(end=padding + ' ')
|
|
print(' '.join(border[board_dim:board_dim * 2]))
|
|
print()
|
|
|
|
def backtrack_skyscrapers(board: list, sols: list, border: list) -> None:
|
|
if board.count(0) == 0:
|
|
sols.append(board)
|
|
print_board(sols[0], border)
|
|
sys.exit(0)
|
|
next_candidate_index = board.index(0)
|
|
candidates = get_candidates(board, next_candidate_index)
|
|
for candidate in candidates:
|
|
board[next_candidate_index] = candidate
|
|
if DEBUG:
|
|
# sleep(2)
|
|
input()
|
|
os.system('clear')
|
|
print_board(board, border)
|
|
if is_valid_state(board, border, next_candidate_index):
|
|
backtrack_skyscrapers(board, sols, border)
|
|
board[next_candidate_index] = 0
|
|
|
|
def main(*args, **kwargs) -> None:
|
|
if kwargs:
|
|
print('Error (kwargs)')
|
|
sys.exit(1)
|
|
if len(args) != 1:
|
|
print('Error (args)')
|
|
sys.exit(2)
|
|
|
|
try:
|
|
border = args[0].split()
|
|
except:
|
|
print('Error (could not split)')
|
|
sys.exit(3)
|
|
board_dim = len(border) / 4
|
|
if board_dim != int(board_dim):
|
|
print('Board is not square')
|
|
sys.exit(4)
|
|
else:
|
|
board_dim = int(board_dim)
|
|
|
|
board = [
|
|
0 for _ in range(board_dim ** 2)
|
|
]
|
|
solutions = []
|
|
backtrack_skyscrapers(board, solutions, border)
|
|
# print(solutions)
|
|
|
|
if __name__ == '__main__':
|
|
# main('1 1 1 1') # 1 x 1
|
|
# main('2 1 1 2 2 1 1 2') # 2 x 2
|
|
# main('3 2 1 1 2 2 3 2 1 1 2 2') # 3 x 3
|
|
# main('4 3 2 1 1 2 2 2 4 3 2 1 1 2 2 2') # original problem
|
|
# main('') # original problem
|
|
# main('3 2 2 1 1 2 1 1 4 2 1 2 1 2 2 2') # henri's problem
|
|
# main('2 1 2 3 3 2 3 1 3 2 2 1 3 3 2 3 4 2 1 2') # 5 x 5
|
|
# main('1 2 2 4 3 5 4 4 2 2 2 1 1 2 3 4 2 4 5 3 3 2 2 1') # 6 x 6
|
|
# main('6 3 1 3 3 3 2 1 2 3 3 3 3 3 3 7 3 4 3 2 1 2 1 2 2 3 3 4') # 7 x 7
|
|
# main('7 4 2 3 3 2 1 1 2 2 2 3 4 6 6 5 4 2 3 2 1 1 2 2 4 2 4 4') # 7 x 7
|
|
main('4 3 4 1 5 4 3 2 2 4 2 4 1 3 5 4 3 3 5 2 3 1 3 2 2 1 2 3 2 4 3 3') # 8 x 8
|