Prechádzať zdrojové kódy

add second implementation in python

Eren Yilmaz 6 rokov pred
rodič
commit
873e44ab7d
3 zmenil súbory, kde vykonal 219 pridanie a 0 odobranie
  1. 1 0
      python/.gitignore
  2. 151 0
      python/chess_state.py
  3. 67 0
      python/solve.py

+ 1 - 0
python/.gitignore

@@ -0,0 +1 @@
+/img/proof*

+ 151 - 0
python/chess_state.py

@@ -0,0 +1,151 @@
+def empty_board(board_size):
+    return {(x, y): 0 for x in range(1, board_size + 1) for y in range(1, board_size + 1)}
+
+
+def empty_history():
+    return []
+
+
+class IllegalMoveError(ValueError):
+    pass
+
+
+class ChessState:
+    def __init__(self, board=None, history=empty_history(), predecessor=None, board_size=8):
+        self.__board_size = board_size
+        self.__history = history
+        if board is not None:
+            self.__board = board
+        else:
+            self.__board = empty_board(board_size)
+        self.__predecessor = predecessor
+        self._bool_board_cache = None
+        self._bool_board_mirrored_cache = None
+
+    def put(self, x, y, direction):
+        positions = self.move_positions(x, y, direction)
+
+        if not self.possible(x, y, direction):
+            raise IllegalMoveError
+
+        board = self.__board.copy()
+        for pos in positions:
+            board[pos] = len(self.__history) + 1
+
+        return ChessState(board=board,
+                          history=self.__history + [positions],
+                          board_size=self.__board_size,
+                          predecessor=self)
+
+    def undo(self):
+        return self.__predecessor
+
+    def move_positions(self, x, y, direction):
+        if direction not in ['down', 'right']:
+            raise ValueError
+        if direction == 'down':
+            mps = [(x, y), (x, y + 1), (x, y + 2)]
+        else:  # right
+            mps = [(x, y), (x + 1, y), (x + 2, y)]
+        for pos in mps:
+            if pos not in self.__board:
+                raise IndexError
+        return mps
+
+    def possible(self, x, y, direction):
+        try:
+            for pos in self.move_positions(x, y, direction):
+                if self.__board[pos]:
+                    return False
+        except IndexError:
+            return False
+
+        return True
+
+    def board(self):
+        return self.__board.copy()
+
+    def history(self):
+        return self.__history.copy()
+
+    def predecessor(self):
+        return self.__predecessor.copy()
+
+    def board_size(self):
+        return self.__board_size
+
+    def board_label(self):
+        s = ''
+        for x in range(1, self.__board_size + 1):
+            s += '{'
+            for y in range(1, self.__board_size + 1):
+                if self.__board[(x, y)]:
+                    s += str(self.__board[(x, y)])
+                if y < self.__board_size:
+                    s += '|'
+            s += '}'
+            if x < self.__board_size:
+                s += '|'
+        return s
+
+    def bool_board(self):
+        if self._bool_board_cache is None:
+            self._bool_board_cache = {x: self.__board[x] != 0 for x in self.__board}
+
+        return self._bool_board_cache
+
+    def bool_board_mirrored(self):
+        if self._bool_board_mirrored_cache is None:
+            self._bool_board_mirrored_cache = [
+                {self.bool_board()[x, self.__board_size + 1 - y] for (x, y) in self.bool_board()},
+                {self.bool_board()[self.__board_size + 1 - x, y] for (x, y) in self.bool_board()},
+                {self.bool_board()[self.__board_size + 1 - x, self.__board_size + 1 - y] for (x, y) in self.bool_board()},
+            ]
+        return self._bool_board_mirrored_cache
+
+    def symmetric_to(self, other):
+        if self.bool_board() == other.bool_board():
+            return True
+        # # no gain
+        # for board in other.bool_board_mirrored():
+        #     if self.bool_board() == board:
+        #         return True
+        return False
+
+    def next_move(self):
+        # try to find a move where no direction is possible
+        for x in range(1, self.__board_size + 1):
+            for y in range(1, self.__board_size + 1):
+                if (x == 1) and (y == 1):
+                    continue
+                if self.board()[x, y]:
+                    continue
+                left_full = x == 1 or self.board()[x - 1, y]
+                top_full = y == 1 or self.board()[x, y - 1]
+                if not self.possible(x, y, 'down') and not self.possible(x, y, 'right') and left_full and top_full:
+                    return x, y
+
+        # try to find a move where only one direction is possible
+        for x in range(1, self.__board_size - 1):
+            for y in range(1, self.__board_size - 1):
+                if (x == 1) and (y == 1):
+                    continue
+                if self.board()[x, y]:
+                    continue
+                left_full = x == 1 or self.board()[x - 1, y]
+                top_full = y == 1 or self.board()[x, y - 1]
+                if not self.possible(x, y, 'down') and left_full and top_full:
+                    return x, y
+                if not self.possible(x, y, 'right') and left_full and top_full:
+                    return x, y
+
+        # just take the next move in this row
+        for y in range(1, self.__board_size + 1):
+            for x in range(1, self.__board_size + 1):
+                if (x == 1) and (y == 1):
+                    continue
+                if self.board()[x, y]:
+                    continue
+                return x, y
+
+        return None, None

+ 67 - 0
python/solve.py

@@ -0,0 +1,67 @@
+from graphviz import Digraph
+
+from chess_state import ChessState
+
+proof = Digraph(format='svg', node_attr={'shape': 'record'}, comment='Proof that chess domino is not possible')
+
+
+def state_id(index):
+    return f'state{index}'
+
+
+class Solver:
+    def __init__(self, board_size=8):
+        self.board_size = board_size
+        self.steps = 1
+        self.starting_state = ChessState(board_size=board_size)
+        self.states = [self.starting_state]
+
+    def solvable(self, state):
+        current_id = len(self.states) - 1
+
+        x, y = state.next_move()
+        if x is None:
+            return True  # end of board
+
+        for direction in ['right', 'down']:
+            if state.possible(x, y, direction):
+                next_state = state.put(x, y, direction)
+                self.steps += 1
+
+                found_symmetry = -1
+
+                # look for symmetry
+                for i, b in enumerate(self.states):
+                    if next_state.symmetric_to(b):
+                        print(f'found symmetry between states {current_id} and {i}')
+                        found_symmetry = i
+                        break
+
+                # symmetry found
+                if found_symmetry != -1:
+                    proof.edge(state_id(current_id),
+                               state_id(found_symmetry),
+                               label=f'put next brick facing {direction} at {(x,y)}')
+                    continue
+
+                # pace next brick
+                self.states.append(next_state)
+                next_id = len(self.states) - 1
+                proof.edge(state_id(current_id),
+                           state_id(next_id),
+                           label=f'put next brick facing {direction} at {(x,y)}')
+                proof.node(name=state_id(next_id), label=next_state.board_label())
+
+                # try to solve the remaining board
+                if self.solvable(next_state):
+                    return True
+        return False
+
+if __name__ == '__main__':
+    s = Solver()
+    proof.node(name=state_id(0), label=s.starting_state.board_label())
+    result = s.solvable(s.starting_state)
+    print('solvable:', result)
+    print('steps:', s.steps)
+    print('states:', len(s.states))
+    # proof.render(f'img/proof.gv', view=False)