Игра змейка. Проекты. Ардуино

Змейка на Ардуино
Змейка на Ардуино
Змейка на Ардуино

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

В предыдущей статье мы уже начали писать программу для игры. Посмотрите тот пост, если пропустили или уже забыли. Сегодня мы допишем программу, чтобы получить настоящую игру.

Для того, чтобы выполнить этот проект нам понадобиться

  • Ардуино UNO
  • Перемычки
  • Макетная плата
  • Светодиодная матрица
  • Резистор 220 Ом
  • Джойстик
  • Кабель USB

Игра змейка

Мы уже можем управлять змейкой с помощью джойстика и переходить через границы матрицы. Но, чтобы это была настоящая игра, нужно добавить еще несколько моментов.

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

Во-вторых мы должны создать еду, проверять, не съели ли ее. И создавать новую.

И в-третьих, нужно создать условия проигрыша. Иначе какая это будет игра.

Несколько секций змейки

Сложность здесь в том, что при изменении направления движения головы змейки, ее тело должно повторять точный путь. Для этого нам придется записывать координаты каждой секции змейки. И выводить их последовательно при изменении направления движения.

Для этого заведем специальный массив. В нем будут храниться координаты каждой секции змейки. Максимальный размер змеи будет равен размеру матрицы, поэтому сразу зарезервируем 256 элементов. Также создадим переменную для реального размера змейки в текущий момент. И текущую скорость.

int snake[256]; // array of snakes elements
int snakeSize = 2;  // real snake size 
int snakeSpeed = 500; // snake speed

В начале игры, все элементы змеи, кроме ее реального размера, должны быть пустыми. А несколько элементов должны быть рядом со стартовой позицией. Инициализируем это следующим образом.

  for( i=0; i<=255; i++ ){
    snake[i] = 0;
  }
  for( i=0; i<=snakeSize; i++ ){
    snake[i] = lastDirection+i;
  }

Теперь в функции перемещения змейки мы должны нарисовать все элементы, но убрать лишние при движении вперед. Для этого 0 элементу присвоим новое значение из функции Snakedirection, а последующим элементам присвоим значения предыдущих элементов.

  FastLED.clear();
  for(i=snakeSize; i>=1; i--){
    snake[i] = snake[i-1];
  }
  snake[0] = snakeDirection;
  for( i=0; i<=255; i++ ){
    if( snake[i] ){
      leds[snake[i]].setRGB(red, green, blue);
    }
  }
  FastLED.show();

Таким образом, в нашей змейке появилось три секции. И при движении они перемещаются по матрице. А при смене направления движения джойстиком, весь маршрут сохраняется и прорисовывается на матрице.

Движение с сохранением координат
Движение с сохранением координат

Еда

Теперь создадим что-нибудь съедобное. Чтобы змея могла съесть и вырасти. В переменную f запишем координаты точки с едой. Она будет генерироваться случайно.

f = random(0, 255);

И напишем небольшую функцию. Она будет проверять, съедена ли еда. И создавать новую.

void food( int eaten ){
  if( eaten == f ){
    snakeSize++;
    f = random(0, 255);
    red = fred; 
    green = fgreen; 
    blue = fblue;
    fred = random(0, 255);
    fgreen = random(0, 255);
    fblue = random(0, 255);
    snakeSpeed = snakeSpeed / 1.1;
  } else {
    leds[f].setRGB(fred, fgreen, fblue);
    FastLED.show();
  }
}

Если точка съедена, вы увеличим размер змейки, увеличим скорость и создадим новую точку со случайными координатами. Также перекрасим змейку в цвета съеденной ранее еды.

Генерация еды и увеличение размера
Генерация еды и увеличение размера

Конец игры

Для определения конца игры напишем еще одну функцию. Она будет восстанавливать размер змейки, скорость, а также перекрашивать ее в красный цвет.

void death(){
    snakeSize = 2;
    snakeSpeed = 500;
    red = 255;
    green = 0;
    blue = 0;  
}

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

Теперь добавим условие, при котором считать игру законченной. Поскольку мы сделали границы матрицы прозрачными. То, запретим змейке пересекаться со своим собственным телом. Для этого в функцию прорисовки текущего шага добавим проверку.

  for( i=0; i<=255; i++ ){
    if( snake[i] == snakeDirection ){
      death();
    }
  }

Если координаты текущего шага уже есть в составе змейки, вызываем функцию death()

Конец игры
Конец игры

Мы не стали сбрасывать все цифры из массива snake, а значит после смерти несколько элементов останутся лежать на месте. Наезжать на них так же будет нельзя, ведь эти координаты находятся в составе змейки.

Полный текст программы

#include <FastLED.h>

//maatrix settings
#define NUM_LEDS 256
#define DATA_PIN 3
#define BRIGHTNESS 8

//joystick settings
#define pinX    A2  // ось X джойстика
#define pinY    A1  // ось Y джойстика
#define swPin    2  // кнопка джойстика

int snake[256]; // array of snake elements
int snakeSize = 2;  // real snake size 
int snakeSpeed = 500;

int row;        // row number
int col;        // column number

int lastDirection = 135; // start direction
int i, newDirection, OlddX = 1, OlddY, f;

int red, green, blue, fred, fgreen, fblue; //colors
CRGB leds[NUM_LEDS];

void setup() {
  red = random(0, 255);
  green = random(0, 255);
  blue = random(0, 255);
  fred = random(127, 255);
  fgreen = random(127, 255);
  fblue = random(127, 255);
  
  Serial.begin(9600);
  pinMode(pinX, INPUT);
  pinMode(pinY, INPUT);
  pinMode(swPin, INPUT);
  digitalWrite(swPin, HIGH);
    
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHTNESS);
  for( i=0; i<=255; i++ ){
    snake[i] = 0;
  }

  for( i=0; i<=snakeSize; i++ ){
    snake[i] = lastDirection+i;
  }
    
  f = random(0, 255);
  FastLED.show();
}

int Snakedirection(int last, int dX, int dY ){
  dX = map(dX, 0, 1000, -1, 1);
  dY = map(dY, 0, 1000, -1, 1);
  if(dX == 0 && dY == 0 && OlddX != dX){
    dX = OlddX;
  }
  if(dY == 0 && dX == 0 && OlddY != dY){
    dY = OlddY;
  }
  int newDirection = last;
  if( dX != 0 ){ // moving in X direction
    if ( row&1 ){
        if( col == 0 && dX == 1){ newDirection = last -15; } 
        else if( col == 15 && dX == -1){ newDirection = last +15; }
        else newDirection = last + dX; // четная
    } else {
        if( col == 0 && dX == 1){ newDirection = last +15; }
        else if( col == 15 && dX == -1 ){ newDirection = last -15; }
        else newDirection = last - dX; // не четная
    }
  } 
  if( dY < 0){ // moving in Y DOWN direction
    if(row == 15 && dY == -1){newDirection = col;}
    else if ( row&1 ){
      newDirection = last + (col*2)+1; // четная
    } else {
      newDirection = last + (16-col-1)+(16-col); // не четная
    }
  }
  if( dY > 0){ // moving in Y UP direction
    if( row == 0 && dY == 1){ newDirection = 255 - col;}
    else if ( row&1 ){
      newDirection = last - (last - 16*row) - (16 - col); // четная
    } else {
      newDirection = last - (col*2)-1; // не четная
    }
  }
  OlddX = dX;
  OlddY = dY;
  return newDirection;
}

int snakeMove(int snakeDirection){

  for( i=0; i<=255; i++ ){
    if( snake[i] == snakeDirection ){
      death();
    }
  }
  
  FastLED.clear();
  for(i=snakeSize; i>=1; i--){
    snake[i] = snake[i-1];
  }
  snake[0] = snakeDirection;
  for( i=0; i<=255; i++ ){
    if( snake[i] ){
      leds[snake[i]].setRGB(red, green, blue);
    }
  }
  FastLED.show();
  row = (int)(snakeDirection/16);  // row number 
  if ( row&1 ){
    col = (row+1) * 16 - snakeDirection - 1;
  } else {
    col = snakeDirection - row * 16;
  }
  return snakeDirection;
}

void food( int eaten ){
  if( eaten == f ){
    snakeSize++;
    f = random(0, 255);
    red = fred; 
    green = fgreen; 
    blue = fblue;
    fred = random(0, 255);
    fgreen = random(0, 255);
    fblue = random(0, 255);
    snakeSpeed = snakeSpeed / 1.1;
  } else {
    leds[f].setRGB(fred, fgreen, fblue);
    FastLED.show();
  }
}

void death(){
    snakeSize = 2;
    snakeSpeed = 500;
    red = 255;
    green = 0;
    blue = 0;  
}

void color(boolean sw){
  if(!sw){

    red = random(0,255);
    green = random(0,255);
    blue = random(0,255);
    
  }
}

void loop() {
  color( digitalRead(swPin) );
  newDirection = Snakedirection(lastDirection, analogRead(pinX), analogRead(pinY));
  lastDirection = snakeMove(newDirection);
  food(newDirection);
  delay(snakeSpeed);
}

Заключение

Мы написали нашу первую игру для Ардуино. Конечно, в коде еще много возможностей для улучшения игры. И у вас в запасе еще 78% памяти Ардуино. Но это уже готовая классическая аркадная игра.

14 комментариев на “Игра змейка. Проекты. Ардуино

  1. Hi, do you know what I would have to do to change this for an 8×8 matrix? I believe this one is with Individually addressable LEDs, so as long as the 8×8 I use is also Individually addressable, I should be able to use this code, right?
    Thanks in advance!

    1. Hey! It’s been a while since i wrote it, so i’m not sure. I suppose you would have to change a direction calculation. And LED’s order might differ. But, i hope you will figure it out!

      1. Hi, thanks for answering, and on another note; I don’t really understand this line
        int lastDirection = 135; // start direction

        Why do you start it at 135? What does that number represent?
        I would really appreciate it if you could clear that up.

        Thanks in advance.

      2. Hi, thanks for asnwering. I just have one issue with the code that stops me from turning it into 8×8.
        I don’t quite get this line:
        int lastDirection = 135; // start direction

        What does 135 mean here? Why is it 135 and not another number? And would I have to change it if I change the size of the matrix?

        Thanks in advance.

        1. Hey! Ted, everything is ok. Just didnt have time for answer. I’m still not sure, what is the answer for you question, i need to build this scheme again and try it, before i’m ready to give a solution. So, stay tuned, it will take a few days.

          1. Oh, sorry! I was just getting worried that my comments weren’t being uploaded :P. Well, make sure to tell me when you’ve finished it!

            By the way, I asked this on the post where you first explained how to use the matrix, but I guess I’ll ask it on here too, while I’m at it.

            The question is:
            How is your matrix wired? As in, do all rows go from left to right? or does it alternate each row?

            For example, my Matrix goes from left to right on all rows:
            First row, LED 0 to LED 7
            Second row, LED 8 to LED 15
            Third row, LED 16 to LED 23
            And so on…

            However, there are some Matrix that go like this:
            First row, LED 0 to LED 7
            Second row, LED 15 to LED 8
            Third row, LED 16 to LED 23
            Fourth row, LED 31 to LED 24
            And so on …

            So, which one is yours?

            Thanks in advance.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *