
Get full C++ & Python code + lifetime access
The offline Chrome dino game, rebuilt as a real handheld gadget.
Everyone knows the little dinosaur that shows up when your internet goes down. In this project, we bring it to life on an ESP32-S3 with a 0.96" OLED display. Tap the touch sensor to jump, dodge cactus clusters, and chase your high score while a buzzer plays the classic beeps.
It's a perfect project for learning how real games work on a microcontroller: game loops, physics, sprite graphics, collision detection and input handling, all on a 128×64 pixel screen.
| Component | Quantity | Notes | |---|---|---| | ESP32-S3 Dev Board | 1 | ESP32-S3-DevKitC-1 or any S3 board | | 0.96" OLED Display (SSD1306, I2C, 128×64) | 1 | 4-pin version (VCC, GND, SCL, SDA) | | TTP223 Capacitive Touch Sensor | 1 | Small square touch module | | Passive Buzzer | 1 | Passive buzzer recommended for real tones | | Breadboard | 1 | Full or half size | | Jumper Wires | ~10 | Male-to-male or male-to-female | | USB Cable | 1 | Must be a data cable, not charge-only |
| Component | Component Pin | ESP32-S3 Pin | |---|---|---| | OLED Display | VCC | 3.3V | | OLED Display | GND | GND | | OLED Display | SDA | GPIO 8 | | OLED Display | SCL | GPIO 9 | | TTP223 Touch Sensor | VCC | 3.3V | | TTP223 Touch Sensor | GND | GND | | TTP223 Touch Sensor | SIG / I/O | GPIO 4 | | Buzzer | + (long leg) | GPIO 5 | | Buzzer | − (short leg) | GND |
ESP32-S3 DevKit
┌───────────────────┐
│ │
│ 3V3 ●──────────┼────┬──────────────► OLED VCC
│ │ └──────────────► TTP223 VCC
│ │
│ GND ●──────────┼────┬──────────────► OLED GND
│ │ ├──────────────► TTP223 GND
│ │ └──────────────► Buzzer (−)
│ │
│ GPIO8 ●──────────┼───────────────────► OLED SDA
│ GPIO9 ●──────────┼───────────────────► OLED SCL
│ │
│ GPIO4 ●──────────┼───────────────────► TTP223 SIG
│ │
│ GPIO5 ●──────────┼───────────────────► Buzzer (+)
│ │
└───────────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ OLED 0.96" │ │ TTP223 │ │ BUZZER │
│ │ │ ┌───────┐ │ │ ( O ) │
│ ▓▓▓▓▓▓▓▓▓ │ │ │ TOUCH │ │ │ │
│ ▓▓▓▓▓▓▓▓▓ │ │ └───────┘ │ │ + − │
│ │ │ │ └───┬─────┬───┘
│ VCC GND SCL SDA │ VCC SIG GND │ │ │
└──┬───┬───┬───┬┘ └──┬───┬───┬──┘ GPIO5 GND
│ │ │ │ │ │ │
3V3 GND G9 G8 3V3 G4 GND
Power tip: connect the ESP32's 3V3 and GND pins to the breadboard's power rails first. Then connect every module's VCC and GND to those rails. This keeps the wiring clean and easy to check.
Put the ESP32-S3 across the middle gap of the breadboard so the pins on each side sit in separate rows.
Before plugging in USB, check every wire against the pin table. Most problems come from a single wire in the wrong hole.
https://espressif.github.io/arduino-esp32/package_esp32_index.jsonGo to Sketch → Include Library → Manage Libraries and install:
| Setting | Value | |---|---| | Board | ESP32S3 Dev Module | | USB CDC On Boot | Enabled (needed for Serial Monitor over USB) | | Upload Speed | 921600 | | Port | The COM port of your ESP32-S3 |
dino_game.ino inside a folder named dino_game.The ESP32-S3 runs the game at about 30 frames per second. Every frame, it does the same five things:
When you tap, the dino gets an upward speed. Every frame, gravity pulls that speed down until the dino lands. If you release the sensor early, the upward speed is cut short, which creates the small-hop effect.
Every sprite (the dino, the cacti, the clouds) is stored as a grid of # and . characters, where # means a lit pixel. This makes the graphics easy to read and easy to redesign.
The dino uses two hitboxes, one for its head and one for its body, instead of one big rectangle. This means near-misses feel fair, just like the original game.
The distance between cactus groups is calculated from the current speed. As the game speeds up, the gaps scale with it, so every gap is always possible to clear.
The high score is stored using the ESP32's Preferences library, which writes to flash memory. It only writes when you beat your record, which protects the flash from wearing out.
0x3C, but some use 0x3D. Change OLED_ADDR in the code.You probably have a 1.3" OLED with the SH1106 chip, not SSD1306. They look identical but need a different library (Adafruit_SH110X). Check the product listing for the chip name.
ACTIVE_BUZZER to 1 in the code.Enable USB CDC On Boot in the Tools menu, then upload again. Without it, Serial output goes to a different port.
If you change pins, avoid these on the ESP32-S3:
Some long breadboards have a break in the power rails halfway along. If modules on one side work and the other side doesn't, add a small jumper to bridge the gap.
Some OLED modules accept 5V, but the ESP32-S3's GPIO pins run at 3.3V. Powering everything from 3.3V is the safest choice and avoids signal-level problems.
Don't change random numbers throughout the code. Every gameplay value is in the TUNING and Difficulty sections at the top (see the customization guide below).
The high score is stored in flash memory, so re-uploading the code doesn't clear it. To reset it, go to Tools → Erase All Flash Before Sketch Upload → Enabled, upload once, then set it back to Disabled.
All settings are at the top of the code, so there's no need to dig through the logic.
| Setting | What It Does | Default |
|---|---|---|
| JUMP_VEL | Jump height (more negative = higher) | -5.8 |
| GRAVITY | How fast the dino falls | 0.55 |
| START_SPEED | Game speed at the start | 4.0 |
| MAX_SPEED | Maximum game speed | 9.0 |
| ACCEL | How quickly the game speeds up | 0.002 |
| CHANCE_1 | % chance of a single cactus | 40 |
| CHANCE_2 | % chance of a double cactus (the rest are triples) | 35 |
| BIG_CHANCE | % chance each cactus is a large one | 35 |
| GAP_PER_SPEED | Spacing between cactus groups | 16.0 |
| NIGHT_MODE | Turn night mode on (1) or off (0) | 1 |
| VARIABLE_JUMP | Turn hold-to-jump on (1) or off (0) | 1 |
| ACTIVE_BUZZER | 0 = passive buzzer, 1 = active buzzer | 0 |
Each sprite is drawn with # (pixel on) and . (pixel off). Change the characters and you change the graphics: give the dino a hat, make the cacti taller, or create a brand-new character.
| Problem | Most Likely Cause | Fix |
|---|---|---|
| Blank OLED | SDA/SCL swapped or wrong address | Swap wires, or try 0x3D |
| Garbage on OLED | SH1106 display, not SSD1306 | Use the Adafruit_SH110X library |
| No sound | Active buzzer used as passive | Set ACTIVE_BUZZER to 1 |
| Touch not working | Touched during power-up | Power-cycle without touching |
| Jump only toggles | TTP223 pad B bridged | Remove the solder bridge on pad B |
| Upload stuck | Charge-only cable or no boot mode | Change cable, hold BOOT |
| Board keeps resetting | Wrong pin or short circuit | Recheck wiring, avoid strapping pins |
| Game feels laggy | I2C wires too long | Keep OLED wires short (under 20 cm) |
Built with ❤️ by Aditya Invents