Di bawah ini Anda akan menemukan semua komponen yang diperlukan untuk menyelesaikan tutorial Arduino ini. Setiap komponen mencakup tautan afiliasi ke berbagai marketplace tempat Anda dapat membelinya.
Gambar | Komponen | Deskripsi | Harga Terbaik | Beli |
---|---|---|---|---|
![]() | Arduino Uno Mikrokontroler | Mikrokontroler populer untuk proyek elektronik dan prototipe. | Rp 58.980
di Shopee | |
![]() | Layar OLED 128x64 I2C Display | Layar monokrom untuk menampilkan grafis game arcade. | Rp 22.900
di Tiktok | |
![]() | Modul Joystick Input | Joystick analog untuk mengontrol permainan dengan sumbu X, Y, dan tombol seleksi. | Rp 6.120
di Tiktok | |
![]() | Kabel Jumper Male-to-Female Kabel | Kabel untuk menghubungkan komponen ke Arduino. | Rp 9.980
di Shopee |
Mikrokontroler populer untuk proyek elektronik dan prototipe.
Layar monokrom untuk menampilkan grafis game arcade.
Joystick analog untuk mengontrol permainan dengan sumbu X, Y, dan tombol seleksi.
Mikrokontroler populer untuk proyek elektronik dan prototipe.
Bandingkan harga di berbagai marketplace
Layar monokrom untuk menampilkan grafis game arcade.
Bandingkan harga di berbagai marketplace
Joystick analog untuk mengontrol permainan dengan sumbu X, Y, dan tombol seleksi.
Bandingkan harga di berbagai marketplace
Kabel untuk menghubungkan komponen ke Arduino.
Bandingkan harga di berbagai marketplace
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>
#include <Wire.h>
#include <avr/pgmspace.h>
// --- Konfigurasi Dasar ---
// Pengaturan layar OLED (128x64 piksel)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Pin reset OLED (-1 jika tidak menggunakan pin reset khusus)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Pin Joystick
#define JOY_HORZ A0 // Pin analog untuk sumbu horizontal joystick
#define JOY_VERT A1 // Pin analog untuk sumbu vertikal joystick
#define JOY_SEL 2 // Pin digital untuk tombol select joystick (biasanya D2)
// Nilai tengah pembacaan ADC joystick (sekitar 512 untuk 10-bit ADC)
const int ADC_MIDPOINT = 512;
// Ambang batas untuk deteksi gerakan joystick (mengurangi sensitivitas)
const int JOY_THRESHOLD = 100;
// Waktu tunda untuk debounce input (mencegah pembacaan ganda saat tombol ditekan)
const int INPUT_DEBOUNCE_DELAY = 200; // ms
// Waktu tunda untuk splash screen di awal
const int SPLASH_SCREEN_DELAY = 2000; // ms
// Posisi teks untuk layar Game Over
const int GAME_OVER_POS_X = 20;
const int GAME_OVER_TEXT_Y = 20;
const int GAME_OVER_SCORE_Y = 30;
const int GAME_OVER_ACTION_Y = 40;
// --- Struktur Data Menu ---
// Teks item menu disimpan di PROGMEM untuk menghemat SRAM.
// PROGMEM adalah area memori flash Arduino tempat data konstan dapat disimpan.
const char menuItem0[] PROGMEM = "Brick Breaker";
const char menuItem1[] PROGMEM = "Space Invaders";
const char menuItem2[] PROGMEM = "Snake";
const char menuItem3[] PROGMEM = "Flappy Bird";
const char menuItem4[] PROGMEM = "Dino Run";
// Array pointer ke string menu, juga di PROGMEM.
const char *const menuItems[] PROGMEM = {menuItem0, menuItem1, menuItem2, menuItem3, menuItem4};
const int TOTAL_MENU_ITEMS = 5;
// --- Status Game Global ---
// Enum untuk merepresentasikan berbagai status game, lebih mudah dibaca daripada angka.
enum GameStateType
{
STATE_MENU,
STATE_BRICK_BREAKER,
STATE_SPACE_INVADERS,
STATE_SNAKE,
STATE_FLAPPY_BIRD,
STATE_DINO_RUN
};
GameStateType currentGameState = STATE_MENU; // Status game saat ini, dimulai dari menu.
int menuSelection = 0; // Pilihan menu saat ini (0 hingga TOTAL_MENU_ITEMS - 1).
int score = 0; // Skor pemain saat ini.
bool gameOver = false; // Status apakah game sedang berakhir (true) atau tidak (false).
// --- Variabel Game: Brick Breaker ---
#define BRICK_ROWS 3
#define BRICK_COLS 8
#define BRICK_WIDTH 16
#define BRICK_HEIGHT 6
bool bricks[BRICK_ROWS][BRICK_COLS]; // Array 2D untuk status setiap bata.
int paddleX = SCREEN_WIDTH / 2 - 15; // Posisi X paddle.
const int paddleY = SCREEN_HEIGHT - 4; // Posisi Y paddle (tetap).
const int paddleWidth = 30;
const int paddleHeight = 4;
float ballX = SCREEN_WIDTH / 2; // Posisi X bola.
float ballY = SCREEN_HEIGHT - 10; // Posisi Y bola.
float ballVelX = 2; // Kecepatan horizontal bola.
float ballVelY = -2; // Kecepatan vertikal bola.
const int ballSize = 4;
const int paddleSpeed = 3; // Kecepatan gerak paddle.
// --- Variabel Game: Space Invaders ---
#define ENEMY_ROWS 2
#define ENEMY_COLS 8
#define ENEMY_WIDTH 8
#define ENEMY_HEIGHT 6
bool enemies[ENEMY_ROWS][ENEMY_COLS]; // Status setiap musuh.
float enemyBaseX[ENEMY_ROWS][ENEMY_COLS]; // Posisi X dasar untuk setiap musuh
int shipX = SCREEN_WIDTH / 2 - 5; // Posisi X pesawat pemain.
const int shipY = SCREEN_HEIGHT - 8; // Posisi Y pesawat pemain (tetap).
const int shipWidth = 10;
const int shipHeight = 5;
int bulletX = -1; // Posisi X peluru (-1 jika tidak aktif).
int bulletY = -1; // Posisi Y peluru (-1 jika tidak aktif).
const int bulletWidth = 2;
const int bulletHeight = 4;
const int bulletSpeed = -5; // Kecepatan peluru (negatif = ke atas).
bool bulletActive = false; // Status peluru aktif atau tidak.
float enemySpeedX = 0.5; // Kecepatan horizontal musuh.
int enemyDirection = 1; // Arah gerak musuh (1 = kanan, -1 = kiri).
int enemyGlobalYOffset = 0; // Offset Y global untuk seluruh blok musuh Space Invaders
const int ENEMY_Y_STEP_DOWN = ENEMY_HEIGHT / 2; // Seberapa jauh musuh turun dalam satu langkah
// --- Variabel Game: Snake ---
#define GRID_SIZE 4 // Ukuran setiap kotak grid untuk ular dan makanan.
#define MAX_SNAKE_LENGTH 30
byte snakeX[MAX_SNAKE_LENGTH]; // Array posisi X setiap segmen ular.
byte snakeY[MAX_SNAKE_LENGTH]; // Array posisi Y setiap segmen ular.
// Menggunakan `byte` untuk snakeX/Y menghemat SRAM dibandingkan `int`.
int snakeLength = 3; // Panjang awal ular.
int snakeDir = 0; // Arah gerak ular: 0=kanan, 1=kiri, 2=atas, 3=bawah.
int foodX = -1; // Posisi X makanan.
int foodY = -1; // Posisi Y makanan.
unsigned long lastMoveTime = 0; // Waktu terakhir ular bergerak (untuk timing).
const int moveInterval = 150; // Interval pergerakan ular (ms).
// --- Variabel Game: Flappy Bird ---
float birdX = 20; // Posisi X burung.
float birdY = SCREEN_HEIGHT / 2; // Posisi Y burung.
float birdVelY = 0; // Kecepatan vertikal burung.
const float birdGravity = 0.5; // Gaya gravitasi yang mempengaruhi burung.
const float birdFlap = -3.5; // Kekuatan "kepakan" burung ke atas.
const int birdSize = 4;
const int birdSpeed = 1; // Kecepatan horizontal burung (jika bisa dikontrol).
#define PIPE_COUNT 3 // Jumlah pipa yang aktif di layar.
#define PIPE_WIDTH 10
#define PIPE_GAP 20 // Jarak vertikal antara pipa atas dan bawah.
#define PIPE_SPACING 50 // Jarak horizontal antar pipa.
byte pipeX[PIPE_COUNT]; // Posisi X setiap pipa.
byte pipeGapY[PIPE_COUNT]; // Posisi Y tengah dari celah pipa.
bool pipeScored[PIPE_COUNT]; // Apakah pipa ini sudah memberikan skor.
const int pipeMoveSpeed = -2; // Kecepatan pipa bergerak ke kiri.
// `lastUpdateTime` sudah ada di Dino, mungkin perlu nama unik jika dipakai bersamaan
// Untuk Flappy, akan digunakan nama yang lebih spesifik
unsigned long flappyLastUpdateTime = 0;
const int flappyUpdateInterval = 50; // Interval update game Flappy Bird (ms).
// --- Variabel Game: Dinosaur Run ---
#define DINO_WIDTH 16
#define DINO_HEIGHT 16
#define CACTUS_WIDTH 4
#define CACTUS_HEIGHT 8
#define MAX_CACTI 3
#define SCORE_ZONE_X 20 // X position for scoring zone (slightly past dinosaur)
int dinoX = 10; // Posisi X dinosaurus.
int dinoY = SCREEN_HEIGHT - DINO_HEIGHT; // Posisi Y dinosaurus (di atas tanah).
float dinoVelY = 0; // Kecepatan vertikal dinosaurus.
const float dinoGravity = 0.5; // Gaya gravitasi.
const float dinoJump = -5.0; // Kekuatan lompatan dinosaurus.
byte cactusX[MAX_CACTI]; // Posisi X setiap kaktus.
byte cactusActive[MAX_CACTI]; // Status aktif setiap kaktus (0=tidak, 1=aktif).
bool cactusScored[MAX_CACTI]; // Track if cactus has been scored already
const int cactusMoveSpeed = -2; // Kecepatan kaktus bergerak ke kiri.
unsigned long lastCactusTime = 0; // Waktu terakhir kaktus baru muncul.
const int cactusInterval = 2000; // Interval kemunculan kaktus baru (ms).
byte dinoFrame = 0; // Frame animasi dinosaurus (0 atau 1).
unsigned long lastFrameTime = 0; // Waktu terakhir frame animasi diubah.
const int frameInterval = 200; // Interval antar frame animasi (ms).
unsigned long dinoLastUpdateTime = 0; // Waktu terakhir game Dino diupdate (untuk timing).
const int dinoUpdateInterval = 50; // Interval update game Dino (ms), mirip flappyUpdateInterval.
// Sprite T-Rex (16x16 piksel), disimpan di PROGMEM.
// Setiap byte merepresentasikan 8 piksel horizontal.
// Format data: https://learn.adafruit.com/adafruit-gfx-graphics-library/graphics-primitives#bitmaps-2014530
const byte dinoSprite1[] PROGMEM = {
0x00, 0xF0, // .......XXXX.....
0x01, 0xF8, // ......XXXXXX....
0x03, 0xFC, // .....XXXXXXXX...
0x03, 0xFC, // .....XXXXXXXX...
0x03, 0xC0, // .....XXXX.......
0x07, 0x80, // ....XXXX........
0x0F, 0x00, // ...XXXX.........
0x1F, 0x80, // ..XXXXXX........
0x3F, 0xC0, // .XXXXXXXX.......
0x3F, 0x80, // .XXXXXXX........
0x7F, 0xE0, // XXXXXXXXXX......
0x60, 0xE0, // XX..XXX.........
0x00, 0xE0, // ....XXX.........
0x00, 0xE0, // ....XXX.........
0x01, 0xF0, // ...XXXXX........
0x01, 0xF0 // ...XXXXX........
};
const byte dinoSprite2[] PROGMEM = {
0x00, 0xF0, // .......XXXX.....
0x01, 0xF8, // ......XXXXXX....
0x03, 0xFC, // .....XXXXXXXX...
0x03, 0xFC, // .....XXXXXXXX...
0x03, 0xC0, // .....XXXX.......
0x07, 0x80, // ....XXXX........
0x0F, 0x00, // ...XXXX.........
0x1F, 0x80, // ..XXXXXX........
0x3F, 0xC0, // .XXXXXXXX.......
0x3F, 0x80, // .XXXXXXX........
0x7F, 0xE0, // XXXXXXXXXX......
0x60, 0xE0, // XX..XXX.........
0x00, 0xE0, // ....XXX.........
0x00, 0x40, // .....XX.........
0x01, 0xE0, // ....XXXX........
0x01, 0xE0 // ....XXXX........
};
// --- Deklarasi Fungsi (Prototypes) ---
// Memudahkan compiler menemukan fungsi yang didefinisikan di bagian bawah file.
void updateMenu();
void drawMenu();
void resetGame(GameStateType state); // Diubah untuk menggunakan enum GameStateType
void drawGameOverScreen(); // Fungsi baru untuk layar game over
// Brick Breaker
void resetBrickBreaker();
void updateBrickBreaker();
void drawBrickBreaker();
// Space Invaders
void resetSpaceInvaders();
void updateSpaceInvaders();
void drawSpaceInvaders();
// Snake
void resetSnake();
void updateSnake();
void drawSnake();
void placeFood(); // Khusus untuk Snake
// Flappy Bird
void resetFlappyBird();
void updateFlappyBird();
void drawFlappyBird();
// Dinosaur Game
void resetDinoGame();
void updateDinoGame();
void drawDinoGame();
// --- Fungsi Setup ---
// Fungsi setup() dijalankan sekali saat Arduino pertama kali dinyalakan atau di-reset.
void setup()
{
// Mengatur pin tombol joystick sebagai INPUT_PULLUP.
// INPUT_PULLUP berarti pin akan HIGH jika tidak ditekan, dan LOW jika ditekan.
pinMode(JOY_SEL, INPUT_PULLUP);
// Inisialisasi layar OLED.
// 0x3C adalah alamat I2C umum untuk layar SSD1306.
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
{
// Jika inisialisasi gagal (misalnya, layar tidak terhubung),
// Arduino akan berhenti di loop tak terbatas ini.
for (;;)
;
}
// Membersihkan buffer tampilan dan mengatur properti teks default.
display.clearDisplay();
display.setTextSize(1); // Ukuran font default.
display.setTextColor(SSD1306_WHITE); // Warna teks putih (hitam untuk background).
// Menampilkan splash screen.
display.setCursor(20, 28); // Atur posisi kursor untuk teks.
display.println("Mini Arcade");
display.display(); // Kirim buffer ke layar untuk ditampilkan.
delay(SPLASH_SCREEN_DELAY); // Tahan splash screen beberapa saat.
}
// --- Fungsi Loop Utama ---
// Fungsi loop() dijalankan berulang kali setelah setup() selesai.
void loop()
{
// Switch statement untuk menjalankan logika game yang sesuai berdasarkan currentGameState.
switch (currentGameState)
{
case STATE_MENU:
updateMenu(); // Perbarui logika menu (input, dll).
drawMenu(); // Gambar tampilan menu.
break;
case STATE_BRICK_BREAKER:
updateBrickBreaker();
drawBrickBreaker();
break;
case STATE_SPACE_INVADERS:
updateSpaceInvaders();
drawSpaceInvaders();
break;
case STATE_SNAKE:
updateSnake();
drawSnake();
break;
case STATE_FLAPPY_BIRD:
updateFlappyBird();
drawFlappyBird();
break;
case STATE_DINO_RUN:
updateDinoGame();
drawDinoGame();
break;
}
}
// --- Fungsi Helper: Layar Game Over ---
void drawGameOverScreen()
{
display.setCursor(GAME_OVER_POS_X, GAME_OVER_TEXT_Y);
display.println("Game Over!");
display.setCursor(GAME_OVER_POS_X, GAME_OVER_SCORE_Y);
display.print("Skor: ");
display.println(score);
display.setCursor(GAME_OVER_POS_X, GAME_OVER_ACTION_Y);
display.println("Tekan Tombol"); // Instruksi untuk kembali ke menu.
}
// --- Fungsi-fungsi Menu ---
void updateMenu()
{
// Baca nilai analog dari joystick sumbu vertikal (JOY_VERT).
// Nilai ADC biasanya 0-1023. Tengahnya ADC_MIDPOINT (sekitar 512).
int vertValue = analogRead(JOY_VERT);
// Logika navigasi menu:
// Joystick ATAS (nilai < ADC_MIDPOINT - JOY_THRESHOLD) -> pilih item SEBELUMNYA (menu ke atas).
// Joystick BAWAH (nilai > ADC_MIDPOINT + JOY_THRESHOLD) -> pilih item BERIKUTNYA (menu ke bawah).
if (vertValue < ADC_MIDPOINT - JOY_THRESHOLD && menuSelection > 0)
{
menuSelection--;
delay(INPUT_DEBOUNCE_DELAY); // Debounce untuk mencegah multiple selection.
}
else if (vertValue > ADC_MIDPOINT + JOY_THRESHOLD && menuSelection < TOTAL_MENU_ITEMS - 1)
{
menuSelection++;
delay(INPUT_DEBOUNCE_DELAY);
}
// Jika tombol joystick (JOY_SEL) ditekan (LOW karena INPUT_PULLUP).
if (digitalRead(JOY_SEL) == LOW)
{
// Tentukan gameState berdasarkan pilihan menu.
// menuSelection (0-4) dipetakan ke GameStateType (STATE_BRICK_BREAKER, dst.)
// STATE_MENU adalah 0, jadi game dimulai dari 1.
currentGameState = static_cast<GameStateType>(menuSelection + 1);
resetGame(currentGameState); // Reset status game yang dipilih.
delay(INPUT_DEBOUNCE_DELAY); // Debounce.
}
}
void drawMenu()
{
display.clearDisplay(); // Bersihkan layar sebelum menggambar.
display.setCursor(0, 0); // Atur kursor ke pojok kiri atas.
display.println("Pilih Game:");
// Loop untuk menampilkan semua item menu.
for (int i = 0; i < TOTAL_MENU_ITEMS; i++)
{
display.setCursor(10, 16 + i * 10); // Atur posisi untuk setiap item menu.
if (i == menuSelection)
{
display.print("> "); // Tampilkan ">" sebagai indikator pilihan.
}
else
{
display.print(" "); // Spasi jika bukan pilihan.
}
// Baca string menu dari PROGMEM dan salin ke buffer sementara.
char buffer[20]; // Buffer untuk menampung string menu.
strcpy_P(buffer, (char *)pgm_read_word(&(menuItems[i]))); // Fungsi untuk membaca dari PROGMEM.
display.println(buffer); // Tampilkan nama game.
}
display.display(); // Tampilkan semua yang sudah digambar ke layar.
}
// --- Fungsi-fungsi Brick Breaker ---
void resetBrickBreaker()
{
// Mengembalikan semua bata ke status terlihat (true).
for (int i = 0; i < BRICK_ROWS; i++)
{
for (int j = 0; j < BRICK_COLS; j++)
{
bricks[i][j] = true;
}
}
// Reset posisi paddle, bola, dan kecepatan bola.
paddleX = SCREEN_WIDTH / 2 - (paddleWidth / 2); // Paddle di tengah.
ballX = SCREEN_WIDTH / 2;
ballY = SCREEN_HEIGHT - 10;
ballVelX = 2; // Kecepatan awal horizontal bola.
ballVelY = -2; // Kecepatan awal vertikal bola (ke atas).
score = 0; // Reset skor.
gameOver = false; // Game belum berakhir.
}
void updateBrickBreaker()
{
if (gameOver) // Jika game sudah berakhir
{
// Jika tombol JOY_SEL ditekan, kembali ke menu.
if (digitalRead(JOY_SEL) == LOW)
{
currentGameState = STATE_MENU;
delay(INPUT_DEBOUNCE_DELAY);
}
return; // Keluar dari fungsi update jika game over.
}
// Kontrol paddle menggunakan joystick sumbu horizontal (JOY_HORZ).
int joyValue = analogRead(JOY_HORZ);
// Joystick KANAN (nilai < ADC_MIDPOINT - JOY_THRESHOLD) -> paddle ke KANAN.
// Joystick KIRI (nilai > ADC_MIDPOINT + JOY_THRESHOLD) -> paddle ke KIRI.
// Ini adalah skema kontrol "langsung" untuk horizontal.
if (joyValue < ADC_MIDPOINT - JOY_THRESHOLD)
{
paddleX += paddleSpeed;
}
else if (joyValue > ADC_MIDPOINT + JOY_THRESHOLD)
{
paddleX -= paddleSpeed;
}
// Batasi gerakan paddle agar tidak keluar layar.
paddleX = constrain(paddleX, 0, SCREEN_WIDTH - paddleWidth);
// Perbarui posisi bola berdasarkan kecepatannya.
ballX += ballVelX;
ballY += ballVelY;
// Deteksi tabrakan bola dengan dinding kiri/kanan.
if (ballX <= 0 || ballX >= SCREEN_WIDTH - ballSize)
{
ballVelX = -ballVelX; // Balikkan arah horizontal bola.
ballX = constrain(ballX, 0, SCREEN_WIDTH - ballSize); // Pastikan bola tetap di dalam layar
}
// Deteksi tabrakan bola dengan dinding atas.
if (ballY <= 0)
{
ballVelY = -ballVelY; // Balikkan arah vertikal bola.
ballY = constrain(ballY, 0, SCREEN_HEIGHT - ballSize);
}
// Deteksi tabrakan bola dengan paddle
if (ballY + ballSize >= paddleY && ballY <= paddleY + paddleHeight &&
ballX + ballSize >= paddleX && ballX <= paddleX + paddleWidth)
{
ballVelY = -abs(ballVelY); // Pastikan bola memantul ke atas
// Optional: Ubah arah X berdasarkan di mana bola mengenai paddle
// untuk memberikan kontrol arah pantulan
float hitPosition = (ballX + (ballSize / 2)) - (paddleX + (paddleWidth / 2));
float normalizedHitPosition = hitPosition / (paddleWidth / 2); // -1 to 1
ballVelX = normalizedHitPosition * 3; // Kecepatan maksimal 3
}
// Deteksi bola jatuh ke bawah (melewati paddle).
if (ballY >= SCREEN_HEIGHT - ballSize) // Sedikit toleransi agar tidak langsung game over saat menyentuh batas bawah
{
gameOver = true; // Game berakhir.
}
// Deteksi tabrakan bola dengan paddle.
bool allBricksDestroyed = true;
for (int i = 0; i < BRICK_ROWS; i++)
{
for (int j = 0; j < BRICK_COLS; j++)
{
if (bricks[i][j]) // Jika bata masih ada.
{
allBricksDestroyed = false; // Setidaknya satu bata masih ada.
int brickX = j * BRICK_WIDTH;
int brickY = i * BRICK_HEIGHT + 10; // +10 untuk memberi ruang skor di atas.
// Cek tabrakan
if (ballX + ballSize > brickX && ballX < brickX + BRICK_WIDTH &&
ballY + ballSize > brickY && ballY < brickY + BRICK_HEIGHT)
{
bricks[i][j] = false; // Hancurkan bata.
ballVelY = -ballVelY; // Balikkan arah vertikal bola.
score += 10; // Tambah skor.
}
}
}
}
// Jika semua bata hancur, game berakhir (pemain menang).
if (allBricksDestroyed)
{
gameOver = true;
}
}
void drawBrickBreaker()
{
display.clearDisplay();
if (!gameOver)
{
// Gambar paddle.
display.fillRect(paddleX, paddleY, paddleWidth, paddleHeight, SSD1306_WHITE);
// Gambar bola.
display.fillRect(round(ballX), round(ballY), ballSize, ballSize, SSD1306_WHITE);
// Gambar semua bata yang masih ada.
for (int i = 0; i < BRICK_ROWS; i++)
{
for (int j = 0; j < BRICK_COLS; j++)
{
if (bricks[i][j])
{
// Beri jarak antar bata dengan mengurangi ukuran gambar (-2).
display.fillRect(j * BRICK_WIDTH, i * BRICK_HEIGHT + 10, BRICK_WIDTH - 2, BRICK_HEIGHT - 2, SSD1306_WHITE);
}
}
}
}
else
{
drawGameOverScreen(); // Tampilkan layar game over.
}
// Tampilkan skor di pojok kiri atas.
display.setCursor(0, 0);
display.print("Skor: ");
display.println(score);
display.display();
}
// --- Fungsi-fungsi Space Invaders ---
void resetSpaceInvaders()
{
// Mengembalikan semua musuh ke status terlihat (true).
for (int i = 0; i < ENEMY_ROWS; i++)
{
for (int j = 0; j < ENEMY_COLS; j++)
{
enemies[i][j] = true;
// Set posisi awal X untuk setiap musuh
enemyBaseX[i][j] = j * (ENEMY_WIDTH + 4) + 10;
}
}
// Reset posisi pesawat pemain, status peluru, dan parameter musuh.
shipX = SCREEN_WIDTH / 2 - (shipWidth / 2); // Pesawat di tengah.
bulletActive = false;
bulletX = -1;
bulletY = -1;
enemySpeedX = 0.5;
enemyDirection = 1; // Musuh mulai bergerak ke kanan.
enemyGlobalYOffset = 0; // Reset offset Y global musuh
score = 0;
gameOver = false;
}
void updateSpaceInvaders()
{
if (gameOver)
{
if (digitalRead(JOY_SEL) == LOW)
{
currentGameState = STATE_MENU;
delay(INPUT_DEBOUNCE_DELAY);
}
return;
}
// Kontrol pesawat pemain (mirip paddle di Brick Breaker).
int joyValue = analogRead(JOY_HORZ);
if (joyValue < ADC_MIDPOINT - JOY_THRESHOLD)
shipX += paddleSpeed;
else if (joyValue > ADC_MIDPOINT + JOY_THRESHOLD)
shipX -= paddleSpeed;
shipX = constrain(shipX, 0, SCREEN_WIDTH - shipWidth);
// Menembak peluru.
if (digitalRead(JOY_SEL) == LOW && !bulletActive)
{
bulletX = shipX + shipWidth / 2 - bulletWidth / 2;
bulletY = shipY - bulletHeight;
bulletActive = true;
delay(INPUT_DEBOUNCE_DELAY);
}
// Perbarui posisi peluru.
if (bulletActive)
{
bulletY += bulletSpeed;
if (bulletY < 0)
bulletActive = false;
}
// --- Logika Pergerakan Musuh ---
// Cari posisi tepi formasi
float leftmostX = SCREEN_WIDTH;
float rightmostX = 0;
bool anyEnemyExists = false;
// Perbarui posisi dasar setiap musuh dan cek batas
for (int i = 0; i < ENEMY_ROWS; i++)
{
for (int j = 0; j < ENEMY_COLS; j++)
{
if (enemies[i][j])
{
anyEnemyExists = true;
// Gerakkan musuh
enemyBaseX[i][j] += enemyDirection * enemySpeedX;
// Periksa posisi terkini
if (enemyBaseX[i][j] < leftmostX)
leftmostX = enemyBaseX[i][j];
if (enemyBaseX[i][j] + ENEMY_WIDTH > rightmostX)
rightmostX = enemyBaseX[i][j] + ENEMY_WIDTH;
}
}
}
// Periksa apakah formasi mencapai tepi layar
bool hitEdge = false;
if (anyEnemyExists)
{
if (rightmostX >= SCREEN_WIDTH || leftmostX <= 0)
{
hitEdge = true;
}
}
// Jika mencapai tepi, balik arah dan turunkan
if (hitEdge)
{
enemyDirection *= -1;
enemyGlobalYOffset += ENEMY_Y_STEP_DOWN;
// Koreksi posisi semua musuh agar tidak melewati batas layar
for (int i = 0; i < ENEMY_ROWS; i++)
{
for (int j = 0; j < ENEMY_COLS; j++)
{
if (enemies[i][j])
{
if (enemyBaseX[i][j] < 0)
enemyBaseX[i][j] = 0;
else if (enemyBaseX[i][j] + ENEMY_WIDTH > SCREEN_WIDTH)
enemyBaseX[i][j] = SCREEN_WIDTH - ENEMY_WIDTH;
}
}
}
}
// Deteksi tabrakan peluru dengan musuh.
if (bulletActive)
{
for (int i = 0; i < ENEMY_ROWS; i++)
{
for (int j = 0; j < ENEMY_COLS; j++)
{
if (enemies[i][j])
{
int enemyActualX = enemyBaseX[i][j];
int enemyActualY = i * 12 + 5 + enemyGlobalYOffset;
if (bulletX >= enemyActualX && bulletX <= enemyActualX + ENEMY_WIDTH &&
bulletY >= enemyActualY && bulletY <= enemyActualY + ENEMY_HEIGHT)
{
enemies[i][j] = false;
bulletActive = false;
score += 10;
}
}
}
}
}
// Cek apakah semua musuh sudah hancur atau mencapai pemain.
bool allEnemiesDestroyed = true;
for (int i = 0; i < ENEMY_ROWS; i++)
{
for (int j = 0; j < ENEMY_COLS; j++)
{
if (enemies[i][j])
{
allEnemiesDestroyed = false;
int enemyY_check = i * 12 + 5 + enemyGlobalYOffset;
if (enemyY_check + ENEMY_HEIGHT >= shipY)
{
gameOver = true;
}
}
}
}
if (allEnemiesDestroyed && !gameOver)
{
gameOver = true;
}
}
void drawSpaceInvaders()
{
display.clearDisplay();
if (!gameOver)
{
// Gambar pesawat pemain.
display.fillRect(shipX, shipY, shipWidth, shipHeight, SSD1306_WHITE);
// Gambar peluru jika aktif.
if (bulletActive)
{
display.fillRect(bulletX, bulletY, bulletWidth, bulletHeight, SSD1306_WHITE);
}
// Gambar semua musuh yang masih ada menggunakan posisi absolut
for (int i = 0; i < ENEMY_ROWS; i++)
{
for (int j = 0; j < ENEMY_COLS; j++)
{
if (enemies[i][j])
{
int enemyDrawX = enemyBaseX[i][j];
int enemyDrawY = i * 12 + 5 + enemyGlobalYOffset;
display.fillRect(enemyDrawX, enemyDrawY, ENEMY_WIDTH, ENEMY_HEIGHT, SSD1306_WHITE);
}
}
}
}
else
{
drawGameOverScreen();
}
display.setCursor(0, 0);
display.print("Skor: ");
display.println(score);
display.display();
}
// --- Fungsi-fungsi Snake ---
void resetSnake()
{
snakeLength = 3; // Panjang awal ular.
// Atur posisi awal ular di tengah layar.
snakeX[0] = SCREEN_WIDTH / 2;
snakeY[0] = SCREEN_HEIGHT / 2;
// Inisialisasi segmen tubuh ular di belakang kepala.
for (int i = 1; i < snakeLength; i++)
{
snakeX[i] = snakeX[0] - i * GRID_SIZE; // Kepala menghadap ke kanan.
snakeY[i] = snakeY[0];
}
snakeDir = 0; // Arah awal: 0 = kanan.
score = 0;
gameOver = false;
placeFood(); // Tempatkan makanan pertama.
lastMoveTime = millis(); // Set waktu gerakan terakhir.
}
void updateSnake()
{
if (gameOver)
{
if (digitalRead(JOY_SEL) == LOW)
{
currentGameState = STATE_MENU;
delay(INPUT_DEBOUNCE_DELAY);
}
return;
}
// Ular hanya bergerak setelah interval waktu tertentu.
if (millis() - lastMoveTime >= moveInterval)
{
int horzValue = analogRead(JOY_HORZ);
int vertValue = analogRead(JOY_VERT);
// Kontrol arah ular:
// Sumbu Horizontal (JOY_HORZ):
// - Joystick Fisik KANAN (nilai < ADC_MIDPOINT) -> Ular bergerak KANAN di layar (snakeDir = 0).
// - Joystick Fisik KIRI (nilai > ADC_MIDPOINT) -> Ular bergerak KIRI di layar (snakeDir = 1).
// Ini adalah kontrol LANGSUNG untuk horizontal.
// Sumbu Vertikal (JOY_VERT):
// - Joystick Fisik ATAS (nilai < ADC_MIDPOINT) -> Ular bergerak ATAS di layar (snakeDir = 2).
// - Joystick Fisik BAWAH (nilai > ADC_MIDPOINT) -> Ular bergerak BAWAH di layar (snakeDir = 3).
// Prioritaskan input horizontal.
if (horzValue < ADC_MIDPOINT - JOY_THRESHOLD && snakeDir != 1) // Kanan fisik, tidak sedang gerak kiri
{
snakeDir = 0; // Kanan layar
}
else if (horzValue > ADC_MIDPOINT + JOY_THRESHOLD && snakeDir != 0) // Kiri fisik, tidak sedang gerak kanan
{
snakeDir = 1; // Kiri layar
}
// Jika tidak ada input horizontal, cek input vertikal.
else if (vertValue < ADC_MIDPOINT - JOY_THRESHOLD && snakeDir != 3) // Atas fisik, tidak sedang gerak bawah
{
snakeDir = 2; // Atas layar
}
else if (vertValue > ADC_MIDPOINT + JOY_THRESHOLD && snakeDir != 2) // Bawah fisik, tidak sedang gerak atas
{
snakeDir = 3; // Bawah layar
}
// Simpan posisi kepala lama sebelum bergerak.
int oldHeadX = snakeX[0];
int oldHeadY = snakeY[0];
// Perbarui posisi kepala ular berdasarkan arah (snakeDir).
if (snakeDir == 0)
snakeX[0] += GRID_SIZE; // Kanan
else if (snakeDir == 1)
snakeX[0] -= GRID_SIZE; // Kiri
else if (snakeDir == 2)
snakeY[0] -= GRID_SIZE; // Atas
else if (snakeDir == 3)
snakeY[0] += GRID_SIZE; // Bawah
// Cek tabrakan kepala ular dengan dinding.
if (snakeX[0] < 0 || snakeX[0] >= SCREEN_WIDTH || snakeY[0] < 0 || snakeY[0] >= SCREEN_HEIGHT)
{
gameOver = true;
}
// Cek tabrakan kepala ular dengan tubuhnya sendiri.
for (int i = 1; i < snakeLength; i++)
{
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i])
{
gameOver = true;
break; // Keluar loop jika sudah tabrakan.
}
}
if (gameOver)
{ // Jika game over dari tabrakan dinding atau diri sendiri
lastMoveTime = millis(); // Update waktu agar tidak langsung proses input lagi
return;
}
// Cek apakah ular memakan makanan.
if (snakeX[0] == foodX && snakeY[0] == foodY)
{
if (snakeLength < MAX_SNAKE_LENGTH) // Pastikan tidak melebihi panjang maksimal.
{
snakeLength++;
}
score += 10;
placeFood(); // Tempatkan makanan baru.
}
else // Jika tidak makan, geser tubuh ular.
{
// Geser semua segmen tubuh ke posisi segmen di depannya.
// Mulai dari ekor ke arah kepala.
for (int i = snakeLength - 1; i > 0; i--)
{
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
}
// Segmen kedua (setelah kepala baru) mengambil posisi kepala lama.
// Ini hanya berlaku jika ular tidak makan. Jika makan, ekor tidak bergeser maju.
// Perbaikan: posisi kepala lama untuk segmen kedua hanya perlu di-set jika tidak makan.
// Sebenarnya, jika makan, segmen baru akan ada di posisi kepala lama secara implisit
// karena kepala bergerak maju, dan ekor tidak dipotong.
// Perbaikan: posisi kepala lama untuk segmen[1] harus selalu diupdate
if (!(snakeX[0] == foodX && snakeY[0] == foodY) && snakeLength > 1)
{ // Koreksi: snakeX[1] etc. baru diisi jika tidak makan
// Namun logika di atas sudah benar: jika makan, panjang bertambah, loop for menggeser hingga snakeLength-1 (ekor baru)
// Jika tidak makan, loop for menggeser hingga snakeLength-1 (ekor lama)
// Jadi, posisi kepala lama untuk segmen kedua hanya perlu di-set jika tidak makan.
// Sebenarnya, jika makan, segmen baru akan ada di posisi kepala lama secara implisit
// karena kepala bergerak maju, dan ekor tidak dipotong.
// Perbaikan: posisi kepala lama untuk segmen[1] harus selalu diupdate
if (snakeLength > 1)
{ // pastikan ada segmen kedua
snakeX[1] = oldHeadX;
snakeY[1] = oldHeadY;
}
}
lastMoveTime = millis(); // Catat waktu gerakan terakhir.
}
}
void placeFood()
{
bool validPosition;
do
{
validPosition = true;
// Acak posisi makanan dalam grid.
foodX = random(0, SCREEN_WIDTH / GRID_SIZE) * GRID_SIZE;
foodY = random(0, SCREEN_HEIGHT / GRID_SIZE) * GRID_SIZE;
// Pastikan makanan tidak muncul di atas tubuh ular.
for (int i = 0; i < snakeLength; i++)
{
if (foodX == snakeX[i] && foodY == snakeY[i])
{
validPosition = false;
break;
}
}
} while (!validPosition);
}
void drawSnake()
{
display.clearDisplay();
if (!gameOver)
{
// Gambar setiap segmen ular.
for (int i = 0; i < snakeLength; i++)
{
display.fillRect(snakeX[i], snakeY[i], GRID_SIZE, GRID_SIZE, SSD1306_WHITE);
}
// Gambar makanan.
display.fillRect(foodX, foodY, GRID_SIZE, GRID_SIZE, SSD1306_WHITE);
}
else
{
drawGameOverScreen();
}
display.setCursor(0, 0);
display.print("Skor: ");
display.println(score);
display.display();
}
// --- Fungsi-fungsi Flappy Bird ---
void resetFlappyBird()
{
birdX = 20;
birdY = SCREEN_HEIGHT / 2; // Burung mulai di tengah layar secara vertikal.
birdVelY = 0; // Kecepatan vertikal awal nol.
score = 0;
gameOver = false;
// Inisialisasi posisi dan status pipa.
for (int i = 0; i < PIPE_COUNT; i++)
{
pipeX[i] = SCREEN_WIDTH + i * PIPE_SPACING; // Pipa dimulai di luar layar kanan.
// Acak posisi celah pipa secara vertikal.
pipeGapY[i] = random(PIPE_GAP, SCREEN_HEIGHT - PIPE_GAP);
pipeScored[i] = false; // Awalnya belum ada pipa yang dilewati untuk skor.
}
flappyLastUpdateTime = millis();
}
void updateFlappyBird()
{
if (gameOver)
{
if (digitalRead(JOY_SEL) == LOW)
{
currentGameState = STATE_MENU;
delay(INPUT_DEBOUNCE_DELAY);
}
return;
}
if (millis() - flappyLastUpdateTime >= flappyUpdateInterval)
{
// Kontrol horizontal burung (jika diinginkan).
// Saat ini, Flappy Bird klasik biasanya tidak ada kontrol horizontal.
// Jika ingin dihilangkan, hapus blok if/else if ini.
int horzValue = analogRead(JOY_HORZ);
// Joystick KANAN -> burung ke KANAN. Joystick KIRI -> burung ke KIRI.
if (horzValue < ADC_MIDPOINT - JOY_THRESHOLD)
{
birdX += birdSpeed;
}
else if (horzValue > ADC_MIDPOINT + JOY_THRESHOLD)
{
birdX -= birdSpeed;
}
birdX = constrain(birdX, 0, SCREEN_WIDTH - birdSize); // Batasi gerakan burung.
// Kepakan burung jika tombol JOY_SEL ditekan.
if (digitalRead(JOY_SEL) == LOW)
{
birdVelY = birdFlap; // Beri kecepatan ke atas (negatif).
// delay(INPUT_DEBOUNCE_DELAY); // Debounce bisa mengganggu responsivitas Flappy Bird.
// Mungkin lebih baik tanpa delay di sini, atau delay yang sangat singkat.
}
// Terapkan gravitasi dan perbarui posisi Y burung.
birdVelY += birdGravity;
birdY += birdVelY;
// Cek tabrakan burung dengan batas atas/bawah layar.
if (birdY < 0 || birdY + birdSize >= SCREEN_HEIGHT) // Perhatikan birdSize untuk tabrakan bawah
{
gameOver = true;
}
// Perbarui posisi pipa dan cek tabrakan.
for (int i = 0; i < PIPE_COUNT; i++)
{
pipeX[i] += pipeMoveSpeed; // Pipa bergerak ke kiri.
// Jika pipa keluar layar kiri, pindahkan ke kanan dan acak ulang celahnya.
if (pipeX[i] < -PIPE_WIDTH)
{
pipeX[i] += PIPE_COUNT * PIPE_SPACING;
pipeGapY[i] = random(PIPE_GAP / 2 + 10, SCREEN_HEIGHT - PIPE_GAP / 2 - 10); // Pastikan celah tidak terlalu ke tepi
pipeScored[i] = false; // Reset status skor untuk pipa ini.
}
// Deteksi tabrakan burung dengan pipa.
// Cek X: apakah burung berada dalam rentang horizontal pipa.
if (birdX + birdSize > pipeX[i] && birdX < pipeX[i] + PIPE_WIDTH)
{
// Cek Y: apakah burung menabrak bagian atas atau bawah pipa.
if (birdY < pipeGapY[i] - PIPE_GAP / 2 || birdY + birdSize > pipeGapY[i] + PIPE_GAP / 2)
{
gameOver = true;
}
}
// Cek jika burung berhasil melewati pipa untuk menambah skor.
// Skor bertambah jika sisi KANAN burung melewati sisi KANAN pipa, dan belum diskor.
// Lebih umum: ketika sisi KIRI burung melewati sisi KANAN pipa.
if (birdX > pipeX[i] + PIPE_WIDTH && !pipeScored[i])
{
score++;
pipeScored[i] = true; // Tandai pipa ini sudah memberi skor.
}
}
flappyLastUpdateTime = millis(); // Catat waktu update terakhir.
}
}
void drawFlappyBird()
{
display.clearDisplay();
if (!gameOver)
{
// Gambar burung.
display.fillRect(round(birdX), round(birdY), birdSize, birdSize, SSD1306_WHITE);
// Gambar semua pipa.
for (int i = 0; i < PIPE_COUNT; i++)
{
// Pipa atas: dari (pipeX[i], 0) hingga (pipeX[i], pipeGapY[i] - PIPE_GAP / 2)
display.fillRect(pipeX[i], 0, PIPE_WIDTH, pipeGapY[i] - PIPE_GAP / 2, SSD1306_WHITE);
// Pipa bawah: dari (pipeX[i], pipeGapY[i] + PIPE_GAP / 2) hingga dasar layar.
display.fillRect(pipeX[i], pipeGapY[i] + PIPE_GAP / 2, PIPE_WIDTH, SCREEN_HEIGHT - (pipeGapY[i] + PIPE_GAP / 2), SSD1306_WHITE);
}
}
else
{
drawGameOverScreen();
}
display.setCursor(0, 0);
display.print("Skor: ");
display.println(score);
display.display();
}
// --- Fungsi-fungsi Dinosaur Run ---
void resetDinoGame()
{
dinoX = 10;
dinoY = SCREEN_HEIGHT - DINO_HEIGHT; // Dinosaurus mulai di tanah.
dinoVelY = 0; // Tidak ada kecepatan vertikal awal.
dinoFrame = 0; // Mulai dengan frame animasi pertama.
score = 0;
gameOver = false;
// Inisialisasi kaktus.
for (int i = 0; i < MAX_CACTI; i++)
{
// Posisikan kaktus di luar layar kanan, dengan jarak antar kaktus.
cactusX[i] = SCREEN_WIDTH + i * (SCREEN_WIDTH / MAX_CACTI + random(0, 20)); // Jarak lebih variatif
cactusActive[i] = 0; // Awalnya tidak ada kaktus aktif.
cactusScored[i] = false; // Reset scored status
}
lastCactusTime = millis(); // Waktu untuk spawn kaktus berikutnya.
lastFrameTime = millis(); // Waktu untuk update animasi.
dinoLastUpdateTime = millis(); // Waktu untuk update game logic.
}
void updateDinoGame()
{
if (gameOver)
{
if (digitalRead(JOY_SEL) == LOW)
{
currentGameState = STATE_MENU;
delay(INPUT_DEBOUNCE_DELAY);
}
return;
}
// Update game logic berdasarkan interval waktu.
if (millis() - dinoLastUpdateTime >= dinoUpdateInterval)
{
// Lompat jika tombol JOY_SEL ditekan dan dinosaurus ada di tanah.
if (digitalRead(JOY_SEL) == LOW && dinoY == SCREEN_HEIGHT - DINO_HEIGHT)
{
dinoVelY = dinoJump; // Beri kecepatan ke atas (negatif).
// delay(INPUT_DEBOUNCE_DELAY); // Debounce mungkin tidak diperlukan atau perlu sangat singkat.
}
// Terapkan gravitasi dan perbarui posisi Y dinosaurus.
dinoVelY += dinoGravity;
dinoY += dinoVelY;
// Jaga dinosaurus agar tidak jatuh melewati tanah.
if (dinoY > SCREEN_HEIGHT - DINO_HEIGHT)
{
dinoY = SCREEN_HEIGHT - DINO_HEIGHT;
dinoVelY = 0; // Hentikan kecepatan vertikal saat di tanah.
}
// Update animasi dinosaurus (ganti frame).
if (millis() - lastFrameTime >= frameInterval)
{
dinoFrame = !dinoFrame; // Ganti antara frame 0 dan 1.
lastFrameTime = millis();
}
// Munculkan kaktus baru secara berkala.
if (millis() - lastCactusTime >= cactusInterval)
{
for (int i = 0; i < MAX_CACTI; i++)
{
if (cactusActive[i] == 0) // Jika ada slot kaktus yang tidak aktif.
{
cactusX[i] = SCREEN_WIDTH; // Munculkan dari sisi kanan layar.
cactusActive[i] = 1; // Aktifkan kaktus.
cactusScored[i] = false; // Reset scored status
lastCactusTime = millis(); // Reset timer untuk kaktus berikutnya.
break; // Hanya munculkan satu kaktus per interval.
}
}
}
// Perbarui posisi kaktus dan cek tabrakan.
for (int i = 0; i < MAX_CACTI; i++)
{
if (cactusActive[i] == 1)
{
// Update posisi
cactusX[i] += cactusMoveSpeed; // Kaktus bergerak ke kiri.
// Very simple scoring logic - check if cactus has just passed the scoring zone
if (!cactusScored[i] &&
cactusX[i] <= SCORE_ZONE_X &&
cactusX[i] > SCORE_ZONE_X + cactusMoveSpeed) // Just passed the zone in this frame
{
cactusScored[i] = true;
score++; // Increment score
}
// Jika kaktus keluar layar kiri, nonaktifkan
if (cactusX[i] < -CACTUS_WIDTH)
{
cactusActive[i] = 0;
}
// Deteksi tabrakan dinosaurus dengan kaktus.
if (dinoX + DINO_WIDTH > cactusX[i] &&
dinoX < cactusX[i] + CACTUS_WIDTH &&
dinoY + DINO_HEIGHT > SCREEN_HEIGHT - CACTUS_HEIGHT)
{
gameOver = true;
}
}
}
dinoLastUpdateTime = millis(); // Catat waktu update terakhir.
}
}
void drawDinoGame()
{
display.clearDisplay();
if (!gameOver)
{
// Gambar dinosaurus menggunakan sprite yang sesuai dengan frame animasi.
// pgm_read_byte_near() digunakan untuk membaca byte dari PROGMEM.
// Fungsi drawBitmap mengambil pointer ke data bitmap di PROGMEM.
display.drawBitmap(dinoX, round(dinoY), dinoFrame ? dinoSprite2 : dinoSprite1, DINO_WIDTH, DINO_HEIGHT, SSD1306_WHITE);
// Gambar semua kaktus yang aktif.
for (int i = 0; i < MAX_CACTI; i++)
{
if (cactusActive[i] == 1)
{
display.fillRect(cactusX[i], SCREEN_HEIGHT - CACTUS_HEIGHT, CACTUS_WIDTH, CACTUS_HEIGHT, SSD1306_WHITE);
}
}
// Uncomment for debugging - shows scoring zone
// display.drawFastVLine(SCORE_ZONE_X, 0, SCREEN_HEIGHT, SSD1306_WHITE);
}
else
{
drawGameOverScreen();
}
display.setCursor(0, 0);
display.print("Skor: ");
display.println(score);
display.display();
}
// --- Fungsi Reset Game Umum ---
// Fungsi ini dipanggil untuk me-reset state game yang dipilih.
void resetGame(GameStateType state) // Menggunakan GameStateType
{
gameOver = false; // Pastikan game over direset untuk semua game.
score = 0; // Reset skor global.
switch (state)
{
case STATE_BRICK_BREAKER:
resetBrickBreaker();
break;
case STATE_SPACE_INVADERS:
resetSpaceInvaders();
break;
case STATE_SNAKE:
resetSnake();
break;
case STATE_FLAPPY_BIRD:
resetFlappyBird();
break;
case STATE_DINO_RUN:
resetDinoGame();
break;
case STATE_MENU: // Tidak ada reset khusus untuk menu, tapi bisa ditambahkan jika perlu.
default:
break;
}
}
Siapkan Komponen
Siapkan Arduino Uno, layar OLED 128x64 I2C, modul joystick, dan kabel jumper male-to-female.
Hubungkan Layar OLED
Hubungkan pin VCC OLED ke 5V Arduino, GND ke GND, SDA ke pin A4, dan SCL ke pin A5.
Hubungkan Joystick
Hubungkan pin VCC joystick ke 5V, GND ke GND, HORZ ke pin A0, VERT ke pin A1, dan SEL ke pin D2 pada Arduino.
Hubungkan Daya
Gunakan kabel USB untuk menghubungkan Arduino ke laptop untuk daya dan pemrograman.
Unggah Kode ke Arduino
Buka Arduino IDE, salin kode mini arcade, dan unggah ke Arduino Uno. Pastikan library untuk OLED (seperti Adafruit_SSD1306) sudah terinstal.
Mainkan Game
Nyalakan Arduino, dan layar OLED akan menampilkan menu game. Gunakan joystick untuk memilih dan mainkan lima game: Brick Breaker, Space Invaders, Snake, Flappy Bird, dan Dino Run.