Brick-Breaker on UNIHIKER

0 879 Medium

Also in this tutorial I will show you how to develop a game for UNIHIKER with PyGame. But this time we can do without pictures! The development of this game will be very simple, using the accelerometer and a button to cancel/exit the game.

HARDWARE LIST
1 UNIHIKER
STEP 1
Develop the game template
CODE
from sys import exit
from time import sleep
from random import choice

import pygame
from pinpong.board import Board, Pin, Tone
from pinpong.extension.unihiker import button_a, accelerometer


SCREEN_WIDTH = 240
SCREEN_HEIGHT = 320
SOUND_DURATION = .05
COLLISION_TOLERANCE = 5
COLORS = ['black', 'white', 'blue', 'green', 'red']
FPS = 50


if __name__ == '__main__':
    # initialize board
    Board().begin()

    # initialize and configure pygame
    pygame.init()
    pygame.display.set_caption('BrickBreaker')
    pygame.event.set_allowed(None)
    pygame.mouse.set_visible(False)

    # create screen surface, clock object and font
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    clock = pygame.time.Clock()

    while True:
        # exit game loop conditions
        if button_a.is_pressed():
            break

        # set background color
        screen.fill(pygame.Color(COLORS[0]))

        # render game
        pygame.display.flip()
        clock.tick(FPS)

    # stop application
    pygame.quit()
    exit()

At the beginning a very simple template is created. This template can even already be executed. However, only a black screen is displayed, which is ended by pressing the A button.

In the first lines all necessary Python modules/packages are imported. In addition, constants are created and given a fixed value for them.

The first condition "if __name__ == '__main__':" is met as soon as you run the program directly. So you cannot easily call the program from other Python programs.

If the condition is met, PinBoard and PyGame are initialized. PyGame is then configured a bit. The actual game is inside the "while" loop. If button A is pressed, the loop will be broken and the game/program will end.

STEP 2
Striker class
CODE
class Striker(pygame.sprite.Sprite):
    def __init__(self):
        """
        Striker constructor
        """
        super().__init__()
        self._width = 40
        self._height = 5
        self._pos_x = SCREEN_WIDTH / 2
        self._pos_y = SCREEN_HEIGHT - self._height - 5
        self._speed = 10

        self.image = pygame.Surface([self._width, self._height])
        self.image.fill(COLORS[1])
        self.rect = self.image.get_rect()
        self.rect.center = [self._pos_x, self._pos_y]

    @staticmethod
    def _get_direction() -> str:
        """
        get accelerometer direction as string
        :return: str
        """
        y_accel = int(accelerometer.get_y() * 100)

        if y_accel < -10:
            return 'right'
        elif y_accel > 10:
            return 'left'
        else:
            return 'middle'

    def update(self) -> None:
        """
        set striker position
        :return: None
        """
        direction = Striker._get_direction()

        if direction == 'left' and self._pos_x > (self._width / 2):
            self._pos_x -= self._speed

        if direction == 'right' and self._pos_x < (SCREEN_WIDTH - self._width / 2):
            self._pos_x += self._speed

        self.rect.center = [self._pos_x, self._pos_y]

With the class "Striker" you create the paddle that should stop the ball on the lower screen and smash it back. The paddle will move horizontally and is controlled by the user via an accelerometer sensor.

The accelerometer values ​​are read out again and again in the static method. The update method then changes the paddle position.

STEP 3
Ball class
CODE
class Ball(pygame.sprite.Sprite):
    def __init__(self):
        """
        Ball constructor
        """
        super().__init__()
        self._pos_x = None
        self._pos_y = None
        self.speed_x = None
        self.speed_y = None

        self.reset()

        self.image = pygame.Surface([10, 10])
        self.image.fill(COLORS[0])
        self.image.set_colorkey(COLORS[0])

        pygame.draw.circle(self.image, COLORS[1], (5, 5), 5)

        self.rect = self.image.get_rect()
        self.rect.center = [self._pos_x, self._pos_y]

    def reset(self) -> None:
        """
        reset ball x, y-position and x, y-speed
        :return: None
        """
        self._pos_x = SCREEN_WIDTH / 2
        self._pos_y = SCREEN_HEIGHT / 2
        self.speed_x = choice([-3, 3])
        self.speed_y = choice([-3, 3])

    def update(self) -> None:
        """
        set ball position
        :return: None
        """
        self._pos_x += self.speed_x
        self._pos_y += self.speed_y

        self.rect.center = [self._pos_x, self._pos_y]

The class “Ball” is used to create the ball and to update the position on the screen.

The method reset will help to setup on start and if ball get lost, to have a start position.

STEP 4
Brick class
CODE
class Brick(pygame.sprite.Sprite):
    def __init__(self, x: int, y: int, w: int, h: int, c: tuple):
        """
        Brick constructor
        :param x: x-position on screen
        :param y: y-position on screen
        :param w: width in px
        :param h: height in px
        :param c: color
        """
        super().__init__()
        self._width = w
        self._height = h
        self._pos_x = x
        self._pos_y = y

        self.image = pygame.Surface([self._width, self._height])
        self.image.fill(c)
        self.rect = self.image.get_rect()
        self.rect.center = [self._pos_x, self._pos_y]

So that you collect points and the ball doesn't just fly around (as in pong), the game still needs small stones which are destroyed by the ball.

A separate object of this class is created for each stone that we want to create.

STEP 5
Buzzer class
CODE
class Buzzer:
    def __init__(self, pin):
        """
        Buzzer constructor
        :param pin: pin object for buzzer
        """
        self._tone = Tone(Pin(pin))
        self._tone.freq(800)

    def play_tone(self) -> None:
        """
        play buzzer sound
        :return: None
        """
        self._tone.on()
        sleep(SOUND_DURATION)
        self._tone.off()

It gets boring without sound! Since UNIHIKER has already installed a buzzer, we use it too!

STEP 6
generate_bricks function
CODE
def generate_bricks(group, x: int) -> None:
    """
    generate all blocks
    :param group: sprite group object
    :param x: start x-position
    :return: None
    """
    start_y_values = [55, 75, 95]
    colors = [COLOR_BLUE, COLOR_GREEN, COLOR_RED]

    for start_y, color in zip(start_y_values, colors):
        start_x = x
        for _ in range(7):
            brick = Brick(x=start_x, y=start_y, w=20, h=10, c=color)
            group.add(brick)
            start_x += 30

In the game there should be several small stones. This function will save us a lot of work (code) when creating these bricks.

STEP 7
Objects and game logic
CODE
if __name__ == '__main__':
    # initialize board
    Board().begin()

    # initialize and configure pygame
    pygame.init()
    pygame.display.set_caption('BrickBreaker')
    pygame.event.set_allowed(None)
    pygame.mouse.set_visible(False)

    # create screen surface, clock object and font
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("Arial", 15)

    # variables
    score = 0
    lives = 3

    # create striker object
    striker = Striker()
    striker_group = pygame.sprite.Group()
    striker_group.add(striker)

    # create ball object
    ball = Ball()
    ball_group = pygame.sprite.Group()
    ball_group.add(ball)

    # create brick objects
    bricks_group = pygame.sprite.Group()
    generate_bricks(bricks_group, x=30)

    # buzzer sound
    sound = Buzzer(pin=Pin.P26)

    while True:
        # exit game loop conditions
        if button_a.is_pressed():
            break

        if lives == 0:
            break

        # set background color
        screen.fill(pygame.Color(COLORS[0]))

        # draw striker
        striker_group.update()
        striker_group.draw(screen)

        # draw ball
        if ball.rect.left <= 0 or ball.rect.right >= SCREEN_WIDTH:
            ball.speed_x *= -1

        if ball.rect.top <= 25:
            ball.speed_y *= -1

        if pygame.sprite.collide_rect(striker, ball):
            if ball.rect.bottom - striker.rect.top < COLLISION_TOLERANCE and ball.speed_y > 0:
                ball.speed_y *= -1
                sound.play_tone()

        if ball.rect.top > SCREEN_HEIGHT:
            lives -= 1
            ball.reset()

        ball_group.update()
        ball_group.draw(screen)

        # draw bricks
        if not len(bricks_group):
            generate_bricks(bricks_group, x=30)

        for block in bricks_group:
            if pygame.sprite.collide_rect(ball, block):
                score += 1
                sound.play_tone()

                if abs(ball.rect.top - block.rect.bottom) < COLLISION_TOLERANCE and ball.speed_y < 0:
                    ball.speed_y *= -1

                if abs(ball.rect.bottom - block.rect.top) < COLLISION_TOLERANCE and ball.speed_y > 0:
                    ball.speed_y *= -1

                if abs(ball.rect.right - block.rect.left) < COLLISION_TOLERANCE and ball.speed_x > 0:
                    ball.speed_x *= -1

                if abs(ball.rect.left - block.rect.right) < COLLISION_TOLERANCE and ball.speed_x < 0:
                    ball.speed_x *= -1

                bricks_group.remove(block)

        bricks_group.draw(screen)

        # user information
        info_text = f'Lives: {lives} - Score: {score}'
        screen_text = font.render(info_text, True, COLORS[1])
        screen.blit(screen_text, (5, 5))
        pygame.draw.line(screen, COLORS[1], (5, 25), (SCREEN_WIDTH - 5, 25), 1)

        # render game
        pygame.display.flip()
        clock.tick(FPS)

Let's expand the template. Add a few variables (score, lives) as well as all objects (Font, Striker, Ball, Bricks, Sound) before the loop starts. 

Inside the loop:

The ball bounces off the walls on the top, right, and left. If the ball leaves the lower play area, one life is deducted. In addition, the loop is also interrupted when the lives are no longer greater than 0.

If the ball touches the paddle, it is reflected back. If the ball touches a stone, it is destroyed and a point is awarded. When all stones have been destroyed, they are regenerated.

The information about the lives and the points are displayed to the player. As soon as the ball touches the paddle or a stone, a tone is also emitted.

 

STEP 8
The complete application
CODE
from sys import exit
from time import sleep
from random import choice

import pygame
from pinpong.board import Board, Pin, Tone
from pinpong.extension.unihiker import button_a, accelerometer


SCREEN_WIDTH = 240
SCREEN_HEIGHT = 320
SOUND_DURATION = .05
COLLISION_TOLERANCE = 5
COLORS = ['black', 'white', 'blue', 'green', 'red']
FPS = 50


class Striker(pygame.sprite.Sprite):
    def __init__(self):
        """
        Striker constructor
        """
        super().__init__()
        self._width = 40
        self._height = 5
        self._pos_x = SCREEN_WIDTH / 2
        self._pos_y = SCREEN_HEIGHT - self._height - 5
        self._speed = 10

        self.image = pygame.Surface([self._width, self._height])
        self.image.fill(COLORS[1])
        self.rect = self.image.get_rect()
        self.rect.center = [self._pos_x, self._pos_y]

    @staticmethod
    def _get_direction() -> str:
        """
        get accelerometer direction as string
        :return: str
        """
        y_accel = int(accelerometer.get_y() * 100)

        if y_accel < -10:
            return 'right'
        elif y_accel > 10:
            return 'left'
        else:
            return 'middle'

    def update(self) -> None:
        """
        set striker position
        :return: None
        """
        direction = Striker._get_direction()

        if direction == 'left' and self._pos_x > (self._width / 2):
            self._pos_x -= self._speed

        if direction == 'right' and self._pos_x < (SCREEN_WIDTH - self._width / 2):
            self._pos_x += self._speed

        self.rect.center = [self._pos_x, self._pos_y]


class Ball(pygame.sprite.Sprite):
    def __init__(self):
        """
        Ball constructor
        """
        super().__init__()
        self._pos_x = None
        self._pos_y = None
        self.speed_x = None
        self.speed_y = None

        self.reset()

        self.image = pygame.Surface([10, 10])
        self.image.fill(COLORS[0])
        self.image.set_colorkey(COLORS[0])

        pygame.draw.circle(self.image, COLORS[1], (5, 5), 5)

        self.rect = self.image.get_rect()
        self.rect.center = [self._pos_x, self._pos_y]

    def reset(self) -> None:
        """
        reset ball x, y-position and x, y-speed
        :return: None
        """
        self._pos_x = SCREEN_WIDTH / 2
        self._pos_y = SCREEN_HEIGHT / 2
        self.speed_x = choice([-3, 3])
        self.speed_y = choice([-3, 3])

    def update(self) -> None:
        """
        set ball position
        :return: None
        """
        self._pos_x += self.speed_x
        self._pos_y += self.speed_y

        self.rect.center = [self._pos_x, self._pos_y]


class Brick(pygame.sprite.Sprite):
    def __init__(self, x: int, y: int, w: int, h: int, c: tuple):
        """
        Brick constructor
        :param x: x-position on screen
        :param y: y-position on screen
        :param w: width in px
        :param h: height in px
        :param c: color
        """
        super().__init__()
        self._width = w
        self._height = h
        self._pos_x = x
        self._pos_y = y

        self.image = pygame.Surface([self._width, self._height])
        self.image.fill(c)
        self.rect = self.image.get_rect()
        self.rect.center = [self._pos_x, self._pos_y]


class Buzzer:
    def __init__(self, pin):
        """
        Buzzer constructor
        :param pin: pin object for buzzer
        """
        self._tone = Tone(Pin(pin))
        self._tone.freq(800)

    def play_tone(self) -> None:
        """
        play buzzer sound
        :return: None
        """
        self._tone.on()
        sleep(SOUND_DURATION)
        self._tone.off()


def generate_bricks(group, x: int) -> None:
    """
    generate all blocks
    :param group: sprite group object
    :param x: start x-position
    :return: None
    """
    start_y_values = [55, 75, 95]
    colors = [COLORS[2], COLORS[3], COLORS[4]]

    for start_y, color in zip(start_y_values, colors):
        start_x = x
        for _ in range(7):
            brick = Brick(x=start_x, y=start_y, w=20, h=10, c=color)
            group.add(brick)
            start_x += 30


if __name__ == '__main__':
    # initialize board
    Board().begin()

    # initialize and configure pygame
    pygame.init()
    pygame.display.set_caption('BrickBreaker')
    pygame.event.set_allowed(None)
    pygame.mouse.set_visible(False)

    # create screen surface, clock object and font
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("Arial", 15)

    # variables
    score = 0
    lives = 3

    # create striker object
    striker = Striker()
    striker_group = pygame.sprite.Group()
    striker_group.add(striker)

    # create ball object
    ball = Ball()
    ball_group = pygame.sprite.Group()
    ball_group.add(ball)

    # create brick objects
    bricks_group = pygame.sprite.Group()
    generate_bricks(bricks_group, x=30)

    # buzzer sound
    sound = Buzzer(pin=Pin.P26)

    while True:
        # exit game loop conditions
        if button_a.is_pressed():
            break

        if lives == 0:
            break

        # set background color
        screen.fill(pygame.Color(COLORS[0]))

        # draw striker
        striker_group.update()
        striker_group.draw(screen)

        # draw ball
        if ball.rect.left <= 0 or ball.rect.right >= SCREEN_WIDTH:
            ball.speed_x *= -1

        if ball.rect.top <= 25:
            ball.speed_y *= -1

        if pygame.sprite.collide_rect(striker, ball):
            if ball.rect.bottom - striker.rect.top < COLLISION_TOLERANCE and ball.speed_y > 0:
                ball.speed_y *= -1
                sound.play_tone()

        if ball.rect.top > SCREEN_HEIGHT:
            lives -= 1
            ball.reset()

        ball_group.update()
        ball_group.draw(screen)

        # draw bricks
        if not len(bricks_group):
            generate_bricks(bricks_group, x=30)

        for block in bricks_group:
            if pygame.sprite.collide_rect(ball, block):
                score += 1
                sound.play_tone()

                if abs(ball.rect.top - block.rect.bottom) < COLLISION_TOLERANCE and ball.speed_y < 0:
                    ball.speed_y *= -1

                if abs(ball.rect.bottom - block.rect.top) < COLLISION_TOLERANCE and ball.speed_y > 0:
                    ball.speed_y *= -1

                if abs(ball.rect.right - block.rect.left) < COLLISION_TOLERANCE and ball.speed_x > 0:
                    ball.speed_x *= -1

                if abs(ball.rect.left - block.rect.right) < COLLISION_TOLERANCE and ball.speed_x < 0:
                    ball.speed_x *= -1

                bricks_group.remove(block)

        bricks_group.draw(screen)

        # user information
        info_text = f'Lives: {lives} - Score: {score}'
        screen_text = font.render(info_text, True, COLORS[1])
        screen.blit(screen_text, (5, 5))
        pygame.draw.line(screen, COLORS[1], (5, 25), (SCREEN_WIDTH - 5, 25), 1)

        # render game
        pygame.display.flip()
        clock.tick(FPS)

    # stop application
    pygame.quit()
    exit()

That's already the code (with only about 275 lines) for the game! Simply upload to the UNIHIKER, select in the menu and start playing.

License
All Rights
Reserved
licensBg
0