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)