Explained Code of Flappy Bird Game using 16×2 LCD Display on Arduino

Please help me understand this code:

#include<LiquidCrystal.h>
LiquidCrystal lcd (8,7,6,5,4,3); //RS, E, D4, D5, D6, D7
int ledPin = 13;
int buttonPin = 2;

int ledToggle;
int previousState = HIGH;
unsigned int previousPress;
volatile int buttonFlag;
int buttonDebounce = 20;
int i=0;
int pos=0;
int Speed=100;
String typea    = "                      ##        ######     ##       ####     ####       ########       ##                  ";  
String typeaa   = "                 ##       ####          ##     ####       ##         ##           ##                       ";

                                    
                
int  typeA[]    = {22,23,32,33,34,35,36,37,43,44,52,53,54,55,61,62,63,64,72,73,74,75,76,77,78,79,87,88};//28
int typeAA[]    = {17,18,26,27,28,29,40,41,47,48,49,50,58,59,69,70,82,83};//18

void setup()
{
  lcd.begin(16,2);
  lcd.begin(16,2);
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), button_ISR, CHANGE);
  while(ledToggle==0)
  {
    lcd.clear();
    delay(500);
    lcd.print("   START GAME   ");
    delay(500);
  }
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print('O');
  //lcd.print(bala.substring(1,3));
  
}


void loop() 
{
  
while(1)
 {
   for(i=0;i<89;i++)//
   {
    if(pos==0)
    {
      lcd.setCursor(1,0);
      lcd.print(typea.substring(i+1,16+i));
    }
    else
    {
      lcd.setCursor(0,0);
      lcd.print(typea.substring(i,16+i));
    }

    
    if(pos==1)
    {
      lcd.setCursor(1,1);
      lcd.print(typeaa.substring(i+1,16+i));
    }
    else
    {
      lcd.setCursor(0,1);
      lcd.print(typeaa.substring(i,16+i));
    }
    
   
    
    for(int j=0;j<28;j++)
    {
      if(typeA[j]==i)
      {
        if(pos==0)
        {
          gameover();
        }
      }
    }
    for(int j=0;j<18;j++)
    {
      if(typeAA[j]==i)
      {
        if(pos==1)
        {
          gameover();
        }
      }
    }
    delay(Speed);
  }
   winner();
 }
}





void gameover (void)
{
  detachInterrupt(digitalPinToInterrupt(2));
  while(1)
  {
    lcd.clear();
    delay(500);
    lcd.print("    GAME OVER   ");
    delay(500);
  }
}
void winner (void)
{
  detachInterrupt(digitalPinToInterrupt(2));
  while(1)
  {
    lcd.clear();
    delay(500);
    lcd.print(" WINNER! WINNER!");
    lcd.setCursor(0,1);
    lcd.print(" CHICKEN DINNER ");
    delay(500);
  }
}

void button_ISR()
{
   buttonFlag = 1;
   if((millis() - previousPress) > buttonDebounce && buttonFlag)
  {
    previousPress = millis();
    if(digitalRead(buttonPin) == LOW && previousState == HIGH)
    {
      ledToggle =! ledToggle;
      digitalWrite(ledPin, ledToggle);
      previousState = LOW;
      if(ledToggle)
      {
       lcd.setCursor(0,0);
       lcd.print('O');
       lcd.setCursor(0,1);
       lcd.print(' ');
       pos=0;
      }
      else
      {
       lcd.setCursor(0,1);
       lcd.print('O');
       lcd.setCursor(0,0);
       lcd.print(' ');
       pos=1;
      }

    }
    
    else if(digitalRead(buttonPin) == HIGH && previousState == LOW)
    {
      previousState = HIGH;
    }
    buttonFlag = 0;
  }
}

**The code isn't mine. but I tried to follow the guide (link below), and the code works and you can actually play the game on lcd. **

Question:
**Why is it ledPin is 13? **

also...Please help me understand each line. if you can interpret it for me id highly appreciate it.

link:
https://www.electronicshub.org/flappy-bird-game-using-arduino/?unapproved=2474954&moderation-hash=5a2e730e7c3c6886d1c85f3c4dac86b9&fbclid=IwAR0nFzxp299LMa3ksvjj_6NpJbZqInnaSGz5ayg0xzdZEdlJW-_gIjutOc8#comment-2474954

167 lines of code ?

Trya dding comments to the code for the parts that you do understand then maybe we can fill in the gaps

1 Like

Because the Arduino Uno has an LED connected to this pin.

Alright so here's what I understood so far:
PART 1

First, the pins are defined. Pin 13 is connected to built in LED of arduino and defined as ledPin. Pin 2 is connected to the button and defined as buttonPin.

int ledPin = 13;
int buttonPin = 2;

Here are other global variables which will be used later.

int ledToggle;
int previousState = HIGH;
unsigned int previousPress;
volatile int buttonFlag;
int buttonDebounce = 20;
int i=0;
int pos=0;
int Speed=500;

Here are two strings. This set of characters will display as obstacles in the game. One for the top row and the other for the bottom row.

String typea    = "                      ##        ######     ##       ####     ####       ########       ##                  ";  
String typeaa   = "                 ##       ####          ##     ####       ##         ##           ##                       ";                           

These are arrays that have variables indicating which index a character displays based on the strings above. There are 28 characters or obstacles to be seen in the LCD at the top and 18 on the bottom. Example is for the top row, at index 0 to 21, the pixel boxes are empty until at index 22, wherein the symbol is displayed.

int  typeA[]    = {22,23,32,33,34,35,36,37,43,44,52,53,54,55,61,62,63,64,72,73,74,75,76,77,78,79,87,88};//28
int typeAA[]    = {17,18,26,27,28,29,40,41,47,48,49,50,58,59,69,70,82,83};//18

In the setup function:

  • First the 16 by 2 LCD is initialized.
  • LedPin is set as an output, buttonPin is set as input but it is INPUT_PULLUP and not INPUT because an internal 20k ohm pull up resistor is pulled to 5V. This configuration causes the input to read HIGH when the switch is open and LOW when it is closed.
    QUESTION: why is ledPin an output?
 lcd.begin(16,2);
 lcd.begin(16,2);
 pinMode(ledPin, OUTPUT); 
 pinMode(buttonPin, INPUT_PULLUP); 
  • Here below it is specified that pin 2 is for external interrupt, and button_ISR function is called when there is CHANGE, meaning the value transits from LOW to HIGH or HIGH to LOW) at D2 pin. The interrupt is triggered when input falls or rises.
attachInterrupt(digitalPinToInterrupt(2), button_ISR, CHANGE);
  • In this while loop, while ledtoggle is equal to 0, it continues to display the “START GAME” until you press the button it stops.
while(ledToggle==0)
  {
    lcd.clear();
    delay(500);
    lcd.print("   START GAME   ");
    delay(500);
  }
  • And then displays character letter “O” that the user controls in top or bottom movement.
lcd.clear();
  lcd.setCursor(0,0);
  lcd.print('O');
  • Meaning that when you power the arduino, the START GAME displays and when you press the button, it generates the character and the game starts.

If it is driving an LED they why would it be anything other than an OUTPUT

PART 2

  • In loop function:
    • While loop running infinitely the following until the code below is all executed it stops.:
      • The first for loop running the following until the 89th time since there are 89 indexes needed to display both empty characters and symbol #::
        • Given that int pos=0, if pos is equal to zero:
          • Set cursor at column 1, row 0
          • Display the contents of typea from initial “i” incremented by 1 to 16 incremented by “i”
        • If not then:
          • Set cursor at column 0, row 0
          • Display the contents of typea from “i” to 16 incremented by 1
            QUESTION: What is the difference in the else statement? What is the use of pos?
void loop() 
{
  
while(1)
 {
   for(i=0;i<89;i++)//
   {
    if(pos==0)
    {
      lcd.setCursor(1,0);
      lcd.print(typea.substring(i+1,16+i));
    }
    else
    {
      lcd.setCursor(0,0);
      lcd.print(typea.substring(i,16+i));
    }
  • Given that int pos=0, if pos is equal to one:
    • Set cursor at column 1, row 1
    • Display the contents of typeaa from “i” incremented by 1 to 16 incremented by “i”
  • If not then:
    • Set cursor at column 0, row 0
    • Display the contents of typeaa from “i” to 16 incremented by 1
      QUESTION: What's the difference between the first if-statement and then the below if-statement?
if(pos==1)
    {
      lcd.setCursor(1,1);
      lcd.print(typeaa.substring(i+1,16+i));
    }
    else
    {
      lcd.setCursor(0,1);
      lcd.print(typeaa.substring(i,16+i));
    }
    

From what I understand this code make the cursor move one column to another starting from an index up to 16th, since there are only 16 characters that can be displayed in each row. As the cursor moves, contents of typea and typeaa are generated including the empty spaces and “#” and moves along as well until the 89th index.

  • The second For loop running the following until 28th time since there are 28 characters that has “#” in the top row.
    • If a value from array typeA is equal to “i”:
      • Given that int pos=0, if pos is equal to 0:
        • Call the function gameover()
for(int j=0;j<28;j++)
    {
      if(typeA[j]==i)
      {
        if(pos==0)
        {
          gameover();
        }
      }
    }

From what I understand, it’s game over because the position is true when a value from set typeA is equal to the value of “i”. Example when typeA[22] is equal to index 22 where the cursor that holds character “O” is at, and that this position is true, it calls the function that displays game over.

  • The third For loop running the following until 18th time since there are 18 characters in bottom row.
    • If a value from array typeAA is equal to the value “i”:
      • Given that int pos=0, if pos is equal to 1:
        • Call the function gameover()
for(int j=0;j<18;j++)
    {
      if(typeAA[j]==i)
      {
        if(pos==1)
        {
          gameover();
        }
      }
    }
    delay(Speed);
  }

In my understanding, it’s game over because the position is false when a value from set typeAA is equal to the value of “i”. Example when typeAA[17] is equal to index 17 where the cursor that holds character “O” is at, and that this position is not true, it calls the function that displays game over.
QUESTION: Why is it pos==0? What's the difference?

And then calls the function winner() which displays “WINNER” after running the codes in the while loop.

 winner();

So conclusion in the loop function, the “O”, empty spaces for it to move and obstacles “#” are generated. Depending on the user’s gameplay, if “O” hits “#” then, game over. If not, win.

  • In gameover() function:
    • detachInterrupt turns off the given interrupt as we no longer need it.
    • In this while loop, it continues to display the “GAME OVER”
void gameover (void)
{
  detachInterrupt(digitalPinToInterrupt(2));
  while(1)
  {
    lcd.clear();
    delay(500);
    lcd.print("    GAME OVER   ");
    delay(500);
  }
}

  • In winner() function:
    • detachInterrupt turns off the given interrupt as we no longer need it.
    • In this while loop, it continues to display the “WINNER”
void winner (void)
{
  detachInterrupt(digitalPinToInterrupt(2));
  while(1)
  {
    lcd.clear();
    delay(500);
    lcd.print(" WINNER! WINNER!");
    lcd.setCursor(0,1);
    lcd.print(" CHICKEN DINNER ");
    delay(500);
  }
}

PART 3

  • In button_ISR function:
    Given in the global variables,
int ledToggle;
int previousState = HIGH;
unsigned int previousPress;
volatile int buttonFlag;
int buttonDebounce = 20;
int i=0;
int pos=0;
int Speed=500;

QUESTION: What’s the use of buttonFlag and why is it =1?

void button_ISR()
{
   buttonFlag = 1;

In this if-statement, what does the conditions mean? What is previousPress for? buttonDebounce? Why does it use millis? Aren’t millis or delay not work in an ISR? But why does the game still work with it?

   if((millis() - previousPress) > buttonDebounce && buttonFlag)
  {
    previousPress = millis();

Why previousPress = millis()?

If buttonPin is pressed and previousState is equal to high —What is previousState for?:

    if(digitalRead(buttonPin) == LOW && previousState == HIGH)
    {
      ledToggle =! ledToggle;

Why invert ledToggle?

      digitalWrite(ledPin, ledToggle);
  • This digitalWrite indicates state on LED
      previousState = LOW;

Why previousState = LOW?

  • In this if-statement below, if true, run the following but what does it mean when the condition is like that?
    • Set cursor at column 0, row 0
    • Display the “O”
    • Set cursor at column 0, row 1
    • Display the “ “
    • Why pos=0;?
      if(ledToggle)
      {
       lcd.setCursor(0,0);
       lcd.print('O');
       lcd.setCursor(0,1);
       lcd.print(' ');
       pos=0;
      }
  • If above is false, then do:
    • Set cursor at column 0, row 0
    • Display the “O”
    • Set cursor at column 0, row 1
    • Display the “ “
    • Why pos=1;?
      else
      {
       lcd.setCursor(0,1);
       lcd.print('O');
       lcd.setCursor(0,0);
       lcd.print(' ');
       pos=1;
      }

    }
  • If the above is also false, then do the following if buttonPin is pressed and previousState is equal to high.
    • Why previousState = HIGH;?
    else if(digitalRead(buttonPin) == HIGH && previousState == LOW)
    {
      previousState = HIGH;
    }
  • What’s the use of buttonflag and why is it now =0?
    buttonFlag = 0;
  }
}

I would be very much grateful if you could help me understand especially the questions. :slight_smile:

ahh okay. I've explained what I could in the rest of PART 2 and 3 sir. I have questions there and I hope u can help me.

Thank youuu i get it now

Let's start with the while(1) statement which creates an infinite loop. This is not necessary because the code in loop() repeats in any case

The code in the else is different to the code in the if. The value of pos determines which row of the LCD is printed on

None apart from the if/else order is reversed because the value of pos tested for is also reversed. I cannot immediately see why the 2 sets of if/else are there

The code seems to be checking the condition for both rows. Which row is checked is determined by the value of pos

ISRs should be as short as possible. Setting a flag variable to indicate that the ISR has been triggered, in this case by a button press, is a common way to do this. The flag value is checked in the main code so that the button press can be acted upon then set back to zero to stop it being acted upon again. Note that the buttonPress variable is declared volatile because it is changed in the ISR. Now is not the time to explain why volatile is needed

1 Like

Hii I'm so grateful you helped me with some parts. how could I rewrite the ISR to make it shorter?

To be honest I would be surprised if it worked at all as it is because printing uses interrupts and interrupts are disabled when in an ISR

Instead, the ISR should just set a volatile variable to a value such as true to indicate that the ISR had been triggered and for the value of that flag variable to be tested in loop() and acted upon

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.