maze

The game screen appears here if your browser supports the Canvas API.

Attribution

How not to code: a guide to concise programming, pages 58-63, by Andrew Gillett.

Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported.

Original Python code


import pgzrun, pygame
from pgzero.actor import Actor
from abc import ABC, abstractmethod

GRID = ['XXXXXXXXX',
        'X       X',
        'X X X X X',
        'X       X',
        'XXXXXXXXX']

GRID_SQ_SIZE = 64
HALF_GRID_SQ_SIZE = 64 // 2
SPEED = 2

# Convert pixel coordinates to grid coordinates
def get_grid_pos(x, y):
    return x // GRID_SQ_SIZE, y // GRID_SQ_SIZE

def move_towards(n, target, speed):
    if n < target:
        return min(n + speed, target)
    else:
        return max(n - speed, target)

class Player(Actor):
    def __init__(self, pos, controls):
        super().__init__('player', pos, anchor=('left', 'top'))
        self.controls = controls

    def update(self):
        x_dir = self.controls.get_x_dir()
        if x_dir == 0:
            y_dir = self.controls.get_y_dir()
        else:
            y_dir = 0

        if x_dir != 0 or y_dir != 0:
            self.move(x_dir, y_dir)

    def move(self, x_dir, y_dir):
        horizontal = x_dir != 0

        centre_x = int(self.x) + HALF_GRID_SQ_SIZE
        centre_y = int(self.y) + HALF_GRID_SQ_SIZE

        # Determine leading edge
        if horizontal:
            new_leading_edge_x = int(self.x) - SPEED if x_dir < 0 else int(self.x) + GRID_SQ_SIZE + SPEED - 1
            new_grid_x, new_grid_y = get_grid_pos(new_leading_edge_x, centre_y)
        else:
            new_leading_edge_y = int(self.y) - SPEED if y_dir < 0 else int(self.y) + GRID_SQ_SIZE + SPEED - 1
            new_grid_x, new_grid_y = get_grid_pos(centre_x, new_leading_edge_y)

        if GRID[new_grid_y][new_grid_x] == ' ':
            # The square ahead does not have a wall
            self.x += x_dir * SPEED
            self.y += y_dir * SPEED

            # Lane alignment
            # If we're going horizontally, we want to align on the Y axis and vice versa
            grid_x, grid_y = get_grid_pos(centre_x, centre_y)
            if horizontal:
                self.y = move_towards(self.y, grid_y * GRID_SQ_SIZE, 1)
            else:
                self.x = move_towards(self.x, grid_x * GRID_SQ_SIZE, 1)

class Controls(ABC):
    @abstractmethod
    def get_x_dir(self):
        pass

    @abstractmethod
    def get_y_dir(self):
        pass

class KeyboardControls(Controls):
    def get_x_dir(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            return -1
        elif keys[pygame.K_RIGHT]:
            return 1
        else:
            return 0

    def get_y_dir(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_UP]:
            return -1
        elif keys[pygame.K_DOWN]:
            return 1
        else:
            return 0

class JoystickControls(Controls):
    def __init__(self, joystick_id):
        self.joystick = pygame.joystick.Joystick(joystick_id)

    def get_axis_dir(self, axis_id):
        axis_value = self.joystick.get_axis(axis_id)
        if abs(axis_value) < 0.5:
            return 0
        else:
            return 1 if axis_value > 0 else -1

    def get_x_dir(self):
        return self.get_axis_dir(0)

    def get_y_dir(self):
        return self.get_axis_dir(1)

WIDTH,HEIGHT = 576,320

controls = JoystickControls(0) if pygame.joystick.get_count() > 0 else KeyboardControls()
player = Player( (64,64), controls )

def update():
    player.update()

def draw():
    screen.clear()

    for row_index in range(len(GRID)):
        for column_index in range(len(GRID[row_index])):
            if GRID[row_index][column_index] != ' ':
                x, y = column_index * GRID_SQ_SIZE, row_index * GRID_SQ_SIZE
                screen.blit('gridblock',(x,y))

    player.draw()

pgzrun.go()