Lab 2 - Code

🧩 Syntax:
#!/usr/bin/env python
# -----------------
# Modified 2018-09-30 to use Matt Hawkins' LCD library for I2C available
# from https://bitbucket.org/MattHawkinsUK/rpispy-misc/raw/master/python/lcd_i2c.py
#
# A little test program to demo/test the ability to manage custom characters
# on a 44780 display.
# 2019-09-21   HME    Initial entry
#
# Modified by Joshua Simard, 9/7/2023
#

# The GPIO library. This program doesn't use it but most COMP430 code does
import RPi.GPIO as GPIO
# The Python time library
import time
# Random numbers are something you can count on
from random import *
# Game functions - these let you play sounds, read the keyboard and display
# images and geometric graphics
import pygame

# Additional stuff for LCD
import smbus


#LCD pin assignments, constants, etc
I2C_ADDR  = 0x27 # I2C device address
LCD_WIDTH = 16   # Maximum characters per line

# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command

LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off

ENABLE = 0b00000100 # Enable it

# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005

# LCD commands
LCD_CMD_4BIT_MODE = 0x28   # 4 bit mode, 2 lines, 5x8 font
LCD_CMD_CLEAR = 0x01
LCD_CMD_HOME = 0x02   # goes to position 0 in line 0
LCD_CMD_POSITION = 0x80  # Add this to DDRAM address

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)       # Numbers GPIOs by standard marking

#Open I2C interface
#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1

# Code for push button(s) is below
# The two values below are what we use to determine what pin the buttons are connected to
buttonPin = 17
buttonPin2 = 22
# Here, we will set up the buttons and set them to Pull Up
GPIO.setup(buttonPin, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(buttonPin2, GPIO.IN, pull_up_down = GPIO.PUD_UP)

def lcd_init():
  # Initialise display
  lcd_byte(0x33,LCD_CMD) # 110011 Initialise
  lcd_byte(0x32,LCD_CMD) # 110010 Initialise
  lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
  lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
  lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
  lcd_byte(0x01,LCD_CMD) # 000001 Clear display
  time.sleep(E_DELAY)

def lcd_byte(bits, mode):
  # Send byte to data pins
  # bits = the data
  # mode = 1 for data
  #        0 for command

  bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
  bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT

  # High bits
  bus.write_byte(I2C_ADDR, bits_high)
  lcd_toggle_enable(bits_high)

  # Low bits
  bus.write_byte(I2C_ADDR, bits_low)
  lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
  # Toggle enable
  time.sleep(E_DELAY)
  bus.write_byte(I2C_ADDR, (bits | ENABLE))
  time.sleep(E_PULSE)
  bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
  time.sleep(E_DELAY)

# This takes a single byte (n) and rearranges the lower bits 0-4
# by swaping bits 0/4 and 1/3
def horz_flip_char(n):
  n2 = n & 0b00000100  # start with bit 2
  n2 = n2 | ((n & 0b00010000) >> 4) # move bit 4 to bit 0
  n2 = n2 | ((n & 0b00001000) >> 2) # move bit 3 to bit 1
  n2 = n2 | ((n & 0b00000010) << 2) # move bit 1 to bit 3
  n2 = n2 | ((n & 0b00000001) << 4) # move bit 0 to bit 4
  return n2

def custom_chars():
  lcd_byte(0x40, LCD_CMD) # set the memory address to 00 (start of CGRAM)
  # For this example, this is the only custom char we will use.
  lcd_byte(0b00001110, LCD_CHR) # ____XXX_
  lcd_byte(0b00011111, LCD_CHR) # ___XXXXX
  lcd_byte(0b00011111, LCD_CHR) # ___XXXXX
  lcd_byte(0b00011111, LCD_CHR) # ___XXXXX
  lcd_byte(0b00011111, LCD_CHR) # ___XXXXX
  lcd_byte(0b00011111, LCD_CHR) # ___XXXXX
  lcd_byte(0b00001110, LCD_CHR) # ____XXX_
  lcd_byte(0b00000000, LCD_CHR) # ________

def lcd_string(message,line):
  # Send string to display

  message = message.ljust(LCD_WIDTH," ")
  lcd_byte(line, LCD_CMD)
  for i in range(LCD_WIDTH):
    lcd_byte(ord(message[i]),LCD_CHR)

# functions not in the original library
# -------------------------------------

# Positions the cursor so that the next write to the LCD
# appears at a specific row & column, 0-org'd
def lcd_xy(col, row):
        lcd_byte(LCD_CMD_POSITION+col+(64*row), LCD_CMD)

# Begins writing a string to the LCD at the current cursor
# position. It doesn't concern itself with whether the cursor
# is visible or not. Go off the screen? Your bad.
def lcd_msg(msg_string):
        for i in range(0, len(msg_string)):
                lcd_byte(ord(msg_string[i]), LCD_CHR)

pygame.init()   # Sets up pygame

lcd_init()  # set up the LCD and clear the screen
lcd_string("JSimard", LCD_LINE_1)
lcd_string("       Fall 2023", LCD_LINE_2)

custom_chars()  # load in our custom char



# This variable is used to determine the Y position, I.E. if the player is jumping
# Note that we do not need to record the X value for the player, because it will always be 1
player_pos_y = 1

# A list to hold the barriers that are on screen
# We use two lists, one of the X position and one of the Y
# Note that the barriers will always be evenly spaced along the X axis
barrier_poses_x = [4, 8, 12, 16]
barrier_poses_y = [0, 1, 0, 1]

# The following timers will be used to determine the game speed and final score.
# Because the 'time' function simply returns an actual unit of time,
# We cannot use it like as if the timer had started at the beginning of the program.
# Instead, we record the last time, and then when we need to retrieve the time, we
# subtract the current time from that.
last_timer = 0

# This will be used to determine your final score, among other things
game_start_time = int(time.time()*1000)

# This variable determines the interval at which the sprites move
game_speed = 500

# Define the gameover variable
gameOver = False

# Place the player character on the screen
# This could be removed without any issues, but I find it helpful
lcd_xy(1, player_pos_y)
lcd_msg(chr(0))

# Give the player a bit of time to prepare for game start
time.sleep(5)


while not gameOver:

        # Adding a short sleep helps not burn through excessive CPU cycles, and also assists with Debounce.
        time.sleep(.1)

        # Update the timer...
        timer = int(time.time()*1000)

        # All controls for the character are below
        if(0 == GPIO.input(buttonPin) and player_pos_y == 1):
          print('Up Button Pressed')

          # Generate the new position we want the player to be
          # In this case, one position upwards.
          newPos = player_pos_y - 1

          # Remove the old player
          lcd_xy(1, player_pos_y)
          lcd_msg(" ")

          # Find the coords for the new player
          lcd_xy(1, newPos)

          # Place the player from our custom chars
          lcd_msg(chr(0))
          player_pos_y = newPos

        if (0 == GPIO.input(buttonPin2) and player_pos_y == 0):
          print('Down Button Pressed')
          # Generate the new position we want it at
          # Again, one position down this time
          newPos = player_pos_y + 1

          # Remove the old player
          lcd_xy(1, player_pos_y)
          lcd_msg(" ")

          # Find the coords for the new player
          lcd_xy(1, newPos)

          #Place the player from our custom chars
          lcd_msg(chr(0))
          player_pos_y = newPos

        # Code for the barriers is below
        # if we've elapsed the game update interval...
        if(timer - last_timer >= game_speed):
          # Step through numbers 0 through 4 (not including 4)
          for i in range(4):
            print(i)

            # Generate the new position we want it at
            newBarrierPos = barrier_poses_x[i]
            newBarrierPos = newBarrierPos - 1

            # Remove the old barrier
            # This also checks if the player has hit a barrier, by checking to see
            # if the position of any barrier is exactly equal to the position of the player
            if(barrier_poses_x[i] == 1 and barrier_poses_y[i] == player_pos_y):
              gameOver = True
            else:
              last_timer = timer

              # Find the coords for the new barrier
              lcd_xy(newBarrierPos, barrier_poses_y[i])

              # Place the barrier from our custom chars
              lcd_msg(chr(0))
              lcd_xy(barrier_poses_x[i], barrier_poses_y[i])
              lcd_msg(" ")
            # Update the list to reflect the new position
            barrier_poses_x[i] = newBarrierPos

            # Handle barriers hitting the end of the playing area
            if(barrier_poses_x[i] < 0):
              barrier_poses_x[i] = 16
              # Reset the Y value of the barrier to random
              barrier_poses_y[i] = randint(0, 1)
        # If more than 2 seconds has elapsed, increase the game speed
        if(int(time.time()*1000) - game_start_time) > 2000:
          game_speed = 300

# The final score is equal to the total game time divided by 20.
score = round((int(time.time()*1000) - game_start_time) / 20)
print('Score: ', score)
lcd_string("GAME            ", LCD_LINE_1)
# First, make a new string which is the score plus a lot of whitespace
finalScore = str(score) + "               "
# Then, truncate it to 11 characters, and add 'over' to the end
finalScore = finalScore[0:12] + "OVER"
# Now print it to the screen
lcd_string(finalScore, LCD_LINE_2)
Guest

Guest