Space Invaders on RasPi with OLED and Capacitive Touch

This project implements Capacitive Touch to a Space Invaders game that runs on a Raspberry Pi.

BeginnerFull instructions provided1 hour2,764
Space Invaders on RasPi with OLED and Capacitive Touch

Things used in this project

Hardware components

Raspberry Pi 2 Model B
Raspberry Pi 2 Model B
With Power Supply and SD card loaded with Raspbian
×1
OD01
XinaBox OD01
×1
BR01
XinaBox BR01
×1
XinaBox SH01
×1
XC10
XinaBox XC10
×1

Story

Read more

Code

invaders.py

Python
This is the code, originally from Richard Hull's LUMA examples:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2014-17 Richard Hull and contributors
# See LICENSE.rst for details.
# PYTHON_ARGCOMPLETE_OK
# Cap Touch by XinaBox 
"""
Space Invaders demo.

Ported from:
https://gist.github.com/TheRayTracer/dd12c498e3ecb9b8b47f#file-invaders-py
"""
import os.path
import time
import random
from demo_opts import get_device
from PIL import Image
from luma.core.render import canvas
from luma.core.sprite_system import framerate_regulator

# Cap Touch modification Start
import sys
import smbus2
bus = smbus2.SMBus(1)
DEVICE_ADDRESS = 0x28
CAP_TOUCH_GENERAL_STATUS = 0x02
CAP_TOUCH_SENSOR_INPUT_STATUS = 0x03
bus.write_byte_data(DEVICE_ADDRESS, 0x27, 0x0)
bus.write_byte_data(DEVICE_ADDRESS, 0x21, 0x39)
bus.write_byte_data(DEVICE_ADDRESS, 0x0, 0x0)
# Cap Touch modification End


arrow = [0x04, 0x02, 0x01, 0x02, 0x04]
alien1 = [0x4C, 0x1A, 0xB6, 0x5F, 0x5F, 0xB6, 0x1A, 0x4C]
alien2 = [0x18, 0xFD, 0xA6, 0x3C, 0x3C, 0xA6, 0xFD, 0x18]
alien3 = [0xFC, 0x98, 0x35, 0x7E, 0x7E, 0x35, 0x98, 0xFC]
ARMY_SIZE_ROWS = 2
ARMY_SIZE_COLS = 6


class bullet(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.alive = False

    def render(self, draw):
        if self.alive:
            draw.line((self.x, self.y, self.x, self.y + 2), fill="white")

    def reset(self, x, y):
        self.x = x
        self.y = y
        self.alive = True
        return

    def update(self, direction):
        if self.alive:
            self.y = self.y + (direction * 4)
            if self.y < 10:
                self.alive = False


class player(object):
    def __init__(self):
        self.x = 48
        self.y = 54
        self.bullets = [bullet(0, 0) for _ in range(4)]

    def render(self, draw):
        for i in range(len(arrow)):
            line = arrow[i]
            for j in range(3):
                if line & 0x1:
                    draw.point((self.x - 2 + i, self.y + j), fill="white")
                line >>= 1

        for bullet in self.bullets:
            bullet.render(draw)

    def update(self, direction):
        t = self.x + (direction * 2)
        if t > 4 and t < 92:
            self.x = t
        for bullet in self.bullets:
            bullet.update(-1)

    def shoot(self):
        for bullet in self.bullets:
            if not bullet.alive:
                bullet.reset(self.x, self.y)
                break


class invader(object):
    def __init__(self, minx, maxx, x, y):
        self.x = x
        self.y = y
        self._direction = 1
        self.alive = True
        self.score = 10
        self._minx = minx
        self._maxx = maxx
        return

    def render(self, draw):
        if self.alive:
            for i in range(len(alien2)):
                line = alien2[i]
                for j in range(8):
                    if line & 0x1:
                        draw.point((self.x - 4 + i, self.y - 4 + j), "green")
                    line >>= 1

    def update(self):
        invaded = False
        if self.alive:
            t = self.x + self._direction
            if t > self._minx and t < self._maxx:
                self.x = self.x + self._direction
            else:
                self._direction = self._direction * -1
                self.y = self.y + 2
                if self.y > 44:
                    invaded = True
        return invaded


class army(object):
    def __init__(self):
        self.invaded = False
        self.invaders = []
        for i in range(ARMY_SIZE_ROWS):
            for j in range(ARMY_SIZE_COLS):
                minx = 4 + (j * 12)
                maxx = 30 + (j * 12)
                x = 4 + (j * 12)
                y = 14 + (i * 12)
                self.invaders.append(invader(minx, maxx, x, y))

    def render(self, draw):
        for invader in self.invaders:
            invader.render(draw)

    def update(self, bullets):
        for invader in self.invaders:
            if invader.update():
                self.invaded = True

        for invader in self.invaders:
            if invader.alive:
                for bullet in bullets:
                    if bullet.alive:
                        t = (invader.x - bullet.x) * (invader.x - bullet.x) + (invader.y - bullet.y) * (invader.y - bullet.y)
                        # if point is in circle
                        if t < 25:  # 5 * 5 = r * r
                            invader.alive = False
                            bullet.alive = False

    def size(self):
        size = 0
        for invader in self.invaders:
            if invader.alive:
                size += 1
        return size

    def score(self):
        score = 0
        for invader in self.invaders:
            if not invader.alive:
                score += invader.score
        return score


def ai_logic_shoot(army, plyr):
    for invader in army.invaders:
        if invader.alive:
            if plyr.x > (invader.x - 2) and plyr.x < (invader.x + 2):
                if random.random() < 0.75:
                    plyr.shoot()
                    return


def ai_logic_move(army, plyr, rows):
    for i in rows:
        invader = army.invaders[i]
        if invader.alive:
            if plyr.x < invader.x:
                plyr.update(1)
                return
            elif plyr.x > invader.x:
                plyr.update(-1)
                return
        i += 1


if __name__ == '__main__':

    device = get_device()

    if device.width < 96 or device.height < 64:
        raise ValueError("Unsupported mode: {0}x{1}".format(device.width, device.height))

    regulator = framerate_regulator()
    plyr = player()
    army = army()
    rows = random.sample(range(12), 12)

    img_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'images', 'splash.bmp'))
    splash = Image.open(img_path) \
        .transform((device.width, device.height), Image.AFFINE, (1, 0, 0, 0, 1, 0), Image.BILINEAR) \
        .convert(device.mode)

    try:
        # Double buffering in pygame?
        device.display(splash)
        device.display(splash)

        time.sleep(3)
        device.clear()

        while not army.invaded and army.size() > 0:
            with regulator:
                with canvas(device) as draw:
                    draw.line((0, 61, 95, 61), fill="white")
                    draw.line((0, 63, 95, 63), fill="white")

                    # Cap Touch Code Start
                    touch_type = bus.read_byte_data(DEVICE_ADDRESS,CAP_TOUCH_GENERAL_STATUS)
                    if touch_type == 0x01:
                        button = bus.read_byte_data(DEVICE_ADDRESS,CAP_TOUCH_SENSOR_INPUT_STATUS)
                        if button == 0x01:
                            sys.exit("User exit!")
                        if button == 0x20:
                            plyr.update(1)
                        if button == 0x08:
                            plyr.shoot()
                            plyr.update(0)
                        if button == 0x10:
                            plyr.update(-1)
                        bus.write_byte_data(DEVICE_ADDRESS, 0x0, 0x0)
                    # ai_logic_shoot(army, plyr)
                    # ai_logic_move(army, plyr, rows)
                    # Cap Touch Code End

                    army.update(plyr.bullets)

                    army.render(draw)
                    plyr.render(draw)

                    draw.text((8, 0), text="Score: {0}".format(army.score()), fill="blue")

        # Double buffering in pygame?
        for i in range(2):
            with canvas(device) as draw:
                if army.size() == 0:
                    draw.text((27, 28), text="Victory", fill="blue")
                else:
                    draw.text((30, 28), text="Defeat", fill="red")

        time.sleep(5)
    except KeyboardInterrupt:
        pass

Credits

Comments