=============== Fifteen Puzzle =============== Information about the `fifteen puzzle` can be found at http://en.wikipedia.org/wiki/15_puzzle .. figure:: board.png :alt: 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. .. code-block:: python >>> 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. .. code-block:: python >>> 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. .. code-block:: python >>> 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. .. code-block:: python 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 =================== .. code-block:: python def shuffle(): choices = range(rows*cols-1) for i in range(500): slide(None, system.rectangles[random.choice(choices)]) Shuffle button ================ .. code-block:: python 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 ============= .. code-block:: python 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