/*
Hearts Chase Game Code
Player controls little octopus character that chases after hearts.
If you catch a heart, you get a point. If you hit a bomb, you lose a point.
Game end conditions - win if you get to 10 points, lose if you go below 0 points.
Made to run on arduino uno (or hero board) with LCD shield.
Written By: Jess D. Johnson, 08/09/2022
Octopus character design by Calvin Johnson
*/
#include <LiquidCrystal.h>
//Set up pins for the LCD display
const int RS = 8;
const int EN = 9;
const int d4 = 4;
const int d5 = 5;
const int d6 = 6;
const int d7 = 7;
const int pin_BL = 10; // arduino pin wired to LCD backlight circuit
// Initialize LCD
LiquidCrystal lcd( RS, EN, d4, d5, d6, d7);
// define some values used by the panel and buttons
int lcd_key = 0;
int adc_key_in = 0;
#define btnRIGHT 0
#define btnUP 1
#define btnDOWN 2
#define btnLEFT 3
#define btnSELECT 4
#define btnNONE 5
// octopus position counters
int o_x = random(16);
int o_y = random(2);
//heart position counters
int h_x = random(16);
int h_y = random(2);
//bomb1 position counters
int b_x = random(16);
int b_y = random(2);
//score counter
int score = 0;
//time counter
int timecount = 0;
// random value storage
int randval = 0;
// Creating custom character icons for the game
// smily face - used when octopus catches heart
byte smiley[8] = {
B00000,
B01010,
B00000,
B10001,
B10001,
B01110,
B00000,
};
// sad face - used when octopus hits bomb
byte sadFace[8] = {
B00000,
B01010,
B00000,
B01110,
B10001,
B10001,
B00000,
B00000
};
// Heart
byte fullHeart[8] = {
B00000,
B01010,
B11111,
B11111,
B01110,
B00100,
B00000
};
byte octopus[8] = {
B00100,
B01110,
B10101,
B11111,
B11011,
B01110,
B11011,
B10101
};
byte bomb[8] = {
B10101,
B10101,
B01110,
B11111,
B01110,
B10101,
B10101
};
int i = 0;
void setup() {
//initialize heart character
lcd.createChar(1, fullHeart);
//start-up game, displaying welcome screen
lcd.begin(16, 2);
lcd.setCursor(0,0);
lcd.print("Welcome to Heart");
for (int i=0; i<4; i++){
lcd.setCursor(i,1);
lcd.write(byte(1));
}
lcd.setCursor(5,1);
lcd.print("Chase!");
for (int i=15; i>11; i--){
lcd.setCursor(i,1);
lcd.write(byte(1));
}
delay(1500);
// start-up game, display game instructions
lcd.setCursor(0,0);
lcd.print("Catch the Heart!");
lcd.setCursor(0,1);
lcd.print("Avoid the Bomb! ");
delay(1500);
//initialize game characters
lcd.createChar(0, octopus);
lcd.createChar(1, fullHeart);
lcd.createChar(2, smiley);
lcd.createChar(3, sadFace);
lcd.createChar(4, bomb);
}
// function for reading the LCD puttons -- taken from LCD shield test code written by Mark Bramwell, July 2010
int read_LCD_buttons()
{
adc_key_in = analogRead(0); // read the value from the sensor
// my buttons when read are centered at these valies: 0, 144, 329, 504, 741
// we add approx 50 to those values and check to see if we are close
if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
// For V1.1 us this threshold
if (adc_key_in < 50) return btnRIGHT;
if (adc_key_in < 250) return btnUP;
if (adc_key_in < 450) return btnDOWN;
if (adc_key_in < 650) return btnLEFT;
if (adc_key_in < 850) return btnSELECT;
return btnNONE; // when all others fail, return this...
}
//Main game loop
void loop() {
// reinitialize characters - sometimes they seem to drift and pixels change if not reinitialized??
lcd.begin(16, 2);
lcd.createChar(0, octopus);
lcd.createChar(1, fullHeart);
lcd.createChar(2, smiley);
lcd.createChar(3, sadFace);
lcd.createChar(4, bomb);
// check position of octopus to see if it has caught the heart
if ((o_x == h_x)&&(o_y == h_y)){
//change to smiley when it catches the heart
lcd.setCursor(o_x,o_y);
lcd.write(byte(2)); //smiley
//update score
score++; //increment score by 1
//check for game end conditons
if (score < 10){
//if not game end, display score in upper right corner of screen
lcd.setCursor(15,0);
lcd.print(score);
delay(200);
}
else{
//if game end triggered, display game win message
lcd.begin(16,2);
//cute characters around win message
lcd.setCursor(0,0);
lcd.write(byte(0));
lcd.setCursor(1,0);
lcd.write(byte(1));
lcd.setCursor(2,0);
lcd.write(byte(0));
// game win message
lcd.setCursor(4,0);
lcd.print("YOU WIN!!");
//cute characters around win message
lcd.setCursor(13,0);
lcd.write(byte(0));
lcd.setCursor(14,0);
lcd.write(byte(1));
lcd.setCursor(15,0);
lcd.write(byte(0));
// second row of cute characters
for (int i=0; i<16; i++){
lcd.setCursor(i,1);
lcd.write(byte(1));
}
// delay so that message stays on screen for a while
delay(1500);
//reset score, so user can continue playing
score = 0;
}
//Update heart position once caught, being sure not to reinitialize it on top of the octopus
while ((h_x == o_x) && (h_y == o_y)){
h_x = random(16);
h_y = random(2);
}
//Every other point, reset the bomb position - so that you can't get complacent!
// Get new position for bomb that is not the same as the octopus or the heart
if (score%2 == 1){
b_x = random(16);
b_y = random(2);
//check that new position isn't heart or octopus position!
while (((b_x == o_x) && (b_y == o_y)) || ((b_x == h_x) && (b_y == h_y))){
b_x = random(16);
b_y = random(2);
}
}
}
//if did not catch the heart, checks to see if you caught the bomb
else if ((o_x == b_x)&&(o_y == b_y)){
//if you caught the bomb, writes a sad face
lcd.setCursor(o_x,o_y);
lcd.write(byte(3)); // sadFace
delay(500);
//update score, taking away a point for hitting the bomb
score--;
//checks game lose conditions
if (score >= 0){
//if score not less than zero, shows lowered score in upper right corner
lcd.setCursor(15,0);
lcd.print(score);
delay(200);
}
else{
//if score less than zero, you lose - display game lose message and reset.
lcd.begin(16,2);
//display cute sad characters
lcd.setCursor(0,0);
lcd.write(byte(4));
lcd.setCursor(1,0);
lcd.write(byte(3));
lcd.setCursor(2,0);
lcd.write(byte(4));
//display lose message
lcd.setCursor(4,0);
lcd.print("YOU LOSE!");
//display cute sad characters
lcd.setCursor(13,0);
lcd.write(byte(4));
lcd.setCursor(14,0);
lcd.write(byte(3));
lcd.setCursor(15,0);
lcd.write(byte(4));
//display second row full of BOMBS
for (int i=0; i<16; i++){
lcd.setCursor(i,1);
lcd.write(byte(4));
}
//hold message on screen for user to read.
delay(1000);
//Reset score to zero to allow user to play again
score = 0;
}
// Get new position for bomb
b_x = random(16);
b_y = random(2);
//check that new position isn't same as a heart or octopus position! (this seems to have a bug sometimes...?)
while (((b_x == o_x) && (b_y == o_y)) || ((b_x == h_x) && (b_y == h_y))){
b_x = random(16);
b_y = random(2);
}
}
//if octopus position does not match heart or bomb, then update positions of both heart and bomb
else{
//only update the positions every 4th time step (this decreases difficulty - could change the 4 to 2-3 to increase difficulty - or 7-8 for younger kids...)
//#todo: possible easy improvement here - start the game at a lower difficulty level and level up, changing this position refresh number and/or the delay between loops
//and showing what level you reached when you win/lose.
//checks if timecount is divisible by 4 - if so, updates positions.
if (timecount%4 == 0){
//Update bomb x (horizontal) position. Possible values [0-15]
//pick a random step for the bomb's x position change: -1, 0, +1
randval = random(-1,2);
//update value
b_x = b_x + randval;
//double check if new value carried bomb off the left side of the screen
if (b_x < 0){
//if it did, wrap bomb around, having it reaappear on the right hand side
b_x = 15;
}
//check if updated value carried bomb off the right hand side of the screen
if (b_x > 15){
//if it did, wrap bomb around, having it reappear on the left hand side
b_x = 0;
}
//if bomb x position was not updated then update the y position
//note: because I did x and then y, the bomb is more likely to move around in the x direction than the y direction....making the game easier.
//#todo: this could be updated, so that it alternates or picks y direction first as the "difficulty" level of the game increases?
//Experientially, y direction changes tend to be more difficult for the game player to control
if (randval == 0){
//if x direction did not change, get new y (vertical) bomb position. (random(2) will generate [0-1] values.)
b_y = b_y + random(2);
//double check that updated value didn't go off the bottom of the screen, if it did, wrap it around, having bomb reappear on the top line
if (b_y > 1){
b_y = 0;
}
}
//update heart position
//get random x value for heart to move - options: [-1,0,1]
randval = random(-1,2);
//update heart position with random step
h_x = h_x + randval;
//check if heart went off left side of screen
if (h_x < 0){
//if it did, have reappear on the rhs
h_x = 15;
}
//check if heart went of rhs of screen
if (h_x > 15){
//if it did, have reappear on the lhs
h_x = 0;
}
//if heart did not move in the horizontal/x direction, than heart can move in y.
if (randval == 0){
//update heart's y position, adding [0,1]
h_y = h_y + random(2);
//check if heart went off the bottom of the screen, if it did, have it reappear on the top line
if (h_y > 1){
h_y = 0;
}
}
}
}
//Read user inputs from the keyboard
lcd_key = read_LCD_buttons(); // read the buttons
//Depending on button pressed, determine octopus motion to perform
switch (lcd_key)
{
case btnRIGHT:
{
//if pressed right button, move octopus to the right
o_x = o_x + 1;
//check if update sends octopus off lcd rhs, if so reappear on lhs
if (o_x > 15){
o_x = 0;
}
break;
}
case btnLEFT:
{
//if pressed left button, move octopus to the left
o_x = o_x - 1;
//check if update sends octopus off lhs, if so reappear on the rhs
if (o_x < 0){
o_x = 15;
}
break;
}
case btnUP:
{
//check if up button was pressed, if so move octopus up
o_y = o_y + 1;
// if moving up sent octopus off the top of the screen, reappears in the bottom row
if (o_y > 1){
o_y = 0;
}
break;
}
case btnDOWN:
{
//check if down button was pressed and move octopus down
o_y = o_y - 1;
//if moving octopus down sent it off the screen, reappear in the top row
if (o_y < 0 ){
o_y = 1;
}
break;
}
case btnSELECT:
{
//Pause functionality
lcd.begin(16,2);
//display pause on the screen
lcd.print("Pause");
//read key presses - to ensure that pause still pressed.
lcd_key = read_LCD_buttons();
//while select button is held down, game stays paused.
//#todo: could be improved to stay paused until the button is pressed a second time, allowing the user to walk away while game is paused.
while (lcd_key == btnSELECT){
lcd_key = read_LCD_buttons();
}
break;
}
case btnNONE:
{
//if no button was pressed, do nothing
break;
}
}
// Update the game characters displayed
lcd.setCursor(o_x, o_y);
lcd.write(byte(0));
lcd.setCursor(h_x, h_y);
lcd.write(byte(1));
lcd.setCursor(b_x, b_y);
lcd.write(byte(4));
//delay - changing this delay changes the pacing of the game! could be used to toggle the difficulty level of the game
delay(100);
//increment time counter used for updated bomb and heart motions
timecount++;
}