-
Notifications
You must be signed in to change notification settings - Fork 0
/
nonogram.py
134 lines (118 loc) · 5.43 KB
/
nonogram.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# -------------------------------------------------------------------------------------------------------------------- #
# IMPORTS #
# -------------------------------------------------------------------------------------------------------------------- #
import os, re, copy
from itertools import zip_longest
from typing import Union
import numpy as np
ALPHABET_SIZE = 3
EMPTY, CROSS, BOX = range(ALPHABET_SIZE)
# -------------------------------------------------------------------------------------------------------------------- #
# BOARD CLASS #
# -------------------------------------------------------------------------------------------------------------------- #
class Nonogram:
"""
Attributes:
board (np.ndarray): The nonogram puzzle (0 == empty, 1 == cross, 2 == box).
row_numbers
column_numbers
"""
def __read_matrix(self, lines: list[str], column_major: bool) -> list[list[int]]:
result = []
for line in lines:
line = line.strip()
separated_values = re.split(r"[\|,;]", line) #list(filter(lambda x: x.strip() != "", line.split(",")))
if column_major:
#if there are more items than currently detected, add some more lists
if len(separated_values) > len(result):
result.extend([] for _ in range(len(separated_values) - len(result)))
#add the values to the lists
for value, column in zip(separated_values, result):
if value.strip() and int(value) > 0:
column.append(int(value))
else:
result.append([int(value) for value in separated_values if value.strip() and int(value) > 0])
return result
def __init__(self, file: Union[str, bytes, os.PathLike, None], dimensions: Union[tuple[int, int], None] = None):
self.board: np.ndarray[np.ndarray[int]]
self.__correctly_read = False
if file is None:
self.board = np.zeros(dimensions, np.ubyte)
else:
try:
with open(file, "r") as f:
#column_dimensions, self.column_numbers = self.__read_number_block_from_file(f)
#row_dimensions, self.row_numbers = self.__read_number_block_from_file(f)
lines = f.readlines()
#find the separator line
index = next(i for i, line in enumerate(lines) if re.search(r"[^\d\s\t\|,;]", line))
if index == 0:
raise IndexError
self.column_numbers = self.__read_matrix(lines[:index], True)
self.row_numbers = self.__read_matrix(lines[index + 1:], False)
except OSError:
print("File not found. Please enter a valid filename.")
return
except StopIteration:
print("Separator could not be found. Please check the file for validity.")
return
except IndexError:
print("At least one table is empty. Please add it to the file.")
return
except ValueError:
print("Unknown characters found. Please check the file for validity.")
return
self.board = np.zeros((len(self.row_numbers), len(self.column_numbers)), np.ubyte)
#self.__correctly_read = self.board_valitity()
def __repr__(self) -> str:
return "Nonogram Puzzle (%dx%d)".format(*self.board.shape)
def __str__(self) -> str:
result = ""
left_spacing = max(len(row) for row in self.row_numbers) * 3
for row in zip_longest(*self.column_numbers, fillvalue = " " * 3):
result += " " * (left_spacing + 1) + "".join(str(n).center(3) for n in row) + "\n"
result += " " * left_spacing + "┌" + "───"*self.board.shape[1] + "┐\n" #top bar
#https://stackabuse.com/how-to-print-colored-text-in-python/
for row_numbers_row, board_row in zip(self.row_numbers, self.board):
if row_numbers_row == []:
row_numbers_row = [0]
result += "".join(str(n).center(3) for n in row_numbers_row).rjust(left_spacing) + "│" #left bar
for cell in board_row:
if cell == BOX:
result += "\x1b[0;37;46m " #cyan square
elif cell == CROSS:
result += "\x1b[0;37;40m × " #X
else:
result += "\x1b[0;37;40m " #black square
result += "\x1b[0;37;40m│\n" #color reset, right bar and newline
result += " " * left_spacing + "└" + "───"*self.board.shape[1] + "┘\n" #bottom bar
return result
def __eq__(self, __o: object) -> bool:
return isinstance(__o, Nonogram) and (self.board == __o.board).all() and self.row_numbers == __o.row_numbers and self.column_numbers == __o.column_numbers
def copy(self):
temp = Nonogram(None, (self.board.shape))
temp.board = copy.deepcopy(self.board)
#temp.row_numbers = copy.deepcopy(self.row_numbers)
#temp.row_numbers = self.row_numbers.copy()
temp.row_numbers = self.row_numbers
#temp.column_numbers = copy.deepcopy(self.column_numbers)
#temp.column_numbers = self.column_numbers.copy()
temp.column_numbers = self.column_numbers
temp.__correctly_read = self.__correctly_read
return temp
def assert_correctness(self):
"""Prevents the program from running if the input file was not read correctly."""
if not self.__correctly_read:
print("The data was not correctly read. Please check for file validity and try again.")
exit(1)
def board_valitity(self) -> bool:
"""Checks if the board is valid."""
if (len(self.row_numbers), len(self.column_numbers)) != self.board.shape:
print("Board size error")
return False
if not np.all(self.board < ALPHABET_SIZE):
print("Board filled with invalid values")
return False
return True
def is_solved(self) -> bool:
return np.all(self.board != EMPTY)