294 lines
9.4 KiB
Python
294 lines
9.4 KiB
Python
# 0h h1 Solver. Solves grids of 0h h1 game.
|
|
# Copyright (C) 2015 Gabriel Augendre <gabriel@augendre.info>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
__author__ = 'gaugendre'
|
|
import sys
|
|
|
|
|
|
def string_from_list(line):
|
|
string = ""
|
|
for square in line:
|
|
string += square.state
|
|
return string
|
|
|
|
|
|
class Grid:
|
|
def __init__(self, size, array=None):
|
|
_squares = []
|
|
square_list = []
|
|
i = 0
|
|
while i < size:
|
|
_squares.append([])
|
|
j = 0
|
|
while j < size:
|
|
if array:
|
|
_squares[i].append(Square(self, i, j, array[i][j]))
|
|
else:
|
|
_squares[i].append(Square(self, i, j))
|
|
j += 1
|
|
square_list.extend(_squares[i])
|
|
i += 1
|
|
|
|
self._squares = _squares
|
|
self.size = size
|
|
self.square_list = square_list
|
|
|
|
def _get_squares(self):
|
|
return self._squares
|
|
|
|
squares = property(fget=_get_squares)
|
|
|
|
def square(self, horiz, vert):
|
|
return self._squares[vert][horiz]
|
|
|
|
def __repr__(self):
|
|
representation = ""
|
|
for line in self._squares:
|
|
for square in line:
|
|
to_print = square.state
|
|
if to_print == ' ':
|
|
to_print = '_'
|
|
representation += to_print + ' '
|
|
representation += "\n"
|
|
return representation
|
|
|
|
def squares_on_line(self, line_number):
|
|
return self.squares[line_number]
|
|
|
|
def squares_on_column(self, col_number):
|
|
col = []
|
|
for line in self.squares:
|
|
col.append(line[col_number])
|
|
return col
|
|
|
|
def solve_three_in_a_row(self):
|
|
for square in self.square_list:
|
|
if square.is_empty():
|
|
v_prev = square.prev_vert()
|
|
v_next = square.next_vert()
|
|
h_prev = square.prev_horiz()
|
|
h_next = square.next_horiz()
|
|
|
|
if not square.switched and v_prev and v_prev.state != ' ':
|
|
v_p_prev = v_prev.prev_vert()
|
|
if v_p_prev and v_p_prev.state == v_prev.state:
|
|
square.state = v_prev.opposite_state()
|
|
elif v_next and v_next.state == v_prev.state:
|
|
square.state = v_prev.opposite_state()
|
|
|
|
if not square.switched and v_next and v_next.state != ' ':
|
|
v_n_next = v_next.next_vert()
|
|
if v_n_next and v_n_next.state == v_next.state:
|
|
square.state = v_next.opposite_state()
|
|
|
|
if not square.switched and h_prev and h_prev.state != ' ':
|
|
h_p_prev = h_prev.prev_horiz()
|
|
if h_p_prev and h_p_prev.state == h_prev.state:
|
|
square.state = h_prev.opposite_state()
|
|
elif h_next and h_next.state == h_prev.state:
|
|
square.state = h_prev.opposite_state()
|
|
|
|
if not square.switched and h_next and h_next.state != ' ':
|
|
h_n_next = h_next.next_horiz()
|
|
if h_n_next and h_n_next.state == h_next.state:
|
|
square.state = h_next.opposite_state()
|
|
|
|
def solve(self):
|
|
for i in range(0, 2, 1):
|
|
self.solve_three_in_a_row()
|
|
self.solve_same_number()
|
|
self.solve_different_lines_or_columns()
|
|
|
|
def solve_same_number(self):
|
|
for square in self.square_list:
|
|
if square.is_empty():
|
|
same_line = square.same_line()
|
|
count_red = 0
|
|
count_blue = 0
|
|
for line_square in same_line:
|
|
if line_square.state == 'B':
|
|
count_blue += 1
|
|
elif line_square.state == 'R':
|
|
count_red += 1
|
|
|
|
if count_red == self.size / 2:
|
|
square.state = 'B'
|
|
elif count_blue == self.size / 2:
|
|
square.state = 'R'
|
|
|
|
if not square.switched:
|
|
same_column = square.same_column()
|
|
count_red = 0
|
|
count_blue = 0
|
|
for line_square in same_column:
|
|
if line_square.state == 'B':
|
|
count_blue += 1
|
|
elif line_square.state == 'R':
|
|
count_red += 1
|
|
|
|
if count_red == self.size / 2:
|
|
square.state = 'B'
|
|
elif count_blue == self.size / 2:
|
|
square.state = 'R'
|
|
|
|
def solve_different_lines_or_columns(self):
|
|
pass
|
|
# for square in self.square_list:
|
|
# line = string_from_list(square.same_line())
|
|
|
|
|
|
class Square:
|
|
"""
|
|
Represents a square in the grid.
|
|
A square can be either Red, Blue, or Nothing, depending on the text
|
|
written in it and displayed ('R', 'B' or ' ').
|
|
"""
|
|
|
|
def __init__(self, grid, vert, horiz, state=' '):
|
|
self.horiz = horiz
|
|
self.vert = vert
|
|
self.switched = False
|
|
if isinstance(grid, Grid):
|
|
self.grid = grid
|
|
else:
|
|
print("Warning : Attribute grid not instance of Grid",
|
|
file=sys.stderr)
|
|
self.grid = None
|
|
if state in (' ', 'R', 'B'):
|
|
self._state = state
|
|
else:
|
|
print("Warning : Attribute state not in ('R', 'B', ' ')",
|
|
file=sys.stderr)
|
|
self._state = ' '
|
|
|
|
def next_horiz(self):
|
|
if self.horiz == self.grid.size - 1:
|
|
return None
|
|
return self.grid.square(self.horiz + 1, self.vert)
|
|
|
|
def prev_horiz(self):
|
|
if self.horiz == 0:
|
|
return None
|
|
return self.grid.square(self.horiz - 1, self.vert)
|
|
|
|
def next_vert(self):
|
|
if self.vert == self.grid.size - 1:
|
|
return None
|
|
return self.grid.square(self.horiz, self.vert + 1)
|
|
|
|
def prev_vert(self):
|
|
if self.vert == 0:
|
|
return None
|
|
return self.grid.square(self.horiz, self.vert - 1)
|
|
|
|
def __eq__(self, other):
|
|
if other is None or not isinstance(other, Square):
|
|
return False
|
|
else:
|
|
return self.__hash__() == other.__hash__()
|
|
|
|
def __hash__(self):
|
|
return hash((self.horiz, self.vert, self.grid))
|
|
|
|
def __repr__(self):
|
|
return "({}, {}) : '{}'".format(self.horiz, self.vert, self.state)
|
|
|
|
def _get_state(self):
|
|
"""
|
|
Allow to get square state.
|
|
:return: The square state. Either ' ', 'R' or 'B'
|
|
"""
|
|
return self._state
|
|
|
|
def _set_state(self, new_state):
|
|
"""
|
|
Changes square state. Accepts only 'R', 'B', or ' '.
|
|
Other values are not accepted and the square is not modified.
|
|
"""
|
|
if new_state in ('R', 'B', ' '):
|
|
self._state = new_state
|
|
self.switched = True
|
|
else:
|
|
print("Error :", new_state, "not in ('R', 'B', ' ').")
|
|
|
|
state = property(_get_state, _set_state)
|
|
|
|
def opposite_state(self):
|
|
if self.state == 'R':
|
|
return 'B'
|
|
elif self.state == 'B':
|
|
return 'R'
|
|
else:
|
|
return ' '
|
|
|
|
def is_empty(self):
|
|
return self.state == ' '
|
|
|
|
def all_prev_horiz(self):
|
|
h_prev = self.prev_horiz()
|
|
all_prev_horiz_list = []
|
|
while h_prev:
|
|
all_prev_horiz_list.append(h_prev)
|
|
h_prev = h_prev.prev_horiz()
|
|
return all_prev_horiz_list
|
|
|
|
def all_next_horiz(self):
|
|
h_next = self.next_horiz()
|
|
all_next_horiz_list = []
|
|
while h_next:
|
|
all_next_horiz_list.append(h_next)
|
|
h_next = h_next.next_horiz()
|
|
return all_next_horiz_list
|
|
|
|
def all_prev_vert(self):
|
|
v_prev = self.prev_vert()
|
|
all_prev_vert_list = []
|
|
while v_prev:
|
|
all_prev_vert_list.append(v_prev)
|
|
v_prev = v_prev.prev_vert()
|
|
return all_prev_vert_list
|
|
|
|
def all_next_vert(self):
|
|
v_next = self.next_vert()
|
|
all_next_vert_list = []
|
|
while v_next:
|
|
all_next_vert_list.append(v_next)
|
|
v_next = v_next.next_vert()
|
|
return all_next_vert_list
|
|
|
|
def same_line(self):
|
|
"""
|
|
List of squares in the same line.
|
|
Does not include the considered square.
|
|
:return: The list of the squares in the same line.
|
|
"""
|
|
line_list = []
|
|
line_list.extend(self.all_prev_horiz())
|
|
line_list.append(self)
|
|
line_list.extend(self.all_next_horiz())
|
|
return line_list
|
|
|
|
def same_column(self):
|
|
"""
|
|
List of squares in the same column.
|
|
Does not include the considered square.
|
|
:return: The list of the squares in the same column.
|
|
"""
|
|
line_list = []
|
|
line_list.extend(self.all_prev_vert())
|
|
line_list.append(self)
|
|
line_list.extend(self.all_next_vert())
|
|
return line_list
|