Sliding Tile Puzzle

Tags: large, game, puzzle

This classic puzzle relies on a 4 × 4 board with 15 numbered tiles and one free space. The objective is to slide the tiles until the numbers are in the correct order, going left to right and top to bottom. Tiles can only slide; you’re not allowed to directly pick them up and rearrange them. Some versions of this puzzle toy feature scrambled images that form a complete picture once solved.

More information about sliding tile puzzles can be found at https://en.wikipedia.org/wiki/Sliding_puzzle.

sliding_tile_puzzle.py
  1"""Sliding Tile Puzzle, by Al Sweigart al@inventwithpython.com
  2Slide the numbered tiles into the correct order.
  3This code is available at https://nostarch.com/big-book-small-python-programming
  4Tags: large, game, puzzle"""
  5
  6import random, sys
  7
  8BLANK = '  '  # Note: This string is two spaces, not one.
  9
 10
 11def main():
 12    print('''Sliding Tile Puzzle, by Al Sweigart al@inventwithpython.com
 13
 14    Use the WASD keys to move the tiles
 15    back into their original order:
 16           1  2  3  4
 17           5  6  7  8
 18           9 10 11 12
 19          13 14 15   ''')
 20    input('Press Enter to begin...')
 21
 22    gameBoard = getNewPuzzle()
 23
 24    while True:
 25        displayBoard(gameBoard)
 26        playerMove = askForPlayerMove(gameBoard)
 27        makeMove(gameBoard, playerMove)
 28
 29        if gameBoard == getNewBoard():
 30            print('You won!')
 31            sys.exit()
 32
 33
 34def getNewBoard():
 35    """Return a list of lists that represents a new tile puzzle."""
 36    return [['1 ', '5 ', '9 ', '13'], ['2 ', '6 ', '10', '14'],
 37            ['3 ', '7 ', '11', '15'], ['4 ', '8 ', '12', BLANK]]
 38
 39
 40def displayBoard(board):
 41    """Display the given board on the screen."""
 42    labels = [board[0][0], board[1][0], board[2][0], board[3][0],
 43              board[0][1], board[1][1], board[2][1], board[3][1],
 44              board[0][2], board[1][2], board[2][2], board[3][2],
 45              board[0][3], board[1][3], board[2][3], board[3][3]]
 46    boardToDraw = """
 47+------+------+------+------+
 48|      |      |      |      |
 49|  {}  |  {}  |  {}  |  {}  |
 50|      |      |      |      |
 51+------+------+------+------+
 52|      |      |      |      |
 53|  {}  |  {}  |  {}  |  {}  |
 54|      |      |      |      |
 55+------+------+------+------+
 56|      |      |      |      |
 57|  {}  |  {}  |  {}  |  {}  |
 58|      |      |      |      |
 59+------+------+------+------+
 60|      |      |      |      |
 61|  {}  |  {}  |  {}  |  {}  |
 62|      |      |      |      |
 63+------+------+------+------+
 64""".format(*labels)
 65    print(boardToDraw)
 66
 67
 68def findBlankSpace(board):
 69    """Return an (x, y) tuple of the blank space's location."""
 70    for x in range(4):
 71        for y in range(4):
 72            if board[x][y] == '  ':
 73                return (x, y)
 74
 75
 76def askForPlayerMove(board):
 77    """Let the player select a tile to slide."""
 78    blankx, blanky = findBlankSpace(board)
 79
 80    w = 'W' if blanky != 3 else ' '
 81    a = 'A' if blankx != 3 else ' '
 82    s = 'S' if blanky != 0 else ' '
 83    d = 'D' if blankx != 0 else ' '
 84
 85    while True:
 86        print('                          ({})'.format(w))
 87        print('Enter WASD (or QUIT): ({}) ({}) ({})'.format(a, s, d))
 88
 89        response = input('> ').upper()
 90        if response == 'QUIT':
 91            sys.exit()
 92        if response in (w + a + s + d).replace(' ', ''):
 93            return response
 94
 95
 96def makeMove(board, move):
 97    """Carry out the given move on the given board."""
 98    # Note: This function assumes that the move is valid.
 99    bx, by = findBlankSpace(board)
100
101    if move == 'W':
102        board[bx][by], board[bx][by+1] = board[bx][by+1], board[bx][by]
103    elif move == 'A':
104        board[bx][by], board[bx+1][by] = board[bx+1][by], board[bx][by]
105    elif move == 'S':
106        board[bx][by], board[bx][by-1] = board[bx][by-1], board[bx][by]
107    elif move == 'D':
108        board[bx][by], board[bx-1][by] = board[bx-1][by], board[bx][by]
109
110
111def makeRandomMove(board):
112    """Perform a slide in a random direction."""
113    blankx, blanky = findBlankSpace(board)
114    validMoves = []
115    if blanky != 3:
116        validMoves.append('W')
117    if blankx != 3:
118        validMoves.append('A')
119    if blanky != 0:
120        validMoves.append('S')
121    if blankx != 0:
122        validMoves.append('D')
123
124    makeMove(board, random.choice(validMoves))
125
126
127def getNewPuzzle(moves=200):
128    """Get a new puzzle by making random slides from a solved state."""
129    board = getNewBoard()
130
131    for i in range(moves):
132        makeRandomMove(board)
133    return board
134
135
136# If this program was run (instead of imported), run the game:
137if __name__ == '__main__':
138    main()

https://inventwithpython.com/bigbookpython/project68.html