Tutorial code (interpolation section)ΒΆ

button quit DOWN
    kbd ESCAPE
    kbd BACKSPACE

button4 move DOWN
    # arrow keys
    left kbd LEFT
    right kbd RIGHT
    up kbd UP
    down kbd DOWN
    # WASD
    left kbd a
    left kbd q
    right kbd d
    right kbd e
    up kbd w
    up kbd z
    up kbd COMMA
    down kbd s
    down kbd o
    # gamepad analogue sticks
    left right pad axis 0 .6 .4
    up down pad axis 1 .6 .4
    left right pad axis 3 .6 .4
    up down pad axis 4 .6 .4

button click DOWN
    mouse button LEFT
    mouse button RIGHT
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import random

import pygame as pg

import engine
from engine import conf, evt, gfx, util


class Conf (object):
    # the width and height of the image we're working with
    IMG_SIZE = (500, 500)
    # the number of tiles, horizontally and vertically
    N_TILES = (5, 5)
    # the size of each actual tile graphic
    TILE_SIZE = (99, 99)
    # the gap between tiles and around the edge of the screen
    TILE_GAP = (1, 1)
    # the number of seconds tiles take to slide to a new position
    MOVE_TIME = .2


class Puzzle (engine.game.World):
    def init (self):
        # register input handlers
        eh = self.evthandler
        eh.load('controls')
        eh['quit'].cb(lambda evts: conf.GAME.quit_world())
        eh['move'].cb(self.move)
        eh['click'].cb(self.click)

        # load image
        img = self.resources.img('img.jpg')

        # split up into tiles
        imgs = []
        alpha = util.has_alpha(img)
        nx, ny = conf.N_TILES
        gap_x, gap_y = conf.TILE_GAP
        tile_w, tile_h = conf.TILE_SIZE
        for i in xrange(nx):
            for j in xrange(ny):
                # create empty surface of the correct size and convert
                sfc = pg.Surface(conf.TILE_SIZE)
                if alpha:
                    sfc = sfc.convert_alpha()
                else:
                    sfc = sfc.convert()
                # copy the correct portion from the source image
                x = (tile_w + gap_x) * i
                y = (tile_h + gap_y) * j
                sfc.blit(img, (0, 0), (x, y, tile_w, tile_h))
                # wrap with a graphic
                imgs.append(((i, j), gfx.Graphic(sfc)))

        # randomise tile positions and remove one
        random.shuffle(imgs)
        missing = random.randrange(nx * ny)
        self.missing = [missing // ny, missing % ny]
        imgs[missing] = (imgs[missing][0], None)
        # create grid for positioning
        grid = util.grid.Grid(conf.N_TILES, conf.TILE_SIZE, conf.TILE_GAP)
        self.grid = grid
        # position graphics
        # and turn the tile list into a grid for easier access
        self.tiles = tiles = []
        for i in xrange(nx):
            col = []
            tiles.append(col)
            for j in xrange(ny):
                orig_pos, graphic = imgs[i * ny + j]
                col.append((orig_pos, graphic))
                # get the tile's top-left corner from the grid
                x, y = grid.tile_pos(i, j)
                if graphic is not None:
                    # we'll use this for movement
                    graphic.interp_to = self.scheduler.interp_simple_locked(
                        graphic, 'pos', t=conf.MOVE_TIME
                    )
                    # and move the graphic there
                    graphic.pos = (x + gap_x, y + gap_y)

        # add to the graphics manager
        # make sure to remove the missing tile
        imgs.pop(missing)
        self.graphics.add(
            # a background to show up between the tiles and in the gap
            # '111' is a CSS-style colour (dark grey)
            # 1 is the layer, which is further back than the default 0
            gfx.Colour('111', self.graphics.rect, 1),
            *(graphic for orig_pos, graphic in imgs)
        )

    def move_tile (self, start_x, start_y):
        """Move the given tile to the missing tile."""
        # set the tile's new position
        dest_x, dest_y = self.missing
        orig_pos, graphic = self.tiles[start_x][start_y]
        self.tiles[dest_x][dest_y] = (orig_pos, graphic)
        # mark the original position as missing
        self.missing = (start_x, start_y)
        self.tiles[start_x][start_y] = None

        # get graphic's new on-screen position
        screen_x, screen_y = self.grid.tile_pos(dest_x, dest_y)
        screen_x += conf.TILE_GAP[0]
        screen_y += conf.TILE_GAP[1]
        # move the graphic
        graphic.interp_to((screen_x, screen_y))

    def move (self, axis, dirn, evts):
        for i in xrange(evts[evt.bmode.DOWN]):
            # get the tile to move
            start = list(self.missing)
            start[axis] -= dirn
            x, y = start
            # check if the tile exists
            if x < 0 or x >= conf.N_TILES[0] or y < 0 or y >= conf.N_TILES[1]:
                # the tile is out of bounds
                return
            # move the tile
            self.move_tile(x, y)

    def click (self, evts):
        # get the tile clicked on
        x, y = pg.mouse.get_pos()
        tile = self.grid.tile_at(x - conf.TILE_GAP[0], y - conf.TILE_GAP[1])
        if tile is None:
            # clicked on the gap between tiles, so do nothing
            return
        x, y = tile
        for i in xrange(evts[evt.bmode.DOWN]):
            if self.tiles[x][y] is None:
                # this is the missing tile
                break
            # make sure the clicked tile is next to the missing tile
            if tuple(self.missing) not in ((x - 1, y), (x, y - 1), (x + 1, y),
                                           (x, y + 1)):
                # it's not
                break
            self.move_tile(x, y)


if __name__ == '__main__':
    # add our settings to the main settings object
    conf.add(Conf)
    # set the window size
    conf.RES_W = (conf.IMG_SIZE[0] + conf.TILE_GAP[0],
                  conf.IMG_SIZE[1] + conf.TILE_GAP[1])
    # make the mouse visible
    conf.MOUSE_VISIBLE[Puzzle.id] = True
    # initialise the engine
    engine.init()
    # run with a Puzzle as the world
    engine.game.run(Puzzle)
    # now we're finished: quit the engine
    engine.quit()