0

Problem Explanation

I wanted to create a Knight's Tour in an 8x8 board and only used recursion to implement the code thus far.

The knight tour for an 8x8 board has around 19 quadrillion moves.

src = https://en.wikipedia.org/wiki/Knight%27s_tour#Number_of_tours

My perspective is that even if it reaches a square where it can no longer make a move it can move back recursively to find a solution every time without terminating.

I use 2 lists, solution_list - that stores the solution of the tour and pos_visited - that stores the positions previously visited, which don't have further possible moves.

Error

This question Knight's Tour in Python - Getting Path from Predecessors helped me to figure out where I went wrong.

Initially, there is no problem in moving to a previous position but as the list pos_visited grows the function Next_move, therefore, can't pick a move from move_set which has no moves and throws 'IndexError: Cannot choose from an empty sequence'.

I have only added the necessary code and relative functions for readability.

import random


def Start_Tour(board, solution_list, move_set, pos_visited):
    # clears board
    clear_board(board, solution_list, move_set, pos_visited)

    # ASKS STARTING POSITION
    print("Give initial coordinates to start: ")
    x_initial, y_initial = first_pos(board)
    board[x_initial][y_initial] = "♘"

    # Displays board
    display_board(board)

    solution_list.append([x_initial, y_initial])
    print(f"Starting position defined at {x_initial, y_initial}")
    count = 0

    while count < 65:

        # Move_set gets valid and unoccupied coordinates in board
        move_set = potential_moves(x_initial, y_initial, board)

        # check if move_set is empty
        if len(move_set) == 0:
            # BACKTRACK if empty
            print("Moving Back")
            retrack(board, solution_list, pos_visited)

        # Else-Place Knight
        else:

            # choose one move as xn,yn at random
            xn, yn = Next_move(move_set, pos_visited)

            # place marker in xn,yn
            board[xn][yn] = "♘"

            # clears previous position
            clear_previous_pos(board, solution_list)

            # adds position to solution_list
            solution_list.append([xn, yn])
            print(f"\n solution_list -> {solution_list}")
            print("\n")

            # displays board
            display_board(board)

            # sets x_initial and y_initial for generating next set of potential moves
            x_initial = xn
            y_initial = yn
            count += 1
            print(f"\n {count}")


def potential_moves(a, b, board):
    """
    Takes current position of knight(a,b) and generates possible moves as a list
    """
    move_set = [
        [a - 1, b - 2],
        [a - 2, b - 1],
        [a - 2, b + 1],
        [a - 1, b + 2],
        [a + 1, b + 2],
        [a + 2, b + 1],
        [a + 2, b - 1],
        [a + 1, b - 2],
    ]
    for x, y in move_set[:]:
        if x in range(0, 8) and y in range(0, 8) and board[x][y] == " ":
            pass
        else:
            move_set.remove([x, y])

    return move_set


def retrack(board, solution_list, pos_visited):
    """
    helps knight to move back to previous position.
    """
    x_current, y_current = solution_list.pop(-1)  # x,y have no more valid moves
    pos_visited.append([x_current, y_current])  # adds x,y to positions already visited
    board[x_current][y_current] = " "  # erases current x,y pos'n
    x, y = solution_list[-1]  # returns pos'n before getting stuck
    if len(potential_moves(x, y, board)) != 0 and [x, y] not in pos_visited:
        return x, y
    else:
        return retrack(board, solution_list, pos_visited)


def Next_move(move_set, pos_visited):
    """
    returns a move at random from move_set if it isn't visited already.
    """
    xn, yn = random.choice(move_set)
    if positions_visited(xn, yn, pos_visited):
        return xn, yn
    else:
        move_set.remove([xn, yn])
        move_set1 = move_set
        return Next_move(move_set, pos_visited)


def positions_visited(n1, n2, pos_visited):  # Checks for position in tracker
    """
    checks if position has been visited already
    """
    if [n1, n2] in pos_visited:
        return False
    else:
        return True

This is the output I get so far before an error is thrown.

P.S: I've been learning python for 2 months now and don't know much about algorithms yet. I first would like to arrive at a solution with a basic approach before trying to use algorithms. It will be very helpful if you could explain in detail if you think I should take a different approach to this.

Thank you

-Santosh

Edit: I have added the traceback error

    
    ---------------------------------------------------------------------------
    IndexError    Traceback (most recent call last)
    <ipython-input-22-aba4d6ed48dc> in <module>
    ----> 1 Start_Tour(cb,solution_list,move_set,pos_visited)
          
    <ipython-input-18-0c959cf546f8> in Start_Tour(board, solution_list, 
    move_set, pos_visited)
          31   
          32         # choose one move as xn,yn
    ----> 33             xn,yn = Next_move(move_set,pos_visited)
          34 
          35         #place marker in xn,yn
          
    <ipython-input-21-c2489e88fd1e> in Next_move(move_set, pos_visited)
          9         move_set.remove([xn,yn])
          10         move_set1 = move_set
     ---> 11         return Next_move(move_set,pos_visited)

    <ipython-input-21-c2489e88fd1e> in Next_move(move_set, pos_visited)
          3     #returns a move at random from move_set if it isn't visited already.
    ----> 4     xn,yn = random.choice(move_set)
          5     if positions_visited(xn,yn,pos_visited):
          6         return xn,yn
          
    F:\Anaconda\lib\random.py in choice(self, sea)
          259             i = self._randbelow(len(seq))
          260         except ValueError:
      --> 261             raise IndexError('Cannot choose from an empty 
sequence') from None
          262         return seq[i]
          263 

    IndexError: Cannot choose from an empty sequence

1 Answers1

0

I feel like your problem lies in this code here. It's obviously not populating your move_set list for some reason. I.E what's happening is that the else part of your for loop is being executed for every possible move. Which makes sense because judging by your board, there is no possible moves for the knight to make. Each board[x][y] does not equal ' '.

def potential_moves(a,b,board):
    '''
    Takes current position of knight(a,b) and generates possible moves as a list
    '''
    move_set = [[a-1,b-2],[a-2,b-1],[a-2,b+1],[a-1,b+2],[a+1,b+2],[a+2,b+1],[a+2,b-1],[a+1,b-2]]
    for x,y in move_set[:]:
      if x in range(0,8) and y in range(0,8) and board[x][y]==' ':
         pass 
      else:
         move_set.remove([x,y])
    
    return move_set

Edit: As has been pointed out by the comments on your post. It looks like the knight has no moves to make because of cb is not defined in your retrack function, which I assume is meant to prevent the knight from having no possible moves.

Pokebab
  • 183
  • 8