Criando um jogo de lógica para uma plataforma de jogos

Olá.

Quero compartilhar minha história de familiaridade com a plataforma de jogos Gameduino 3, bem como um pouco sobre como programar o jogo lógico mais simples para esta plataforma, usado em conjunto com o Arduino Uno.

O que é o Gameduino 3? O Gameduino 3 é uma placa de expansão que permite transformar o Arduino em um console de jogos portátil moderno (tamanho significativo). Para minha surpresa, não consegui encontrar nenhuma informação detalhada nesta placa no hub. Gostaria de preencher essa lacuna, principalmente porque o conselho, na minha opinião, merece atenção.

Um pouco de história


O autor do projeto chamado Gameduino é James Bowman, que em 2011 criou a primeira versão do conselho. Em seguida, foi posicionado como um módulo VGA para o Arduino. O conselho chamava-se Gameduino e era baseado na família de FPGAs lógicos programáveis ​​Xilinx Spartan-3A. Conectores para conectar um monitor VGA e alto-falantes estéreo foram instalados na placa. imagem

Recursos do Gameduino (1):
  • Saída de vídeo VGA com resolução de 400x300 pixels, 512 cores;
  • toda a gama de cores é processada em FPGA com precisão de 15 bits;

gráficos de fundo:

  • área de gráficos de fundo simbólicos de 512x512 pixels;
  • 256 caracteres, cada um com uma paleta de 4 cores independente;
  • implementou o efeito de agrupar seqüências de texto com suavização de pixel;

gráficos de primeiro plano:

  • Cada sprite tem uma resolução de 16x16 pixels;
  • cada sprite pode ter uma paleta de 256, 16 ou 4 cores;
  • suporte para algoritmos de rotação em quatro direções e rotação horizontal;
  • 96 sprites por linha raster, 1536 elementos de textura por linha raster;
  • mecanismo para detectar possíveis interseções de sprites;

saída de áudio:

  • Sintetizador de frequência de canal duplo de 12 bits;
  • Polifonia de 64 vozes na faixa de frequências de 10 - 8000 Hz.

A imagem é exibida na tela de um monitor VGA padrão com uma resolução de 400x300 pixels,
a compatibilidade com qualquer monitor VGA padrão com uma resolução de 800x600 pixels é mantida.

Em 2013, foi lançada a segunda versão da placa - Gameduino 2, na qual, ao contrário da versão anterior, já havia uma tela sensível ao toque resistiva de 4,3 polegadas com resolução de 480x272, um acelerômetro de 3 eixos, um slot de cartão de memória microSD e uma saída de áudio para fones de ouvido.

imagem

O “coração” da placa era o controlador gráfico EVE (Embedded Video Engine - em russo pode ser traduzido como “módulo de vídeo incorporado”) FT800, que possui poderosas capacidades de computação, combinando várias funções ao mesmo tempo: formação da imagem e sua saída para a tela da tela TFT, processamento de tela de toque, geração de som.

Diagrama funcional do controlador gráfico FT800
imagem

Os seguintes blocos funcionais estão incluídos na estrutura do microcircuito: controlador gráfico, controlador de áudio, controlador de painel de toque resistivo. O chip FT800 foi projetado para controlar monitores com uma resolução de até 512 x 512 pixels. O FT800 também suporta LCD WQVGA (480 x 272) e QVGA (320 x 240). O EVE (Embedded Video Engine) FT800 é uma solução pronta para criar uma interface gráfica do usuário. O microcircuito gera sinais de controle de exibição, possui funções gráficas incorporadas para exibir pontos, linhas, imagens bitmap, botões de volume, textos, etc.


Estrutura do sistema baseada no controlador gráfico FT800
A formação da imagem é baseada em um conjunto de comandos (lista de exibição), que é transmitido pelo microcontrolador de controle para o FT800 via interface I2C ou SPI (no Gameduino 2, a comunicação entre o Arduino e o FT800 é realizada via interface SPI). Os recursos do FT800 aliviam significativamente o controlador host do sistema.

imagem

Por exemplo, para exibir vários botões, basta transferir um comando para o controlador gráfico (quatro palavras de 32 bits), e o FT800 formará independentemente a imagem desses botões na tela do visor TFT. O conjunto de comandos do controlador gráfico FTDI inclui mais de 50 funções que podem ser usadas para exibir várias imagens na tela com vários efeitos.

Manual detalhado de programação do controlador e exemplos de trabalho com vários ambientes de design podem ser encontrados nas Notas de Aplicação no site da FTDI .

Em russo, há uma boa descrição da funcionalidade, princípios gerais e exemplos de trabalho.

Recursos do Gameduino 2:
  • resolução de tela de 480x272 pixels em cores de 24 bits;
  • um conjunto de comandos no estilo do OpenGL;
  • até 2000 sprites de qualquer tamanho;
  • 256 KB de memória de vídeo;
  • rotação e escalonamento suaves de sprites com filtragem bilinear;
  • círculo suave e padrão linear no hardware - suavização 16x;
  • Decodificação de hardware JPEG;
  • Renderização embutida de gradientes, texto, mostradores e botões.

O som é emitido através de um fone de ouvido reforçado.
O sistema suporta a seleção de amostras e ferramentas integradas.

A ROM do controlador já está conectada:

  • fontes de alta qualidade (6 tamanhos);
  • amostras de 8 instrumentos musicais tocados por uma nota MIDI;
  • amostras de 10 sons de percussão.

E, é claro, você pode carregar suas próprias fontes e sons em 256 Kbytes de RAM.

O uso da plataforma Arduino não é um pré-requisito: a placa Gameduino 2 pode ser conectada a qualquer microcontrolador ou placa de microcontrolador com uma interface SPI.

Em 2017, foi lançada a terceira versão da placa - Gameduino 3, que parece quase idêntica à Gameduino 2. Em vez do FT800, é usado o novo controlador gráfico FT810, que tem compatibilidade com o software anterior com o FT800 (ou seja, todo o código do Gameduino2 funciona no Gameduino3). mas, ao mesmo tempo, possui recursos de computação 4 vezes maiores, como decodificação JPEG de hardware mais rápida, decodificação de vídeo, até 1 MB de RAM, etc.

Características Gameduino 3:
  • decodificador de vídeo para vídeo em tela cheia a 30 fps;
  • 1 megabyte de RAM interna;
  • conectores para cartões microSD e saída de áudio;
  • Painel LCD de alto contraste de 4,3 ”480x272 com tela de toque resistiva;
  • suporte para mapas criados usando o editor Tiled Map;
  • Faça o download da imagem PNG do microSD;
  • decodificação JPEG acelerada;
  • comutação retrato / paisagem de hardware;
  • suporte para Arduino, ESP8266 e Teensy 3.2;
  • Ferramentas online para preparar gráficos, áudio, fonte e vídeo;


James Bowman publicou uma biblioteca para seu projeto com muitos exemplos que funcionam imediatamente. A biblioteca atual que eu consegui encontrar está aqui . Guia de Programação (em inglês), onde tudo é descrito em detalhes. Muitas informações úteis sobre a instalação de um IDE, etc., etc.

Programação


De alguma forma, vagando pelas extensões do Teatro Bolshoi na Internet, deparei-me com um projeto interessante para o Arduino - o jogo lógico “Columns” , escrito sob o habitual display colorido chinês de 128x160 pixels. Queria repetir esse jogo, mas, no meu tabuleiro, vou chamá-lo de FT810 (com o nome da GPU), que já estava em minhas mãos. Eu já consegui estudar o manual de programação e exemplos da biblioteca, então minhas mãos “coçaram” com o desejo de escrever algo próprio. Que eu comecei imediatamente.

A primeira coisa que tive que fazer foi exibir o texto na tela.

Graças à presença de fontes internas, a saída de texto para a tela é bastante fácil. Aqui está um esboço demo da biblioteca (com meus comentários):

Esboço helloworld.ino
#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(); //         (   ). } 
Como resultado, chegamos aqui um texto tão bonito:
imagem

Em seguida, foi necessário desenhar formas geométricas, por exemplo: linhas.
Para desenhar linhas, você deve usar Begin (LINES) ou Begin (LINE_STRIP).
LINES une cada par de vértices, enquanto LINE_STRIP une todos os vértices.

Vou dar o seguinte esboço demo da biblioteca (com meus comentários):

Sketch lines.ino
 #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(); //   (    - 0000000) 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(); //         (   ). } 

Linhas na tela:

imagem

Do desenho de linha, vamos para o desenho de retângulos.

Para desenhar retângulos, use Begin (RECTS) e defina os cantos opostos do retângulo. A ordem dos dois ângulos não importa. Os retângulos são desenhados com cantos arredondados, usando a largura da linha atual como o raio do canto. Os cantos arredondados estendem-se além das bordas do retângulo, aumentando o raio do ângulo, levando a um aumento no número de pixels. Este exemplo desenha um retângulo 420 × 20 três vezes com raio de canto crescente.

Esboço rectangles.ino
 #include <EEPROM.h> #include <SPI.h> #include <GD2.h> void setup() { Serial.begin(1000000); //   ,    1000000 . GD.begin(); //  ,   . } void loop() { GD.Clear(); //   (    - 0000000) 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(); //         (   ) } 

Resultado:
imagem

Vamos desenhar um círculo - a base dos futuros botões de toque. Vamos voltar ao primeiro exemplo com texto e adicionar algumas linhas ao loop ().

Esboço com desenho de círculos coloridos
 #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(); //         (   ) } 

Resultado:



Para trabalhar com os botões de toque, é necessário organizar o processamento de cliques na tela de toque. Isso é feito da seguinte maneira. Vou explicar brevemente o princípio. Cada pixel (ponto) na tela tem uma cor. Ele também possui um valor de tag invisível, que pode ser atribuído a um ponto (ou a um objeto inteiro, como linha, círculo, retângulo, etc.) e usado para detectar toques nesse objeto. O esboço a seguir mostra um exemplo de configuração do valor da tag para círculos coloridos como 100 e 101.

Esboço da tela de toque
 #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); //       “”  } 

Agora, quando o sistema detecta um toque em qualquer círculo, ele relata o código do sensor, neste caso 100 ou 101. Quando você clica em objetos (círculos coloridos) na tela na janela da porta serial, os valores das tags correspondentes aos objetos pressionados serão exibidos:



Dei exemplos de operações básicas, usando as quais já era possível prosseguir com segurança para a criação do jogo. Obviamente, o jogo não foi criado do zero, mas a base era um código de trabalho pronto que foi adaptado (preservando os gráficos); portanto, a versão original do jogo era muito semelhante à original.

A primeira versão do jogo:



Depois de jogar por vários dias, eu queria mudar alguma coisa no design, por exemplo, para adicionar outro, incomum, em vez de um fundo branco. E então me lembrei de um exemplo da biblioteca, no qual o céu estrelado brilhava ao fundo:

Esboço do slot slotgag.ino
 #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(); } 

Ver:



Para não me aprofundar na selva, não comentei o código inteiro, mas comentei apenas as linhas de código necessárias que copiei no meu esboço de trabalho.

Para adicionar uma imagem do céu estrelado como pano de fundo, tive que fazer o seguinte: primeiro, mude a cor preta das linhas e do texto para branco (para que fiquem visíveis em um fundo preto), escreva o arquivo slotgag.gd2 no cartão micro SD, que armazena a imagem, adicione slotgag_assets.h à pasta do projeto e adicione as 8 linhas de código necessárias ao esboço.

Como resultado, o jogo adquiriu este formulário:



E, claro, que tipo de jogo é sem design de som? Resta adicionar efeitos sonoros, principalmente porque são apresentados em boa qualidade e variedade.

Gameduino 2/3 possui dois sistemas de som. O primeiro - um sintetizador - pode gerar um conjunto de sons fixos e notas musicais. Um sintetizador é útil para adicionar som rapidamente a um projeto, mas como o conjunto de sons é fixo, ele não é muito flexível. O segundo é a reprodução da amostra. Reproduz o som amostrado da memória principal em vários formatos. Esse sistema é muito mais flexível, mas você precisará preparar e carregar amostras na RAM.

Eu usei um sintetizador de som fixo. O sintetizador fornece vários sons curtos de "percussão", principalmente para uso em interfaces de usuário. Para reproduzir um som, você deve chamar GD.play () com um identificador de som. Lista completa de sons disponíveis:

Clique em
INTERRUPTOR
Cowbell
NOTCH
Hihat
Kickdrum
Pop
Clack
CHACK

Sumário


O resultado foi um esboço:

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}; 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*ii); GD.Vertex2ii(DISP_RIGHT + 15 + razmer-1, DISP_TOP + 20 + razmer*ii); GD.Vertex2ii(DISP_RIGHT + 15 + razmer-1, DISP_TOP + 20 + razmer*ii + razmer-1); GD.Vertex2ii(DISP_RIGHT + 15, DISP_TOP + 20 + razmer*ii + razmer-1); GD.Vertex2ii(DISP_RIGHT + 15, DISP_TOP + 20 + razmer*ii); 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*ii, DISP_TOP); GD.Vertex2ii(DISP_LEFT + 3 + razmer*ii, 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-kj] = MasSt[i][MaxY-2-kj]; 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-kj] = MasSt[i][MaxY-2-kj]; 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); } } } 
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 220342UL static const shape_t BACKGROUND_SHAPE = {0, 256, 256, 0}; static const shape_t GAMEDUINO_SHAPE = {1, 395, 113, 0}; 

Acredito que concluí a tarefa de criar meu primeiro esboço de trabalho para este quadro. Espero que pelo menos uma pessoa tenha interesse em ler minha história. Críticas e comentários são bem-vindos. Os planos não param, seguem em frente e, é claro, compartilham experiência, conhecimento.

Para demonstrar o funcionamento do quadro, eu publico um vídeo com som (Cuidado! Som alto!).
Obrigado pela atenção.

Source: https://habr.com/ru/post/pt423577/


All Articles