OTHER PROJECTS

Arduino T-Rex Dinosaur Game with OLED Display

Remember the Chrome browser dinosaur game you play when there is no internet? Now you can build your own physical version using an Arduino Uno! In this project, we recreate the classic T-Rex runner game on an OLED display, complete with jumping, obstacles, score tracking on a 4-digit display, and sound effects from a buzzer. Use the rotary encoder to jump and control the game — it is a fully playable arcade game built from scratch!

What You Will Learn :

  • How to use a U8glib OLED display with Arduino
  • How to display bitmap graphics and animations
  • How to use a rotary encoder as a game controller
  • How to track and display score on a TM1637 4-digit display
  • How to add sound effects using a buzzer

Components Required :

ComponentQuantity
Arduino Uno1
0.96” OLED Display (SH1106, I2C)1
TM1637 4-Digit 7-Segment Display1
Rotary Encoder (with push button)1
Buzzer1
Jumper WiresSeveral
USB Cable1
Computer with Arduino IDE1

Libraries Required :

You will need to install the following library in your Arduino IDE:
• U8glib — for the OLED display

To install: Go to Sketch → Include Library → Manage Libraries and search for U8glib.

The TM1637Display library is a custom library included directly in the project folder. No installation needed — just place TM1637Display.h and TM1637Display.cpp in the same folder as Dino.ino.

Circuit Diagram :

Wiring Table :

4-Digit TM1637 Display → Arduino Uno

Display PinArduino PinWire Color
CLKPin 6Green
DIOPin 5Purple
VCC5VRed
GNDGNDBlack

OLED Display → Arduino Uno

OLED PinArduino PinWire Color
GNDGNDBlack
VCC5VRed
SCLA5Yellow
SDAA4Yellow

Rotary Encoder → Arduino Uno

Encoder PinArduino PinWire Color
CLKPin 2Orange
DTPin 4Blue
SWPin 3Green
VCC5VRed
GNDGNDBlack

Buzzer → Arduino Uno

Buzzer PinArduino Pin
+Pin 10
GND

How It Works :

  • On startup the OLED shows “Welcome to Dino!! Push to begin”
  • Press the rotary encoder button to start the game
  • The T-Rex runs automatically and obstacles (cacti) move from right to left
  • Press the rotary encoder button to make the T-Rex jump over obstacles
  • The score increases over time and is displayed on the 4-digit TM1637 display
  • Every 100 points a double beep plays to celebrate the milestone
  • If the T-Rex hits an obstacle the game ends with a “Game Over” screen and buzzer sound
  • Press the button again to restart the game

Circuit Simulation :

Watch the T-Rex dodge cacti, score points, and trigger sound effects in action!

Project Files :

This project has 3 files that must all be in the same folder:

FileDescription
Dino.inoMain game logic
TM1637Display.hCustom display library header
TM1637Display.cppCustom display library implementation

Arduino Code :

Dino.ino — Main Game Logic

#include <U8glib.h>
#include "TM1637Display.h"


#define SW 3
#define DT 4
#define CLK 2


#define DCLK 6
#define DIO 5


#define buzzer 10


#define jumpSound 700
#define blahSound 125
#define speedSound 1000
#define DBOUNCE 180


#define gameStart 0
#define gameEnd 1
#define gamePlaying 2


volatile int gameStatus = gameStart;


U8GLIB_SH1106_128X64 My_u8g_Panel(U8G_I2C_OPT_NONE);


TM1637Display OurDisplay = TM1637Display(DCLK, DIO);


const uint8_t blank[] = {0x00, 0x00, 0x00, 0x00};


static unsigned char dinoJump [] U8G_PROGMEM = {
  0x00, 0xFC, 0x07, 0x00, 0xFE, 0x07, 0x00, 0xEE, 0x0F, 0x00, 0xFE, 0x0F,
  0x00, 0xFE, 0x0F, 0x00, 0xFE, 0x0F, 0x00, 0xFE, 0x0F, 0x00, 0xFE, 0x07,
  0x06, 0xFF, 0x03, 0xC3, 0xFF, 0x00, 0xE7, 0xFF, 0x00, 0xFF, 0xFF, 0x00,
  0xFF, 0xFF, 0x00, 0xFF, 0x3F, 0x00, 0xFE, 0x3F, 0x00, 0xFC, 0x1F, 0x00,
  0xF8, 0x1F, 0x00, 0xF0, 0x1F, 0x00, 0xF0, 0x0E, 0x00, 0x60, 0x0E, 0x00,
  0xE0, 0x0E, 0x00,
};


static unsigned char dinoLeft [] U8G_PROGMEM = {
  0x00, 0xFC, 0x07, 0x00, 0xFE, 0x07, 0x00, 0xEE, 0x0F, 0x00, 0xFE, 0x0F,
  0x00, 0xFE, 0x0F, 0x00, 0x7E, 0x08, 0x00, 0x7E, 0x00, 0x06, 0xFF, 0x03,
  0x87, 0x3F, 0x00, 0xE7, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00,
  0xFF, 0x3F, 0x00, 0xFE, 0x3F, 0x00, 0xFC, 0x1F, 0x00, 0xF8, 0x1F, 0x00,
  0xF0, 0x1F, 0x00, 0xE0, 0x1E, 0x00, 0x60, 0x00, 0x00, 0xE0, 0x00, 0x00,
  0xE0, 0x00, 0x00,
};


static unsigned char dinoRight [] U8G_PROGMEM = {
  0x00, 0xFC, 0x07, 0x00, 0xEE, 0x07, 0x00, 0xE6, 0x0F, 0x00, 0xFE, 0x0F,
  0x00, 0xFE, 0x0F, 0x00, 0x7C, 0x00, 0x06, 0xFF, 0x03, 0xC3, 0xFF, 0x00,
  0xE7, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x3F, 0x00,
  0xFE, 0x3F, 0x00, 0xFC, 0x1F, 0x00, 0xF8, 0x1F, 0x00, 0xF0, 0x0F, 0x00,
  0xF0, 0x0E, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x0E, 0x00,
  0x00, 0x1E, 0x00,
};


static unsigned char cloud [] U8G_PROGMEM = {
  0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xBE, 0x03, 0x00, 0x00, 0x00,
  0x03, 0x06, 0x00, 0x00, 0x80, 0x01, 0x04, 0x00, 0x00, 0x40, 0x00, 0x1C,
  0x00, 0x00, 0x40, 0x00, 0xE4, 0x03, 0x00, 0x18, 0x00, 0x00, 0x02, 0xE0,
  0x0F, 0x00, 0x00, 0x0E, 0x30, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,
  0x00, 0x20, 0x12, 0x00, 0x00, 0x00, 0x40, 0x03, 0xFF, 0xFF, 0xFF, 0x7F,
};


static unsigned char oneCactus [] U8G_PROGMEM = {
  0x30, 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x01, 0xFB, 0x03,
  0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x03, 0xFF, 0x01,
  0xFE, 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x00, 0x78, 0x00,
  0x78, 0x00, 0x00, 0x00,
};


static unsigned char twoCactus [] U8G_PROGMEM = {
  0x30, 0xC0, 0x00, 0x38, 0xE0, 0x00, 0x38, 0xE8, 0x00, 0x38, 0xEC, 0x00,
  0x38, 0xED, 0x04, 0xBB, 0xED, 0x0E, 0xBB, 0xED, 0x0E, 0xBB, 0xED, 0x0E,
  0xBB, 0xFD, 0x0E, 0xBB, 0xF9, 0x0E, 0xFF, 0xF1, 0x0F, 0xFF, 0xE0, 0x07,
  0x7E, 0xE0, 0x01, 0x38, 0xE0, 0x00, 0x38, 0xE0, 0x00, 0x38, 0xE0, 0x00,
  0x38, 0xE0, 0x00, 0x38, 0xE0, 0x00, 0x38, 0xE0, 0x00, 0x00, 0x00, 0x00,
};


static unsigned char threeCactus [] U8G_PROGMEM = {
  0x00, 0xC0, 0x00, 0x18, 0xC0, 0x01, 0x18, 0xC0, 0x01, 0x58, 0xD8, 0x01,
  0x58, 0xFC, 0x01, 0x58, 0xFC, 0x0F, 0x78, 0xDC, 0x0F, 0x7F, 0xFC, 0x0F,
  0x3B, 0xFD, 0x0D, 0x1B, 0xF9, 0x0C, 0x5B, 0xF5, 0x0F, 0x5B, 0xC5, 0x07,
  0x5F, 0xE7, 0x03, 0xDE, 0xE7, 0x01, 0xD8, 0xC3, 0x01, 0x98, 0xC1, 0x01,
  0x18, 0xC1, 0x01, 0x18, 0xC1, 0x01, 0x18, 0xE1, 0x01, 0x00, 0x00, 0x00,
};


static unsigned char oneCactusSmall [] U8G_PROGMEM = {
  0x0C, 0x0C, 0x3C, 0x3D, 0x2D, 0x3D, 0x1D, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C,
};


static unsigned char twoCactusSmall [] U8G_PROGMEM = {
  0x0C, 0x03, 0x0C, 0x03, 0x6C, 0x0B, 0x6D, 0x0B, 0x6D, 0x0B, 0xBD, 0x0B,
  0x1F, 0x0F, 0x0E, 0x03, 0x0C, 0x03, 0x0C, 0x03, 0x0C, 0x03, 0x0C, 0x03,
};


static unsigned char threeCactusSmall [] U8G_PROGMEM = {
  0x04, 0x41, 0x00, 0x0C, 0x61, 0x00, 0xFC, 0x79, 0x01, 0xFD, 0x7D, 0x01,
  0x7D, 0x6D, 0x01, 0x7D, 0x7D, 0x01, 0xCF, 0xE5, 0x01, 0xCE, 0x67, 0x00,
  0x8C, 0x67, 0x00, 0x0C, 0x63, 0x00, 0x0C, 0x61, 0x00, 0x0C, 0x61, 0x00,
};


static unsigned char dinoBla [] U8G_PROGMEM = {
  0x00, 0xFC, 0x07, 0x00, 0xFE, 0x07, 0x00, 0xC6, 0x0F, 0x00, 0xC6, 0x0F,
  0x00, 0xCE, 0x0F, 0x00, 0xFE, 0x0F, 0x00, 0xFE, 0x0F, 0x06, 0xFF, 0x03,
  0x87, 0x7F, 0x00, 0xE7, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00,
  0xFF, 0x3F, 0x00, 0xFE, 0x3F, 0x00, 0xFC, 0x1F, 0x00, 0xF8, 0x1F, 0x00,
  0xF0, 0x1F, 0x00, 0xF0, 0x0E, 0x00, 0x60, 0x0E, 0x00, 0x60, 0x0E, 0x00,
  0xE0, 0x1E, 0x00,
};


static unsigned char gameOver [] U8G_PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0xF8, 0x01, 0x02, 0x0C, 0x0C, 0x3E, 0x00, 0x78, 0x60,
  0x30, 0x7C, 0xF0, 0x01, 0x0C, 0x01, 0x07, 0x14, 0x0A, 0x02, 0x00, 0x84,
  0x40, 0x10, 0x04, 0x10, 0x02, 0x04, 0x00, 0x05, 0x14, 0x0A, 0x02, 0x00,
  0x02, 0x41, 0x10, 0x04, 0x10, 0x02, 0xC4, 0x81, 0x0D, 0x34, 0x0B, 0x3E,
  0x00, 0x78, 0x00, 0x07, 0x7C, 0x10, 0x02, 0x00, 0x84, 0x00, 0x05, 0x14,
  0x0A, 0x02, 0x00, 0x02, 0x81, 0x0F, 0x04, 0x10, 0x01, 0x04, 0x81, 0x0F,
  0x64, 0x09, 0x02, 0x00, 0x84, 0xC0, 0x18, 0xC4, 0x08, 0x02, 0x00, 0x84,
  0x00, 0x05, 0x04, 0x10, 0x02, 0xF8, 0x41, 0x10, 0xC4, 0x08, 0x3E, 0x00,
  0x78, 0x00, 0x07, 0x7C, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};


int currentStateCLK;
int lastStateCLK;
int MyScore = 0;
int dinoMove = 0;
volatile int jumping = 0;
int cloudx = 128;
int obstacles [2] = {1, 4};
int obstaclex [2] = {128, 200};
int speed = 8;
unsigned long startTime = millis(), curTime;
int lastBeep = 0;


void ShowScore () {
  if (gameStatus == gamePlaying) {
    curTime = millis ();
    MyScore = (curTime - startTime) * speed / 1000;
    OurDisplay.showNumberDecEx(MyScore);
    if (MyScore / 100 > lastBeep) {
      tone (buzzer, 1000, 100);
      delay (150);
      tone (buzzer, 1250, 100);
      lastBeep = MyScore / 100;
    }
  }
}


void StartStopGame () {
  static unsigned long last_interrupt = 0;
  if (millis() - last_interrupt > DBOUNCE) {
    if (gameStatus == gamePlaying) {
      if (jumping == 0) {
        jumping = 1;
        tone (buzzer, jumpSound, 100);
      }
    }
    else if (gameStatus == gameStart) gameStatus = gamePlaying;
    else gameStatus = gameStart;
    last_interrupt = millis();
  }
}


void resetGame () {
  MyScore = 0;
  startTime = millis();
  obstaclex[0] = 128;
  obstaclex [1] = 200;
  dinoMove = 0;
}


void setup() {
  OurDisplay.setBrightness(7);
  OurDisplay.clear();
  resetGame();
  ShowScore ();
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);
  pinMode(buzzer, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(SW), StartStopGame, FALLING);
}


void loop(void) {
  ShowScore();
  My_u8g_Panel.firstPage();
  do {
    draw();
  } while ( My_u8g_Panel.nextPage() );
  if (gameStatus == gamePlaying) {
    moveDino();
    moveCloud();
    moveObstacles();
  }
}


void moveDino () {
  if (jumping == 0) dinoMove = (dinoMove + 1) % 3;
  else {
    if (jumping == 1) {
      dinoMove = dinoMove + 8;
      if (dinoMove > 32) jumping = 2;
    } else {
      dinoMove = dinoMove - 8;
      if (dinoMove < 8) {
        jumping = 0;
        dinoMove = 0;
      }
    }
  }
  checkCollision ();
}


void moveCloud () {
  cloudx --;
  if (cloudx < -38) cloudx = 128;
}


void moveObstacles() {
  int obx = obstaclex [0];
  obx = obx - speed;
  if (obx < -20) {
    obstaclex[0] = obstaclex[1];
    obstaclex[1] = obstaclex[0] + random(80, 125);
    obstacles[0] = obstacles[1];
    obstacles[1] = random (1, 6);
  }
  else {
    obstaclex[0] = obx;
    obstaclex[1] -= speed;
  }
}


void draw(void) {
  u8g_prepare();
  if (gameStatus == gamePlaying) {
    drawDino ();
    drawShape (0, cloudx);
    drawObsticles ();
  }
  else if (gameStatus == gameStart) {
    My_u8g_Panel.drawStr( 0, 10, "Welcome to");
    My_u8g_Panel.drawStr( 10, 30, "Dino!!");
    My_u8g_Panel.drawStr( 0, 50, "Push to begin");
    resetGame();
    ShowScore();
  }
  else {
    My_u8g_Panel.drawXBMP( 14, 12, 100, 15, gameOver);
    drawDino ();
    drawShape (0, cloudx);
    drawObsticles ();
  }
}


void drawDino (void) {
  if (gameStatus == gameEnd) {
    My_u8g_Panel.drawXBMP( 0, 43 - dinoMove, 20, 21, dinoBla);
    return;
  }
  switch (dinoMove) {
    case -1: My_u8g_Panel.drawXBMP( 0, 43, 20, 21, dinoBla); break;
    case 0: My_u8g_Panel.drawXBMP( 0, 43, 20, 21, dinoJump); break;
    case 1: My_u8g_Panel.drawXBMP( 0, 43, 20, 21, dinoLeft); break;
    case 2: My_u8g_Panel.drawXBMP( 0, 43, 20, 21, dinoRight); break;
    default: My_u8g_Panel.drawXBMP( 0, 43 - dinoMove, 20, 21, dinoJump); break;
  }
}


void drawShape (int shape, int x) {
  switch (shape) {
    case 0: My_u8g_Panel.drawXBMP( x, 5, 39, 12, cloud); break;
    case 1: My_u8g_Panel.drawXBMP( x, 44, 10, 20, oneCactus); break;
    case 2: My_u8g_Panel.drawXBMP( x, 44, 20, 20, twoCactus); break;
    case 3: My_u8g_Panel.drawXBMP( x, 44, 20, 20, threeCactus); break;
    case 4: My_u8g_Panel.drawXBMP( x, 52, 6, 12, oneCactusSmall); break;
    case 5: My_u8g_Panel.drawXBMP( x, 52, 12, 12, twoCactusSmall); break;
    case 6: My_u8g_Panel.drawXBMP( x, 52, 17, 12, threeCactusSmall); break;
  }
}


void checkCollision () {
  int obx = obstaclex [0];
  int obw, obh;
  switch (obstacles[0]) {
    case 0: obw = 39; obh = 10; break;
    case 1: obw = 10; obh = 20; break;
    case 2: obw = 17; obh = 20; break;
    case 3: obw = 17; obh = 20; break;
    case 4: obw = 6; obh = 12; break;
    case 5: obw = 12; obh = 12; break;
    case 6: obw = 17; obh = 12; break;
  }
  if (obx > 15 || obx + obw < 5 || dinoMove > obh - 3) {}
  else {
    gameStatus = gameEnd;
    tone (buzzer, 125, 100);
    delay(150);
    tone (buzzer, 125, 100);
  }
}


void drawObsticles () {
  drawShape (obstacles[0], obstaclex[0]);
  drawShape (obstacles[1], obstaclex[1]);
}


void u8g_prepare(void) {
  My_u8g_Panel.setFont(u8g_font_6x10);
  My_u8g_Panel.setFontRefHeightExtendedText();
  My_u8g_Panel.setDefaultForegroundColor();
  My_u8g_Panel.setFontPosTop();
}

TM1637Display.h — Display Library Header

#ifndef __TM1637DISPLAY__
#define __TM1637DISPLAY__


#include <inttypes.h>


#define SEG_A   0b00000001
#define SEG_B   0b00000010
#define SEG_C   0b00000100
#define SEG_D   0b00001000
#define SEG_E   0b00010000
#define SEG_F   0b00100000
#define SEG_G   0b01000000
#define SEG_DP  0b10000000


#define DEFAULT_BIT_DELAY  100


class TM1637Display {


public:
  TM1637Display(uint8_t pinClk, uint8_t pinDIO, unsigned int bitDelay = DEFAULT_BIT_DELAY);
  void setBrightness(uint8_t brightness, bool on = true);
  void setSegments(const uint8_t segments[], uint8_t length = 4, uint8_t pos = 0);
  void clear();
  void showNumberDec(int num, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0);
  void showNumberDecEx(int num, uint8_t dots = 0, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0);
  void showNumberHexEx(uint16_t num, uint8_t dots = 0, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0);
  uint8_t encodeDigit(uint8_t digit);


protected:
  void bitDelay();
  void start();
  void stop();
  bool writeByte(uint8_t b);
  void showDots(uint8_t dots, uint8_t* digits);
  void showNumberBaseEx(int8_t base, uint16_t num, uint8_t dots = 0, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0);


private:
  uint8_t m_pinClk;
  uint8_t m_pinDIO;
  uint8_t m_brightness;
  unsigned int m_bitDelay;
};


#endif

TM1637Display.cpp — Display Library Implementation

extern "C" {
  #include <stdlib.h>
  #include <string.h>
  #include <inttypes.h>
}


#include "TM1637Display.h"
#include <Arduino.h>


#define TM1637_I2C_COMM1    0x40
#define TM1637_I2C_COMM2    0xC0
#define TM1637_I2C_COMM3    0x80


const uint8_t digitToSegment[] = {
  0b00111111,  // 0
  0b00000110,  // 1
  0b01011011,  // 2
  0b01001111,  // 3
  0b01100110,  // 4
  0b01101101,  // 5
  0b01111101,  // 6
  0b00000111,  // 7
  0b01111111,  // 8
  0b01101111,  // 9
  0b01110111,  // A
  0b01111100,  // b
  0b00111001,  // C
  0b01011110,  // d
  0b01111001,  // E
  0b01110001   // F
};


static const uint8_t minusSegments = 0b01000000;


TM1637Display::TM1637Display(uint8_t pinClk, uint8_t pinDIO, unsigned int bitDelay)
{
  m_pinClk = pinClk;
  m_pinDIO = pinDIO;
  m_bitDelay = bitDelay;
  pinMode(m_pinClk, INPUT);
  pinMode(m_pinDIO, INPUT);
  digitalWrite(m_pinClk, LOW);
  digitalWrite(m_pinDIO, LOW);
}


void TM1637Display::setBrightness(uint8_t brightness, bool on)
{
  m_brightness = (brightness & 0x7) | (on? 0x08 : 0x00);
}


void TM1637Display::setSegments(const uint8_t segments[], uint8_t length, uint8_t pos)
{
  start();
  writeByte(TM1637_I2C_COMM1);
  stop();
  start();
  writeByte(TM1637_I2C_COMM2 + (pos & 0x03));
  for (uint8_t k=0; k < length; k++)
    writeByte(segments[k]);
  stop();
  start();
  writeByte(TM1637_I2C_COMM3 + (m_brightness & 0x0f));
  stop();
}


void TM1637Display::clear()
{
  uint8_t data[] = { 0, 0, 0, 0 };
  setSegments(data);
}


void TM1637Display::showNumberDec(int num, bool leading_zero, uint8_t length, uint8_t pos)
{
  showNumberDecEx(num, 0, leading_zero, length, pos);
}


void TM1637Display::showNumberDecEx(int num, uint8_t dots, bool leading_zero,
                                    uint8_t length, uint8_t pos)
{
  showNumberBaseEx(num < 0? -10 : 10, num < 0? -num : num, dots, leading_zero, length, pos);
}


void TM1637Display::showNumberHexEx(uint16_t num, uint8_t dots, bool leading_zero,
                                    uint8_t length, uint8_t pos)
{
  showNumberBaseEx(16, num, dots, leading_zero, length, pos);
}


void TM1637Display::showNumberBaseEx(int8_t base, uint16_t num, uint8_t dots, bool leading_zero,
                                     uint8_t length, uint8_t pos)
{
  bool negative = false;
  if (base < 0) {
    base = -base;
    negative = true;
  }


  uint8_t digits[4];


  if (num == 0 && !leading_zero) {
    for(uint8_t i = 0; i < (length-1); i++)
      digits[i] = 0;
    digits[length-1] = encodeDigit(0);
  }
  else {
    for(int i = length-1; i >= 0; --i) {
      uint8_t digit = num % base;
      if (digit == 0 && num == 0 && leading_zero == false)
        digits[i] = 0;
      else
        digits[i] = encodeDigit(digit);
      if (digit == 0 && num == 0 && negative) {
        digits[i] = minusSegments;
        negative = false;
      }
      num /= base;
    }
  }


  if(dots != 0) {
    showDots(dots, digits);
  }
  setSegments(digits, length, pos);
}


void TM1637Display::bitDelay()
{
  delayMicroseconds(m_bitDelay);
}


void TM1637Display::start()
{
  pinMode(m_pinDIO, OUTPUT);
  bitDelay();
}


void TM1637Display::stop()
{
  pinMode(m_pinDIO, OUTPUT);
  bitDelay();
  pinMode(m_pinClk, INPUT);
  bitDelay();
  pinMode(m_pinDIO, INPUT);
  bitDelay();
}


bool TM1637Display::writeByte(uint8_t b)
{
  uint8_t data = b;
  for(uint8_t i = 0; i < 8; i++) {
    pinMode(m_pinClk, OUTPUT);
    bitDelay();
    if (data & 0x01)
      pinMode(m_pinDIO, INPUT);
    else
      pinMode(m_pinDIO, OUTPUT);
    bitDelay();
    pinMode(m_pinClk, INPUT);
    bitDelay();
    data = data >> 1;
  }
  pinMode(m_pinClk, OUTPUT);
  pinMode(m_pinDIO, INPUT);
  bitDelay();
  pinMode(m_pinClk, INPUT);
  bitDelay();
  uint8_t ack = digitalRead(m_pinDIO);
  if (ack == 0)
    pinMode(m_pinDIO, OUTPUT);
  bitDelay();
  pinMode(m_pinClk, OUTPUT);
  bitDelay();
  return ack;
}


void TM1637Display::showDots(uint8_t dots, uint8_t* digits)
{
  for(int i = 0; i < 4; ++i) {
    digits[i] |= (dots & 0x80);
    dots <<= 1;
  }
}


uint8_t TM1637Display::encodeDigit(uint8_t digit)
{
  return digitToSegment[digit & 0x0f];
}

Code Explanation :

Pin Setup: Pins are defined for the rotary encoder (CLK, DT, SW), TM1637 display (DCLK, DIO), and buzzer. The OLED uses I2C pins A4 and A5 automatically.

Bitmap Graphics: All game graphics including the dinosaur animations (running left, right, jumping, game over), clouds, and 6 types of cacti are stored as bitmap arrays in program memory using U8G_PROGMEM to save RAM.

Game States: The game has 3 states — gameStart (welcome screen), gamePlaying (active game), and gameEnd (game over screen).

ShowScore(): Calculates the score based on elapsed time and speed, displays it on the TM1637 display, and plays a double beep every 100 points.

StartStopGame(): Interrupt-driven function triggered by the rotary encoder button. Starts the game, makes the dino jump, or restarts after game over.

moveDino(): Handles the dinosaur movement animation. When jumping, the dino moves up 8 pixels per frame until it reaches height 32, then comes back down.

moveObstacles(): Moves obstacles from right to left at the current speed. When an obstacle goes off screen, a new random obstacle is generated.

checkCollision(): Checks if the dinosaur has collided with an obstacle based on position and size. If collision is detected, the game ends and the buzzer plays two short beeps.

drawDino(): Draws the correct dinosaur bitmap based on the current movement state — running left leg, running right leg, jumping, or game over pose.

How to Use :

  • Create a new folder and place all 3 files inside: Dino.ino, TM1637Display.h, TM1637Display.cpp
  • Wire the circuit as shown in the diagram above
  • Install the U8glib library in Arduino IDE
  • Open Dino.ino in Arduino IDE and upload to your Arduino Uno
  • The OLED will show “Welcome to Dino!! Push to begin”
  • Press the rotary encoder button to start the game
  • Press the button again to make the T-Rex jump over cacti
  • Try to survive as long as possible and beat your high score!

Customisation Tips :

  • Change game speed: Modify int speed = 8 to make the game faster or slower
  • Change jump height: Modify if (dinoMove > 32) jumping = 2 to change the jump arc
  • Change obstacle spacing: Modify random(80, 125) to change how far apart obstacles appear
  • Change score milestone: Modify MyScore / 100 > lastBeep to change when the beep plays
  • Change display brightness: Modify OurDisplay.setBrightness(7) from 0 (dim) to 7 (brightest)

You have successfully built a fully playable Arduino T-Rex Dinosaur Game using an OLED display, rotary encoder, buzzer, and score display! This is one of the most impressive Arduino projects you can build, combining game physics, bitmap graphics, collision detection, sound effects, and score tracking all in one.

Challenge your friends to beat your high score and share your build with us at Tinkercircuits.com!

Happy Tinkering! The Tinker Circuits Team

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button