- PVSM.RU - https://www.pvsm.ru -
Здравствуйте.
Хочу поделиться своей историей знакомства с игровой платформой Gameduino 3, а также немного расскажу о программировании простейшей логической игры под данную платформу, используемой совместно с Arduino Uno.
Что же такое Gameduino 3? Gameduino 3 — это плата расширения, которая позволяет превратить Arduino в современную карманную (имеется в виду размер) игровую консоль. К моему удивлению, мне не удалось найти на хабре какой-либо подробной информации о данной плате. Хотел бы восполнить этот пробел, тем более что плата, на мой взгляд, заслуживает внимания.
Автором проекта под названием Gameduino является Джеймс Боуман (James Bowman), который в 2011 году создал первую версию платы. Тогда она позиционировалась как модуль VGA для Arduino. Плата называлась Gameduino и была выполнена на основе программируемой логической матрицы FPGA компании Xilinx семейства Spartan-3A. На плате были установлены разъемы для подключения VGA монитора и стерео акустики.
фоновая графика:
графика переднего плана:
аудио выход:
Изображение выводится на экран стандартного VGA монитора с разрешением 400х300 точек,
сохраняется совместимость с любыми стандартными VGA мониторами с разрешением 800х600 точек.
В 2013 году была выпущена вторая версия платы — Gameduino 2, в которой, в отличие от предыдущей версии, уже имелся 4,3 дюймовый резистивный сенсорный дисплей с разрешением 480х272, 3-осевой акселерометр, слот карты памяти microSD, аудио выход на наушники.
«Сердцем» платы стал графический контроллер EVE (Embedded Video Engine — по-русски можно перевести как «встраиваемый видеомодуль») FT800, который обладает мощными вычислительными возможностями, совмещая в себе одновременно несколько функций: формирование изображения и вывод его на экран TFT-дисплея, обработка сенсорного экрана, генерирование звука.
В структуру микросхемы включены следующие функциональные блоки: графический контроллер, контроллер аудио, контроллер резистивной сенсорной панели. Микросхема FT800 предназначена для управления дисплеями с разрешением до 512 х 512 пикселей. FT800 также поддерживает LCD WQVGA (480 x 272) и QVGA (320 x 240). EVE (Embedded Video Engine) FT800 – это готовое решения для создания графического пользовательского интерфейса. Микросхема формирует сигналы управления дисплеем, имеет встроенные графические функции для отображения точек, линий, растровых картинок, объемных кнопок, текстов и т. д.
Например, для вывода ряда кнопок достаточно передать в графический контроллер одну команду (четыре 32-х разрядных слова), и FT800 самостоятельно сформирует изображение этих кнопок на экране TFT-дисплея. Набор команд графических контроллеров FTDI включает в себя более 50 функций, с помощью которых можно выводить различные изображения на экран дисплея с теми или иными эффектами.
Подробное руководство по программированию контроллера и примеры работы с различными средами проектирования можно найти в Application Notes на сайте FTDI [1].
На русском языке хорошее описание функциональных возможностей, общих принципов и примеров работы есть тут [2].
Выход звука осуществляется через усиленный разъем для наушников.
Система поддерживает выбор встроенных образцов и инструментов.
В ПЗУ контроллера уже зашиты:
И, конечно же, вы можете загрузить свои собственные шрифты и звуковые фрагменты в ОЗУ 256 Кбайт.
Использование платформы Arduino не является обязательным условием: плату Gameduino 2 можно подключать к любому микроконтроллеру или микроконтроллерной плате с интерфейсом SPI.
В 2017 году была выпущена третья версия платы – Gameduino 3, которая внешне практически не отличается от Gameduino 2. Вместо FT800 используется новый графический контроллер FT810, который имеет обратную программную совместимость с FT800 (т.е. весь код для Gameduino2 работает на Gameduino3), но при этом имеет в 4 раза большие вычислительные возможности, такие, как более быстрое аппаратное декодирование JPEG, декодирование видео, увеличенная до 1 Мбайт оперативная память и др.
Джеймс Боуман опубликовал для своего проекта библиотеку с множеством примеров, которые работают „прямо из коробки“. Актуальная библиотека, которую мне удалось найти, находится тут [3]. Руководство по программированию [4] (на английском языке), где подробно все описывается. Много полезной информации [5] по установке IDE, и т.п., и т.д.
Как-то, блуждая по просторам Большого театра интернета, я наткнулся на интересный проект для Ардуино — логическая игра „Columns“ [6], написанная под обычный недорогой цветной китайский дисплей 128х160 пикселов. Мне захотелось повторить эту игру, но уже на своей плате, назову её FT810 (по названию графического процессора), которая к тому времени уже была у меня на руках. Руководство по программированию и примеры из библиотеки я тоже уже успел изучить, поэтому руки просто „чесались“ от желания написать что-нибудь свое. К чему я незамедлительно и приступил.
Первое, что мне предстояло сделать — это вывод текста на экран.
Благодаря наличию встроенных шрифтов вывод текста на экран осуществляется достаточно легко. Приведу демонстрационный скетч из библиотеки (с моими комментариями):
#include <EEPROM.h>
#include <SPI.h>
#include <GD2.h>
void setup()
{
Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод
GD.begin(0); // Инициализация процессора, периферии и пр.
}
void loop()
{
GD.ClearColorRGB(0x103000); // Установка цвета фона экрана.
GD.Clear(); // Очистка экрана (заполнение заданным цветом фона)
GD.cmd_text( // печать текста, где
GD.w / 2, // ширина экрана деленная на 2
GD.h / 2, // высота экрана деленная на 2
31, // номер шрифта
OPT_CENTER, // опция, обозначающая отрисовку надписи с центром в указанной выше координате.
"Hello world"); // текст надписи
GD.swap(); // команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора).
}
В итоге на экране получим вот такой красивый текст:
Далее необходимо было нарисовать геометрические фигуры, например: линии.
Чтобы нарисовать линии, необходимо использовать Begin(LINES) или Begin(LINE_STRIP).
LINES соединяет каждую пару вершин, тогда как LINE_STRIP объединяет все вершины вместе.
Приведу следующий демоскетч из библиотеки (с моими комментариями):
#include <EEPROM.h>
#include <SPI.h>
#include <GD2.h>
void setup()
{
Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод.
GD.begin(); // Инициализация процессора, периферии и пр.
}
static void zigzag(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); // Установка координаты конечной точки (вершины)
}
void loop()
{
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(); // Команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора).
}
Линии на экране:
От рисования линий перейдем к рисованию прямоугольников.
Чтобы нарисовать прямоугольники, используем Begin (RECTS) и устанавливаем противоположные углы прямоугольника. Порядок двух углов не имеет значения. Прямоугольники рисуются с закругленными углами, используя ширину текущей линии в качестве радиуса угла. Закругленные углы выходят за границы прямоугольника, поэтому увеличение радиуса угла приводит к увеличению количества пикселей. Этот пример рисует прямоугольник 420 × 20 три раза с увеличением радиуса скругления угла.
#include <EEPROM.h>
#include <SPI.h>
#include <GD2.h>
void setup()
{
Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод.
GD.begin(); // Инициализация процессора, периферии и пр.
}
void loop()
{
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(); // Команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора)
}
Результат:
Перейдем к рисованию круга – основе будущих сенсорных кнопок. Вернемся к первому примеру с текстом и добавим в loop() несколько строк.
#include <EEPROM.h>
#include <SPI.h>
#include <GD2.h>
void setup()
{
Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод
GD.begin(0); // Инициализация процессора, периферии и пр.
}
void loop()
{
GD.ClearColorRGB(0x103000); // Установка цвета фона экрана.
GD.Clear(); // Очистка экрана (заполнение заданным цветом фона)
GD.cmd_text( // печать текста, где
GD.w / 2, // ширина экрана деленная на 2
GD.h / 2, // высота экрана деленная на 2
31, // номер шрифта
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(); // Команда на копирование данных из видеобуфера на экран (аппаратная функция графического процессора)
}
Результат:
Для работы с сенсорными кнопками необходимо организовать обработку нажатий сенсорного экрана. Делается это следующим образом. В кратце объясню принцип. Каждый пиксель (точка) на экране имеет цвет. Он также имеет невидимое значение тега, которое может присваиваться точке (или целому объекту типа линия, круг, прямоугольник и т.д) и в дальнейшем использоваться для обнаружения касаний данного объекта. В следующем скетче приведен пример установки значения тега для цветных кругов на значение 100 и 101.
#include <EEPROM.h>
#include <SPI.h>
#include <GD2.h>
void setup()
{
Serial.begin(1000000); // Инициализация отладочного порта, установка скорости передачи 1000000 бод
GD.begin(0); // Инициализация процессора, периферии и пр.
}
void loop()
{
GD.ClearColorRGB(0x103000); // Установка цвета фона экрана.
GD.Clear(); // Очистка экрана (заполнение заданным цветом фона)
GD.cmd_text( // печать текста, где
GD.w / 2, // ширина экрана деленная на 2
GD.h / 2, // высота экрана деленная на 2
31, // номер шрифта
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. При нажатии на объекты (цветные круги) на экране в окне последовательного порта отобразятся значения тегов, соответствующих нажатым объектам:
Я привел примеры основных операций, используя которые уже можно было смело переходить к созданию игры. Конечно игра создавалась не с нуля, а за основу был взят готовый рабочий код, который был адаптирован (с сохранением графики), поэтому первоначальный вариант игры был очень похож на оригинал.
Первый вариант оформления игры:
Поиграв несколько дней, захотелось что-нибудь изменить в оформлении, например добавить вместо белого фона какой-нибудь другой, необычный. И тут я вспомнил один пример из библиотеки, в котором на заднем фоне красовалось звездное небо:
#include <EEPROM.h>
#include <SPI.h>
#include <GD2.h>
#include "slotgag_assets.h" // файл с константами и адресами
void setup()
{
Serial.begin(1000000);
GD.begin();
LOAD_ASSETS(); // загрузка изображений в ОЗУ
}
void loop()
{
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);
static int 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();
}
Вид:
Чтобы не углубляться в дебри, я не стал комментировать весь код, а прокомментировал только нужные строчки кода, которые скопировал в свой рабочий скетч.
Для добавления картинки звездного неба в качестве заднего фона пришлось сделать следующее: во-первых, изменить черный цвет линий и текста на белый (чтобы они были видны на черном фоне), записать на микро SD карту файл slotgag.gd2, в котором хранится изображение, добавить в папку проекта slotgag_assets.h и добавить в скетч необходимые 8 строчек кода.
В итоге игра приобрела вот такой вид:
И конечно же, какая игра без звукового оформления? Осталось добавить звуковые эффекты, тем более что они представлены в хорошем качестве и разнообразии.
Gameduino 2/3 имеет две звуковые системы. Первый — синтезатор — может генерировать набор фиксированных звуков и музыкальных нот. Синтезатор полезен для быстрого добавления звука в проект, но поскольку набор звуков фиксирован, он не очень гибкий. Второй — воспроизведение образца. Он воспроизводит дискретизированный звук из основной памяти в различных форматах. Эта система намного более гибкая, но вам нужно будет подготовить и загрузить образцы в оперативную память.
Я использовал синтезатор фиксированых звуков. Синтезатор предоставляет несколько коротких «ударных» звуков, в основном для использования в пользовательских интерфейсах. Чтобы воспроизвести звук, необходимо вызвать GD.play () со звуковым идентификатором. Полный список доступных звуков:
CLICK
SWITCH
COWBELL
NOTCH
HIHAT
KICKDRUM
POP
CLACK
CHACK
В итоге получился такой скетч:
#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};
unsigned long 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;
unsigned long time_count;
byte prevkey;
uint32_t btn_color = 0xff0000;
/****************************************************************************************************************/
void setup(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((unsigned char *)1);
time_count = millis() + 1000;
}
static struct {
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 }
};
//==================================================
void loop(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
void ViewQuad(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 Top
sprintf(myStr2,"%05d",myrecord );
GD.cmd_text(DISP_LEFT - 30, DISP_TOP + 130, 27, OPT_CENTER, myStr2);
// Print digit Level
sprintf(myStr2,"%02d",Level );
GD.cmd_text(DISP_LEFT - 30, DISP_TOP + 40, 31, OPT_CENTER, myStr2);
// Draw color squares
for (byte j=3;j<MaxY;j++)
for (byte i=0;i<MaxX;i++)
ViewQuad(i,j,MasSt[i][j]);
// Draw Next Figure
for (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 lines
for (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);
}
}
//==================================================
void ClearMas(byte MasStx[MaxX][MaxY])
{
for (byte j=0;j<MaxY;j++)
for (byte i=0;i<MaxX;i++)
MasStx[i][j]=0;
}
//==================================================
void Sosed(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]++; else
if (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;
}
}
}
}
}
//==================================================
void Sos(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 figure
void GetNext(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 elements
bool FindFull(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 down
bool fig_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 {
return true;
}
} 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)
bool fig_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-machine
void ProcGame(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) {
// Demo
case 0:
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;
case 1:
FindFull();
if (TScore>0)
{
FlZ=true;
time_count = millis() + 500;
}
state_game = 2;
break;
case 2:
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;
// Arbeiten
case 3:
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;
case 4:
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;
case 5:
state_game = 3;
FlZ=false;
break;
default:
break;
}
}
}
//================================================
// start new game
void NewGame()
{
Score = 0;
FlZ = false;
ClearMas(MasSt);
Arbeiten = true;
GetNext();
Counter = 0;
Level = 1;
tempspeed = MasSpeed[0];
Record = myrecord;
state_game = 3;
}
//================================================
// draw "GAME OVER"
void gameover()
{
if (Arbeiten==true) {
GD.play(SWITCH);
Arbeiten=false;
if (Score>myrecord) {
myrecord=Score;
eeprom_write_byte((unsigned char *) 1, myrecord);
}
}
}
#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 220342UL
static const shape_t BACKGROUND_SHAPE = {0, 256, 256, 0};
static const shape_t GAMEDUINO_SHAPE = {1, 395, 113, 0};
Считаю, что задачу по созданию своего первого рабочего скетча для данной платы я выполнил. Надеюсь, что хотя бы одному человеку было интересно читать мой рассказ. Критика и замечания приветствуются. В планах не останавливаться, двигаться дальше и, конечно же, делиться опытом, знаниями.
Для демонстрации работы платы выкладываю видео [7] со звуком (Осторожно! Громкий звук!).
Спасибо за внимание.
Автор: FedAll
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/292887
Ссылки в тексте:
[1] FTDI: http://www.ftdichip.com/Products/ICs/FT800.html
[2] тут: http://mymcu.ru/storage/content/articles/FTDI/EVE.pdf
[3] тут: https://github.com/jamesbowman/gd2-lib
[4] Руководство по программированию: http://excamera.com/files/gd2book_v0.pdf
[5] Много полезной информации: http://excamera.com/sphinx/gameduino2/code.html
[6] логическая игра „Columns“: https://mysku.ru/blog/diy/65413.html
[7] видео: https://youtu.be/az3YBakPDSo
[8] Источник: https://habr.com/post/423577/?utm_campaign=423577
Нажмите здесь для печати.