Собираем змейку на Arduino
hard

Собираем змейку на Arduino

Это будет самая необычная змейка, в которую вы играли.

Змейка — очень благодатная тема для программистов, и вот почему:

  • понятные и простые правила,
  • минимум логики, которую нужно запрограммировать,
  • примитивная графика,
  • простое управление,
  • море кайфа.

У нас уже был разбор того, как в неё играет искусственный интеллект, и статья о том, как за 10 минут сделать змейку для браузера. Теперь шагнём дальше и не только напишем код, но и соберём железо. Результат — ваша собственная змейка на микропроцессорной плате, с экраном.

Что понадобится

Arduino Nano

Есть много вариантов платформы Adruino, но нам нужна одна из самых простых — микросхема на плате с ножками для монтажа в специальную плату. Обратите внимание: при покупке вам нужно указать, что хотите плату с уже припаянными ногами (soldered headers / soldered pins). Иначе придётся паять ноги на плату, а смысл был в том, чтобы не паять.

Собираем змейку на Arduino

Пластмассовая плата для монтажа (breadboard)

Примерно вот так мы соединим между собой все элементы на плате.

Светодиодная матрица 8 × 8

По ней и будет бегать наша змейка. Можно, конечно, использовать ЖК-матрицы, чтобы картинка была получше, но они стоят дороже и программировать их сложнее.

Собираем змейку на Arduino

Переменный резистор (потенциометр)

Обычный переменный резистор на 10 кОм. Суть этих устройств в том, что, вращая регулятор, мы можем изменять сопротивление резистора. В нашем проекте он будет отвечать за скорость движения змейки. Если она двигается слишком быстро — крутите ручку и змейка замедлится.

Собираем змейку на Arduino

Джойстик

Работает так же, как на обычной приставке. Им мы будем управлять змейкой.

Собираем змейку на Arduino

Провода

Самый кайф этого проекта в том, что нам не придётся паять, чтобы собрать все модули вместе. Вместо этого используем провода с разными разъёмами, поэтому лучше сразу заказать полный комплект.

Собираем змейку на Arduino

Загружаем код игры

Мы сейчас не будем разбирать особенности программирования под Arduino, а вместо этого сразу дадим готовый скетч. Скетч — это программа для Arduino, которая написана на C++ и которую сохранили как файл с расширением .ino.

#include "LedControl.h" // LedControl library is used for controlling a LED matrix. Find it using Library Manager or download zip here: https://github.com/wayoda/LedControl
// --------------------------------------------------------------- //
// ------------------------- user config ------------------------- //
// --------------------------------------------------------------- //
// there are defined all the pins
struct Pin {
    static const short joystickX = A2;   // joystick X axis pin
    static const short joystickY = A3;   // joystick Y axis pin
    static const short joystickVCC = 15; // virtual VCC for the joystick (Analog 1) (to make the joystick connectable right next to the arduino nano)
    static const short joystickGND = 14; // virtual GND for the joystick (Analog 0) (to make the joystick connectable right next to the arduino nano)
    static const short potentiometer = A7; // potentiometer for snake speed control
    static const short CLK = 10;   // clock for LED matrix
    static const short CS  = 11; // chip-select for LED matrix
    static const short DIN = 12; // data-in for LED matrix
};
// LED matrix brightness: between 0(darkest) and 15(brightest)
const short intensity = 8;
// lower = faster message scrolling
const short messageSpeed = 5;
// initial snake length (1...63, recommended 3)
const short initialSnakeLength = 3;
void setup() {
    Serial.begin(115200);  // set the same baud rate on your Serial Monitor
    initialize();         // initialize pins & LED matrix
    calibrateJoystick(); // calibrate the joystick home (do not touch it)
    showSnakeMessage(); // scrolls the 'snake' message around the matrix
}
void loop() {
    generateFood();    // if there is no food, generate one
    scanJoystick();    // watches joystick movements & blinks with food
    calculateSnake();  // calculates snake parameters
    handleGameStates();
// uncomment this if you want the current game board to be printed to the serial (slows down the game a bit)
// dumpGameBoard();
}
// --------------------------------------------------------------- //
// -------------------- supporting variables --------------------- //
// --------------------------------------------------------------- //
LedControl matrix(Pin::DIN, Pin::CLK, Pin::CS, 1);
struct Point {
    int row = 0, col = 0;
    Point(int row = 0, int col = 0): row(row), col(col) {}
};
struct Coordinate {
    int x = 0, y = 0;
    Coordinate(int x = 0, int y = 0): x(x), y(y) {}
};
bool win = false;
bool gameOver = false;
// primary snake head coordinates (snake head), it will be randomly generated
Point snake;
// food is not anywhere yet
Point food(-1, -1);
// construct with default values in case the user turns off the calibration
Coordinate joystickHome(500, 500);
// snake parameters
int snakeLength = initialSnakeLength; // choosed by the user in the config section
int snakeSpeed = 1; // will be set according to potentiometer value, cannot be 0
int snakeDirection = 0; // if it is 0, the snake does not move
// direction constants
const short up     = 1;
const short right  = 2;
const short down   = 3; // 'down - 2' must be 'up'
const short left   = 4; // 'left - 2' must be 'right'
// threshold where movement of the joystick will be accepted
const int joystickThreshold = 160;
// artificial logarithmity (steepness) of the potentiometer (-1 = linear, 1 = natural, bigger = steeper (recommended 0...1))
const float logarithmity = 0.4;
// snake body segments storage
int gameboard[8][8] = {};
// --------------------------------------------------------------- //
// -------------------------- functions -------------------------- //
// --------------------------------------------------------------- //
// if there is no food, generate one, also check for victory
void generateFood() {
    if (food.row == -1 || food.col == -1) {
// self-explanatory
        if (snakeLength >= 64) {
            win = true;
            return; // prevent the food generator from running, in this case it would run forever, because it will not be able to find a pixel without a snake
        }
// generate food until it is in the right position
        do {
            food.col = random(8);
            food.row = random(8);
        } while (gameboard[food.row][food.col] > 0);
    }
}
// watches joystick movements & blinks with food
void scanJoystick() {
    int previousDirection = snakeDirection; // save the last direction
    long timestamp = millis();
    while (millis() < timestamp + snakeSpeed) {
// calculate snake speed exponentially (10...1000ms)
        float raw = mapf(analogRead(Pin::potentiometer), 0, 1023, 0, 1);
        snakeSpeed = mapf(pow(raw, 3.5), 0, 1, 10, 1000); // change the speed exponentially
        if (snakeSpeed == 0) snakeSpeed = 1; // safety: speed can not be 0
// determine the direction of the snake
        analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold ? snakeDirection = up    : 0;
        analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold ? snakeDirection = down  : 0;
        analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold ? snakeDirection = left  : 0;
        analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold ? snakeDirection = right : 0;
// ignore directional change by 180 degrees (no effect for non-moving snake)
        snakeDirection + 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0;
        snakeDirection - 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0;
// intelligently blink with the food
        matrix.setLed(0, food.row, food.col, millis() % 100 < 50 ? 1 : 0);
    }
}
// calculate snake movement data
void calculateSnake() {
    switch (snakeDirection) {
    case up:
        snake.row--;
        fixEdge();
        matrix.setLed(0, snake.row, snake.col, 1);
        break;
    case right:
        snake.col++;
        fixEdge();
        matrix.setLed(0, snake.row, snake.col, 1);
        break;
    case down:
        snake.row++;
        fixEdge();
        matrix.setLed(0, snake.row, snake.col, 1);
        break;
    case left:
        snake.col--;
        fixEdge();
        matrix.setLed(0, snake.row, snake.col, 1);
        break;
    default: // if the snake is not moving, exit
        return;
    }
// if there is a snake body segment, this will cause the end of the game (snake must be moving)
    if (gameboard[snake.row][snake.col] > 1 && snakeDirection != 0) {
        gameOver = true;
        return;
    }
// check if the food was eaten
    if (snake.row == food.row && snake.col == food.col) {
        food.row = -1; // reset food
        food.col = -1;
// increment snake length
        snakeLength++;
// increment all the snake body segments
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                if (gameboard[row][col] > 0 ) {
                    gameboard[row][col]++;
                }
            }
        }
    }
// add new segment at the snake head location
    gameboard[snake.row][snake.col] = snakeLength + 1; // will be decremented in a moment
// decrement all the snake body segments, if segment is 0, turn the corresponding led off
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 8; col++) {
// if there is a body segment, decrement it's value
            if (gameboard[row][col] > 0 ) {
                gameboard[row][col]--;
            }
// display the current pixel
            matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 0 : 1);
        }
    }
}
// causes the snake to appear on the other side of the screen if it gets out of the edge
void fixEdge() {
    snake.col < 0 ? snake.col += 8 : 0;
    snake.col > 7 ? snake.col -= 8 : 0;
    snake.row < 0 ? snake.row += 8 : 0;
    snake.row > 7 ? snake.row -= 8 : 0;
}
void handleGameStates() {
    if (gameOver || win) {
        unrollSnake();
        showScoreMessage(snakeLength - initialSnakeLength);
        if (gameOver) showGameOverMessage();
        else if (win) showWinMessage();
// re-init the game
        win = false;
        gameOver = false;
        snake.row = random(8);
        snake.col = random(8);
        food.row = -1;
        food.col = -1;
        snakeLength = initialSnakeLength;
        snakeDirection = 0;
        memset(gameboard, 0, sizeof(gameboard[0][0]) * 8 * 8);
        matrix.clearDisplay(0);
    }
}
void unrollSnake() {
// switch off the food LED
    matrix.setLed(0, food.row, food.col, 0);
    delay(800);
// flash the screen 5 times
    for (int i = 0; i < 5; i++) {
// invert the screen
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 1 : 0);
            }
        }
        delay(20);
// invert it back
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 0 : 1);
            }
        }
        delay(50);
    }
    delay(600);
    for (int i = 1; i <= snakeLength; i++) {
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                if (gameboard[row][col] == i) {
                    matrix.setLed(0, row, col, 0);
                    delay(100);
                }
            }
        }
    }
}
// calibrate the joystick home for 10 times
void calibrateJoystick() {
    Coordinate values;
    for (int i = 0; i < 10; i++) {
        values.x += analogRead(Pin::joystickX);
        values.y += analogRead(Pin::joystickY);
    }
    joystickHome.x = values.x / 10;
    joystickHome.y = values.y / 10;
}
void initialize() {
    pinMode(Pin::joystickVCC, OUTPUT);
    digitalWrite(Pin::joystickVCC, HIGH);
    pinMode(Pin::joystickGND, OUTPUT);
    digitalWrite(Pin::joystickGND, LOW);
    matrix.shutdown(0, false);
    matrix.setIntensity(0, intensity);
    matrix.clearDisplay(0);
    randomSeed(analogRead(A5));
    snake.row = random(8);
    snake.col = random(8);
}
void dumpGameBoard() {
    String buff = "\n\n\n";
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 8; col++) {
            if (gameboard[row][col] < 10) buff += " ";
            if (gameboard[row][col] != 0) buff += gameboard[row][col];
            else if (col == food.col && row == food.row) buff += "@";
            else buff += "-";
            buff += " ";
        }
        buff += "\n";
    }
    Serial.println(buff);
}
// --------------------------------------------------------------- //
// -------------------------- messages --------------------------- //
// --------------------------------------------------------------- //
const PROGMEM bool snakeMessage[8][56] = {
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
const PROGMEM bool gameOverMessage[8][90] = {
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
const PROGMEM bool scoreMessage[8][58] = {
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
const PROGMEM bool digits[][8][8] = {
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 1, 1, 1, 0},
        {0, 1, 1, 1, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 0, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0},
        {0, 0, 1, 1, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 1, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 0, 0, 0, 1, 1, 0},
        {0, 0, 0, 0, 1, 1, 0, 0},
        {0, 0, 1, 1, 0, 0, 0, 0},
        {0, 1, 1, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 1, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 0, 0, 0, 1, 1, 0},
        {0, 0, 0, 1, 1, 1, 0, 0},
        {0, 0, 0, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 0, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 1, 1, 0, 0},
        {0, 0, 0, 1, 1, 1, 0, 0},
        {0, 0, 1, 0, 1, 1, 0, 0},
        {0, 1, 0, 0, 1, 1, 0, 0},
        {0, 1, 1, 1, 1, 1, 1, 0},
        {0, 0, 0, 0, 1, 1, 0, 0},
        {0, 0, 0, 0, 1, 1, 0, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 1, 0},
        {0, 1, 1, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 0, 0},
        {0, 0, 0, 0, 0, 1, 1, 0},
        {0, 0, 0, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 0, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 0, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 0, 0, 1, 1, 0, 0},
        {0, 0, 0, 0, 1, 1, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0},
        {0, 0, 0, 1, 1, 0, 0, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 0, 0}
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 1, 1, 1, 1, 0, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 1, 0},
        {0, 0, 0, 0, 0, 1, 1, 0},
        {0, 1, 1, 0, 0, 1, 1, 0},
        {0, 0, 1, 1, 1, 1, 0, 0}
    }
};
// scrolls the 'snake' message around the matrix
void showSnakeMessage() {
    [&] {
        for (int d = 0; d < sizeof(snakeMessage[0]) - 7; d++) {
            for (int col = 0; col < 8; col++) {
                delay(messageSpeed);
                for (int row = 0; row < 8; row++) {
// this reads the byte from the PROGMEM and displays it on the screen
                    matrix.setLed(0, row, col, pgm_read_byte(&(snakeMessage[row][col + d])));
                }
            }
// if the joystick is moved, exit the message
            if (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold
                    || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold
                    || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold
                    || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {
                return; // return the lambda function
            }
        }
    }();
    matrix.clearDisplay(0);
// wait for joystick co come back
    while (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold
            || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold
            || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold
            || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {}
}
// scrolls the 'game over' message around the matrix
void showGameOverMessage() {
    [&] {
        for (int d = 0; d < sizeof(gameOverMessage[0]) - 7; d++) {
            for (int col = 0; col < 8; col++) {
                delay(messageSpeed);
                for (int row = 0; row < 8; row++) {
// this reads the byte from the PROGMEM and displays it on the screen
                    matrix.setLed(0, row, col, pgm_read_byte(&(gameOverMessage[row][col + d])));
                }
            }
// if the joystick is moved, exit the message
            if (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold
                    || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold
                    || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold
                    || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {
                return; // return the lambda function
            }
        }
    }();
    matrix.clearDisplay(0);
// wait for joystick co come back
    while (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold
            || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold
            || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold
            || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {}
}
// scrolls the 'win' message around the matrix
void showWinMessage() {
// not implemented yet // TODO: implement it
}
// scrolls the 'score' message with numbers around the matrix
void showScoreMessage(int score) {
    if (score < 0 || score > 99) return;
// specify score digits
    int second = score % 10;
    int first = (score / 10) % 10;
    [&] {
        for (int d = 0; d < sizeof(scoreMessage[0]) + 2 * sizeof(digits[0][0]); d++) {
            for (int col = 0; col < 8; col++) {
                delay(messageSpeed);
                for (int row = 0; row < 8; row++) {
                    if (d <= sizeof(scoreMessage[0]) - 8) {
                        matrix.setLed(0, row, col, pgm_read_byte(&(scoreMessage[row][col + d])));
                    }
                    int c = col + d - sizeof(scoreMessage[0]) + 6; // move 6 px in front of the previous message
// if the score is < 10, shift out the first digit (zero)
                    if (score < 10) c += 8;
                    if (c >= 0 && c < 8) {
                        if (first > 0) matrix.setLed(0, row, col, pgm_read_byte(&(digits[first][row][c]))); // show only if score is >= 10 (see above)
                    } else {
                        c -= 8;
                        if (c >= 0 && c < 8) {
                            matrix.setLed(0, row, col, pgm_read_byte(&(digits[second][row][c]))); // show always
                        }
                    }
                }
            }
// if the joystick is moved, exit the message
            if (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold
                    || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold
                    || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold
                    || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {
                return; // return the lambda function
            }
        }
    }();
    matrix.clearDisplay(0);
// // wait for joystick co come back
// while (analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold
//         || analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold
//         || analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold
//         || analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold) {}
}
// standard map function, but with floats
float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Чтобы загрузить код игры в микроконтроллер, вам понадобится:

  1. Сохранить программу в виде .ino-файла.
  2. Подключить плату к компьютеру с помощью USB-кабеля.
  3. Установить Arduino IDE — среду разработки под Arduino для компьютера.
  4. Настроить компьютер для работы с платой. Нужно поставить драйверы для контроллера и убедиться, что компьютер всё распознал верно. Чтобы понять, как это правильно сделать, поищите в интернете «Как подключить arduino nano к компьютеру» или почитайте эту статью (не нашу).
  5. Открыть в Arduino IDE наш файл с программой.
  6. Там же нажать кнопку «Загрузить».
  7. Подождать сообщения о том, что программа загружена.

Та самая кнопка «Загрузить» в Arduino IDE.

Собираем и запускаем

Главное на этом этапе — не спешить и делать всё по картинке. Чтобы подключить провода к Arduino, нужно вставить его в центр белой пластиковой монтажной платы и подключить нужные провода рядом с нужными входами. «Рядом» — то есть на том же, грубо говоря, столбце: дырки на монтажной плате соединены как бы поперёк платы, то есть все провода, которые вы воткнёте в один столбец, будут соединены.

Собираем змейку на Arduino

Если всё равно непонятно, что и куда подключать, посмотрите видео:

Чтобы игра заработала после сборки, достаточно подключить плату к любому блоку питания или компьютеру.

Обложка:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
hard