comp-think/2020-2021

Lecture "Backtracking algorithms", exercise 2

Opened this issue · 5 comments

We define a labyrinth as a set of tuples representing the various cells of the paths within the labyrinth. The tuples are organised in an x/y grid, similar to the way used in Listing 1 for the peg solitaire, such as the one proposed as follows:

      (1,0)       (3,0) (4,0) (5,0)
(0,1) (1,1) (2,1) (3,1)       (5,1)
(0,2)       (2,2)       (4,2) (5,2)
(0,3)       (2,3) (3,3)       (5,3)
(0,4)                   (4,4)      
(0,5) (1,5) (2,5) (3,5) (4,5)      

Write the function solve_labyrinth(paths, entrance, exit, last_move), which takes as input the paths of the labyrinth (such as the ones mentioned above), two tuples representing the entrance and the exit of the labyrinth, and the last move did. The function returns the last move done to reach the exit if the labyrinth has an escape; otherwise, it returns None. Accompany the implementation of the function with the appropriate test cases.

This function won't find the fastest route but it returns the first it finds.

from anytree import Node
from collections import deque

#test
def test(paths, entrance, exit, last_move, expected):
    result = solve_labyrinth(paths, entrance, exit, last_move)
    return result.name["back"] == expected


bad_moves = set()

def solve_labyrinth(paths, entrance, exit, last_move):
    result = None
    if last_move not in bad_moves:
        if entrance == exit:
            result = last_move
        else:
            last_move.children = possible_paths(entrance, paths)
            if len(last_move.children) == 0:
                if [last_move.siblings] != []:
                    bad_moves.add(last_move)
                    undo_move(last_move, paths)
            else:
                move_stack = deque(last_move.children)
                while result == None and len(move_stack) > 0:
                    current_move = move_stack.pop()
                    new_pos = make_move(current_move, paths)[0]
                    new_path = make_move(current_move, paths)[1]
                    result = solve_labyrinth(new_path, new_pos, exit, current_move)
                if result == None:
                    bad_moves.add(last_move)
                    undo_move(last_move, paths)
    else:
        undo_move(last_move, paths)
    return result

#create labyrinth
def create_labyrinth():
    begin = (2,3) #begin mayn not be in labyrinth, but end has to be!
    end = (5, 2)
    all_path = {(1, 0), (3, 0), (4, 0), (5, 0),
                    (0, 1), (1, 1), (2, 1), (3, 1), (5, 1),
                    (0, 2), (2, 2), (4, 2), (5, 2),
                    (0, 3), (2, 3), (3, 3), (5, 3),
                    (0, 4), (4, 4),
                    (0, 5), (1, 5), (2, 5), (3, 5), (4, 5)}
    return all_path, begin, end

def make_move(node, pth):
    move= node.name
    new_pos = move.get("move")
    old_pos = move.get("back")
    if old_pos in pth:
        pth.remove(old_pos)
    return new_pos, pth #returns tuple!!!!!!!!!

def undo_move(node, pth):
    move = node.name
    old_pos = move.get("back")
    pth.add(old_pos)

#check possible paths
def possible_paths(entr, pth):
    result = list()
    x = entr[0]
    y = entr[1]
    if (x+1, y) in pth:
        result.append(Node({"move": (x+1, y), "back": (x,y)}))
    if (x-1, y) in pth:
        result.append(Node({"move": (x-1, y), "back": (x,y)}))
    if (x, y+1) in pth:
        result.append(Node({"move": (x, y+1), "back": (x,y)}))
    if (x, y-1) in pth:
        result.append(Node({"move": (x, y-1), "back": (x,y)}))
    return result

path, entrance, exit = create_labyrinth()

print(test(path, entrance, exit, Node("start"), (3,5))) # True

I tried putting begin = (2,3) and end = (5,2):
print(test(path, entrance, exit, Node("start"), (5,1))) # True

I have replaced your "last_move" with "previous_position". At the end the algorithm returns the position occupied before finding the exit.

from anytree import Node
from collections import deque

paths = [
            (1, 0),         (3, 0), (4, 0), (5, 0),
    (0, 1), (1, 1), (2, 1), (3, 1),         (5, 1),
    (0, 2),         (2, 2),         (4, 2), (5, 2),
    (0, 3),         (2, 3), (3, 3),         (5, 3),
    (0, 4),                         (4, 4),
    (0, 5), (1, 5), (2, 5), (3, 5), (4, 5)
]


def test_solve_labyrinth(paths, entrance, exit, previous_position,expected):
    result = solve_labyrinth(paths,entrance,exit,previous_position)
    return result==expected

def solve_labyrinth(paths, entrance, exit, previous_position):
    result = None
    entrance.children = valid_moves(paths, entrance, previous_position)  # generate children
    possible_moves = deque(entrance.children)
    if len(entrance.children) > 0:
        if entrance.name != exit:  # keep moving
            while result == None and len(possible_moves) > 0:
                current_move = possible_moves.pop()
                result = solve_labyrinth(paths, current_move, exit, entrance)   # recursion
        else:                                                                             # win, provided that you are in the exit box
            result = previous_position.name
        return result
    elif len(entrance.children) <= 0 and entrance.name != exit:  # leaf-node lose
        return result                                         # backtracking
    elif len(entrance.children) <= 0 and entrance.name == exit:   # leaf-node win
        result = previous_position.name
        return result


def valid_moves(paths, current_position, previous_position):
    result = list()
    x = current_position.name[0]
    y = current_position.name[1]
    if (x + 1, y) in paths and (x + 1, y) != previous_position.name:
        result.append(Node((x + 1, y)))
    if (x - 1, y) in paths and (x - 1, y) != previous_position.name:
        result.append(Node((x - 1, y)))
    if (x, y + 1) in paths and (x, y + 1) != previous_position.name:
        result.append(Node((x, y + 1)))
    if (x, y - 1) in paths and (x, y - 1) != previous_position.name:
        result.append(Node((x, y - 1)))
    return result


none_node = Node(None)   # previous position of the start position
print(test_solve_labyrinth(paths, Node((2, 3)), (4, 4), none_node,(4,5))) # returns True
print(test_solve_labyrinth(paths, Node((0, 1)), (5, 3), none_node,(5,2))) # returns True
print(test_solve_labyrinth(paths, Node((2, 1)), (4, 2), none_node,(5,2))) # returns True
print(test_solve_labyrinth(paths, Node((2, 1)), (5, 5), none_node,None)) # returns True
from anytree import Node
from collections import deque


# Test case for the algorithm
def test_solve_labirynth(paths,entrance,exit,last_move,expected):
    result = solve_labirynth(paths,entrance,exit,last_move)
    if (expected==result==None):
        return True
    elif result!=None:
        return (expected == result.name["to"])
    else:
        return False


def create_labirynth():

    paths = set([
        (1, 0),(3, 0),(4, 0),(5, 0),
        (0, 1),(1, 1),(2, 1),(3, 1),(5, 1),
        (0, 2),(2, 2),(4, 2),(5, 2),
        (0, 3),(2, 3),(3, 3),(5, 3),
        (0, 4),(4, 4),
        (0, 5),(1, 5),(2, 5),(3, 5), (4, 5)
    ])

    return paths


def valid_moves(paths,cell):
    result = list()
    pos_x=cell[0]
    pos_y=cell[1]

    if (pos_x-1, pos_y) in paths:
        result.append(Node({"from": (pos_x, pos_y), "to": (pos_x-1, pos_y)}))
    if (pos_x+1, pos_y) in paths:
        result.append(Node({"from": (pos_x, pos_y), "to": (pos_x+1, pos_y)}))
    if (pos_x, pos_y-1) in paths:
        result.append(Node({"from": (pos_x, pos_y), "to": (pos_x, pos_y-1)}))
    if (pos_x, pos_y+1) in paths:
        result.append(Node({"from": (pos_x, pos_y), "to": (pos_x, pos_y+1)}))

    return result


def apply_move(node, paths):
    move = node.name
    prev_pos = move.get("from")

    paths.remove(prev_pos)


def undo_move(node, paths):
    move = node.name
    curr_pos = move.get("to")
    prev_pos = move.get("from")

    paths.remove(curr_pos)
    paths.add(prev_pos)


def solve_labirynth(paths,entrance,exit,last_move):
    result = None

    if (exit not in paths): return None

    if (entrance==exit):  # leaf-win base case
        result = last_move
    else:
        last_move.children = valid_moves(paths,entrance)

        if len(last_move.children) == 0:  # leaf-lose base case
            undo_move(last_move, paths)  # backtracking
        else:  # recursive step
            possible_moves = deque(last_move.children)

            while result is None and len(possible_moves) > 0:
                current_move = possible_moves.pop()
                apply_move(current_move, paths)
                result = solve_labirynth(paths, current_move.name.get("to"),exit, current_move)

            if (result is None) and (last_move!=last_move.root):  # If the last_move is back to the root, it means the exit has not been found after trying all the paths
                undo_move(last_move, paths)  # backtracking

    return result


# TEST 1: solvable labirynth
paths=create_labirynth()
entrance=(5,3)
exit=(4,4)
print(test_solve_labirynth(paths,entrance,exit,Node('Start'),exit))


# TEST 2: same labirynth but exit out of paths
entrance=(5,3)
exit=(5,5)
print(test_solve_labirynth(paths,entrance,exit,Node('Start'),None))


# TEST 3: existent but unreachable exit
paths=set([
        (1, 0),(3, 0),(4, 0),(5, 0),
        (0, 1),(1, 1),(2, 1),(3, 1),(5, 1),
        (0, 2),(2, 2),(4, 2),(5, 2),
        (0, 3),(2, 3),(3, 3),(5, 3),
        (0, 4),(4, 4),
        (0, 5),(1, 5),(2, 5),(3, 5)
    ])
entrance=(5,3)
exit=(4,4)
print(test_solve_labirynth(paths,entrance,exit,Node('Start'),None))

from anytree import Node
from collections import deque
from time import sleep


# Test case for the algorithm
def test_solve_labyrinth(paths, entrance, exit, last_move, expected):
    result = solve_labyrinth(paths, entrance, exit, last_move)
    if expected == result:
        return True
    else:
        return False


def solve_labyrinth(paths, entrance, exit, last_move):
    result = None

    if last_move.name["new_position"] == exit:  # leaf-win base case
        result = last_move.name["old_position"]

    else:
        if entrance == last_move:  # only first case
            last_move.children = valid_moves(entrance, paths)
            move = last_move.name
            street.append(move["new_position"])
            paths.remove(move["new_position"])
        else:
            last_move.children = valid_moves(last_move, paths)

        possible_moves = deque(last_move.children)

        while result is None and len(possible_moves) > 0:
            current_position = possible_moves.pop()
            apply_move(current_position, paths)
            result = solve_labyrinth(paths, entrance, exit, current_position)

    return result


def create_board():
    entrance = Node({"new_position": (int(input("starting_x: ")), int(input("starting_y: "))) , "old_position": (None, None)})

    paths = {(1, 0), (3, 0), (4, 0), (5, 0), (0, 1), (1, 1), (2, 1), (3, 1), (5, 1),
             (0, 2), (2, 2), (4, 2), (5, 2), (0, 3), (2, 3), (3, 3), (5, 3), (0, 4), (4, 4),
             (0, 5), (1, 5), (2, 5), (3, 5), (4, 5)}

    exit = (int(input("finishing_x: ")), int(input("finishing_y: ")))
    return paths, entrance, exit


def valid_moves(last_move, paths):
    result = list()
    move = last_move.name
    x, y = move["new_position"]
    if (x - 1, y) in paths:
        result.append(Node({"new_position": (x - 1, y), "old_position": (x, y)}))
    if (x + 1, y) in paths:
        result.append(Node({"new_position": (x + 1, y), "old_position": (x, y)}))
    if (x, y - 1) in paths:
        result.append(Node({"new_position": (x, y - 1), "old_position": (x, y)}))
    if (x, y + 1) in paths:
        result.append(Node({"new_position": (x, y + 1), "old_position": (x, y)}))
    return result


def apply_move(current_positon, paths):
    move = current_positon.name
    new_pos = move.get("new_position")

    paths.remove(new_pos)


# pegs, holes = create_6x6_square_board()
paths, entrance, exit = create_board()
print(test_solve_labyrinth(paths, entrance, exit, entrance, (5, 0)))
from anytree import Node
from collections import deque


def test_solve_labyrinth(paths, entrance, exit, last_move, expected):
    return solve_labyrinth(paths, entrance, exit, last_move) == expected


def valid_moves(entrance, paths):
    result = []
    x = entrance[0]
    y = entrance[1]
    if (x,y) in paths:
        if (x + 1, y) in paths:
            result.append(Node({"move from": (x, y), "to": (x + 1, y)}))
        if (x - 1, y) in paths:
            result.append(Node({"move from": (x, y), "to": (x - 1, y)}))
        if (x, y + 1) in paths:
            result.append(Node({"move from": (x, y), "to": (x, y + 1)}))
        if (x, y - 1) in paths:
            result.append(Node({"move from": (x, y), "to": (x, y - 1)}))
    return result


def apply_move(node, paths):
    move = node.name
    old_pos = move.get("move from")
    entrance = move.get("to")
    if old_pos in paths:
        paths.remove(old_pos)
    return entrance, paths, old_pos


def undo_move(node, paths):
    move = node.name
    old_pos = move.get("move from")
    paths.add(old_pos)


def solve_labyrinth(paths, entrance, exit, last_move):
    result = None
    initial_paths = set()
    initial_paths.update(paths)
    if entrance not in paths:
        return "No access to labyrinth"
    if exit not in paths:
        return result
    if entrance == exit:
        result = last_move.name
    else:
        last_move.children = valid_moves(entrance, paths)
        if len(last_move.children) == 0:
            undo_move(last_move, paths)
        else:
            possible_moves = deque(last_move.children)
            while result is None and len(possible_moves) > 0:
                cur_move = possible_moves.pop()
                new_entr = apply_move(cur_move, paths)[0]
                paths_updated = apply_move(cur_move, paths)[1]
                result = solve_labyrinth(paths_updated, new_entr, exit, cur_move)
            if result is None:
                undo_move(last_move, paths)
    paths.update(initial_paths)
    return result


cells = {(1,0), (3,0), (4,0), (5,0),
         (0,1), (1,1), (2,1), (3,1), (5,1),
         (0,2), (2,2), (4,2), (5,2),
         (0,3), (2,3), (3,3), (5,3),
         (0,4), (4,4),
         (0,5), (1,5), (2,5), (3,5), (4,5)}


print(test_solve_labyrinth(cells, (1,0), (3,3), Node("Start"), {'move from': (2, 3), 'to': (3, 3)}))   # True
print(test_solve_labyrinth(cells, (4,4), (5,3), Node("Start"), {'move from': (5, 2), 'to': (5, 3)}))   # True
print(test_solve_labyrinth(cells, (0,0), (4,4), Node("Start"), "No access to labyrinth"))   # True 
print(test_solve_labyrinth(cells, (2,1), (3,6), Node("Start"), None))   # True