GitHub link: https://github.com/AlAminAshik/Mini-game-consol-using-lora-display-esp32.git
I tried to keep the code simple to allow everyone to understand. The code could be made neater by creating some custom libraries, but for now, I kept everything under main.cpp
# Important: To use the built-in OLED display of the development board, there are some adjustments to be made to the code. On the Heltec board, the OLED display can be powered on/off, so to turn it on, pull the GPIO 16 pin to HIGH at the initial setup. I have attached a screenshot of the code.
The code is divided into different sections, and I have discussed the different blocks of code below:
The menu is created by calling two functions:
The drawMenu() shows the name and icons
and menuAction() executes the game selected
//show menu item name and cursor
void drawMenu() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// Menu item 1
display.setCursor(20, 10);
display.print("Play Hamba Match");
// Menu item 2
display.setCursor(20, 30);
display.print("Play Hamba Run");
// Menu item 3
display.setCursor(20, 50);
display.print("Play Number Hold");
// Draw arrow
if (currentMenu == 0) {
display.setCursor(5, 10);
display.print(">");
}
else if(currentMenu == 1) {
display.setCursor(5, 30);
display.print(">");
}
else if(currentMenu == 2) {
display.setCursor(5, 50);
display.print(">");
}
display.display();
}
// Execute action based on selected menu item
void menuAction(int menuIndex) {
if (menuIndex == 0) {
runHambaMatch = true; // Set flag to run Hamba game
Serial.println("Function 1 executed");
} else if (menuIndex == 1) {
runHambaRun = true; // Set flag to run Hamba Run game
Serial.println("Function 2 executed");
} else if (menuIndex == 2) {
targetNumber = random(1, 11); // Set a initial random target number between 1 and 10
runNumberHold = true; // Set flag to run Number Hold game
Serial.println("Function 3 executed");
}
display.display();
}
The input button is coded as an interrupt so that the button function runs at a point of the game without any delay.
You must add ICACHE_RAM_ATTR to avoid unexpected resets.
ICACHE_RAM_ATTR void Play_button_pressed() {
Serial.println("Play button pressed!");
if (digitalRead(playButton) == LOW) {
pressStartTime = millis();
buttonHeld = false;
buttonPressed = true;
}
}
The Hamba match game function: (watch the video)
void play_hamba_match_game(){
//run filled cow image
for (cow_position = -100; cow_position < 100; cow_position=cow_position+5) {
display.clearDisplay(); // clear the display
display.setCursor(0,0); // set cursor to top left corner
display.setTextSize(1); // set text size to 1
display.setTextColor(SSD1306_WHITE); // set text color to white
display.print("Level: "); // show level text
display.print(diffLevel); // print the current difficulty level
display.drawBitmap(cow_position, 0, cowFilled, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
//hold outline of the cow
display.drawBitmap(0, 0, cowOutline, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
display.display(); // update the display
delay(40-(int)pow(diffLevel, 2)*1.5); // wait for difflevel milliseconds
//loop until button is pressed
if(buttonPressed == true) { //stop when button is pressed
break;
}
//flash the red and blue lights
unsigned long currentMillis = millis(); // Get the current time
if(currentMillis - previousMillis >= 500) { // If 500 milliseconds have passed
previousMillis = currentMillis; // Store the current time
ledState = !ledState; // Change the state of the LED
digitalWrite(RedLights, ledState); // Turn on RedLights
digitalWrite(GreenLights, !ledState); // Turn off GreenLights
//play buzzer sound
ledcWriteTone(Buzzer_pin, 1000);
}
else {
ledcWriteTone(Buzzer_pin, 0); // Turn off buzzer sound
}
}
if(buttonPressed){
delay(200); //wait to show the last cow position frame
// If the game is not running, turn off the lights
digitalWrite(RedLights, LOW); // Turn on RedLights
digitalWrite(GreenLights, LOW); // Turn on GreenLights
if(cow_position >= -10 && cow_position <= -6) {
playWinSound(); // Play the winning sound
digitalWrite(RedLights, HIGH); // Turn off RedLights
digitalWrite(GreenLights, HIGH); // Turn off GreenLights
switch (diffLevel) {
case 1:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 2:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 3:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 4:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 5:
celebration(); // Call the celebration function to show the celebration graphics
display.clearDisplay(); // Clear the display
display.setCursor(14, 15); // Set cursor to top left corner
display.setTextSize(2); // Set text size to 2
display.setTextColor(SSD1306_WHITE); // Set text color to white
display.print("Play"); // Print "Play" on the display
display.setCursor(14, 35); // Set cursor to next line
display.print("Again!!"); // Print "Again!!" on the display
display.display(); // Update the display
ledcWriteTone(Buzzer_pin, 0); // Play no sound while waiting
diffLevel = 1; //reset difficulty level
break;
}
//wait until the play button is pressed to play again
while (digitalRead(playButton) == HIGH) {
delay(50);
}
buttonPressed = false; //reset button pressed flag
}
else {
diffLevel = 1; //reset difficulty level
playLoseSound(); // Play the losing sound
while (digitalRead(playButton) == HIGH) {
//display win or loose
display.clearDisplay(); // Clear the display
display.setCursor(10, 20); // Set cursor to top left corner
display.setTextSize(2); // Set text size to 2
display.setTextColor(SSD1306_WHITE); // Set text color to white
display.print("You Lose!"); // Print "You Lose!" on the display
display.setCursor(12, 40); // Set cursor to top left corner
display.setTextSize(1); // Set text size to 1
display.setTextColor(SSD1306_WHITE); // Set text color to white
display.print("Try Again!"); // Print "Try Again!" on the display
display.display(); // Update the display
}
buttonPressed = false; //reset button pressed flag
}
}
}
Number hold game function: (watch the video)
void play_number_hold_game(){
// while(buttonPressed == false) { //loop until button is pressed
currentNumber = (currentNumber % 10) + 1;
display.clearDisplay();
//show level at top left
display.setCursor(0,0); // set cursor to top left corner
display.setTextSize(1); // set text size to 1
display.setTextColor(SSD1306_WHITE); // set text color to white
display.print("L: "); // show level text
display.print(diffLevel); // print the current difficulty level
//Target number at top
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(16, 45);
display.print("Target:");
display.setCursor(100, 45);
display.print(targetNumber);
// Circulating number at bottom
display.setTextSize(4);
display.setCursor(50, 10);
display.print(currentNumber);
display.display();
//delay(150); // Small delay to allow display to update
delay(150-(int)pow(diffLevel, 3)); // wait for difflevel milliseconds
//flash the red and blue lights
unsigned long currentMillis = millis(); // Get the current time
if(currentMillis - previousMillis >= 500) { // If 500 milliseconds have passed
previousMillis = currentMillis; // Store the current time
ledState = !ledState; // Change the state of the LED
digitalWrite(RedLights, ledState); // Turn on RedLights
digitalWrite(GreenLights, !ledState); // Turn off GreenLights
//play buzzer sound
ledcWriteTone(Buzzer_pin, 1000);
}
else {
ledcWriteTone(Buzzer_pin, 0); // Turn off buzzer sound
}
// }
//check for win or lose condition
if(buttonPressed){
delay(200); //wait to show the last cow position frame
// If the game is not running, turn off the lights
digitalWrite(RedLights, LOW); // Turn on RedLights
digitalWrite(GreenLights, LOW); // Turn on GreenLights
if(currentNumber == targetNumber) {
playWinSound(); // Play the winning sound
digitalWrite(RedLights, HIGH); // Turn off RedLights
digitalWrite(GreenLights, HIGH); // Turn off GreenLights
switch (diffLevel) {
case 1:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 2:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 3:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 4:
diffLevel = diffLevel + 1; //increase difficulty level for next round
showextLevel(diffLevel);
break;
case 5:
celebration(); // Call the celebration function to show the celebration graphics
display.clearDisplay(); // Clear the display
display.setCursor(14, 15); // Set cursor to top left corner
display.setTextSize(2); // Set text size to 2
display.setTextColor(SSD1306_WHITE); // Set text color to white
display.print("Play"); // Print "Play" on the display
display.setCursor(14, 35); // Set cursor to next line
display.print("Again!!"); // Print "Again!!" on the display
display.display(); // Update the display
ledcWriteTone(Buzzer_pin, 0); // Play no sound while waiting
diffLevel = 1; //reset difficulty level
break;
}
//wait until the play button is pressed to play again
while (digitalRead(playButton) == HIGH) {
delay(50);
}
buttonPressed = false; //reset button pressed flag
}
else {
diffLevel = 1; //reset difficulty level
playLoseSound(); // Play the losing sound
while (digitalRead(playButton) == HIGH) {
//display win or loose
display.clearDisplay(); // Clear the display
display.setCursor(10, 20); // Set cursor to top left corner
display.setTextSize(2); // Set text size to 2
display.setTextColor(SSD1306_WHITE); // Set text color to white
display.print("You Lose!"); // Print "You Lose!" on the display
display.setCursor(12, 40); // Set cursor to top left corner
display.setTextSize(1); // Set text size to 1
display.setTextColor(SSD1306_WHITE); // Set text color to white
display.print("Try Again!"); // Print "Try Again!" on the display
display.display(); // Update the display
}
buttonPressed = false; //reset button pressed flag
}
targetNumber = random(1, 11); // reset random 1–10
currentNumber = 0; //reset current number to 0
}
}
Hamba Run Game function: (watch the video)
void play_hamba_run_game(){
int8_t boxHeight = 0;
uint8_t numOfObstacles = 0;
bool cowJump = false;
unsigned long currentObstacleTime = millis();
if(currentObstacleTime - lastObstacleTime >= random(100,1000)) {
lastObstacleTime = currentObstacleTime; // Store the current time
//select obstacle number and height to send from right to left
numOfObstacles = random(0,3); //random number of obstacles between 0 and 2
boxHeight = random(5, 22); //random height of obstacles
}
//scan through the whole screen width
for(int i=display.width(); i>=-4; i-=4) {
//increment score
score += 0.2; // Increase score
int scoreInt = round(score); // Round score to nearest integer
//check if the button is pressed to bool jump the cow
if(buttonPressed == true) { //stop when button is pressed
cowJump = true;
buttonPressed = false; //reset button pressed flag
}
if (cowJump) {
cowPosition += 5; // Move the cow up
if (cowPosition >= 30) { // If the cow reaches the peak height
cowJump = false; // Start moving down
}
} else {
if (cowPosition > 0) {
cowPosition -= 2; // Move the cow down
}
}
//check if the cow hits the obstacle
//if the number of obstacles is more than 0 and the obstacle is in the cow's x range and the cow's y position is less than the obstacle height
//if the obstacle count is 2 then the obstacle must pass more to the left before hitting the cow
if (numOfObstacles > 0 && i <= 26 && i >= (numOfObstacles == 2 ? 2 : 16) && (cowPosition+10) <= boxHeight + 10) {
// Cow hits the obstacle
playLoseSound(); // Play the losing sound
delay(500); // Wait for a moment
showLose(); // Show lose message and update
//show score
display.setCursor(30, 50); // Set cursor below text
display.setTextSize(1); // Set text size to 1
display.setTextColor(SSD1306_WHITE); // Set text color to white
display.print("Score: "); // Print "Score: " on the display
display.print(scoreInt); // Print the score on the display
display.display(); // Update the display
cowPosition = 0; // Reset cow position
numOfObstacles = 0; // Reset obstacles
lastObstacleTime = millis(); // Reset obstacle timer
delay(2500); // Show the message for 2 seconds
score = 0; // Reset score
break; // Exit the for loop to restart the game
}
else{
//show game on the screen
display.clearDisplay(); // clear the display
//draw the cow horn
display.drawBitmap(20, 48-cowPosition, cow_horn, 12, 16, SSD1306_WHITE); // Draw cow with horn
//draw the moving obstacle
switch (numOfObstacles) {
case 0:
//no obstacle
break;
case 1:
display.fillRect(i, 64-boxHeight, 4, boxHeight, SSD1306_WHITE); // Draw rectangle
break;
case 2:
display.fillRect(i, 64-boxHeight, 4, boxHeight, SSD1306_WHITE); // Draw rectangle
display.fillRect(i+10, 64-boxHeight, 4, boxHeight, SSD1306_WHITE); // Draw rectangle
break;
}
//draw the score
display.setCursor(0,0); // set cursor to top left corner
display.setTextSize(1); // set text size to 1
display.setTextColor(SSD1306_WHITE); // set text color to white
display.print("Score: "); // show level text
display.print(scoreInt); // print the current difficulty level
display.display(); // Update screen with each newly-drawn rectangle
}
//flash the red and blue lights
unsigned long currentMillis = millis(); // Get the current time
if(currentMillis - previousMillis >= 500) { // If 500 milliseconds have passed
previousMillis = currentMillis; // Store the current time
ledState = !ledState; // Change the state of the LED
digitalWrite(RedLights, ledState); // Turn on RedLights
digitalWrite(GreenLights, !ledState); // Turn off GreenLights
//play buzzer sound
ledcWriteTone(Buzzer_pin, 1000);
}
else {
ledcWriteTone(Buzzer_pin, 0); // Turn off buzzer sound
}
}
}
The code under "void loop()" only checks the status of the button, which game to play, and when to stop a game, and shows the menu.
void loop() {
if (endGame) drawMenu(); //show menu when no gameq is running
else if (runHambaRun == true && !endGame) play_hamba_run_game(); // Call the play_hamba_run_game function to run the game
else if (runHambaMatch == true && !endGame) play_hamba_match_game(); // Call the play_hamba_match_game function to run the game
else if (runNumberHold == true && !endGame) play_number_hold_game(); // Call the play_number_hold_game function to run the game
//exit from any game if button is held for more than 2 seconds
if (digitalRead(playButton) == LOW){
if(millis() - pressStartTime > 2000) {
if(digitalRead(playButton) == LOW) {
digitalWrite(RedLights, LOW); // Turn off RedLights
digitalWrite(GreenLights, LOW); // Turn off GreenLights
ledcWriteTone(Buzzer_pin, 0); // Turn off buzzer sound
runHambaRun = false;
runHambaMatch = false;
runNumberHold = false;
endGame = true; // Set flag to indicate game is over
diffLevel = 1; //reset difficulty level
}
}
}
//show menu until a game is selected
if (buttonPressed) {
buttonPressed = false;
// Debounce check
delay(50);
if (digitalRead(playButton) == LOW) {
unsigned long pressDuration = 0;
// Wait until button released
while (digitalRead(playButton) == LOW) {
pressDuration = millis() - pressStartTime;
if (pressDuration > 1000 && !buttonHeld) { // long press
buttonHeld = true;
endGame = false; // Exit menu mode
menuAction(currentMenu);
break;
}
}
// If short press (less than 1s)
if (!buttonHeld) {
currentMenu = (currentMenu + 1) % menuCount; // Move to next menu item
drawMenu();
}
}
}
}
GitHub link: https://github.com/AlAminAshik/Mini-game-consol-using-lora-display-esp32.git
All the comments are added to the code, so tweak the code as you wish!