Собираем змейку на Arduino
Большой разбор: как ИИ играет в змейку Своя игра: создаём собственную «Змейку»
Собираем змейку на Arduino

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

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

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

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

Arduino Nano

https://aliexpress.ru/item/33011803738.html

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

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

https://aliexpress.ru/item/32957396174.html


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

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

https://aliexpress.ru/item/32888107767.html

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

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

https://aliexpress.ru/item/32845927611.html

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

Джой­стик

https://aliexpress.ru/item/33001603885.html

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

Про­во­да

https://aliexpress.ru/item/32990573297.html

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

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

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

КОД ИГРЫ

    
language: С++
#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, нуж­но вста­вить его в центр белой пла­сти­ко­вой мон­таж­ной пла­ты и под­клю­чить нуж­ные про­во­да рядом с нуж­ны­ми вхо­да­ми. «Рядом» — то есть на том же, гру­бо гово­ря, столб­це: дыр­ки на мон­таж­ной пла­те соеди­не­ны как бы попе­рёк пла­ты, то есть все про­во­да, кото­рые вы воткнё­те в один стол­бец, будут соеди­не­ны.

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

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

Железо — не главное.
Глав­ное — уметь напи­сать код, что­бы это желе­зо зара­бо­та­ло. При­хо­ди­те в Прак­ти­кум, научим все­му, что нуж­но любо­му про­грам­ми­сту.