// Stacker Game Code
#include <MD_MAX72xx.h>
#include <MD_Parola.h>
#include <SPI.h>
#include <OneButton.h>
// #include "Font5x3.h"
MD_MAX72XX::fontType_t StackerFont[] PROGMEM = {
'F', 1, 32, 127, 5,
2, 0, 0, // 32 - 'Space'
4, 16, 16, 0, 16, // 33 //1, 23, // 33 - '!'
3, 3, 0, 3, // 34 - '"'
3, 31, 10, 31, // 35 - '#'
3, 22, 31, 13, // 36 - '$'
3, 9, 4, 18, // 37 - '%'
3, 10, 21, 26, // 38 - '&'
1, 3, // 39
2, 14, 17, // 40 - '('
2, 17, 14, // 41 - ')'
3, 10, 4, 10, // 42 - '*'
3, 4, 14, 4, // 43 - '+'
2, 16, 8, // 44 - ','
3, 4, 4, 4, // 45 - '-'
1, 16, // 46 - '.'
3, 8, 4, 2, // 47 - '/'
3, 31, 17, 31, // 48 - '0'
2, 0, 31, // 49 - '1'
3, 29, 21, 23, // 50 - '2'
3, 17, 21, 31, // 51 - '3'
3, 7, 4, 31, // 52 - '4'
3, 23, 21, 29, // 53 - '5'
3, 31, 21, 29, // 54 - '6'
3, 1, 1, 31, // 55 - '7'
3, 31, 21, 31, // 56 - '8'
3, 23, 21, 31, // 57 - '9'
1, 10, // 58 - ':'
2, 16, 10, // 59 - ';'
3, 4, 10, 17, // 60 - '<'
3, 10, 10, 10, // 61 - '='
3, 17, 10, 4, // 62 - '>'
3, 1, 21, 3, // 63 - '?'
3, 14, 21, 22, // 64 - '@'
5, 8, 20, 28, 20, 20, // 65 - 'A'
5, 24, 20, 24, 20, 24, // 66 - 'B'
4, 12, 16, 16, 12, // 67 - 'C'
4, 24, 20, 20, 24, // 68 - 'D'
5, 28, 16, 24, 16, 28, // 69 - 'E'
5, 28, 16, 28, 16, 16, // 70 - 'F'
5, 12, 16, 20, 20, 12, // 71 - 'G'
5, 20, 20, 28, 20, 20, // 72 - 'H'
4, 28, 8, 8, 28, // 73 - 'I'
5, 28, 8, 8, 40, 16, // 74 - 'J'
5, 20, 20, 24, 20, 20, // 75 - 'K'
4, 16, 16, 16, 28, // 76 - 'L'
4, 34, 54, 42, 34, // 77 - 'M'
4, 36, 52, 44, 36, // 78 - 'N'
4, 8, 20, 20, 8, // 79 - 'O'
5, 28, 20, 28, 16, 16, // 80 - 'P'
5, 8, 20, 20, 8, 4, // 81 - 'Q'
5, 28, 20, 24, 20, 20, // 82 - 'R'
5, 12, 16, 8, 4, 24, // 83 - 'S'
4, 28, 8, 8, 8, // 84 - 'T'
4, 20, 20, 20, 28, // 85 - 'U'
4, 20, 20, 20, 8, // 86 - 'V'
4, 34, 34, 42, 54, // 87 - 'W'
4, 36, 24, 24, 36, // 88 - 'X'
5, 20, 20, 8, 8, 8, // 89 - 'Y'
5, 28, 4, 8, 16, 28, // 90 - 'Z'
2, 31, 17, // 91 - '['
3, 2, 4, 8, // 92 - '\'
2, 17, 31, // 93 - ']'
3, 2, 1, 2, // 94 - '^'
3, 16, 16, 16, // 95 - '_'
2, 1, 2, // 96 - '`'
5, 24, 4, 28, 20, 12, // 97 - 'a'
5, 16, 16, 28, 20, 28, // 98 - 'b'
4, 12, 16, 16, 12, // 99 - 'c'
5, 4, 4, 28, 20, 28, // 100 - 'd'
4, 8, 20, 24, 12, // 101 - 'e'
5, 12, 16, 24, 16, 16, // 102 - 'f'
5, 8, 20, 28, 4, 24, // 103 - 'g'
5, 16, 16, 28, 20, 20, // 104 - 'h'
4, 16, 0, 16, 16, // 105 - 'i'
5, 4, 0, 4, 20, 8, // 106 - 'j'
5, 16, 16, 20, 24, 20, // 107 - 'k'
4, 16, 16, 16, 12, // 108 - 'l'
4, 24, 60, 36, 36, // 109 - 'm'
4, 8, 20, 20, 20, // 110 - 'n'
4, 8, 20, 20, 8, // 111 - 'o'
5, 24, 20, 24, 16, 16, // 112 - 'p'
5, 12, 20, 12, 4, 4, // 113 - 'q'
4, 12, 16, 16, 16, // 115 - 'r'
5, 12, 16, 8, 4, 24, // 115 - 's'
5, 16, 16, 28, 16, 12, // 116 - 't'
4, 20, 20, 20, 28, // 117 - 'u'
4, 20, 20, 20, 8, // 118 - 'v'
4, 36, 36, 60, 24, // 119 - 'w'
3, 20, 8, 20, // 120 - 'x'
4, 20, 20, 8, 16, // 121 - 'y'
5, 28, 4, 8, 16, 28, // 122 - 'z'
3, 4, 27, 17, // 123 - '{'
1, 27, // 124 - '|'
3, 17, 27, 4, // 125 - '}'
3, 6, 2, 3, // 126 - '~'
3, 31, 31, 31, // 127 - 'Full Block'
};
// Gunakan tombol untuk mengubah antara mode atau lakukan dengan timer
#define USE_SWITCH_INPUT 1
#define SWITCH_PIN 4 // pin untuk tombol (mode aktif LOW)
OneButton button(4, true); // mendeklarasikan tombol dengan pustaka OneButton
// Interval waktu antara pembaruan tampilan
static int DELAYTIME = 150; // dalam milidetik
// Konfigurasi jumlah perangkat di rangkaian dan antarmuka hardware
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
// #define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CLK_PIN 13 // atau SCK
#define DATA_PIN 11 // atau MOSI
#define CS_PIN 10 // atau SS
// Variabel permainan
int dir = 1, prevX = 0, curX = 0, curY = 0, len = 3, prevLen = 3, difficulty = 0, inProgress = 0, isStart = 1, btnActive = 1, btnDelay = 300;
uint32_t btnLimit = 0;
boolean btnLastState = HIGH;
// Inisialisasi matriks LED
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
void doStacker()
{
Serial.print("curX =>"); Serial.println(curX);
static uint32_t lastTime = 0;
if (millis() - lastTime >= 1000)//DELAYTIME
{
// Jika mencapai tepi layar, ganti arah gerakan
if (curX == 0 || curX == 7 + len - 1) dir = !dir;
// Jika arah (dir) 0, bergerak ke kanan; jika 1, bergerak ke kiri
curX = (dir == 0) ? curX + 1 : curX - 1;
// Mengatur titik trailing point untuk dihapus, tergantung arah
int removePoint = (dir == 0) ? curX - len : curX + 1;
mx.setPoint(removePoint, curY, false); // menghapus trailing point
mx.setPoint(removePoint, curY + 1, false);
// Menyalakan blok sesuai panjang `len`
for (int i = 0; i < len; i++) {
mx.setPoint(curX - i, curY, true);
mx.setPoint(curX - i, curY + 1, true);
}
lastTime = millis();
}
mx.control(MD_MAX72XX::INTENSITY, 3);
}
// Fungsi ini dipanggil saat tombol ditekan
void hitButton() {
// Jika permainan baru dimulai
if (isStart) {
// Menyesuaikan panjang jika blok melewati batas
if (curX > 7) {
len = abs(len - (curX - 7)); // mengganti panjang `len`
curX = curX - (curX - 7); // mengatur posisi X yang baru
} else if (curX < 2) { // jika posisi terlalu ke kanan
len = (curX == 0) ? 1 : 2; // sesuaikan `len` berdasarkan posisi
}
prevX = curX;
isStart = 0; // menandakan game sudah mulai
}
// Memotong panjang blok jika tidak sesuai dengan posisi sebelumnya
if (prevX != curX && prevX - prevLen != curX - len) {
len = (prevX < curX) ? len - abs(curX - prevX) : prevLen - abs(curX - prevX);
for (int i = 0; i < abs(curX - prevX); i++) {
int j = (prevX < curX) ? curX - i : curX - len - i;
mx.setPoint(j, curY, false); // menghapus bagian yang overhang
mx.setPoint(j, curY + 1, false);
}
if (len > 0) doFall((prevX < curX) ? curX : curX - len, curY, abs(curX - prevX));
if (len < 1) {
reset(0); // reset permainan jika gagal
return;
}
if (prevX < curX) curX = curX - abs(curX - prevX);
}
prevX = curX;
if (curY + 2 == 10) {
reset(1); // reset dengan status menang
return;
}
prevLen = len; // menyimpan nilai `len` untuk perbandingan berikutnya
if (curY == 8 && len == 3) len--; // menyesuaikan panjang di level tertentu
if (curY == 18 && len == 2) len--;
curY += 2;
DELAYTIME -= (difficulty) ? 8 : 5; // mempercepat permainan tergantung kesulitan
btnActive = 1;
}
// Animasi jatuh ketika blok tidak tersusun
void doFall(int x, int y, int l) {
bool removeIt = false;
if (l == 2) x--;
for (int i = y; i > -1; i--) {
removeIt = false;
for (int j = x; j < x + l; j++) {
if (!mx.getPoint(j, i)) {
mx.setPoint(j, i, true);
removeIt = true;
}
}
delay(50);
for (int j = x; j < x + l; j++) if (removeIt) mx.setPoint(j, i, false);
}
}
// Mengatur ulang permainan
void reset(int win) {
if (win) {
for (int i = 0; i < 15; i++) flash_every_other();
for (int i = 0; i < 4; i++) {
P.print("WINNER");
delay(500);
mx.clear();
delay(500);
}
mx.clear();
} else {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < prevLen; j++) {
mx.setPoint(curX - j, curY, true);
mx.setPoint(curX - j, curY + 1, true);
}
delay(300);
for (int j = 0; j < prevLen; j++) {
mx.setPoint(curX - j, curY, false);
mx.setPoint(curX - j, curY + 1, false);
}
delay(300);
}
}
// Reset semua variabel ke kondisi awal
dir = 1; prevX = 0; curX = 0; curY = 0; len = 3; prevLen = 3; inProgress = 0; isStart = 1; btnActive = 1; btnLimit = 0; DELAYTIME = 150;
mx.clear();
}
void setup()
{
Serial.begin(9600);
Serial.println("STACKER MATRIX MAX 7219");
button.attachDoubleClick(doubleClick); // atur fungsi untuk klik ganda
button.attachClick(singleClick); // atur fungsi untuk klik tunggal
mx.begin();
pinMode(SWITCH_PIN, INPUT_PULLUP);
P.begin();
P.setFont(StackerFont);
P.setTextAlignment(PA_CENTER);
P.print("STACKR");
delay(1000);
mx.clear();
scrollText("CHOOSE MODE");
}
void scrollText(char curMessage[75]) {
mx.control(MD_MAX72XX::INTENSITY, 1);
P.displayClear();
P.displayScroll(curMessage, PA_CENTER, PA_SCROLL_LEFT, 30);
}
void loop()
{
if (inProgress) {
if (digitalRead(SWITCH_PIN) == HIGH) doStacker();
if (digitalRead(SWITCH_PIN) == LOW && btnActive && millis() > btnLimit) {
btnActive = 0;
btnLimit = millis() + btnDelay;
hitButton();
}
} else {
if (isStart) {
if (P.displayAnimate()) {
mx.clear();
P.setFont(StackerFont);
P.print("EASY");
isStart = 0;
}
}
button.tick();
}
}
void singleClick() {
P.print((difficulty) ? "EASY" : "HARD");
difficulty = !difficulty; // ganti mode kesulitan
}
void doubleClick() {
inProgress = 1;
isStart = 1;
mx.clear();
btnLimit = millis() + btnDelay;
delay(500);
}
// Efek berkedip secara bergantian
void flash_every_other() {
for (int a = 0; a < 4; a++) {
for (int i = 0; i < 8; i += 2) {
mx.setRow(a, i, 170);
mx.setRow(a, i + 1, 85);
}
}
delay(200);
for (int a = 0; a < 4; a++) {
for (int i = 0; i < 8; i += 2) {
mx.setRow(a, i, 85);
mx.setRow(a, i + 1, 170);
}
}
delay(200);
}