Creating a logical game for the gaming platform

Hello.

I want to share my story of acquaintance with the gaming platform Gameduino 3, and also talk a little about programming the simplest logical game for this platform, used in conjunction with Arduino Uno.

What is Gameduino 3? Gameduino 3 is an expansion card that allows you to turn an Arduino into a modern pocket (meaning size) game console. To my surprise, I could not find any detailed information on this board in Habré. I would like to fill this gap, especially since the board, in my opinion, deserves attention.

A bit of history


The author of the project called Gameduino is James Bowman, who in 2011 created the first version of the board. Then it was positioned as a VGA module for the Arduino. The board was called Gameduino and was made on the basis of the Xilinx Spartan-3A programmable logic array FPGA. The board has installed connectors for connecting a VGA monitor and stereo speakers.image

Gameduino (1) features:
  • VGA видео выход с разрешением 400х300 точек, 512 цветов;
  • вся цветовая гамма обрабатывается в FPGA с 15-разрядной точностью;

фоновая графика:

  • область символьной фоновой графики 512х512 точек;
  • 256 символов, каждый с независимой 4-цветной палитрой;
  • реализован эффект заворачивания текстовых строк с пиксельным сглаживанием;

графика переднего плана:

  • каждый спрайт имеет разрешение 16х16 точек;
  • каждый спрайт может иметь 256-, 16- или 4-цветную палитру;
  • поддержка алгоритмов 4-стороннего вращения и горизонтального вращения;
  • 96 спрайтов на растровую строку, 1536 элементов текстуры на растровую строку;
  • механизм обнаружения возможных пересечений спрайтов;

аудио выход:

  • 12-разрядный двухканальный синтезатор частоты;
  • 64-голосая полифония в частотном диапазоне 10 — 8000 Гц.

Изображение выводится на экран стандартного VGA монитора с разрешением 400х300 точек,
сохраняется совместимость с любыми стандартными VGA мониторами с разрешением 800х600 точек.

In 2013, the second version of the board was released - Gameduino 2, which, unlike the previous version, already had a 4.3-inch resistive touchscreen display with a resolution of 480x272, a 3-axis accelerometer, a microSD memory card slot, and an audio output to headphones.

image

The “heart” of the board is the EVE (Embedded Video Engine - in Russian language can be translated as “embedded video module”) FT800, which has powerful computational capabilities, combining several functions simultaneously: image formation and output to the TFT display screen, touch screen processing, sound generation.

Functional diagram of the graphics controller FT800
image

В структуру микросхемы включены следующие функциональные блоки: графический контроллер, контроллер аудио, контроллер резистивной сенсорной панели. Микросхема FT800 предназначена для управления дисплеями с разрешением до 512 х 512 пикселей. FT800 также поддерживает LCD WQVGA (480 x 272) и QVGA (320 x 240). EVE (Embedded Video Engine) FT800 – это готовое решения для создания графического пользовательского интерфейса. Микросхема формирует сигналы управления дисплеем, имеет встроенные графические функции для отображения точек, линий, растровых картинок, объемных кнопок, текстов и т. д.


System structure based on the FT800 graphics controller
Формирование изображения происходит на основе набора команд (дисплей-листа), который передается управляющим микроконтроллером в FT800 через интерфейс I2C или SPI (в Gameduino 2 связь между Arduino и FT800 осуществляется по интерфейсу SPI). Возможности FT800 позволяют существенно разгрузить хост-контроллер системы.

image

Например, для вывода ряда кнопок достаточно передать в графический контроллер одну команду (четыре 32-х разрядных слова), и FT800 самостоятельно сформирует изображение этих кнопок на экране TFT-дисплея. Набор команд графических контроллеров FTDI включает в себя более 50 функций, с помощью которых можно выводить различные изображения на экран дисплея с теми или иными эффектами.

Подробное руководство по программированию контроллера и примеры работы с различными средами проектирования можно найти в Application Notes на сайте FTDI.

На русском языке хорошее описание функциональных возможностей, общих принципов и примеров работы есть тут.

Gameduino 2 features:
  • разрешение экрана 480x272 пикселей в 24-битном цвете;
  • набор команд в стиле OpenGL;
  • до 2000 спрайтов любого размера;
  • 256 Кбайт видеопамяти;
  • плавный поворот sprite и масштабирование с билинейной фильтрацией;
  • гладкий круг и линейный рисунок в аппаратном обеспечении — 16x сглаживание;
  • аппаратное декодирование JPEG;
  • встроенный рендеринг градиентов, текста, циферблатов и кнопок.

Выход звука осуществляется через усиленный разъем для наушников.
Система поддерживает выбор встроенных образцов и инструментов.

В ПЗУ контроллера уже зашиты:

  • высококачественные шрифты (6 размеров);
  • образцы из 8 музыкальных инструментов, воспроизводимые нотой MIDI;
  • образцы из 10 ударных звуков.

И, конечно же, вы можете загрузить свои собственные шрифты и звуковые фрагменты в ОЗУ 256 Кбайт.

Использование платформы Arduino не является обязательным условием: плату Gameduino 2 можно подключать к любому микроконтроллеру или микроконтроллерной плате с интерфейсом SPI.

In 2017, the third version of the board was released - Gameduino 3, which practically does not differ from Gameduino 2. Instead, the FT800 uses the new graphics controller FT810, which has backward software compatibility with the FT800 (i.e., all the code for Gameduino2 runs on Gameduino3) but it also has 4 times more computational capabilities, such as faster hardware JPEG decoding, video decoding, increased up to 1 MB of RAM, etc.

Gameduino 3 features:
  • видео декодер для полноэкранного 30 fps видео;
  • 1 мегабайт внутреннего ОЗУ;
  • разъемы для подключения microSD-карты и аудиовыход;
  • высококонтрастная ЖК-панель с диагональю 4,3 «480x272 с резистивным сенсорным экраном;
  • поддержка карт, созданных с помощью редактора Tiled Map;
  • загрузка изображения PNG с microSD;
  • ускоренное декодирование JPEG;
  • аппаратное переключение портрета / альбомной ориентации;
  • поддержка плат Arduino, ESP8266 и Teensy 3.2;
  • онлайн-инструменты для подготовки графики, аудио, шрифта и видео;


James Bowman has published a library for his project with many examples that work "right out of the box." The current library that I managed to find is here . Programming Guide (in English), where everything is described in detail. Lots of useful information on installing IDEs, etc., etc.

Programming


Somehow, wandering through the expanses of the Bolshoi Theater of the Internet, I came across an interesting project for Arduino - a logical game "Columns" , written under the usual inexpensive Chinese color display of 128x160 pixels. I wanted to repeat this game, but on my board, I’ll call it the FT810 (after the name of the graphics processor), which I already had in my hands. I also had time to study the programming manual and examples from the library, so my hands simply “itched” from the desire to write something of my own. What I immediately and started.

The first thing I had to do was display the text on the screen.

Due to the presence of built-in fonts, text output on the screen is quite easy. I will give a demo sketch from the library (with my comments):

Sketch helloworld.ino
#include<EEPROM.h>#include<SPI.h>#include<GD2.h>voidsetup(){
  Serial.begin(1000000);  // Инициализация отладочного порта, установка скорости передачи 1000000 бод
  GD.begin(0);            // Инициализация процессора, периферии и пр.
}
voidloop(){
  GD.ClearColorRGB(0x103000); // Установка цвета фона экрана.
  GD.Clear();                 // Очистка экрана (заполнение заданным цветом фона)
  GD.cmd_text(                // печать текста, где
           GD.w / 2,          // ширина экрана деленная на 2
           GD.h / 2,          // высота экрана деленная на 231,                // номер шрифта
           OPT_CENTER,        // опция, обозначающая отрисовку надписи с центром в указанной выше координате."Hello world");    // текст надписи
  GD.swap();                  // команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора).
}
В итоге на экране получим вот такой красивый текст:
image

Next, it was necessary to draw geometric shapes, for example: lines.
To draw lines, you must use Begin (LINES) or Begin (LINE_STRIP).
LINES connects each pair of vertices, while LINE_STRIP merges all vertices together.

I will cite the following demosquets from the library (with my comments):

Sketch lines.ino
#include<EEPROM.h>#include<SPI.h>#include<GD2.h>voidsetup(){
  Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод.
  GD.begin();            // Инициализация процессора, периферии и пр.
}
staticvoidzigzag(int x){
  GD.Vertex2ii(x - 10,   10); // Установка координаты начальной точки
  GD.Vertex2ii(x + 10,   60); // Установка координаты следующей точки (вершины)
  GD.Vertex2ii(x - 10,  110);
  GD.Vertex2ii(x + 10,  160);
  GD.Vertex2ii(x - 10,  210);
  GD.Vertex2ii(x + 10,  260); // Установка координаты конечной точки (вершины)
}
voidloop(){
  GD.Clear();            // Очистка фона (по умолчанию цвет черный - 0х000000)
  GD.Begin(LINES);       // Установка режима прерывистой линии
  zigzag(140);           // Вызов подпрограммы zigzag с аргументом - координата х
  GD.Begin(LINE_STRIP);  // Установка режима непрерывной линии
  zigzag(240);
  GD.LineWidth(16 * 10); // Установка ширины отрисовки линии в единицах измерения 1/16 пиксела, т.е. 1/16 * 16 * 10 = 10 пикселов
  GD.Begin(LINE_STRIP);
  zigzag(340);
  GD.swap();             // Команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора).
}

Линии на экране:

image

From drawing lines go to drawing rectangles.

To draw rectangles, use Begin (RECTS) and set the opposite corners of the rectangle. The order of the two corners does not matter. Rectangles are drawn with rounded corners, using the width of the current line as the radius of the corner. Rounded corners go beyond the boundaries of the rectangle, so an increase in the radius of the corner leads to an increase in the number of pixels. This example draws a rectangle 420 × 20 three times with an increase in the corner radius.

Sketch rectangles.ino
#include<EEPROM.h>#include<SPI.h>#include<GD2.h>voidsetup(){
  Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод.
  GD.begin();            // Инициализация процессора, периферии и пр.
}
voidloop(){
  GD.Clear();             // Очистка фона (по умолчанию цвет черный - 0х000000)
  GD.Begin(RECTS);        // Установка режима рисования прямоугольника
  GD.Vertex2ii(30, 30);   // Установка координаты левого верхнего угла прямоугольника
  GD.Vertex2ii(450, 50);  // Установка координаты правого нижнего угла прямоугольника
  GD.LineWidth(16 * 10);  // Установка радиуса скругления угла прямоугольника в единицах измерения 1/16 пиксела, т.е. 1/16 * 16 * 10 = 10 пикселов
  GD.Vertex2ii(30, 120);  // Установка координаты левого верхнего угла прямоугольника
  GD.Vertex2ii(450, 140); // Установка координаты правого нижнего угла прямоугольника
  GD.LineWidth(16 * 20);  // Установка радиуса скругления угла прямоугольника в единицах измерения 1/16 пиксела, т.е. 1/16 * 16 * 20 = 20 пикселов
  GD.Vertex2ii(30, 220);  // Установка координаты левого верхнего угла прямоугольника
  GD.Vertex2ii(450, 230); // Установка координаты правого нижнего угла прямоугольника
  GD.swap();              // Команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора)
}

Результат:
image

Let us turn to drawing a circle - the basis of future touch buttons. Let's go back to the first example with the text and add a few lines to the loop ().

Sketch with drawing color circles
#include<EEPROM.h>#include<SPI.h>#include<GD2.h>voidsetup(){
  Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод
  GD.begin(0);           // Инициализация процессора, периферии и пр.
}
voidloop(){
  GD.ClearColorRGB(0x103000); // Установка цвета фона экрана.
  GD.Clear();                 // Очистка экрана (заполнение заданным цветом фона)
  GD.cmd_text(                // печать текста, где
           GD.w / 2,          // ширина экрана деленная на 2
           GD.h / 2,          // высота экрана деленная на 231,                // номер шрифта
           OPT_CENTER,        // опция, обозначающая отрисовку надписи с центром в указанной выше координате"Hello world");    // текст надписи
  GD.PointSize(16 * 30);      // установка радиуса точки (круга) в единицах измерения 1/16 пиксела, т.е. 1/16 * 16 * 30 = 30 пикселов
  GD.Begin(POINTS);           // устанавливаем режим отрисовки точки (круга)
  GD.ColorRGB(0xff8000);      // устанавливаем цвет orange
  GD.Vertex2ii(220, 100);     // отрисовываем точку (круг) в координате 220,100
  GD.ColorRGB(0x0080ff);      // устанавливаем цвет teal
  GD.Vertex2ii(260, 170);     // отрисовываем точку (круг) в координате 260,170
  GD.swap();                  // Команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора)
}

Результат:



To work with touch buttons, it is necessary to organize processing of pressing the touch screen. This is done as follows. In short, I will explain the principle. Each pixel (point) on the screen has a color. It also has an invisible tag value that can be assigned to a point (or a line, circle, rectangle, etc.) type object and later used to detect tangencies of the object. The following sketch shows an example of setting the tag value for color circles to 100 and 101.

Sketch with touchscreen processing
#include<EEPROM.h>#include<SPI.h>#include<GD2.h>voidsetup(){
  Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод
  GD.begin(0);           // Инициализация процессора, периферии и пр.
}
voidloop(){
  GD.ClearColorRGB(0x103000); // Установка цвета фона экрана.
  GD.Clear();                 // Очистка экрана (заполнение заданным цветом фона)
  GD.cmd_text(                // печать текста, где
           GD.w / 2,          // ширина экрана деленная на 2
           GD.h / 2,          // высота экрана деленная на 231,                // номер шрифта
           OPT_CENTER,        // опция, обозначающая отрисовку надписи с центром в указанной выше координате"Hello world");    // текст надписи
  GD.PointSize(16 * 30);      // установка радиуса точки (круга) в единицах измерения 1/16 пиксела, т.е. 1/16 * 16 * 30 = 30 пикселов
  GD.Begin(POINTS);           // устанавливаем режим отрисовки точки (круга)
  GD.ColorRGB(0xff8000);      // устанавливаем цвет orange
  GD.Tag(100);                // устанавливаем значение тега для следующего объекта (круга)
  GD.Vertex2ii(220, 100);     // отрисовываем точку (круг) в координате 220,100
  GD.ColorRGB(0x0080ff);      // устанавливаем цвет teal
  GD.Tag(101);                // устанавливаем значение тега для следующего объекта (круга)
  GD.Vertex2ii(260, 170);     // отрисовываем точку (круг) в координате 260,170
  GD.swap();                  // Команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора)
  GD.get_inputs();            // производим опрос сенсорного экранаif(GD.inputs.tag > 0)       // проверяем условие нажатия сенсорного экрана
    Serial.println(GD.inputs.tag);  // отправляем в последовательный порт значение тега “нажатого” объекта
}

Теперь, когда система обнаруживает прикосновение к любому кругу, он сообщает о своем сенсорном коде, в этом случае 100 или 101. При нажатии на объекты (цветные круги) на экране в окне последовательного порта отобразятся значения тегов, соответствующих нажатым объектам:



I gave examples of basic operations, using which it was already possible to safely proceed to the creation of the game. Of course, the game was not created from scratch, but the basis was taken as a ready-made working code, which was adapted (with preservation of graphics), so the original version of the game was very similar to the original.

The first version of the design of the game:



After playing for several days, I wanted to change something in the design, for example, to add something different and unusual instead of a white background. And then I remembered one example from the library, in which the starry sky was in the background:

Demonstration sketch slotgag.ino
#include<EEPROM.h>#include<SPI.h>#include<GD2.h>#include"slotgag_assets.h"// файл с константами и адресамиvoidsetup(){
  Serial.begin(1000000);
  GD.begin();
  LOAD_ASSETS(); // загрузка изображений в ОЗУ
}
voidloop(){
  GD.Clear(); // очистка экрана (заполнение заданным, по умолчанию черным цветом фона)
  GD.ColorMask(1, 1, 1, 0); // установка маски разрешения записи в канал цветности R, G, B, запрет прозрачности
  GD.Begin(BITMAPS); // установка режима работы с битмап
  GD.BitmapHandle(BACKGROUND_HANDLE); // установка текущего битмап-дескриптора
  GD.BitmapSize(NEAREST, REPEAT, REPEAT, 480, 272); // управление отображением текущего растрового изображения на экране
  GD.Vertex2ii(0, 0, BACKGROUND_HANDLE); // выводим изображение фона с координаты   0,0
  GD.ColorMask(1, 1, 1, 1);
  GD.ColorRGB(0xa0a0a0);  
  GD.Vertex2ii(240 - GAMEDUINO_WIDTH / 2,
               136 - GAMEDUINO_HEIGHT / 2,
               GAMEDUINO_HANDLE);
  staticint x = 0;
  GD.LineWidth(20 * 16);
  GD.BlendFunc(DST_ALPHA, ONE);
  GD.Begin(LINES);
  GD.Vertex2ii(x, 0);
  GD.Vertex2ii(x + 100, 272);
  x = (x + 20) % 480; //' }a
  GD.swap();
}

Вид:



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

To add a picture of the starry sky as a background, I had to do the following: first, change the black color of the lines and the text to white (so that they were visible on a black background), write a slotgag.gd2 file in which the image is stored to the micro SD card add to the project folder slotgag_assets.h and add the necessary 8 lines of code to the sketch.

As a result, the game has acquired this form:



And of course, what kind of game without sound design? It remains to add sound effects, especially since they are presented in good quality and variety.

Gameduino 2/3 has two sound systems. The first, a synthesizer, can generate a set of fixed sounds and musical notes. A synthesizer is useful for quickly adding sound to a project, but since the sound set is fixed, it is not very flexible. The second is reproduction of the sample. It reproduces the sampled sound from the main memory in various formats. This system is much more flexible, but you will need to prepare and load samples into RAM.

I used a fixed sound synthesizer. The synthesizer provides several short “drum” sounds, mainly for use in user interfaces. To play a sound, you need to call GD.play () with a sound ID. Full list of available sounds:

CLICK
SWITCH
COWBELL
NOTCH
HIHAT
KICKDRUM
POP
CLACK
CHACK

Total


The result was this sketch:

Sketch Columns.ino
#include<SPI.h>#include<GD2.h>#include<avr/eeprom.h>#include"slotgag_assets.h"#define TAG_BUTTON_LEFT    201#define TAG_BUTTON_RIGHT   202#define TAG_BUTTON_ROT     203#define TAG_BUTTON_DROP    204#define X_BUTTON_LEFT       50#define Y_BUTTON_LEFT      222#define X_BUTTON_RIGHT     430#define Y_BUTTON_RIGHT     222#define X_BUTTON_ROT       430#define Y_BUTTON_ROT        50#define X_BUTTON_DROP       50#define Y_BUTTON_DROP       50// Color definitions #define BLACK         0x000000#define RED           0xFF0000 #define GREEN         0x00FF00 #define BLUE          0x0000FF #define YELLOW        0xFFFF00#define MAGENTA       0xFF00FF #define CYAN          0x00FFFF#define WHITE         0xFFFFFF #define DISPLAY_MAX_X      480#define DISPLAY_MAX_Y      272#define MaxX                 8 #define MaxY                17#define SmeX                 3#define SmeY                 3#define razmer              18#define NumCol               6#define MaxLevel             8#define NextLevel           80#define DISP_LEFT    ((DISPLAY_MAX_X - MaxX*razmer)/2 - 2)#define DISP_RIGHT   ((DISPLAY_MAX_X + MaxX*razmer)/2 + 2)#define DISP_TOP     ((DISPLAY_MAX_Y - (MaxY-4)*razmer)/2 - 2)#define DISP_BOT     ((DISPLAY_MAX_Y + (MaxY-4)*razmer)/2 + 2)uint8_t  MasSt[MaxX][MaxY], MasTmp[MaxX][MaxY], fignext[3];
uint8_t  Level=1, dx, dy, tr, flfirst=1; 
uint32_t MasCol[]={WHITE, BLACK, RED, BLUE, GREEN, YELLOW, MAGENTA, CYAN};
unsignedlong Counter, Score=0, TScore=0, Record=0, myrecord;
uint16_t tempspeed = 1000;
bool fl, Demo=true, Arbeiten=false, FlZ=false;
int8_t   x,y;
int8_t   mmm [4][2]={{-1,0},{0,-1},{1,0},{0,1}};
uint16_t MasSpeed[MaxLevel]={500,450,400,350,300,250,200,100};
uint8_t  state_game = 0;
unsignedlong time_count;
byte     prevkey;
uint32_t btn_color = 0xff0000;
/****************************************************************************************************************/voidsetup(void){
  Serial.begin(1000000);
  Serial.println("Columns");
  GD.begin();
  LOAD_ASSETS();
  GD.BitmapHandle(BACKGROUND_HANDLE);
  GD.BitmapSize(NEAREST, REPEAT, REPEAT, 480, 272);
  randomSeed(analogRead(5));
  myrecord = eeprom_read_byte((unsignedchar *)1);
  time_count = millis() + 1000;
}
staticstruct {
  byte t, note;
} pacman[]  = {
  { 0, 71 },
  { 2, 83 },
  { 4, 78 },
  { 6, 75 },
  { 8, 83 },
  { 9, 78 },
  { 12, 75 },
  { 16, 72 },
  { 18, 84 },
  { 20, 79 },
  { 22, 76 },
  { 24, 84 },
  { 25, 79 },
  { 28, 76 },
  { 32, 71 },
  { 34, 83 },
  { 36, 78 },
  { 38, 75 },
  { 40, 83 },
  { 41, 78 },
  { 44, 75 },
  { 48, 75 },
  { 49, 76 },
  { 50, 77 },
  { 52, 77 },
  { 53, 78 },
  { 54, 79 },
  { 56, 79 },
  { 57, 80 },
  { 58, 81 },
  { 60, 83 },
  { 255, 255 }
};
//==================================================voidloop(void){
  GD.get_inputs();
  byte    key = GD.inputs.tag;
  int8_t  VAL = 0;
  if (prevkey == 0x00) 
  {
    switch (key) {
      case TAG_BUTTON_LEFT:
        VAL = -1;
        break;
      case TAG_BUTTON_RIGHT:
        VAL = 1;
        break;
      case TAG_BUTTON_ROT:
        if (!FlZ)
        {
          GD.play(HIHAT);
          byte aa=MasSt[x][y];
          MasSt[x][y]=MasSt[x][y+2];
          MasSt[x][y+2]=MasSt[x][y+1];
          MasSt[x][y+1]=aa;
        }
        break;
      case TAG_BUTTON_DROP:
        if (Arbeiten) {
          if (!FlZ) {
            tempspeed=50;
            GD.play(NOTCH);
          }
        } else {
          GD.play(CLICK);
          Demo=false;
          NewGame();
        }
        break;
    }
  }
  prevkey = key;
  if (VAL!=0 && fig_shift(VAL) && !FlZ) {
    for (byte i=0;i<3;i++) {
      MasSt[x+VAL][y+i]=MasSt[x][y+i];
      MasSt[x][y+i]=0; 
    }
    x=x+VAL; 
  }
  ProcGame();
  ViewStacan();
  GD.swap();
}
//==================================================// redraw one square voidViewQuad(byte i,byte  j,byte mycolor){
  if (j<3) return; 
  uint16_t wy=DISP_TOP + SmeY+(j-3)*razmer-j;
  uint16_t wx=DISP_LEFT + SmeX+i*razmer-i;
  if (mycolor!=0) {
    GD.LineWidth(16*1);
    GD.ColorRGB(WHITE);
    GD.Begin(LINE_STRIP);
    GD.Vertex2ii(wx,wy);
    GD.Vertex2ii(wx + razmer-1,wy);
    GD.Vertex2ii(wx + razmer-1,wy + razmer-1);
    GD.Vertex2ii(wx,wy + razmer-1);
    GD.Vertex2ii(wx,wy);
    GD.Begin(RECTS);
    GD.ColorRGB(MasCol[mycolor]);
    GD.Vertex2ii(wx+1, wy+1); GD.Vertex2ii(wx+1 + razmer-2 - 1, wy+1 + razmer-2 - 1);
  } else {
  }
}
//==================================================void  ViewStacan(void)
{
  char myStr2[5];
  // Draw background fone
  GD.Clear();
  GD.ColorMask(1, 1, 1, 0);
  GD.Begin(BITMAPS);
  GD.BitmapHandle(BACKGROUND_HANDLE);
  GD.BitmapSize(NEAREST, REPEAT, REPEAT, 480, 272);
  GD.Vertex2ii(0, 0, BACKGROUND_HANDLE);
  // Print text
  GD.ColorRGB(WHITE);
  GD.cmd_text(DISP_LEFT  - 30, DISP_TOP +   3, 27, OPT_CENTER, "LEVEL");
  GD.cmd_text(DISP_RIGHT + 30, DISP_TOP +   3, 27, OPT_CENTER, "NEXT");
  GD.cmd_text(DISP_RIGHT + 30, DISP_TOP + 100, 27, OPT_CENTER, "SCORE");
  GD.cmd_text(DISP_LEFT  - 30, DISP_TOP + 100, 27, OPT_CENTER, "TOP");
  // Print digit Score
  GD.ColorRGB(RED);
  sprintf(myStr2,"%05d",Score );
  GD.cmd_text(DISP_RIGHT + 30, DISP_TOP + 130, 27, OPT_CENTER, myStr2);
  // Print digit Topsprintf(myStr2,"%05d",myrecord );
  GD.cmd_text(DISP_LEFT  - 30, DISP_TOP + 130, 27, OPT_CENTER, myStr2);
  // Print digit Levelsprintf(myStr2,"%02d",Level );
  GD.cmd_text(DISP_LEFT  - 30, DISP_TOP +  40, 31, OPT_CENTER, myStr2);
  // Draw color squaresfor (byte j=3;j<MaxY;j++)
    for (byte i=0;i<MaxX;i++)
      ViewQuad(i,j,MasSt[i][j]);
  // Draw Next Figurefor (byte i=0;i<3;i++) {
    GD.ColorRGB(WHITE);
    GD.Begin(LINE_STRIP);
    GD.LineWidth(16*1);
    GD.Vertex2ii(DISP_RIGHT + 15,            DISP_TOP + 20 + razmer*i-i);
    GD.Vertex2ii(DISP_RIGHT + 15 + razmer-1, DISP_TOP + 20 + razmer*i-i);
    GD.Vertex2ii(DISP_RIGHT + 15 + razmer-1, DISP_TOP + 20 + razmer*i-i + razmer-1);
    GD.Vertex2ii(DISP_RIGHT + 15,            DISP_TOP + 20 + razmer*i-i + razmer-1);
    GD.Vertex2ii(DISP_RIGHT + 15,            DISP_TOP + 20 + razmer*i-i);
    GD.Begin(RECTS);
    GD.ColorRGB(MasCol[fignext[i]]);
    GD.Vertex2ii(DISP_RIGHT+15+1, DISP_TOP+20+razmer*i-i+1);
    GD.Vertex2ii(DISP_RIGHT+15+1+razmer-2-1, DISP_TOP+20+razmer*i-i+1+razmer-2-1);
  }
  // Draw "stacan"
  GD.ColorRGB(WHITE);
  GD.Begin(LINE_STRIP);
  GD.LineWidth(16*1);
  GD.Vertex2ii(DISP_LEFT + 1, DISP_TOP); GD.Vertex2ii(DISP_LEFT + 1, DISP_BOT);
  GD.Vertex2ii(DISP_LEFT + 1 + razmer*MaxX+5-MaxX - 1, DISP_BOT);
  GD.Vertex2ii(DISP_LEFT + 1 + razmer*MaxX+5-MaxX - 1, DISP_TOP);
  // Draw 9 vertical linesfor (byte i=0; i<9; i++) {
    GD.ColorRGB(WHITE);
    GD.Begin(LINE_STRIP);
    GD.LineWidth(16*1);
    GD.Vertex2ii(DISP_LEFT + 3 + razmer*i-i, DISP_TOP);
    GD.Vertex2ii(DISP_LEFT + 3 + razmer*i-i, DISP_BOT - 2);
  }
  // Draw 1 horizontal line
  GD.ColorRGB(WHITE);
  GD.Begin(LINE_STRIP);
  GD.Vertex2ii(DISP_LEFT + 3, DISP_BOT - 2);
  GD.Vertex2ii(DISP_LEFT + 3 + razmer*MaxX-MaxX - 1, DISP_BOT - 2);
  // Draw "Game Over"if (!Demo && !Arbeiten) {
    GD.Begin(RECTS);
    GD.ColorRGB(WHITE);
    GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 - 60, (DISP_TOP + DISP_BOT)/2 - 40);
    GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 + 60, (DISP_TOP + DISP_BOT)/2 + 40);
    GD.ColorRGB(BLACK);
    GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 - 58, (DISP_TOP + DISP_BOT)/2 - 38);
    GD.Vertex2ii((DISP_LEFT + DISP_RIGHT)/2 + 58, (DISP_TOP + DISP_BOT)/2 + 38);
    GD.ColorRGB(RED);
    GD.cmd_text((DISP_LEFT + DISP_RIGHT)/2, (DISP_TOP + DISP_BOT)/2 - 20, 30, OPT_CENTER, "GAME");
    GD.cmd_text((DISP_LEFT + DISP_RIGHT)/2, (DISP_TOP + DISP_BOT)/2 + 20, 30, OPT_CENTER, "OVER");
  }
  // Draw Buttons
  GD.Begin(POINTS);
  GD.PointSize(16*50);                            // Set size of buttons (50 pix)
  GD.ColorRGB(btn_color);                         // Set fone color of buttons
  GD.Tag(TAG_BUTTON_LEFT);                        // Set TAG for BUTTON_LEFT
  GD.Vertex2ii( X_BUTTON_LEFT, Y_BUTTON_LEFT);    // Place BUTTON1
  GD.Tag(TAG_BUTTON_RIGHT);                       // Set TAG for BUTTON_RIGHT
  GD.Vertex2ii(X_BUTTON_RIGHT, Y_BUTTON_RIGHT);   // Place BUTTON2
  GD.Tag(TAG_BUTTON_ROT);                         // Set TAG for BUTTON_ROT
  GD.Vertex2ii( X_BUTTON_ROT, Y_BUTTON_ROT);      // Place BUTTON3
  GD.Tag(TAG_BUTTON_DROP);                        // Set TAG for BUTTON_DROP
  GD.Vertex2ii(X_BUTTON_DROP, Y_BUTTON_DROP);   // Place BUTTON4// Draw figures in buttons circles
  GD.Tag(255);
  GD.ColorRGB(0xffff00);
  GD.LineWidth(16*2);
  GD.Begin(LINE_STRIP);
  GD.Vertex2ii(X_BUTTON_LEFT + 30, Y_BUTTON_LEFT - 20);  GD.Vertex2ii(X_BUTTON_LEFT,      Y_BUTTON_LEFT - 20);
  GD.Vertex2ii(X_BUTTON_LEFT,      Y_BUTTON_LEFT - 40);  GD.Vertex2ii(X_BUTTON_LEFT - 40, Y_BUTTON_LEFT);
  GD.Vertex2ii(X_BUTTON_LEFT,      Y_BUTTON_LEFT + 40);  GD.Vertex2ii(X_BUTTON_LEFT,      Y_BUTTON_LEFT + 20);
  GD.Vertex2ii(X_BUTTON_LEFT + 30, Y_BUTTON_LEFT + 20);  GD.Vertex2ii(X_BUTTON_LEFT + 30, Y_BUTTON_LEFT - 20);
  GD.Begin(LINE_STRIP);
  GD.Vertex2ii(X_BUTTON_RIGHT - 30, Y_BUTTON_RIGHT - 20);  GD.Vertex2ii(X_BUTTON_RIGHT,      Y_BUTTON_RIGHT - 20);
  GD.Vertex2ii(X_BUTTON_RIGHT,      Y_BUTTON_RIGHT - 40);  GD.Vertex2ii(X_BUTTON_RIGHT + 40, Y_BUTTON_RIGHT);
  GD.Vertex2ii(X_BUTTON_RIGHT,      Y_BUTTON_RIGHT + 40);  GD.Vertex2ii(X_BUTTON_RIGHT,      Y_BUTTON_RIGHT + 20);
  GD.Vertex2ii(X_BUTTON_RIGHT - 30, Y_BUTTON_RIGHT + 20);  GD.Vertex2ii(X_BUTTON_RIGHT - 30, Y_BUTTON_RIGHT - 20);
  GD.Begin(LINE_STRIP);
  GD.Vertex2ii(X_BUTTON_ROT - 40, Y_BUTTON_ROT);  GD.Vertex2ii(X_BUTTON_ROT,      Y_BUTTON_ROT - 40);
  GD.Vertex2ii(X_BUTTON_ROT + 40, Y_BUTTON_ROT);  GD.Vertex2ii(X_BUTTON_ROT,      Y_BUTTON_ROT + 40);
  GD.Vertex2ii(X_BUTTON_ROT - 40, Y_BUTTON_ROT);
  GD.Begin(LINE_STRIP);
  if (Arbeiten) {
    GD.Vertex2ii(X_BUTTON_DROP - 40, Y_BUTTON_DROP - 10);  GD.Vertex2ii(X_BUTTON_DROP + 40, Y_BUTTON_DROP - 10);
    GD.Vertex2ii(X_BUTTON_DROP,      Y_BUTTON_DROP + 30);  GD.Vertex2ii(X_BUTTON_DROP - 40, Y_BUTTON_DROP - 10);
  } else {
    GD.Vertex2ii(X_BUTTON_DROP - 10, Y_BUTTON_DROP - 40);  GD.Vertex2ii(X_BUTTON_DROP + 30, Y_BUTTON_DROP);
    GD.Vertex2ii(X_BUTTON_DROP - 10, Y_BUTTON_DROP + 40);  GD.Vertex2ii(X_BUTTON_DROP - 10, Y_BUTTON_DROP - 40);
  }
}
//==================================================voidClearMas(byte MasStx[MaxX][MaxY]){
  for (byte j=0;j<MaxY;j++)
    for (byte i=0;i<MaxX;i++)
      MasStx[i][j]=0;
}
//==================================================voidSosed(int i,int j,int dx,int dy, byte mode){
  int nx=i+dx;
  int ny=j+dy;
  if (nx>=0 && ny>=0 && nx<MaxX && ny<MaxY && MasSt[nx][ny]==MasSt[i][j]) {
    if (mode==1) MasTmp[i][j]++; elseif (mode==2 && (MasTmp[nx][ny]>1 || MasTmp[i][j]>2 )) {
      MasTmp[nx][ny]=3; 
      MasTmp[i][j]=3;
    } else {
      if (mode==3 && MasTmp[nx][ny]==3) {
        if (MasTmp[i][j]!=3) {
          MasTmp[i][j]=3; 
          fl=true;
        }
      }
    }
  }
}
//==================================================voidSos(int i,int j, byte mode){
  for (byte k=0;k<4;k++)
    Sosed(i,j,mmm[k][0],mmm[k][1],mode);      
}
//==================================================// create next figurevoidGetNext(void){
  x=3; y=0;
  for (byte i=0;i<3;i++) {
    if (!Demo) MasSt[x][i]=fignext[i];
    fignext[i]=random(NumCol)+2;
  }
  if (!Demo) {
    Counter++;
    if (Counter==NextLevel) {
      Counter=0; 
      Level++; 
      if (Level>MaxLevel) Level=MaxLevel;
    }
    tempspeed=MasSpeed[Level-1];
  }
}
//==================================================// find onecolor elementsboolFindFull(void){ 
  byte i,j,k; bool res; 
  res=false; 
  for (byte k=2;k<8;k++) { // by every color
    ClearMas(MasTmp);
    for (j=3;j<MaxY;j++)
      for (i=0;i<MaxX;i++)
        if (MasSt[i][j]==k) Sos(i,j,1);
    for (j=3;j<MaxY;j++)
      for (i=0;i<MaxX;i++)
        if (MasTmp[i][j]>1) Sos(i,j,2);
    do {
      fl=false;
      for (j=3;j<MaxY;j++)
        for (i=0;i<MaxX;i++)
          if (MasTmp[i][j]>0) Sos(i,j,3);
    } while (fl);
    for (j=3;j<MaxY;j++)
      for (i=0;i<MaxX;i++)
        if (MasTmp[i][j]==3) {
          MasSt[i][j]=1;
          TScore++;
        }
  }
  return(res);
}
//================================================// move figure downboolfig_drop(int dy){
  if (dy>0 && !FlZ) {
    if (y+dy+2>MaxY-1  || MasSt[x+dx][y+dy+2]>0) {
      if (y<3) {
        gameover();
      } else {
        returntrue;
      }
    } else {
      if (y+dy+dy+2>MaxY-1  || MasSt[x+dx][y+dy+dy+2]>0) {
        GD.play(COWBELL);
      }
      for (byte i=0;i<3;i++) MasSt[x][y+2-i+dy]=MasSt[x][y+2-i];
      MasSt[x][y]=0;
      y=y+dy;
    }
  }
  return(false);
}
//================================================// move figure left/right (shift)boolfig_shift(int dx){
  if (x+dx<0 || x+dx>MaxX-1) {
    GD.play(COWBELL);
    return(false);
  }
  if (dx!=0) {
    if (MasSt[x+dx][y+dy+2]==0) {
      if (x+dx+dx<0 || x+dx+dx>MaxX-1)
        GD.play(COWBELL);
      else
        GD.play(CHACK);
      return(true);
    } else {
      GD.play(COWBELL);
      return(false);
    }
  }
  return(false);
}
//==================================================// State-machinevoidProcGame(void){
  byte i,j,k; bool res = false; 
  if (time_count < millis()) {
    if (Arbeiten)
      time_count = millis() + tempspeed;
    else
      time_count = millis() + 1000;
    switch (state_game) {
      // Democase0:
        Score=0;
        GetNext();
        for (byte j=3;j<MaxY;j++)
          for (byte i=0;i<MaxX;i++)
            MasSt[i][j]=random(6)+2;
        state_game = 1;
        TScore=0;
        break;
      case1:
        FindFull();
        if (TScore>0)
        {
          FlZ=true;
          time_count = millis() + 500;
        }
        state_game = 2;
        break;
      case2:
        for (j=0;j<MaxY;j++) {
          for (i=0;i<MaxX;i++) {
            while (MasSt[i][MaxY-1-j]==1) {
              for (k=0;k<MaxY-2-j;k++) MasSt[i][MaxY-1-k-j] = MasSt[i][MaxY-2-k-j];
              res=true;  
            }
          }
        }
        if(res) {
          if (TScore>7) Score=Score+TScore+(TScore-8)*2;
          else Score=Score+TScore;
          state_game = 1;
        } else {
          state_game = 0;
        }
        break;
      // Arbeitencase3:
        if (fig_drop(1))
        {
          tempspeed=MasSpeed[Level-1];
          TScore=0;
          FindFull();
          if (TScore>0)
          {
            GD.play(KICKDRUM);
            FlZ=true;
            state_game = 4;
          } else {
            FlZ=false;
            GetNext();
          }
        }
        break;
      case4:
        for (j=0;j<MaxY;j++) {
          for (i=0;i<MaxX;i++) {
            while (MasSt[i][MaxY-1-j]==1) {
              for (k=0;k<MaxY-2-j;k++) MasSt[i][MaxY-1-k-j] = MasSt[i][MaxY-2-k-j];
              res=true;  
            }
          }
        }
        if(res) {
          if (TScore>7) Score=Score+TScore+(TScore-8)*2;
          else Score=Score+TScore;
          state_game = 5;
          FlZ=true;
          GD.play(CLACK);
        } else {
          state_game = 3;
          FlZ=false;
          time_count = millis() + 100;
        }
        break;
      case5:
        state_game = 3;
        FlZ=false;
        break;
      default:
        break;
    }
  }
}
//================================================// start new gamevoidNewGame(){
  Score      = 0;
  FlZ        = false;
  ClearMas(MasSt);
  Arbeiten   = true;
  GetNext();
  Counter    = 0;
  Level      = 1;
  tempspeed  = MasSpeed[0];
  Record     = myrecord;
  state_game = 3;
}
//================================================// draw "GAME OVER"voidgameover(){
  if (Arbeiten==true) {
    GD.play(SWITCH);
    Arbeiten=false;
    if (Score>myrecord) {
      myrecord=Score;
      eeprom_write_byte((unsignedchar *) 1, myrecord);
    }
  }
}
slotgag_assets.h
#define LOAD_ASSETS()  GD.safeload("slotgag.gd2");#define BACKGROUND_HANDLE 0#define BACKGROUND_WIDTH 256#define BACKGROUND_HEIGHT 256#define BACKGROUND_CELLS 1#define GAMEDUINO_HANDLE 1#define GAMEDUINO_WIDTH 395#define GAMEDUINO_HEIGHT 113#define GAMEDUINO_CELLS 1#define ASSETS_END 220342ULstaticconstshape_t BACKGROUND_SHAPE = {0, 256, 256, 0};
staticconstshape_t GAMEDUINO_SHAPE = {1, 395, 113, 0};

I believe that I have completed the task of creating my first working sketch for this board. I hope that at least one person was interested in reading my story. Criticism and comments are welcome. The plans do not stop, move on and, of course, share experience and knowledge.

To demonstrate the operation of the board, I post a video with sound (Caution! Loud sound!).
Thanks for attention.

Also popular now: