#include <Adafruit_NeoPixel.h>
#include <Adafruit_NeoMatrix.h>
#define MATRIX_WIDTH 7
#define MATRIX_HEIGHT 12
#define PIN 3 // Menggunakan pin D3 untuk LED
Adafruit_NeoPixel strip = Adafruit_NeoPixel(MATRIX_HEIGHT * MATRIX_WIDTH, PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(MATRIX_WIDTH, MATRIX_HEIGHT, PIN,
NEO_MATRIX_TOP + NEO_MATRIX_LEFT +
NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
NEO_GRB + NEO_KHZ800);
int x = matrix.width();
const char* text = "STACKER";
int strobeIndex = 0;
// State variable using integers
int currentState = 0; // 0 for STROBE, 1 for SCROLL
// Timing variables
unsigned long previousMillis = 0;
const long strobeInterval = 300; // Time between strobe flashes
const long scrollInterval = 100; // Time between scroll updates
unsigned long strobeTiming = 0; // To control strobe effect timing
bool isCharacterVisible = false; // Track visibility of the character
unsigned long lastButtonPressTime = 0;
byte jeda = 0;
const uint16_t colors[] = {
matrix.Color(0, 0, 255), matrix.Color(255, 0, 0)
};
#define BUTTON_PIN 4
bool clicked = false;
bool game = 0;
bool goingLeft = true; // Bergerak ke kiri jika true, ke kanan jika false.
const int buzzerPin = 6; // Jika ada buzzer, atur ke -1 untuk menonaktifkan.
const int top = MATRIX_HEIGHT; // Tinggi strip, dapat disesuaikan jika menggunakan matriks lebih besar
const int bottom = top - 1; // Kurangi satu dari atas jika menggunakan seluruh strip.
const int maxHeight = bottom - top;
unsigned long prevMillis = 0; // Waktu sebelumnya dalam milidetik
unsigned long curMillis = 0; // Waktu saat ini dalam milidetik, untuk mencegah panggilan ganda ke millis()
unsigned long buttonwait = 0;
const long animationDuration = 2000; // Durasi animasi saat kehilangan blok
long defaultInterval = 300; // Waktu mulai antara pembaruan
int intervalModifier = 25; // pengurangan waktu atau delay kecepatan
long maxSpeed = 50; // Kecepatan maksimum yang diizinkan untuk blok. Jangan atur ke negatif
long interval = defaultInterval; // Waktu antara pembaruan. Ini tidak boleh dibuat sebagai const, karena akan berubah.
int cursize = 3; // Ukuran blok yang ditumpuk
int curpos = 3; // Posisi blok
int currow = bottom; // Baris LED tempat blok berada
// Untuk mencerminkan strip, semua "hal khusus" terjadi hanya di setup.
void setup() {
Serial.begin(115200);
strip.begin();
matrix.begin();
matrix.setTextWrap(false);
matrix.setBrightness(255);
for (int i = 0; i < MATRIX_HEIGHT * MATRIX_WIDTH; i++) {
strip.setPixelColor(i, strip.Color(0, 0, 0));
}
strip.show();
if (maxSpeed < 0) {
maxSpeed = 0;
Serial.println("Error: Kecepatan maksimum diatur ke negatif.");
}
pinMode(BUTTON_PIN, INPUT_PULLUP);
int value = digitalRead(BUTTON_PIN);
}
int lastState = HIGH;
int value = HIGH;
bool checkAir(); // Memeriksa apakah blok berada di udara atau di atas blok lain
void drawBlock(); // Panggilan untuk menggambar
void onUpdate(); // Panggilan untuk pembaruan
void resetGame(); // Fungsi untuk mereset permainan
void failState(int echo); // Dipanggil saat pemain kalah
void winState(); // Dipanggil saat pemain menang
void animatedrop(int pos, int width); // Animasi jatuh
void animatedrop(int pos, int width, bool isFail); // Animasi jatuh jika pemain kalah
void buttonPressed(); // Dipanggil saat tombol ditekan.
int led_from_pixel(byte row, byte col) {
int a = 0;
if (row % 2 == 0) {
a = (row * MATRIX_HEIGHT) + col; // Baris genap
} else {
a = ((row + 1) * MATRIX_HEIGHT) - 1 - col; // Baris ganjil
}
return a;
}
void onUpdate() {
if (goingLeft) { // Jika bergerak ke kiri
if (curpos > 0) { // Periksa jika bisa bergerak ke kiri
curpos--; // Jika bisa, mari kita pergi ke kiri
} else { // Jika tidak...
curpos++; // Mari kita pergi ke kanan
goingLeft = false; // Dan pastikan kita benar-benar bergerak ke kanan.
}
} else { // Jika bergerak ke kanan
if (curpos + cursize < 7) { // Periksa jika bisa bergerak ke kanan
curpos++; // Jika bisa, mari kita pergi ke kanan.
} else { // Jika tidak...
curpos--; // Pergi ke kiri
goingLeft = true; // Dan benar-benar pergi ke kiri
}
}
drawBlock(); // Akhirnya kita bisa menggambar
prevMillis = curMillis;
}
void drawBlock() {
int r = curpos;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(255, 0, 0));
if (cursize > 1) { // Periksa jika ada lebih dari satu
if (cursize == 3) { // Jika ada tiga, kita bisa menggambar yang ketiga
int r = curpos + 2;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(255, 0, 0));
}
int r = curpos + 1;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(255, 0, 0));
}
if (goingLeft) { // Jika pergerakan ke kiri
int r = curpos + cursize;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(0, 0, 0));
} else {
int r = curpos - 1;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(0, 0, 0));
}
strip.show();
}
bool checkAir() {
Serial.println("Memeriksa");
if (strip.getPixelColor(led_from_pixel(curpos, currow + 1)) == strip.Color(0, 0, 0)) {
if (cursize > 1) { // Jika ada lebih dari satu blok
if (cursize == 3) { // Jika 3 lebar
if (strip.getPixelColor(led_from_pixel(curpos + 2, currow + 1)) == strip.Color(0, 0, 0)) {
failState(1); // Kondisi gagal, blok triple jatuh
return true;
} else if (strip.getPixelColor(led_from_pixel(curpos + 1, currow + 1)) == strip.Color(0, 0, 0)) {
cursize -= 2; // 2 blok telah jatuh
animatedrop(curpos, 2);
} else {
cursize--; // 1 blok telah jatuh
animatedrop(curpos, 1);
}
} else if (strip.getPixelColor(led_from_pixel(curpos + 1, currow + 1)) == strip.Color(0, 0, 0)) {
failState(2); // Kondisi gagal, blok ganda jatuh
return true;
} else {
cursize--; // 1 blok telah jatuh
animatedrop(curpos, 1);
}
} else { // Jika hanya ada satu blok
failState(3); // Kondisi gagal, blok tunggal jatuh
return true;
}
} else { // Jika blok paling kiri belum jatuh
if (cursize > 1) { // Jika ada lebih dari satu blok
if (cursize == 3) { // Jika 3 lebar
if (strip.getPixelColor(led_from_pixel(curpos + 1, currow + 1)) == strip.Color(0, 0, 0)) {
cursize -= 2; // 2 blok telah jatuh
animatedrop(curpos + 1, 2);
} else if (strip.getPixelColor(led_from_pixel(curpos + 2, currow + 1)) == strip.Color(0, 0, 0)) {
cursize -= 1; // 1 blok telah jatuh
animatedrop(curpos + 2, 1);
} else { // Tidak ada yang jatuh
if (buzzerPin != -1) {
tone(buzzerPin, 1000, 100);
}
}
} else if (strip.getPixelColor(led_from_pixel(curpos + 1, currow + 1)) == strip.Color(0, 0, 0)) {
cursize -= 1; // 1 blok telah jatuh
animatedrop(curpos + 1, 1);
} else { // Tidak ada yang jatuh, dengan hanya dua blok
if (buzzerPin != -1) {
tone(buzzerPin, 1000, 100); // Bunyi
}
}
} else { // Jika hanya ada satu blok
if (buzzerPin != -1) {
tone(buzzerPin, 1000, 100);
}
}
}
return false;
}
void resetGame() {
Serial.println("resetGame");
curpos = 2;
currow = bottom;
cursize = 3;
defaultInterval = random(6, 11) * 50;
Serial.print("defaultInterval =>"); Serial.println(defaultInterval);
interval = defaultInterval;
for (int i = 0; i < MATRIX_HEIGHT * MATRIX_WIDTH; i++) {
strip.setPixelColor(i, strip.Color(0, 0, 0));
}
strip.show();
curMillis = millis();
prevMillis = curMillis; // Waktu sebelumnya dalam milidetik
}
void winState() {
Serial.println("Winstate!"); // Mengeluarkan sukses
if (buzzerPin != -1) { // Mainkan sedikit lagu jika bisa
tone(buzzerPin, 1000, 100);
delay(100);
tone(buzzerPin, 1200, 100);
delay(100);
tone(buzzerPin, 1000, 100);
}
resetGame(); // Reset permainan untuk bermain lagi
}
void failState(int echo) { // Fungsi untuk akhir permainan
Serial.print("\nFailstate! ");
Serial.print(echo);
Serial.print("\n");
// Animasi kegagalan
animatedrop(curpos, cursize, true);
resetGame(); // Reset permainan untuk bermain lagi
}
void animatedrop(int pos, int width) { // Fungsi ini menganimasi kehilangan blok saat pemain melewati batas
Serial.println("Kurang Satu");
unsigned long tillAnimation = curMillis + animationDuration;
unsigned long blinkTimer = curMillis + (animationDuration / 8);
bool blink = false;
while (tillAnimation > curMillis) {
curMillis = millis();
prevMillis = curMillis;
if (blinkTimer < curMillis) {
if (blink) { // jika mati
for (int i = 0; i < width; i++) {
int r = pos + i;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(255, 0, 0));
}
strip.show();
blink = false;
blinkTimer = curMillis + (animationDuration / 8);
if (buzzerPin != -1) {
tone(buzzerPin, 262, (animationDuration / 8));
}
} else {
for (int i = 0; i < width; i++) {
int r = pos + i;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(0, 0, 0));
}
strip.show();
if (buzzerPin != -1) {
tone(buzzerPin, 288, (animationDuration / 8));
}
blink = true;
blinkTimer = curMillis + (animationDuration / 8);
}
strip.show();
}
}
}
void animatedrop(int pos, int width, bool isFail) { // Ini hanya dipanggil jika gagal
Serial.println("Gagal");
unsigned long tillAnimation = curMillis + animationDuration;
unsigned long blinkTimer = curMillis + (animationDuration / 8);
unsigned int buzz = 600;
unsigned int buzzI = 10;
bool blink = false;
while (tillAnimation > curMillis) {
curMillis = millis();
prevMillis = curMillis;
if (blinkTimer < curMillis) {
if (blink) { // jika mati
for (int i = 0; i < width; i++) {
int r = pos + i;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(255, 0, 0));
}
strip.show();
blink = false;
blinkTimer = curMillis + (animationDuration / 8);
} else {
for (int i = 0; i < width; i++) {
int r = pos + i;
int c = currow;
int index_led = led_from_pixel(r, c);
strip.setPixelColor(index_led, strip.Color(0, 0, 0));
}
strip.show();
blink = true;
blinkTimer = curMillis + (animationDuration / 8);
}
if (buzzerPin != -1) {
tone(buzzerPin, buzz, (animationDuration / 8));
}
buzz -= buzzI;
strip.show();
}
}
}
void buttonPressed() {
if (value == HIGH) {
clicked = true;
buttonwait = curMillis;
} else if (value == LOW) {
if (!clicked) {
if (currow != bottom) {
if (checkAir()) {
return;
}
} else {
if (buzzerPin != -1) {
tone(buzzerPin, 1000, 100);
}
}
currow--; // Bergerak ke atas pada baris
if (currow <= maxHeight) {
winState();
return;
}
if (currow < (bottom - (top / 4)) && cursize == 3) {
cursize--;
} else if (currow < (bottom - (top / 2)) && cursize == 2) {
cursize--;
}
onUpdate();
intervalModifier = random(5, 11) * 5; // Rentang 25 (5*5) hingga 50 (10*5)
interval -= intervalModifier;
if (interval < maxSpeed) { // Pencegahan agar tidak melewati kecepatan maksimum
interval = maxSpeed;
}
Serial.print("intervalModifier =>"); Serial.print(intervalModifier);
Serial.print("|| interval =>"); Serial.println(interval);
clicked = true;
buttonwait = curMillis;
}
}
}
void loop() {
unsigned long currentMillis = millis();
curMillis = millis();
value = digitalRead(BUTTON_PIN);
if (value != lastState) {
lastState = value;
buttonPressed();
}
if (millis() - lastButtonPressTime > 1000 && clicked == 0 ) {
jeda++;
lastButtonPressTime = millis();
}
if (jeda > 11) {
jeda = 1;
game = 0;
previousMillis = currentMillis;
resetGame();
currentState = 0;
}
if (clicked) {
currentState = 3;
jeda = 1;
if (curMillis - buttonwait >= 50) {
clicked = false;
}
}
switch (currentState) {
case 0: // STROBE
if (currentMillis - previousMillis >= strobeInterval) {
previousMillis = currentMillis; // Update the last time
strobe();
}
break;
case 1: // SCROLL
if (currentMillis - previousMillis >= scrollInterval) {
previousMillis = currentMillis; // Update the last time
scroll();
}
break;
case 3: // SCROLL
if (game == 0) {
resetGame();
game = 1;
}
currentState = 4;
break;
case 4: // SCROLL
if (curMillis - prevMillis >= interval) { // Jika interval antara pembaruan telah tercapai
onUpdate(); // Pembaruan
}
break;
}
}
void scroll() {
matrix.fillScreen(0); // Turn off all the LEDs
matrix.setTextColor(colors[0]); // Set text color
matrix.setCursor(x, 2);
matrix.print(text);
x--; // Move left
// Reset position if the text has completely scrolled off the screen
if (x < -56) {
x = matrix.width();
currentState = 0;
}
matrix.show();
}
void strobe() {
int textLength = strlen(text);
if (strobeIndex < textLength) {
unsigned long currentMillis = millis();
if (currentMillis - strobeTiming >= strobeInterval / 2) { // Flash every half of the interval
strobeTiming = currentMillis; // Update the last time
if (isCharacterVisible) {
matrix.fillScreen(0); // Clear the matrix
} else {
matrix.fillScreen(0); // Clear the matrix
matrix.setTextColor(colors[1]); // Set text color
// Display the current character
matrix.setCursor(1, 2); // Fixed position
matrix.print(text[strobeIndex]);
}
matrix.show(); // Update display
isCharacterVisible = !isCharacterVisible; // Toggle visibility
if (!isCharacterVisible) { // Move to the next character if the character was shown
strobeIndex++;
if (strobeIndex >= textLength) {
strobeIndex = 0; // Reset index after displaying all characters
currentState = 1; // Switch to scrolling
}
}
}
}
}