Project Overview

Python Space Shooter with BrainPad Pulse

Embark on an exciting journey into the Games domain with our Python Space Shooter, seamlessly integrated with the BrainPad Pulse microcomputer. This project showcases the compatibility between Python programming and BrainPad microcomputers, bringing the game development field’s timeless excitement with the electronic circuits and microcomputers.

How It Works

the spaceship will move on the X-axis, navigating randomly while taking on three aliens. Armed with one bullet at a time, you must shoot the aliens to prevent them from reaching your space. Be cautious; each alien breach costs you one of your three lives.

Hardware Requirements

The Enjoy a straightforward setup with minimal hardware requirements – all you need is a BrainPad Pulse and a standard USB cable for a quick and easy connection.

Software Requirements

Executing this project successfully requires a Python version 3.10 or above. Additionally, the DUE Link library using the command:

pip install DUELink

Code Overview

The first module is Sprite.py, which contains the init method serving as the constructor method to initialize the attributes of an instance of the class.

Properties:

  • Name: representing the name of the sprite.
  • X and Y: representing the coordinates of the sprite on a 2D plane.
  • Height and Width: representing the height and width of the sprite.
  • Image: An array of integers representing the image data of the sprite.
  • Character: A property representing a character associated with the sprite.
class Sprite:
    def __init__(self, name="", x=0, y=0, height=0, width=0, image=None):
        self.Name = name
        self.X = x
        self.Y = y
        self.Height = height
        self.Width = width
        self.Image = image if image is not None else []
        self.Character = ""

Let’s dive into the SpriteMaster.py module that’s has a SpriteMaster class and its methods:

  • __init__(self, bp):This method is the constructor for the SpriteMaster class. It initializes various attributes that will be used to control the game loop and manage game state.
  • self.bp: Represents an external object, possibly related to the game’s hardware or display. It is assumed to be passed as an argument during the instantiation of SpriteMaster.
  • self.enemy, self.enemy2, self.enemy3: Instances of the Sprite class representing three different enemies. Each enemy has a starting position, size, and an associated image.
  • self.bullet: An instance of the Sprite class representing the player’s bullet. It starts with a position and image, and its Y-coordinate is adjusted during the game loop to simulate movement.
  • self.player: An instance of the Sprite class representing the player. It has a starting position, size, and an associated image.
  • self.score, self.lives: Variables to keep track of the player’s score and remaining lives.
  • self.enemyAlive, self.enemyAlive2, self.enemyAlive3: Boolean flags indicating whether the respective enemies are currently alive.
  • self.BulletIsOut: A flag to indicate whether the bullet is currently on the screen.
  • self.enemySpeed1, self.enemySpeed2, self.enemySpeed3: Speeds of the enemies in the X-axis. These values control how fast the enemies move horizontally.
  • DoGame(self) This method is the main game loop responsible for updating the game state, handling user input, and rendering the display.

Rendering:

  • Clears the display.
  • Draws a line on the display to separate the player’s area from the game area.
  • Displays the player’s remaining lives as “^” symbols.
  • Displays the player’s score.

Bullet Movement:

  • Moves the bullet upward on the screen.
  • Checks if the bullet is out of the screen; if so, sets BulletIsOut to False.

Collision Detection:

  • Checks if the bullet collides with any of the enemies and updates the score accordingly.
  • If an enemy is hit, sets the corresponding enemyAlive flag to False.

Enemy Movement:

  • Moves the enemies horizontally and updates their positions.
  • Changes the direction of enemy movement when an enemy reaches the screen boundaries.
  • If an enemy reaches the bottom of the screen, resets its position and decreases the player’s lives.

Player Input:

  • Checks if the ‘b’ button is pressed. If pressed, plays a beep sound, resets the bullet’s position, and sets BulletIsOut to True.

Display:

  • Calls the DrawSprite method to render the player, enemies, and bullet on the display.

Random Enemy Movement:

  • Randomly moves the player horizontally based on a random number.

DrawSprite(self, sprite)

  • This method is responsible for rendering a sprite on the display.
  • Iterates through the sprite’s image and sets pixels on the display based on the sprite’s position and image.
  • Uses the self.bp.Display.SetPixel method to set pixels with the appropriate color (white or black) based on the sprite’s image.
  • This method is crucial for visually representing the game’s entities (player, enemies, and bullets) on the display.
import random
import time

from Sprite import Sprite

class SpriteMaster():
    def __init__(self, bp):
        self.bp= bp
        self.row = 0
        self.enemy = Sprite()
        self.enemy2 = Sprite()
        self.enemy3 = Sprite()
        self.enemyAlive = True
        self.enemyAlive2 = True
        self.enemyAlive3 = True
        self.bullet = Sprite()
        self.player = Sprite()

        self.score = 0
        self.white = 1
        self.black = 0
        self.lives = 3
        self.BulletIsOut = True

        self.enemySpeed1 = 5
        self.enemySpeed2 = 5
        self.enemySpeed3 = 5

        self.fps_counter = 0
        self.fps_timer = time.time()

    def DoGame(self):
        frame1 = [1, 0, 0, 1, 1, 1, 1, 1, 0,
                  0, 1, 1, 1, 1, 1, 1, 1, 1,
                  1, 0, 0, 0, 0, 1, 0, 0, 0,
                  1, 1, 1, 1, 1, 1, 1, 1, 1,
                  0, 0, 1, 0, 0, 0, 0, 0, 1]

        frame2 = [1, 0, 0, 1, 1, 1, 1, 1, 0,
                  0, 1, 1, 1, 1, 1, 1, 1, 1,
                  1, 0, 1, 0, 0, 1, 1, 0, 0,
                  1, 1, 1, 1, 1, 1, 1, 1, 1,
                  0, 0, 1, 0, 0, 0, 0, 0, 1]

        bulletImage = [1, 0,
                       0, 1,
                       1, 0,
                       0, 1]

        playerImage = [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
                       0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
                       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

        self.bullet = Sprite("",150, 150, 2, 4, bulletImage)
        self.enemy = Sprite("",10, 10, 5, 9, frame1)
        self.enemy2 = Sprite("",20, 10, 5, 9, frame2)
        self.enemy3 = Sprite("",30, 10, 5, 9, frame1)
        self.player = Sprite("",75, 60, 4, 11, playerImage)

        while self.lives > 0:
            self.bp.Display.Clear(0)
            self.bp.Display.DrawLine(self.white, 103, 0, 103, 64)

            for i in range(self.lives):
                self.bp.Display.DrawText("^", self.white, 110, i * 10)

            self.bp.Display.DrawText(str(self.score), self.white, 110, 55)

            self.bullet.Y -= 12
            if self.bullet.Y < 0:
                self.BulletIsOut = False
            else:
                self.DrawSprite(self.bullet)
            if self.enemy.X - 3 <= self.bullet.X <= (self.enemy.X + self.enemy.Width) + 3 and \
                    self.enemy.Y - 3 <= self.bullet.Y <= self.enemy.Y + self.enemy.Height + 3:
                self.score += 10
                self.enemyAlive = False

            if self.enemy2.X - 3 <= self.bullet.X <= self.enemy2.X + self.enemy2.Width + 3 and \
                    self.enemy2.Y - 3 <= self.bullet.Y <= self.enemy2.Y + self.enemy2.Height + 3:
                self.score += 10
                self.enemyAlive2 = False

            if self.enemy3.X - 3 <= self.bullet.X <= self.enemy3.X + self.enemy3.Width + 3 and \
                    self.enemy3.Y - 3 <= self.bullet.Y <= self.enemy3.Y + self.enemy3.Height + 3:
                self.score += 10
                self.enemyAlive3 = False

            if not self.enemyAlive:
                self.enemyAlive = True
                self.enemy.X = 10
                self.enemy.Y = 10
            if not self.enemyAlive2:
                self.enemyAlive2 = True
                self.enemy2.X = 20
                self.enemy2.Y = 10
            if not self.enemyAlive3:
                self.enemyAlive3 = True
                self.enemy3.X = 30
                self.enemy3.Y = 10

            else:
                buttonshotB = self.bp.Button.JustPressed('b')
                if buttonshotB:
                    self.bp.System.Beep('p', 80, 100)
                    self.bullet.Y = 64
                    self.bullet.X = self.player.X + 5
                    self.BulletIsOut = True

            if self.enemyAlive:
                self.DrawSprite(self.enemy)
            if self.enemyAlive2:
                self.DrawSprite(self.enemy2)
            if self.enemyAlive3:
                self.DrawSprite(self.enemy3)

            self.DrawSprite(self.player)

            random_rocket = random.Random()
            rockerX = random_rocket.randint(0, 100)  # generate random number between 0 and 100

            if rockerX < 50:
                self.player.X += 5
            if rockerX > 50:
                self.player.X -= 5

            if self.player.X < 0:
                self.player.X = 0
            if self.player.X > 90:
                self.player.X = 90

            self.enemy.X += self.enemySpeed1
            self.enemy2.X += self.enemySpeed2
            self.enemy3.X += self.enemySpeed3

            if self.enemy.X < 5 or self.enemy.X > 85:
                self.enemySpeed1 *= -1
                self.enemy.Y += 5
            if self.enemy2.X < 5 or self.enemy2.X > 85:
                self.enemySpeed2 *= -1
                self.enemy2.Y += 5
            if self.enemy3.X < 5 or self.enemy3.X > 85:
                self.enemySpeed3 *= -1
                self.enemy3.Y += 5

            if self.enemy.Y > 56:
                self.enemy.X = 10
                self.enemy.Y = 10
                self.lives -= 1

            if self.enemy2.Y > 56:
                self.enemy2.X = 20
                self.enemy2.Y = 10
                self.lives -= 1

            if self.enemy3.Y > 56:
                self.enemy3.X = 30
                self.enemy3.Y = 10
                self.lives -= 1

            self.bp.Display.Show()

    def DrawSprite(self,sprite):
        index = 0
        row = -2
        while index <= len(sprite.Image) - 1:
            for y in range(sprite.Height):
                for x in range(sprite.Width):
                    if sprite.Image[index] == 1:
                        self.bp.Display.SetPixel(self.white, sprite.X + x, sprite.Y + row)
                    else:
                        self.bp.Display.SetPixel(self.black, sprite.X + x, sprite.Y + row)
                    index += 1
                row += 1
            row = 0

finally, the last script is Game.py

The script essentially sets up the hardware (BrainPad and its button) and initializes the game by creating an instance of SpriteMaster. Then, it starts the game loop by calling the DoGame method, allowing the game to run until the player runs out of lives.

  • Importing the SpriteMaster class from the SpriteMaster module.
  • Importing the DUELinkController class from the DUELink.DUELinkController module.
  • Invoking the method GetConnectionPort of the DUELinkController class to obtain the available port for communication.
  • Initializing an instance of the DUELinkController class with the obtained port.
  • Enabling the ‘b’ button on the BrainPad.
  • Creating an instance of the SpriteMaster class, passing the BrainPad instance as an argument.
  • Calling the DoGame method on the created SpriteMaster instance to initiate the game loop and start the game.
from SpriteMaster import SpriteMaster
from DUELink.DUELinkController import DUELinkController

availablePort = DUELinkController.GetConnectionPort()
BrainPad = DUELinkController(availablePort)

if __name__ == '__main__':
 
    BrainPad.Button.Enable('b', True)

    spritemasterdisplay = SpriteMaster(BrainPad)

    spritemasterdisplay.DoGame()

Customization:

  • Enhance Game Ending:

After losing all three lives, let’s make something special happen in the game—like showing the final score and playing a sound. Check out the Python library here.

  • Change Spaceship Movement:

Instead of moving randomly, let’s add buttons to control the spaceship. Connect them using alligator clips to the edge connector. this way, players can enjoy a more interactive gaming experience. See the BrainClip kit here for reference.

  • Explore Different Programming Languages with BrainPad Pulse:

If Python isn’t your go-to choices, you can create the same game using alternative languages such as Python, JavaScript, Due, and more. Discover the various coding options available here.