Adaptacja na Arduino kultowej zręcznościowej gry mobilnej, polegająca na przechodzeniu przez ulicę pomiędzy poruszającymi się samochodami.
1. Płytka Arduino UNO
2. Monochromatyczny wyświetlacz OLED SH1106G
3. Konsola do grania z przyciskami
Grę rozpoczynamy od ekranu startowego umożliwiającego graczowi wybór poziomu (maksymalnie do dziesiątego). Rozgrywka zaczyna się po lewej stronie ekranu w bezpiecznej strefie. Naszym celem jest przejście na drugą stronę ulicy za pomocą przycisków, umożliwiających poruszanie się w czterech kierunkach. Przeszkadzają nam w tym poruszające się po planszy samochody, pomiędzy którymi gracz ma do dyspozycji przerwy, pozwalające na zatrzymanie się i zastanowienie nad dalszym ruchem. Po przejściu na drugą stronę gracz jest przenoszony do następnego poziomu, gdzie musi liczyć się z szybszymi pojazdami oraz ewentualnymi dodatkowymi przeszkodami. Mają one za zadanie urozmaicenie gry oraz oczywiście jej utrudnienie. Po przegranej gracz ma możliwość rozpoczęcia gry od poziomu, który sprawiał trudności. Głównym celem jest dodarcie jak najdalej i dobra zabawa. Mimo swojej prostoty gra jest wciągająca i pozwala na rozwijanie zręczności, refleksu i zdolności obserwacji. Projekt gry powstał w celach akademickich.
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define i2c_Address 0x3c
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
const int PIN_GORA = 7;
const int PIN_DOL = 9;
const int PIN_LEWO = 10;
const int PIN_PRAWO = 8;
int gameState = 0;
int playerX = 5;
int playerY = 32;
const int playerSize = 3;
int currentLevel = 1;
const int maxCars = 15;
const int obstacleX = 64;
const int obstacleWidth = 10;
const int gapSize = 16;
struct Car {
float x;
float y;
float speed;
int w;
int h;
int direction;
bool active;
};
Car cars[maxCars];
bool gameOver = false;
bool win = false;
void setup() {
pinMode(PIN_GORA, INPUT_PULLUP);
pinMode(PIN_DOL, INPUT_PULLUP);
pinMode(PIN_LEWO, INPUT_PULLUP);
pinMode(PIN_PRAWO, INPUT_PULLUP);
display.begin(i2c_Address, true);
display.setRotation(2);
display.clearDisplay();
}
void initGame() {
playerX = 5;
playerY = 32;
gameOver = false;
win = false;
float speedBoost = (currentLevel - 1) * 0.15;
int currentLanes = (currentLevel > 5) ? 7 : 6;
int carsPerLane = (currentLevel >= 4) ? 2 : 1;
for(int i=0; i<maxCars; i++) cars[i].active = false;
int carIdx = 0;
float startArea = 28;
float endArea = 115;
float laneSpacing = (endArea - startArea) / (currentLanes - 1);
for (int l = 0; l < currentLanes; l++) {
float targetX = startArea + (l * laneSpacing);
if (currentLevel > 5) {
if (targetX > obstacleX - 12 && targetX < obstacleX) targetX = obstacleX - 6;
else if (targetX > obstacleX && targetX < obstacleX + obstacleWidth + 4) targetX = obstacleX + obstacleWidth;
}
for (int c = 0; c < carsPerLane; c++) {
if(carIdx < maxCars) {
cars[carIdx].active = true;
cars[carIdx].x = targetX;
cars[carIdx].y = (c == 0) ? 2 : (SCREEN_HEIGHT - 12);
cars[carIdx].speed = (random(12, 22) / 10.0) + speedBoost;
cars[carIdx].w = 6;
cars[carIdx].h = 10;
cars[carIdx].direction = (c == 0) ? 1 : -1;
carIdx++;
}
}
}
}
void loop() {
if (gameState == 0) showStartScreen();
else if (gameState == 1) {
updateGame();
drawGame();
if (gameOver || win) gameState = 2;
}
else if (gameState == 2) showResult();
}
void showStartScreen() {
display.clearDisplay();
display.drawRect(0, 0, 128, 64, SH110X_WHITE);
display.setTextSize(1);
display.setTextColor(SH110X_WHITE);
display.setCursor(25, 10);
display.print("CUBE CROSSING");
display.setTextSize(1);
display.setCursor(23, 30);
display.print("WYBIERZ POZIOM:");
display.setTextSize(2);
display.setCursor(55, 42);
display.print(currentLevel);
display.display();
if (digitalRead(PIN_LEWO) == LOW) { currentLevel--; if (currentLevel < 1) currentLevel = 10; delay(150); }
if (digitalRead(PIN_PRAWO) == LOW) { currentLevel++; if (currentLevel > 10) currentLevel = 1; delay(150); }
if (digitalRead(PIN_GORA) == LOW) { initGame(); gameState = 1; delay(200); }
}
void updateGame() {
int nextX = playerX;
int nextY = playerY;
if (digitalRead(PIN_GORA) == LOW && nextY > 0) nextY -= 2;
if (digitalRead(PIN_DOL) == LOW && nextY < SCREEN_HEIGHT - playerSize) nextY += 2;
if (digitalRead(PIN_LEWO) == LOW && nextX > 0) nextX -= 2;
if (digitalRead(PIN_PRAWO) == LOW) nextX += 2;
if (currentLevel > 5) {
if (nextX + playerSize > obstacleX && nextX < obstacleX + obstacleWidth) {
int gapTop = (SCREEN_HEIGHT / 2) - (gapSize / 2);
int gapBottom = (SCREEN_HEIGHT / 2) + (gapSize / 2);
if (nextY < gapTop || nextY + playerSize > gapBottom) return;
}
}
playerX = nextX;
playerY = nextY;
for (int i = 0; i < maxCars; i++) {
if (!cars[i].active) continue;
cars[i].y += (cars[i].speed * cars[i].direction);
if (cars[i].y <= 0) { cars[i].y = 0; cars[i].direction = 1; }
if (cars[i].y >= SCREEN_HEIGHT - cars[i].h) { cars[i].y = SCREEN_HEIGHT - cars[i].h; cars[i].direction = -1; }
for (int j = 0; j < maxCars; j++) {
if (i == j || !cars[j].active) continue;
if (abs(cars[i].x - cars[j].x) < 1) {
if (cars[i].y < cars[j].y + cars[j].h && cars[i].y + cars[i].h > cars[j].y) {
cars[i].direction *= -1;
cars[j].direction *= -1;
cars[i].y += cars[i].direction * 2;
cars[j].y += cars[j].direction * 2;
}
}
}
if (playerX < cars[i].x + cars[i].w && playerX + playerSize > cars[i].x &&
playerY < cars[i].y + cars[i].h && playerY + playerSize > cars[i].y) {
gameOver = true;
}
}
if (playerX >= SCREEN_WIDTH - 5) win = true;
}
void drawGame() {
display.clearDisplay();
display.drawFastVLine(22, 0, 64, SH110X_WHITE);
if (currentLevel > 5) {
int gapTop = (SCREEN_HEIGHT / 2) - (gapSize / 2);
int gapBottom = (SCREEN_HEIGHT / 2) + (gapSize / 2);
display.drawRect(obstacleX, 0, obstacleWidth, gapTop, SH110X_WHITE);
display.drawRect(obstacleX, gapBottom, obstacleWidth, SCREEN_HEIGHT - gapBottom, SH110X_WHITE);
}
display.fillRect(playerX, playerY, playerSize, playerSize, SH110X_WHITE);
for (int i = 0; i < maxCars; i++) {
if (cars[i].active) {
display.fillRect((int)cars[i].x, (int)cars[i].y, cars[i].w, cars[i].h, SH110X_WHITE);
}
}
display.setTextSize(1);
display.setCursor(0, 0);
display.print("LVL:"); display.print(currentLevel);
display.display();
}
void showResult() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SH110X_WHITE);
display.setCursor(15, 10);
if (win) {
display.print("LEVEL ");
display.print(currentLevel);
display.print(" CLEAR!");
display.setTextSize(2);
display.setCursor(15, 30);
display.print("NEXT...");
display.display();
// Zwiększamy poziom i restartujemy grę
currentLevel++;
if (currentLevel > 10) currentLevel = 1; // Powrót do 1 po wygraniu całości
delay(2000);
initGame();
gameState = 1; // Od razu wracamy do gry
} else {
display.setTextSize(2);
display.setCursor(10, 20);
display.print("KOLIZJA!");
display.display();
delay(2000);
gameState = 0; // Po przegranej wracamy do menu wyboru poziomu
}
}