Программа

Для создания устройства используем универсальную библиотеку графических дисплеев U8g2lib. Эта библиотека работает со множеством разных дисплеев, в том числе и с HX1230.

Вторая важная библиотека — RobotClass_SnakeGame, содержит всю механику игры змейка. Нам лишь останется правильно её подключить и доработать некоторые функции.

В самом начале программы, после подключения библиотек, определим размер дисплея в пикселах. Делаем это с помощью директивы #define.

#define WIDTH 96
#define HEIGHT 68

Затем создадим объект класса, отвечающего за работу с дисплеем.

U8G2_HX1230_96X68_1_3W_SW_SPI disp(U8G2_R0, 13, 11, 10, 8); // SPI SCK,MISO,CS,RST

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

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

  • во время заставки, циклично
  • во время старта игры, один раз
  • и во время проигрыша, тоже один раз

Задаем мелодию в виде последовательности пар частота-длительность. В отдельной статье я расскажу, как самому перекодировать мелодии в такой формат.

const uint16_t melody_intro[] = {440,90,0,90,440,90,0,90,220,90,220,90,220,90,0,900,440,90,0,90,440,90,0,90,220,90,220,90,220,90,0,900,440,90,0,90,440,90,0,90,220,90,220,90,220,90,0,900,293,90,0,90,293,90,0,90,146,90,146,90,146,90,0,900,4096};
const uint16_t melody_gamestart[] = {659,98,783,98,987,98,783,98,622,98,739,98,987,98,739,98,523,98,659,98,783,98,659,98,493,98,622,98,739,98,987,98,987,293,4096};
const uint16_t melody_gameover[] = {369, 75, 349, 75, 311, 75, 277, 75, 261, 75, 233, 75, 220, 450, 220, 225, 4096};

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

class Snake: public RobotClass_SnakeGame {
    public:
      Snake(int pinUp, int pinDown, int pinLeft, int pinRight, int pinSpeaker);
      void setPixel(int16_t x, int16_t y, uint16_t color);
      void drawFrame();
      void drawIntro();
      void drawGameover();
      void drawInterface();
      uint16_t *getMelody(uint8_t m);
};

// конструктор объекта класса
Snake::Snake(int pinUp, int pinDown, int pinLeft, int pinRight, int pinSpeaker) : RobotClass_SnakeGame(pinUp, pinDown, pinLeft, pinRight, pinSpeaker, WIDTH, HEIGHT){
    // указываем прямоугольник игрового поля, исходя из разрешения дисплея
    setGameFrame(1,16,WIDTH-2,HEIGHT-2);

    // используемая в игре палитра
    //У нас монохромный дисплей, поэтому черный будет 0,
    //а красный, синий и зеленый - 1
    setPallete(0,1,1,1);
}

// функция, которая зажигает дисплей с координатами x и y
void Snake::setPixel(int16_t x, int16_t y, uint16_t color){
    disp.setDrawColor(color);
    disp.drawPixel(x, y);
}

// функция отрисовки экрана, она такая хитрая из-за особенностей U8g2lib
void Snake::drawFrame(){
    disp.firstPage();  
    do {
        draw();
    } while( disp.nextPage() );  
}

// отрисовка экрана заставки
void Snake::drawIntro(){
    disp.setDrawColor(COLOR_BLUE);
    disp.setFont(u8g2_font_ncenB10_tr);
    disp.drawStr(6, 24, "RobotClass");
    disp.drawStr(20, 40, "Snake");

    disp.setFont(u8g2_font_ncenB08_tr);
    int fw = disp.getStrWidth("Press any key");
    int fh = disp.getMaxCharHeight();
    if( intro_state ){
        disp.setDrawColor(COLOR_BLUE);
        disp.drawStr(10, 55, "Press any key");
    } else {
        disp.setDrawColor(COLOR_BLACK);
        disp.drawBox(10,55-fh,fw,fh); // remove text
    }
}

// отрисовка экрана проигрыша
void Snake::drawGameover(){
    disp.setDrawColor(COLOR_BLUE);
    disp.setFont(u8g2_font_ncenB10_tr);
    disp.drawStr(8, 24, "Gameover");
    disp.setFont(u8g2_font_ncenB08_tr);
    disp.drawStr(20, 40, "Points: ");
    disp.drawStr(64, 40, st_itoa(player_points));
}

// отрисовка контура игрового поля и текущих очков
void Snake::drawInterface(){
    // draw border
    disp.setDrawColor(COLOR_BLUE);
    disp.drawVLine( 0, 15, height-10 );
    disp.drawVLine( width-1, 15, height-10 );
    disp.drawHLine( 1, 15, width-2 );
    disp.drawHLine( 1, height-1, width-2 );

    // draw player points
    disp.setFont(u8g2_font_ncenB08_tr);
    int fh = disp.getMaxCharHeight();
    disp.drawStr(2, 0+fh, "points: ");
    disp.drawStr(45, 0+fh, st_itoa(player_points));
}

// функция получения мелодии
// здесь мы должны указать массивы, в которых храним мелодии
uint16_t *Snake::getMelody(uint8_t m){
  switch( m ){
  case MELODY_INTRO:
      return melody_intro;
      break;
  case MELODY_GAMESTART:
      return melody_gamestart;
      break;
  case MELODY_GAMEOVER:
      return melody_gameover;
      break;
  }
}

После описания класса, создаем его экземпляр.

Snake game(A0, A1, A2, A3, 3); // пины кнопок вверх, вниз, влево, вправо, а также пин динамика

А теперь самое простое, функции setup и loop.

void setup() {
    // инициализируем дисплей
    disp.begin();
    disp.setContrast(128);

    // инициализируем игру
    game.setup();
    game.resetGame();
}

void loop() {
    game.handler(); // обработчик событий игры, нажатий клавиш, мелодии
    game.drawFrame(); // обработчик отрисовки
}

Загружаем это всё на Ардуино и готово!


Изменено: