Creando un juego de lógica para una plataforma de juego

Hola

Quiero compartir mi historia de conocimiento con la plataforma de juegos Gameduino 3, así como una pequeña charla sobre la programación del juego de lógica más simple para esta plataforma, utilizado en conjunto con Arduino Uno.

¿Qué es Gameduino 3? Gameduino 3 es una placa de expansión que te permite convertir el Arduino en una consola de juegos moderna de mano (que significa tamaño). Para mi sorpresa, no pude encontrar ninguna información detallada sobre esta placa en el centro. Me gustaría llenar este vacío, especialmente porque la junta, en mi opinión, merece atención.

Un poco de historia


El autor del proyecto llamado Gameduino es James Bowman, quien en 2011 creó la primera versión del tablero. Luego se posicionó como un módulo VGA para Arduino. La placa se llamaba Gameduino y estaba basada en la familia Xilinx Spartan-3A de FPGA lógicas programables. Se instalaron conectores para conectar un monitor VGA y altavoces estéreo en la placa. imagen

Características de Gameduino (1):
  • Salida de video VGA con una resolución de 400x300 píxeles, 512 colores;
  • toda la gama de colores se procesa en FPGA con una precisión de 15 bits;

gráficos de fondo:

  • área de gráficos de fondo simbólicos 512x512 píxeles;
  • 256 caracteres, cada uno con una paleta independiente de 4 colores;
  • implementado el efecto de envolver cadenas de texto con suavizado de píxeles;

gráficos en primer plano:

  • Cada sprite tiene una resolución de 16x16 píxeles;
  • cada sprite puede tener una paleta de 256, 16 o 4 colores;
  • soporte para rotación de 4 vías y algoritmos de rotación horizontal;
  • 96 sprites por línea de trama, 1536 elementos de textura por línea de trama;
  • mecanismo para detectar posibles intersecciones de sprites;

salida de audio:

  • Sintetizador de frecuencia de doble canal de 12 bits;
  • Polifonía de 64 voces en el rango de frecuencia de 10 - 8000 Hz.

La imagen se muestra en la pantalla de un monitor VGA estándar con una resolución de 400x300 píxeles,
Se mantiene la compatibilidad con cualquier monitor VGA estándar con una resolución de 800x600 píxeles.

En 2013, se lanzó la segunda versión de la placa: Gameduino 2, en la que, a diferencia de la versión anterior, ya había una pantalla táctil resistiva de 4.3 pulgadas con una resolución de 480x272, un acelerómetro de 3 ejes, una ranura para tarjeta de memoria microSD y una salida de audio para auriculares.

imagen

El "corazón" de la placa fue el controlador gráfico EVE (Embedded Video Engine - en ruso se puede traducir como "módulo de video incorporado") FT800, que tiene potentes capacidades informáticas, combinando varias funciones al mismo tiempo: formación de imágenes y su salida a la pantalla de la pantalla TFT, procesamiento de pantalla táctil, generación de sonido.

Diagrama funcional del controlador gráfico FT800
imagen

Los siguientes bloques funcionales están incluidos en la estructura del microcircuito: controlador gráfico, controlador de audio, controlador de panel táctil resistivo. El chip FT800 está diseñado para controlar pantallas con una resolución de hasta 512 x 512 píxeles. El FT800 también es compatible con LCD WQVGA (480 x 272) y QVGA (320 x 240). El EVE (Embedded Video Engine) FT800 es una solución llave en mano para crear una interfaz gráfica de usuario. El microcircuito genera señales de control de visualización, tiene funciones gráficas incorporadas para mostrar puntos, líneas, imágenes de mapa de bits, botones volumétricos, textos, etc.


Estructura del sistema basada en el controlador de gráficos FT800
La formación de imágenes se basa en un conjunto de comandos (lista de visualización), que el microcontrolador de control transmite al FT800 a través de la interfaz I2C o SPI (en Gameduino 2, la comunicación entre el Arduino y el FT800 se realiza a través de la interfaz SPI). Las características del FT800 alivian significativamente el controlador host del sistema.

imagen

Por ejemplo, para mostrar una cantidad de botones, es suficiente transferir un comando al controlador gráfico (cuatro palabras de 32 bits), y el FT800 formará independientemente la imagen de estos botones en la pantalla de la pantalla TFT. El conjunto de comandos del controlador de gráficos FTDI incluye más de 50 funciones que se pueden usar para mostrar varias imágenes en la pantalla con varios efectos.

El manual de programación detallado del controlador y ejemplos de trabajo con diversos entornos de diseño se pueden encontrar en las Notas de aplicación en el sitio web de FTDI .

En ruso, una buena descripción de la funcionalidad, principios generales y ejemplos de trabajo está aquí .

Características de Gameduino 2:
  • resolución de pantalla de 480x272 píxeles en color de 24 bits;
  • un conjunto de comandos al estilo de OpenGL;
  • hasta 2000 sprites de cualquier tamaño;
  • 256 KB de memoria de video;
  • rotación suave del sprite y escalado con filtrado bilineal;
  • círculo liso y patrón lineal en hardware - 16x suavizado;
  • Decodificación de hardware JPEG;
  • Representación integrada de degradados, texto, diales y botones.

El sonido se emite a través de una toma de auriculares reforzada.
El sistema admite la selección de muestras y herramientas integradas.

La ROM del controlador ya está conectada:

  • fuentes de alta calidad (6 tamaños);
  • muestras de 8 instrumentos musicales tocados por una nota MIDI;
  • Muestras de 10 sonidos de percusión.

Y, por supuesto, puede cargar sus propias fuentes y fragmentos de sonido en 256 Kbytes de RAM.

El uso de la plataforma Arduino no es un requisito previo: la placa Gameduino 2 se puede conectar a cualquier microcontrolador o placa microcontroladora con una interfaz SPI.

En 2017, se lanzó la tercera versión de la placa: Gameduino 3, que se ve casi idéntico a Gameduino 2. En lugar de FT800, se usa el nuevo controlador de gráficos FT810, que tiene compatibilidad de software con FT800 (es decir, todo el código para Gameduino2 funciona en Gameduino3). pero al mismo tiempo tiene capacidades informáticas 4 veces mayores, como una decodificación JPEG de hardware más rápida, decodificación de video, hasta 1 MB de RAM, etc.

Características de Gameduino 3:
  • decodificador de video para video a pantalla completa de 30 fps;
  • 1 megabyte de RAM interna;
  • conectores para tarjetas microSD y salida de audio;
  • Panel LCD de alto contraste de 4.3 ”480x272 con pantalla táctil resistiva;
  • compatibilidad con mapas creados con el editor de mapas en mosaico;
  • Descargar imagen PNG desde microSD;
  • decodificación acelerada de JPEG;
  • hardware de conmutación vertical / horizontal;
  • soporte para Arduino, ESP8266 y Teensy 3.2;
  • herramientas en línea para preparar gráficos, audio, fuentes y videos;


James Bowman ha publicado una biblioteca para su proyecto con muchos ejemplos que funcionan directamente. La biblioteca actual que logré encontrar está aquí . Guía de programación (en inglés), donde todo se describe en detalle. Mucha información útil sobre la instalación de un IDE, etc., etc.

Programacion


De alguna manera, deambulando por las extensiones del Teatro de Internet Bolshoi , me encontré con un proyecto interesante para Arduino: el juego lógico "Columnas" , escrito bajo la habitual pantalla a color china de 128x160 píxeles. Quería repetir este juego, pero en mi placa base, lo llamaré FT810 (por el nombre de la GPU), que para entonces ya estaba en mis manos. Ya me las arreglé para estudiar el manual de programación y los ejemplos de la biblioteca, por lo que mis manos simplemente "picaron" por el deseo de escribir algo propio. Lo cual comencé de inmediato.

Lo primero que tuve que hacer fue mostrar el texto en la pantalla.

Gracias a la presencia de fuentes incorporadas, la salida de texto a la pantalla es bastante fácil. Aquí hay un boceto de demostración de la biblioteca (con mis comentarios):

Bosquejo 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, obtenemos aquí un texto tan hermoso:
imagen

Luego, era necesario dibujar formas geométricas, por ejemplo: líneas.
Para dibujar líneas, debe usar Begin (LINES) o Begin (LINE_STRIP).
LINES une cada par de vértices, mientras que LINE_STRIP une todos los vértices.

Daré el siguiente boceto de demostración de la biblioteca (con mis comentarios):

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(); //         (   ). } 

Líneas en la pantalla:

imagen

Desde el dibujo lineal, pasemos a dibujar rectángulos.

Para dibujar rectángulos, use Comenzar (RECTS) y establezca las esquinas opuestas del rectángulo. El orden de los dos ángulos no importa. Los rectángulos se dibujan con esquinas redondeadas, utilizando el ancho de la línea actual como el radio de la esquina. Las esquinas redondeadas se extienden más allá de los bordes del rectángulo, por lo que aumentar el radio del ángulo conduce a un aumento en el número de píxeles. Este ejemplo dibuja un rectángulo 420 × 20 tres veces con un radio de esquina creciente.

Sketch 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:
imagen

Pasemos a dibujar un círculo: la base de futuros botones táctiles. Volvamos al primer ejemplo con texto y agreguemos algunas líneas a loop ().

Boceto con dibujo de círculos de colores
 #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 trabajar con botones táctiles, es necesario organizar el procesamiento de presionar la pantalla táctil. Esto se hace de la siguiente manera. Explicaré brevemente el principio. Cada píxel (punto) en la pantalla tiene un color. También tiene un valor de etiqueta invisible, que puede asignarse a un punto (o a un objeto completo, como una línea, un círculo, un rectángulo, etc.) y usarse para detectar toques de este objeto. El siguiente boceto muestra un ejemplo de configuración del valor de etiqueta para círculos de colores en 100 y 101.

Touch Screen Sketch
 #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); //       “”  } 

Ahora, cuando el sistema detecta un toque en cualquier círculo, informa sobre su código de sensor, en este caso 100 o 101. Al hacer clic en objetos (círculos de colores), los valores de las etiquetas correspondientes a los objetos presionados se mostrarán en la pantalla en la ventana del puerto serie:



Di ejemplos de operaciones básicas, utilizando las cuales ya era posible proceder de forma segura a la creación del juego. Por supuesto, el juego no fue creado desde cero, pero la base fue un código de trabajo listo para usar que fue adaptado (al tiempo que conservaba los gráficos), por lo que la versión original del juego era muy similar al original.

La primera versión del juego:



Después de jugar durante varios días, quería cambiar algo en el diseño, por ejemplo, para agregar otro fondo inusual en lugar de blanco. Y luego recordé un ejemplo de la biblioteca, en el que el cielo estrellado brilló en el fondo:

Slot Sketch 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(); } 

Vista:



Para no adentrarme en la jungla, no hice comentarios sobre el código completo, sino solo sobre las líneas de código necesarias que copié en mi boceto de trabajo.

Para agregar una imagen del cielo estrellado como fondo, tuve que hacer lo siguiente: en primer lugar, cambiar el color negro de las líneas y el texto a blanco (para que fueran visibles sobre un fondo negro), escribir el archivo slotgag.gd2 en la tarjeta micro SD, que almacena la imagen, agregue slotgag_assets.h a la carpeta del proyecto y agregue las 8 líneas de código necesarias al boceto.

Como resultado, el juego adquirió esta forma:



Y, por supuesto, ¿qué tipo de juego es sin diseño de sonido? Queda por agregar efectos de sonido, especialmente porque se presentan en buena calidad y variedad.

Gameduino 2/3 tiene dos sistemas de sonido. El primero, un sintetizador, puede generar un conjunto de sonidos fijos y notas musicales. Un sintetizador es útil para agregar sonido rápidamente a un proyecto, pero como el conjunto de sonidos es fijo, no es muy flexible. El segundo es la reproducción de muestras. Reproduce el sonido muestreado de la memoria principal en varios formatos. Este sistema es mucho más flexible, pero deberá preparar y cargar muestras en la RAM.

Usé un sintetizador de sonido fijo. El sintetizador proporciona varios sonidos cortos de "percusión", principalmente para su uso en interfaces de usuario. Para reproducir un sonido, debe llamar a GD.play () con un identificador de sonido. Lista completa de sonidos disponibles:

Haga clic en
INTERRUPTOR
Cencerro
Muesca
Hola
Kickdrum
Pop
Clack
CHACK

Resumen


El resultado fue un bosquejo:

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}; 

Creo que completé la tarea de crear mi primer boceto de trabajo para este tablero. Espero que al menos una persona esté interesada en leer mi historia. Críticas y comentarios son bienvenidos. Los planes no se detienen, siguen adelante y, por supuesto, comparten experiencias y conocimientos.

Para demostrar el funcionamiento del tablero, publico un video con sonido (¡Precaución! ¡Sonido fuerte!).
Gracias por su atencion

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


All Articles