Arduino UNO LED Clock–With a Built-in Game!

by ArduinoPrints3D in Circuits > Clocks

50 Views, 2 Favorites, 0 Comments

Arduino UNO LED Clock–With a Built-in Game!

Clock cover art (2).png
Arduino UNO LED Clock–With a Built in Game!
I am a student and I designed this instructable. I hope you like it.😊

This project is based on my earlier "Arduino Uno LED Matrix Clock" which does not have the game.

After I made my last Instructable, I was thinking about how my project would work well for a simple game to pass the time. As with my last project, it is based on the Arduino Uno R4 and MAX7219 LED Matrix. I also used a rotary encoder and a DS1307 RTC Module. All 3D models are designed in Autodesk TinkerCAD. Enjoy!🙂

Supplies

FEU9211MI4XSA8J.jpg
FK11ARIMI4XSA8R.jpg
F02KXGFMI4XSA92.jpg
FES5T7XMI4XSA9L.jpg
FXD8DWXMI4XSA9E.jpg
FOY4FIMMI4XSA9T.jpg
FUTE53XMI4XSAA4.jpg

To build this project you will need:

  1. Arduino Uno R4
  2. 32 x 8 MAX7219 LED matrix
  3. Rotary encoder
  4. DS1307 module
  5. Mini solderless breadboard
  6. Jumper wires
  7. 10k Ω resistor
  8. 100 µF electrolytic capacitor
  9. 0.1 µF ceramic capacitor

Make Sure All Components Work

circuit_image.png

You should test each component individually before assembling the clock. I used example sketches from each library to do this.

Connect all components as shown in the wiring diagram above.

This first one is for the Parola library, which controls the MAX7219 module.

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 8

byte counter = 0;
char counterChar[4] = {0};
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

void setup()
{
P.begin();
P.setTextAlignment(PA_CENTER);
P.print("Hello!");
delay(4000);
P.displayText("LED Matrix test.", PA_CENTER, 50, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}

void loop()
{
if(P.displayAnimate()){
counter++;
sprintf(counterChar, "%d", counter);
P.displayText(counterChar, PA_CENTER, 20, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
}

This next code is for the CTRL library. The CTRL library handles encoder presses and turns.

#include <CtrlBtn.h>
#include <CtrlEnc.h>

// Define an onPress handler.
void onPress() {
Serial.println("Basic button pressed");
}

// Define an onRelease handler.
void onRelease() {
Serial.println("Basic button released");
}

// Define an onTurnleft handler.
void onTurnLeft() {
Serial.println("Basic rotary encoder turn left");
}

// Define an onTurnRight handler.
void onTurnRight() {
Serial.println("Basic rotary encoder turn right");
}
/*
Create a button with:
- signal pin.
- bounce duration.
- onPress handler (optional).
- onRelease handler (optional).
*/
CtrlBtn button(2, 15, onPress, onRelease);

// Create a rotary encoder with the clk signal pin number, dt signal pin number,
// onTurnleft & onTurnRight handler.
CtrlEnc encoder(4, 3, onTurnLeft, onTurnRight);

void setup() {
Serial.begin(115200);
}

void loop() {
// The process method will poll the button object and handle all it's functionality.
button.process();
encoder.process();
}

the last code is for the RTClib library. It communicates with the DS1307 module.

// Date and time functions using a DS1307 RTC connected via I2C and Wire lib
#include "RTClib.h"

RTC_DS1307 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void setup () {
Serial.begin(57600);

#ifndef ESP8266
while (!Serial); // wait for serial port to connect. Needed for native USB
#endif

if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
while (1) delay(10);
}

if (! rtc.isrunning()) {
Serial.println("RTC is NOT running, let's set the time!");
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}

// When time needs to be re-set on a previously configured device, the
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
// rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}

void loop () {
DateTime now = rtc.now();

Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
Serial.print(") ");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();

Serial.print(" since midnight 1/1/1970 = ");
Serial.print(now.unixtime());
Serial.print("s = ");
Serial.print(now.unixtime() / 86400L);
Serial.println("d");

// calculate a date which is 7 days, 12 hours, 30 minutes, and 6 seconds into the future
DateTime future (now + TimeSpan(7,12,30,6));

Serial.print(" now + 7d + 12h + 30m + 6s: ");
Serial.print(future.year(), DEC);
Serial.print('/');
Serial.print(future.month(), DEC);
Serial.print('/');
Serial.print(future.day(), DEC);
Serial.print(' ');
Serial.print(future.hour(), DEC);
Serial.print(':');
Serial.print(future.minute(), DEC);
Serial.print(':');
Serial.print(future.second(), DEC);
Serial.println();

Serial.println();
delay(3000);
}

The Code

IMG_2550.JPG
IMG_2551.JPG
IMG_2552.JPG
IMG_2556.JPG
IMG_2557.JPG

My code is written in Arduino IDE and is just my original code from my earlier clock project merged with a simple falling blocks game I made.

Simply paste this into the Arduino IDE, install all the libraries used, and upload to your board.

////////////////////ARDUINO UNO R4////////////////////
////////////////////LED MATRIX CLOCK////////////////////

////////////////////LIBRARIES////////////////////

#include "RTClib.h"
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <CtrlBtn.h>
#include <CtrlEnc.h>
#include <BlockNot.h>

////////////////////VARIABLES////////////////////

static char displayChar[9] = {0}; //stores the text for the display to show
char status = 'N';
// status variable — indicates the clock's current mode.
//
// Legend:
// 'N' - Normal display (time view)
// 'h' - Enter hour-edit menu
// 'm' - Enter minute-edit menu
// 'H' - Hour edit screen
// 'M' - Minute edit screen
// 's' - Set/confirm time
// 'e' - Exit menu / return to normal
// 'g' - Enter game menu
// 'G' - Game playing screen
uint8_t oldMin; //holds the last minute so screen updates when minute changes
int8_t setMinuteVar = 0; //holds the value when using the encoder to change the minute
int8_t setHourVar = 0; //holds the value when using the encoder to change the Hour
uint8_t gameBlocks[32] = {255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, };
int8_t gamePlayer = 0;
uint16_t gameScore = 0;

////////////////////PAROLA SETUP////////////////////

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 8
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
MD_MAX72XX *mx;

////////////////////RTC SETUP////////////////////

RTC_DS1307 rtc;

////////////////////BLOCKNOT SETUP////////////////////

BlockNot rtcRefreshTimer(1000);
BlockNot menuTimer(5000);
BlockNot gameBlockTimer(200);

////////////////////CTRL SETUP AND MENU HANDLERS////////////////////
//Switch...Case is used to handle the status of the variables
// Define an onTurnleft handler.
void onTurnLeft() {
menuTimer.start(WITH_RESET);
switch (status) {
case 'm':
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'H':
setHourVar--;
if(setHourVar <= -1){setHourVar = 23;}
snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
P.displayClear();
P.print(displayChar);
break;
case 'M':
setMinuteVar--;
if(setMinuteVar <= -1){setMinuteVar = 59;}
snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
P.displayClear();
P.print(displayChar);
break;
case 's':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 'e':
status = 's';
P.displayClear();
P.print("Set");
break;
case 'g':
status = 'e';
P.displayClear();
P.print("Exit");
break;
case 'G':
if (gamePlayer + 1 != gameBlocks[0]) {
if(((gamePlayer == 7) && (gameBlocks[0] == 0)) == 0){
gamePlayer++;
if(gamePlayer > 7){
gamePlayer = 0;
}
}
}
gameUpdateScreen();
break;
default:
break;
}
}

// Define an onTurnRight handler.
void onTurnRight() {
menuTimer.start(WITH_RESET);
switch (status) {
case 'h':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 'm':
status = 's';
P.displayClear();
P.print("Set");
break;
case 'H':
setHourVar++;
if(setHourVar >= 24){setHourVar = 0;}
snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
P.displayClear();
P.print(displayChar);
break;
case 'M':
setMinuteVar++;
if(setMinuteVar >= 60){setMinuteVar = 0;}
snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
P.displayClear();
P.print(displayChar);
break;
case 's':
status = 'e';
P.displayClear();
P.print("Exit");
break;
case 'e':
status = 'g';
P.displayClear();
P.print("Game");
break;
case 'G':
if (gamePlayer - 1 != gameBlocks[0]) {
if(((gamePlayer == 0) && (gameBlocks[0] == 7)) == 0){
gamePlayer--;
if(gamePlayer < 0){
gamePlayer = 7;
}
}
}
gameUpdateScreen();
break;
default:
break;
}
}

// Define an onPress handler.
void onPress() {
menuTimer.start(WITH_RESET);
DateTime now = rtc.now();
switch (status) {
case 'N':
setHourVar = now.hour();
setMinuteVar = now.minute();
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'h':
status = 'H';
snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
P.displayClear();
P.print(displayChar);
break;
case 'm':
status = 'M';
snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
P.displayClear();
P.print(displayChar);
break;
case 'H':
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'M':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 's':
status = 'N';
setTime(setHourVar, setMinuteVar);
showTime();
break;
case 'e':
status = 'N';
showTime();
break;
case 'g':
status = 'G';
P.displayClear();
P.displaySuspend(true);
playGame();
break;
case 'G':
status = 'g';
gameScore = 0;
for (uint8_t i = 0; i <= 31; i++) {
gameBlocks[i] = 255;
}
P.displaySuspend(false);
P.displayClear();
P.print("Game");
break;
default:
break;
}
}

CtrlBtn button(2, 50, onPress);
CtrlEnc encoder(4, 3, onTurnLeft, onTurnRight);

////////////////////TIME SETTING FUNCTIONS////////////////////

void setTime(int8_t newHour, int8_t newMinute) {//take two variables and sets the time of the rtc to them
DateTime before = rtc.now();
DateTime updated(before.year(), before.month(), before.day(), newHour, newMinute, 0);
rtc.adjust(updated);
}

void showTime() {//updates the display and shows the current time.
DateTime now = rtc.now();
oldMin = now.minute();
if( status == 'N'){
snprintf(displayChar, sizeof(displayChar), "%02d:%02d", now.hour(), now.minute());
P.displayClear();
P.print(displayChar);
}
}

////////////////////SETUP CODE////////////////////

void setup () {
P.begin();
P.setIntensity(0);
mx = P.getGraphicObject(); // store pointer globally
if (! rtc.begin()) {
while (1) delay(10);
}
if (! rtc.isrunning()) {
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
DateTime now = rtc.now();
oldMin = now.minute();
P.setTextAlignment(PA_CENTER);
P.print("Hello!");//Turns the screen on and says hello
delay(4000);
showTime();
menuTimer.stop();
rtcRefreshTimer.start(WITH_RESET);
}

void loop () {
if (menuTimer.triggered()) {//This exits the menu automatically
switch (status) {
case 'h':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'm':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'H':
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'M':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 's':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'e':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'g':
status = 'N';
showTime();
menuTimer.stop();
break;
default:
break;
}

}

if(rtcRefreshTimer.triggered()){//checks if time has changed
DateTime now = rtc.now();
if (oldMin != now.minute()) {
showTime();
}
}
encoder.process();
button.process();
}

void playGame(){
gameUpdateScreen();
while (status == 'G') {
encoder.process();
button.process();
if(gameBlockTimer.TRIGGERED){
gameScore++;
gameMoveBlocks();
}
}
}
void gameMoveBlocks() {
for (uint8_t i = 1; i <= 31; i++) {
if (gameBlocks[i] != 255) {
gameBlocks[i - 1] = gameBlocks[i];
}
}
gameBlocks[31] = rand() % 8;
if (gameBlocks[0] == gamePlayer){
delay(1000);
gamePrintScore();
gameScore = 0;
delay(1000);
for (uint8_t i = 0; i <= 31; i++) {
gameBlocks[i] = 255;
}
}
gameUpdateScreen();
}

void gameUpdateScreen() {
mx->clear();
mx->setPoint(gamePlayer, 0, true);
for(uint8_t i = 0; i <= 31; i++){
if(gameBlocks[i] != 255){
mx->setPoint(gameBlocks[i], i, true);
}
}
}

void gamePrintScore(){
P.displaySuspend(false);
snprintf(displayChar, sizeof(displayChar), "%05u", gameScore);
P.displayClear();
P.print(displayChar);
delay(4000);
P.displayClear();
P.displaySuspend(true);
}


Code Breakdown


This section just includes all libraries used in the code.

#include "RTClib.h"
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <CtrlBtn.h>
#include <CtrlEnc.h>
#include <BlockNot.h>

displayChar stores the text shown on the display.

static char displayChar[9] = {0};

status stores the mode the clock is in.

char status = 'N';
// status variable — indicates the clock's current mode.
//
// Legend:
// 'N' - Normal display (time view)
// 'h' - Enter hour-edit menu
// 'm' - Enter minute-edit menu
// 'H' - Hour edit screen
// 'M' - Minute edit screen
// 's' - Set/confirm time
// 'e' - Exit menu / return to normal
// 'g' - Enter game menu
// 'G' - Game playing screen

oldMin, setMinuteVar, and setHourVar store values used for setting and showing time

uint8_t oldMin;

gameBlocks and gamePlayer store the positions of blocks and the player.

uint8_t gameBlocks[32] = {255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, };
int8_t gamePlayer = 0;

gameScore stores the current score for the game

uint16_t gameScore = 0;

This initializes the LED matrix and sets hardware type.

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 8
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
MD_MAX72XX *mx;

Here it initializes the RTC.

RTC_DS1307 rtc;

This initializes timers to refresh the clock, exit the time, and move the blocks down.

BlockNot rtcRefreshTimer(1000);
BlockNot menuTimer(5000);
BlockNot gameBlockTimer(200);

This block of code initializes the CTRL library. It sets up handlers for turning the encoder left, turning it right, and pressing it. Each handler checks the current value of status and runs the code associated with that value.

void onTurnLeft() {
menuTimer.start(WITH_RESET);//resets the timer that automatically exits the menu
switch (status) {
case 'm':
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'H':
setHourVar--;
if(setHourVar <= -1){setHourVar = 23;}
snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
P.displayClear();
P.print(displayChar);
break;
case 'M':
setMinuteVar--;
if(setMinuteVar <= -1){setMinuteVar = 59;}
snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
P.displayClear();
P.print(displayChar);
break;
case 's':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 'e':
status = 's';
P.displayClear();
P.print("Set");
break;
case 'g':
status = 'e';
P.displayClear();
P.print("Exit");
break;
case 'G':
if (gamePlayer + 1 != gameBlocks[0]) {
if(((gamePlayer == 7) && (gameBlocks[0] == 0)) == 0){
gamePlayer++;
if(gamePlayer > 7){
gamePlayer = 0;
}
}
}
gameUpdateScreen();
break;
default:
break;
}
}

// Define an onTurnRight handler.
void onTurnRight() {
menuTimer.start(WITH_RESET);
switch (status) {
case 'h':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 'm':
status = 's';
P.displayClear();
P.print("Set");
break;
case 'H':
setHourVar++;
if(setHourVar >= 24){setHourVar = 0;}
snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
P.displayClear();
P.print(displayChar);
break;
case 'M':
setMinuteVar++;
if(setMinuteVar >= 60){setMinuteVar = 0;}
snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
P.displayClear();
P.print(displayChar);
break;
case 's':
status = 'e';
P.displayClear();
P.print("Exit");
break;
case 'e':
status = 'g';
P.displayClear();
P.print("Game");
break;
case 'G':
if (gamePlayer - 1 != gameBlocks[0]) {
if(((gamePlayer == 0) && (gameBlocks[0] == 7)) == 0){
gamePlayer--;
if(gamePlayer < 0){
gamePlayer = 7;
}
}
}
gameUpdateScreen();
break;
default:
break;
}
}

// Define an onPress handler.
void onPress() {
menuTimer.start(WITH_RESET);
DateTime now = rtc.now();
switch (status) {
case 'N':
setHourVar = now.hour();
setMinuteVar = now.minute();
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'h':
status = 'H';
snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
P.displayClear();
P.print(displayChar);
break;
case 'm':
status = 'M';
snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
P.displayClear();
P.print(displayChar);
break;
case 'H':
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'M':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 's':
status = 'N';
setTime(setHourVar, setMinuteVar);
showTime();
break;
case 'e':
status = 'N';
showTime();
break;
case 'g':
status = 'G';
P.displayClear();
P.displaySuspend(true);
playGame();
break;
case 'G':
status = 'g';
gameScore = 0;
for (uint8_t i = 0; i <= 31; i++) {
gameBlocks[i] = 255;
}
P.displaySuspend(false);
P.displayClear();
P.print("Game");
break;
default:
break;
}
}

CtrlBtn button(2, 50, onPress);
CtrlEnc encoder(4, 3, onTurnLeft, onTurnRight);

This function is used when changing time. It takes the time in hours and minutes and changes the time on the RTC accordingly.

void setTime(int8_t newHour, int8_t newMinute) {//take two variables and sets the time of the rtc to them
DateTime before = rtc.now();
DateTime updated(before.year(), before.month(), before.day(), newHour, newMinute, 0);
rtc.adjust(updated);
}

showTime updates the display with the current time. this is used whenever the time changes or the menu is exited.

void showTime() {//updates the display and shows the current time.
DateTime now = rtc.now();
oldMin = now.minute();
if( status == 'N'){
snprintf(displayChar, sizeof(displayChar), "%02d:%02d", now.hour(), now.minute());
P.displayClear();
P.print(displayChar);
}
}

Now we get to the setup code

void setup () {

This code starts the Parola library and sets the brightness.

P.begin();
P.setIntensity(0);
mx = P.getGraphicObject();

This sets up the RTC, makes sure it’s running, and sets the time if needed. It also save the current minute so the display updates correctly.

if (! rtc.begin()) {
while (1) delay(10);
}
if (! rtc.isrunning()) {
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
DateTime now = rtc.now();
oldMin = now.minute();

This show the text "Hello!" for 4 seconds and then shows the time.

P.setTextAlignment(PA_CENTER);
P.print("Hello!");//Turns the screen on and says hello
delay(4000);
showTime();

This stops the timer for exiting the menu and starts the RTC refresh time which is used to check if the time has changed

menuTimer.stop();
rtcRefreshTimer.start(WITH_RESET);

Now we get to the loop. this runs repeatedly while the code runs

void loop () {

This automatically exits the menu if it hasn’t been used for a while, so the clock always returns to the normal time display.

if (menuTimer.triggered()) {//This exits the menu automatically
switch (status) {
case 'h':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'm':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'H':
status = 'h';
P.displayClear();
P.print("Hours");
break;
case 'M':
status = 'm';
P.displayClear();
P.print("Minutes");
break;
case 's':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'e':
status = 'N';
showTime();
menuTimer.stop();
break;
case 'g':
status = 'N';
showTime();
menuTimer.stop();
break;
default:
break;
}
}

This checks if the time has switched to the next minute once a second.

if(rtcRefreshTimer.triggered()){//checks if time has changed
DateTime now = rtc.now();
if (oldMin != now.minute()) {
showTime();
}
}

This checks for changes in rotary encoder state and triggers one of the handlers if the rotary encoder has been turned or pressed

encoder.process();
button.process();

Now we get to the functions that run the game

playGame updates the screen to be showing the game and then starts a loop while the status variable equals G. In the loop it procceses the encoder. if the gameBlockTimer is triggered it increments the score and moves the blocks down.

void playGame(){
gameUpdateScreen();
while (status == 'G') {
encoder.process();
button.process();
if(gameBlockTimer.TRIGGERED){
gameScore++;
gameMoveBlocks();
}
}
}

gameMoveBlocks moves the variable for the horisontal position of each blcok to position right below it to simulate falling blocks. If a block directly above the player collides with the player it resets the game.

void gameMoveBlocks() {
for (uint8_t i = 1; i <= 31; i++) {
if (gameBlocks[i] != 255) {
gameBlocks[i - 1] = gameBlocks[i];
}
}
gameBlocks[31] = rand() % 8;
if (gameBlocks[0] == gamePlayer){
delay(1000);
gamePrintScore();
gameScore = 0;
delay(1000);
for (uint8_t i = 0; i <= 31; i++) {
gameBlocks[i] = 255;
}
}
gameUpdateScreen();
}

void gameUpdateScreen() {
mx->clear();
mx->setPoint(gamePlayer, 0, true);
for(uint8_t i = 0; i <= 31; i++){
if(gameBlocks[i] != 255){
mx->setPoint(gameBlocks[i], i, true);
}
}
}

gameUpdateScreen draws dots for the position of each block and the player.

void gameUpdateScreen() {
mx->clear();
mx->setPoint(gamePlayer, 0, true);
for(uint8_t i = 0; i <= 31; i++){
if(gameBlocks[i] != 255){
mx->setPoint(gameBlocks[i], i, true);
}
}
}

gamePrintScore just prints the value of gameScore for four seconds.

void gamePrintScore(){
P.displaySuspend(false);
snprintf(displayChar, sizeof(displayChar), "%05u", gameScore);
P.displayClear();
P.print(displayChar);
delay(4000);
P.displayClear();
P.displaySuspend(true);
}


3D Modeling

Screenshot 2025-11-29 143912.png
Screenshot 2025-11-29 143309.png
Screenshot 2025-11-29 143422.png
FRTRIOIMI4XSAAE.jpg

All models are designed in Autodesk TinkerCAD. I made thes with the help of my brother. It is a variant of a model I posted on NexPrint. We added a mounting bracket for the rotary encoder and made the case larger.

Print files on a 3D printer.

Innitial Assembly

F52JX7XMI4XSABB.jpg
F5870T6MI4XSABU.jpg
FFNN7XWMI4XSAC8.jpg
FZ1K25YMI4XSADK.jpg
F1R3RKKMI4XSAE4.jpg
FN9YJXTMI4XSAFS.jpg
FNJZ8F0MI4XSAGE.jpg
FISESTLMI4XSAH7.jpg

Now you are ready for assembly.

  1. Slide the UNO into the slot in the base.
  2. Hot glue the breadboard into the base, next to the arduino.
  3. Hot glue the RTC into the corner of the base.
  4. Screw the LED matrix onto the diffuser with the MAX7219 bracket.
  5. Screw the rotarty encoder onto the diffuser with the encoder bracker.
  6. Press the knob onto the rotary encoder.

Wiring

circuit_image.png
FEMAKOQMI4XSAIO.jpg
FO66IIEMI4XSAHX.jpg
F1KNROSMI4XSAJG.jpg
FWM5WR1MI4XSAK9.jpg
FYF9KSMMI4XSAL1.jpg
FV18H8HMI4XSALU.jpg

Follow these general steps for wiring, using the wiring guide as a reference,

  1. Connect the arduino to the breadboard power rails.
  2. Connect all modules to the bradboard power rails.
  3. Connect the capacitors to the power rails. making sure the longer pin of the electrolydic capacitor is in the + rail.
  4. Connect each module to the correct data pins.

Final Assembly

FGSLUU3MI4XSANJ.jpg
FJOGMXRMI4XSAOF.jpg
F1R9RJVMI4XSAPG.jpg
F7JK245MI4XSAQI.jpg

Now you are ready to finish this project!

  1. Slide the diffuser into the base.
  2. Insert nuts into the base.
  3. Screw the lid on.

Done!

You now have a finished clock! Thank you for making this project! I would love if you would post a make and feel free to comment if you have any questions or problems. I would also really love to see and adapatations or alternate games on this project. Feel free to post a new instructable if you want to adapt this project.

Have fun!🙂