Fifteen Puzzle

Information about the fifteen puzzle can be found at http://en.wikipedia.org/wiki/15_puzzle

board

Figure 1: Fifteen puzzle

Creating the tiles

Although it is possible to create all fifteen tiles manually, it is a lot easier to create them with a short Python script. With scripting, creating a 40 by 40 grid is the same effort as creating a four by four grid.

>>> rows = cols = 4
>>> length = 3
>>> for row in range(rows):
        for col in range(cols):
            if row == rows - 1 and col == cols - 1:
                break
            else:
                rect = rectangle(x=col*length, y=row*length, width=length, height=length)
                rect.label = str(row * cols + col + 1)

Sliding tiles

When the user clicks on a tile we will slide the tile to the empty space if the clicked tile is next to the empty space. To accomplish this we will keep track of the position of the tiles and empty space in terms of the row and column they occupy. For example, in Figure 1 the empty space is located at the (3, 3) position (remember, Python uses zero based indexing).

The slide function can be summarized as:

If the clicked tile is in the same row as the empty tile and located next to the empty tile then change the clicked tile’s x (horizontal) position. If the tile is located on the right hand side of the empty tile then decrease the clicked tile’s x value by length (remember that we set the tile width to length). If the tile is located on the left hand side of the empty tile then increase the clicked tile’s x value by length. Then swap the empty tile’s col value (empty_col) with that of the clicked one.

If the clicked tile is in the same column as the empty tile and located next to the empty tile then change the clicked tile’s y (vertical) position. If the tile is located above the empty tile then decrease the clicked tile’s y value by length (remember that we set the tile height to length). If the tile is located below the empty tile then increase the clicked tile’s y value by length. Then swap the empty tile’s row value (empty_row) with that of the clicked one.

>>> rows = cols = 4
>>> length = 3
>>> for row in range(rows):
        for col in range(cols):
            if row == rows - 1 and col == cols - 1:
                break
            else:
                rect = rectangle(x=col*length, y=row*length, width=length, height=length)
                rect.label = str(row * cols + col + 1)
                rect.row = row
                rect.col = col

>>> empty_row = rows - 1
>>> empty_col = cols - 1
>>> def slide(tile):
        global empty_row, empty_col
        if empty_row == tile.row and abs(empty_col - tile.col) == 1:
            tile.x += (empty_col - tile.col) * length
            empty_col, tile.col = tile.col, empty_col
        elif empty_col == tile.col and abs(empty_row - tile.row) == 1:
            tile.y += (empty_row - tile.row) * length
            empty_row, tile.row = tile.row, empty_row

Now we can test the slide function from the Python interpreter.

>>> slide(rectangle15)
>>> slide(rectangle11)
>>> slide(rectangle10)
>>> slide(rectangle6)
>>> slide(rectangle1)

Although we have not tested all possible combinations, our function seems to be working.

Interacting with mouse

When the user clicks on a tile next to the empty space we would like to slide the tile into the empty space. We will bind the slide function to the LEFTDOWNON event for each tile we create. Functions need to be defined before they can be bound to an event.

rows = cols = 4
length = 3
empty_row = rows - 1
empty_col = cols - 1

def slide(event, source):
    global empty_row, empty_col
    if empty_row == source.row and abs(empty_col - source.col) == 1:
        source.x += (empty_col - source.col) * length
        empty_col, source.col = source.col, empty_col
    elif empty_col == source.col and abs(empty_row - source.row) == 1:
        source.y += (empty_row - source.row) * length
        empty_row, source.row = source.row, empty_row

for row in range(rows):
    for col in range(cols):
        if row == rows - 1 and col == cols - 1:
            break # leave an empty space
        else:
            rect = rectangle(x=col*length, y=row*length, width=length, height=length)
            rect.label = str(row * cols + col + 1)
            rect.draggable = False
            rect.row = row
            rect.col = col
            rect.bind(LEFTDOWNON, slide)

Shuffling tiles

def shuffle():
    choices = range(rows*cols-1)
    for i in range(500):
        slide(None, system.rectangles[random.choice(choices)])

Shuffle button

def shuffle(event, source):
    choices = range(rows*cols-1)
    for i in range(500):
        slide(None, system.rectangles[random.choice(choices)])

shuffleButton = rectangle(x=0, y=(cols+3)*length, width=2*length,
                          height=length, fillColor=(150, 150, 150, 150))
shuffleButton.bind(LEFTDOWNON, shuffle)

Final Listing

rows = cols = 4
length = 3
empty_row = rows - 1
empty_col = cols - 1

def slide(event, source):
    global empty_row, empty_col
    if empty_row == source.row and abs(empty_col - source.col) == 1:
        source.x += (empty_col - source.col) * length
        empty_col, source.col = source.col, empty_col
    elif empty_col == source.col and abs(empty_row - source.row) == 1:
        source.y += (empty_row - source.row) * length
        empty_row, source.row = source.row, empty_row

for row in range(rows):
    for col in range(cols):
        if row == rows - 1 and col == cols - 1:
            break # leave an empty space
        else:
            rect = rectangle(x=col*length, y=row*length, width=length, height=length)
            rect.label = str(row * cols + col + 1)
            rect.draggable = False
            rect.row = row
            rect.col = col
            rect.bind(LEFTDOWNON, slide)

def shuffle(event, source):
    choices = range(rows * cols - 1)
    for i in range(500):
        slide(None, system.rectangles[random.choice(choices)])

shuffleButton = rectangle(x=(cols+3)*length, y=0, width=2*length,
                          height=length, fillColor=(150, 150, 150, 150))
shuffleButton.label = 'Shuffle'
shuffleButton.bind(LEFTDOWNON, shuffle)
system.zeroGravity = True
system.box.active = False

Table Of Contents

This Page