"Mini Console II."

Video: Snake auf der Mini Console II.

Da das 16×2-LCD nur sehr eingeschränkte Möglichkeiten der grafischen Darstellung hat, habe ich es durch ein 0,96″ OLED-Modul mit einer Auflösung von 128×64 Pixeln ersetzt. Die Mikrotaster sind hier wieder jeweils an einem digitalen Eingang angeschlossen und die Status-LEDs entfallen gänzlich.

Verwendete Bauteile

Aufbau der Mini Console II.

Die Mikrotaster benötigen hier keine externen Widerstände, da mit den internen Pull-Ups des Arduino gearbeitet wird. Mehr Details zum Anschließen des Piezo und des OLED-Moduls finden sich auf den entsprechenden Seiten.

Schaltplan der Mini Console II.
Abb.: Schaltplan der Mini Console II.

Spiel "Snake"

Snake ist ein Computerspielklassiker, bei dem eine sich gerade bzw. rechtwinklig bewegende Schlange durch ein Spielfeld gesteuert wird. Ziel des Spieles ist, die als Futter angebotenen zufällig erscheinenden "Happen" aufzunehmen und Hindernissen, einschließlich des eigenen tödlichen Schlangenkörpers, auszuweichen. Während die Schlange mit jedem Happen wächst, wird das Manövrieren bei zunehmend vollerem Spielfeld immer schwieriger.

Sketches

Snake

/*
 * How to play:
 * You are controlling a little snake which has to find food but grows after eating every time.
 * Avoid the walls and the own body or the snake dies!
 */

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define PIN_PIEZO        8
#define PIN_BUTTON_LEFT  2
#define PIN_BUTTON_RIGHT 3
#define PIN_BUTTON_UP    4
#define PIN_BUTTON_DOWN  5
#define PIN_BUTTON_A     6
#define PIN_BUTTON_B     7

#define BUTTON_LEFT  0
#define BUTTON_RIGHT 1
#define BUTTON_UP    2
#define BUTTON_DOWN  3
#define BUTTON_A     4
#define BUTTON_B     5

#define DIR_UP    1
#define DIR_RIGHT 2
#define DIR_DOWN  3
#define DIR_LEFT  4

#define GAME_OVER 1
#define GAME_RUN  2

#define SNAKE_MAX_SIZE 50

int score = 0, snakeLength = 1;
byte snakePixelX[SNAKE_MAX_SIZE], snakePixelY[SNAKE_MAX_SIZE], snakeX, snakeY, snakeDir;
byte minX, minY, maxX, maxY;
byte foodX, foodY, gameStatus = 0;
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

void setup()
{
  pinMode(PIN_BUTTON_LEFT,  INPUT_PULLUP);
  pinMode(PIN_BUTTON_RIGHT, INPUT_PULLUP);
  pinMode(PIN_BUTTON_UP,    INPUT_PULLUP);
  pinMode(PIN_BUTTON_DOWN,  INPUT_PULLUP);
  pinMode(PIN_BUTTON_A,     INPUT_PULLUP);
  pinMode(PIN_BUTTON_B,     INPUT_PULLUP);
  pinMode(PIN_PIEZO, OUTPUT);
  noTone(PIN_PIEZO);
  Serial.begin(9600);

  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    while (true);
  }

  randomSeed(analogRead(A2));

  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.clearDisplay();
  oled.setCursor(15, 15);
  oled.print(F("SNAKE"));
  oled.setTextSize(1);
  oled.setCursor(15, 32);
  oled.print(F("v1.0"));
  oled.display();

  while (true) {
    if (digitalRead(PIN_BUTTON_A) == LOW) {
      initGame();
      break;
    }
    delay(20);
  }
}

void loop()
{
  switch (gameStatus) {
    case GAME_RUN:
      checkSnakeCrashed();
      checkCollectFood();
      updateSnake();
      checkButtons();
      drawScreen();
      delay(70);
      break;
    case GAME_OVER:
      showGameOver();
      break;
  }
}

void initGame()
{
  score = 0;
  snakeLength = 1;
  snakeX = 20;
  snakeY = 20;
  snakePixelX[snakeLength - 1] = snakeX;
  snakePixelY[snakeLength - 1] = snakeY;
  snakeDir = DIR_RIGHT;

  minX = 0;
  minY = 8;
  maxX = oled.width() - 1;
  maxY = oled.height() - 1;

  foodX = 25;
  foodY = 25;
  gameStatus = GAME_RUN;
}

void checkButtons()
{
  if (digitalRead(PIN_BUTTON_LEFT) == LOW) {
    snakeDir = DIR_LEFT;
  } else if (digitalRead(PIN_BUTTON_UP) == LOW) {
    snakeDir = DIR_UP;
  } else if (digitalRead(PIN_BUTTON_RIGHT) == LOW) {
    snakeDir = DIR_RIGHT;
  } else if (digitalRead(PIN_BUTTON_DOWN) == LOW) {
    snakeDir = DIR_DOWN;
  }
}

void checkSnakeCrashed()
{
  if (snakeX <= minX+1 || snakeY <= minY+1 || snakeX >= maxX-1 || snakeY >= maxY-1) {
    gameStatus = GAME_OVER;
    return;
  }
  for (int i = 1; i < snakeLength; i++) {
    if (snakeX == snakePixelX[i] && snakeY == snakePixelY[i]) {
      gameStatus = GAME_OVER;
      return;
    }
  }
}

void checkCollectFood()
{
  if (snakeX != foodX || snakeY != foodY) {
    return;
  }

  score += 5;

  tone(PIN_PIEZO, 500);
  delay(10);
  noTone(PIN_PIEZO);

  if (snakeLength >= SNAKE_MAX_SIZE) {
    gameStatus = GAME_OVER;
    return;
  }

  snakeLength += 1;
  snakePixelX[snakeLength - 1] = snakeX;
  snakePixelY[snakeLength - 1] = snakeY;

  foodX = random(minX + 1, maxX - 1);
  foodY = random(minY + 1, maxY - 1);
}

void showGameOver()
{
  oled.clearDisplay();
  oled.setCursor(15, 15);
  oled.print(F("Game over!"));
  oled.setCursor(15, 30);
  oled.print(F("Score: "));
  oled.print(String(score, DEC));
  oled.display();

  for (int i = 0; i < 25; i++) {
    tone(PIN_PIEZO, 200 - i * 5);
    delay(25);
  }
  delay(500);
  noTone(PIN_PIEZO);

  while (true) {
    if (digitalRead(PIN_BUTTON_A) == LOW) {
      break;
    }
    delay(50);
  }

  initGame();
}

void drawScreen()
{
  oled.clearDisplay();
  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  oled.setCursor(0, 0);
  oled.print(F("Score: "));
  oled.print(String(score, DEC));

  oled.drawRect(minX, minY, maxX - minX, maxY - minY, WHITE);
  oled.drawPixel(foodX, foodY, WHITE);
  for (int i = 0; i < snakeLength; i++) {
    oled.drawPixel(snakePixelX[i], snakePixelY[i], WHITE);
  }
  oled.display();
}

void updateSnake()
{
  for (int i = snakeLength; i > 0; i--) {
    snakePixelX[i] = snakePixelX[i - 1];
    snakePixelY[i] = snakePixelY[i - 1];
  }

  switch (snakeDir) {
    case DIR_UP:
      snakeY -= 1;
      break;
    case DIR_RIGHT:
      snakeX += 1;
      break;
    case DIR_DOWN:
      snakeY += 1;
      break;
    case DIR_LEFT:
      snakeX -= 1;
      break;
  }

  snakePixelX[0] = snakeX;
  snakePixelY[0] = snakeY;
}

Snake2

/*
   How to play:
   You are controlling a little snake which has to find food but grows after eating every time.
   Avoid the walls and the own body or the snake dies!
*/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Hardware settings
#define DISPLAY_WIDTH  128
#define DISPLAY_HEIGHT  64

#define PIN_PIEZO        8
#define PIN_BUTTON_LEFT  2
#define PIN_BUTTON_RIGHT 3
#define PIN_BUTTON_UP    4
#define PIN_BUTTON_DOWN  5
#define PIN_BUTTON_A     6
#define PIN_BUTTON_B     7

#define BUTTON_LEFT  0
#define BUTTON_RIGHT 1
#define BUTTON_UP    2
#define BUTTON_DOWN  3
#define BUTTON_A     4
#define BUTTON_B     5

Adafruit_SSD1306 oled(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);

// Game settings
#define DIR_UP    1
#define DIR_RIGHT 2
#define DIR_DOWN  3
#define DIR_LEFT  4

#define GAME_OVER 1
#define GAME_RUN  2

#define GAME_SPEED 200 // game delay in milliseconds

#define SNAKE_MAX_SIZE 50

#define FIELD_MIN_X 1
#define FIELD_MIN_Y 7
#define FIELD_MAX_X 64
#define FIELD_MAX_Y 31

byte snakeX, snakeY, snakeDir, snakeLength;
byte snakePixelX[SNAKE_MAX_SIZE], snakePixelY[SNAKE_MAX_SIZE];
byte foodX, foodY, gameStatus, gameScore;
unsigned long lastUpdate;

void setup()
{
  pinMode(PIN_BUTTON_LEFT,  INPUT_PULLUP);
  pinMode(PIN_BUTTON_RIGHT, INPUT_PULLUP);
  pinMode(PIN_BUTTON_UP,    INPUT_PULLUP);
  pinMode(PIN_BUTTON_DOWN,  INPUT_PULLUP);
  pinMode(PIN_BUTTON_A,     INPUT_PULLUP);
  pinMode(PIN_BUTTON_B,     INPUT_PULLUP);
  pinMode(PIN_PIEZO, OUTPUT);
  noTone(PIN_PIEZO);
  Serial.begin(9600);

  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    while (true);
  }

  randomSeed(analogRead(A2));

  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.clearDisplay();
  oled.setCursor(15, 15);
  oled.print(F("SNAKE"));
  oled.setTextSize(1);
  oled.setCursor(15, 32);
  oled.print(F("v1.0"));
  oled.display();

  waitForPress(PIN_BUTTON_A);
  initGame();
}

void loop()
{
  switch (gameStatus) {
    case GAME_RUN:
      checkButtons();
      if ((millis() - lastUpdate) > GAME_SPEED) {
        lastUpdate = millis();
        checkSnakeCrashed();
        checkCollectFood();
        updateSnake();
        drawScreen();
      }
      break;
    case GAME_OVER:
      showGameOver();
      break;
  }
}

void initGame()
{
  snakeLength = 1;
  snakeX = 20;
  snakeY = 20;
  snakePixelX[snakeLength - 1] = snakeX;
  snakePixelY[snakeLength - 1] = snakeY;
  snakeDir = DIR_RIGHT;

  foodX = 25;
  foodY = 25;

  lastUpdate = millis();
  gameScore = 0;
  gameStatus = GAME_RUN;
}

void checkButtons()
{
  if (digitalRead(PIN_BUTTON_LEFT) == LOW) {
    snakeDir = DIR_LEFT;
  } else if (digitalRead(PIN_BUTTON_UP) == LOW) {
    snakeDir = DIR_UP;
  } else if (digitalRead(PIN_BUTTON_RIGHT) == LOW) {
    snakeDir = DIR_RIGHT;
  } else if (digitalRead(PIN_BUTTON_DOWN) == LOW) {
    snakeDir = DIR_DOWN;
  }
}

void checkSnakeCrashed()
{
  if (snakeX < FIELD_MIN_X || snakeY < FIELD_MIN_Y || snakeX > FIELD_MAX_X || snakeY > FIELD_MAX_Y) {
    gameStatus = GAME_OVER;
    return;
  }
  for (byte i = 1; i < snakeLength; i++) {
    if (snakeX == snakePixelX[i] && snakeY == snakePixelY[i]) {
      gameStatus = GAME_OVER;
      return;
    }
  }
}

void checkCollectFood()
{
  if (snakeX != foodX || snakeY != foodY) {
    return;
  }

  gameScore ++;
  tone(PIN_PIEZO, 500);
  delay(10);
  noTone(PIN_PIEZO);

  if (snakeLength >= SNAKE_MAX_SIZE) {
    gameStatus = GAME_OVER;
    return;
  }

  snakeLength += 1;
  snakePixelX[snakeLength - 1] = snakeX;
  snakePixelY[snakeLength - 1] = snakeY;

  bool droppedFood;
  do {
    foodX = random(FIELD_MIN_X, FIELD_MAX_X);
    foodY = random(FIELD_MIN_Y, FIELD_MAX_Y);
    droppedFood = true;

    if (snakeX == foodX || snakeY == foodY) {
      droppedFood = false;
      continue;
    }
    for (byte i = 1; i < snakeLength; i++) {
      if (foodX == snakePixelX[i] && foodY == snakePixelY[i]) {
        droppedFood = false;
        continue;
      }
    }
  } while (!droppedFood);
}

void showGameOver()
{
  oled.clearDisplay();
  oled.setCursor(15, 15);
  oled.print(F("Game over!"));
  oled.setCursor(15, 30);
  oled.print(F("Score: "));
  oled.print(String(gameScore, DEC));
  oled.display();

  for (int i = 0; i < 25; i++) {
    tone(PIN_PIEZO, 200 - i * 5);
    delay(25);
  }
  delay(500);
  noTone(PIN_PIEZO);

  waitForPress(PIN_BUTTON_A);
  initGame();
}

void drawScreen()
{
  oled.clearDisplay();
  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  oled.setCursor(0, 0);
  oled.print(F("Score: "));
  oled.print(String(gameScore, DEC));

  byte minX = FIELD_MIN_X*2;
  byte minY = FIELD_MIN_Y*2;
  byte maxX = FIELD_MAX_X*2;
  byte maxY = FIELD_MAX_Y*2;

  oled.drawRect(minX-2, minY-2, maxX-minX+2, maxY-minY+2, WHITE);
  oled.drawRect(minX-1, minY-1, maxX-minX+1, maxY-minY+1, WHITE);
  oled.writeFillRect(foodX*2, foodY*2, 2, 2, WHITE);

  for (int i = 0; i < snakeLength; i++) {
    oled.writeFillRect(snakePixelX[i]*2, snakePixelY[i]*2, 2, 2, WHITE);
  }
  oled.display();
}

void updateSnake()
{
  for (int i = snakeLength; i > 0; i--) {
    snakePixelX[i] = snakePixelX[i - 1];
    snakePixelY[i] = snakePixelY[i - 1];
  }

  switch (snakeDir) {
    case DIR_UP:
      snakeY -= 1;
      break;
    case DIR_RIGHT:
      snakeX += 1;
      break;
    case DIR_DOWN:
      snakeY += 1;
      break;
    case DIR_LEFT:
      snakeX -= 1;
      break;
  }

  snakePixelX[0] = snakeX;
  snakePixelY[0] = snakeY;
}

void waitForPress(byte pinNumber)
{
  while (true) {
    if (digitalRead(pinNumber) == LOW) {
      return;
    }
    delay(50);
  }
}

Arduboy

Einen sehr ähnlichen Aufbau habe ich in diesem großen Projekt gefunden: Arduboy.
Diese Plattform basiert ebenfall auf einem arduino-compatiblem Mikrocontroller und es sind sehr viele Spiele verfügbar. Außerdem gibt es eine gute Anleitung, wie man selbst Spiele für die Plattform programmieren kann.

Weitere Ideen für Spiele

zurück