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

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

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

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

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

  • Ардуино 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% памяти Ардуино. Но это уже готовая классическая аркадная игра.

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

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