Création d'un jeu de logique pour une plateforme de jeu

Bonjour

Je veux partager mon histoire de connaissance avec la plate-forme de jeu Gameduino 3, ainsi qu'une petite discussion sur la programmation du jeu de logique le plus simple pour cette plate-forme, utilisé conjointement avec Arduino Uno.

Qu'est-ce que Gameduino 3? Gameduino 3 est une carte d'extension qui vous permet de transformer l'Arduino en une console de jeu portable moderne (ce qui signifie la taille). À ma grande surprise, je n'ai pas pu trouver d'informations détaillées sur cette carte sur le hub. Je voudrais combler cette lacune, d'autant plus que le conseil, à mon avis, mérite l'attention.

Un peu d'histoire


L'auteur du projet appelé Gameduino est James Bowman, qui a créé en 2011 la première version de la planche. Ensuite, il a été positionné comme un module VGA pour Arduino. La carte s'appelait Gameduino et était basée sur la famille de FPGA logiques programmables Xilinx Spartan-3A. Des connecteurs pour connecter un moniteur VGA et des haut-parleurs stéréo ont été installés sur la carte. image

Caractéristiques de Gameduino (1):
  • Sortie vidéo VGA avec une résolution de 400x300 pixels, 512 couleurs;
  • toute la gamme de couleurs est traitée en FPGA avec une précision de 15 bits;

graphiques d'arrière-plan:

  • zone des graphiques d'arrière-plan symboliques 512x512 pixels;
  • 256 caractères, chacun avec une palette indépendante de 4 couleurs;
  • implémenté l'effet d'envelopper les chaînes de texte avec le lissage des pixels;

graphiques de premier plan:

  • Chaque sprite a une résolution de 16x16 pixels;
  • chaque image-objet peut avoir une palette de 256, 16 ou 4 couleurs;
  • prise en charge des algorithmes de rotation quadridirectionnelle et de rotation horizontale;
  • 96 sprites par ligne raster, 1536 éléments de texture par ligne raster;
  • mécanisme de détection des intersections possibles des sprites;

sortie audio:

  • Synthétiseur de fréquence double canal 12 bits;
  • Polyphonie 64 voix dans la gamme de fréquences de 10 à 8000 Hz.

L'image est affichée sur l'écran d'un moniteur VGA standard avec une résolution de 400x300 pixels,
la compatibilité avec tous les moniteurs VGA standard avec une résolution de 800x600 pixels est maintenue.

En 2013, la deuxième version de la carte est sortie - Gameduino 2, dans laquelle, contrairement à la version précédente, il y avait déjà un écran tactile résistif de 4,3 pouces avec une résolution de 480x272, un accéléromètre à 3 axes, un emplacement pour carte mémoire microSD et une sortie audio pour les écouteurs.

image

Le «cœur» de la carte était le contrôleur graphique EVE (Embedded Video Engine - en russe peut être traduit par «module vidéo intégré») FT800, qui a de puissantes capacités de calcul, combinant plusieurs fonctions en même temps: formation d'image et sa sortie sur l'écran de l'écran TFT, traitement d'écran tactile, génération de sons.

Diagramme fonctionnel du contrôleur graphique FT800
image

Les blocs fonctionnels suivants sont inclus dans la structure du microcircuit: contrôleur graphique, contrôleur audio, contrôleur d'écran tactile résistif. La puce FT800 est conçue pour contrôler les écrans avec une résolution allant jusqu'à 512 x 512 pixels. Le FT800 prend également en charge LCD WQVGA (480 x 272) et QVGA (320 x 240). Le EVE (Embedded Video Engine) FT800 est une solution clé en main pour créer une interface utilisateur graphique. Le microcircuit génère des signaux de contrôle d'affichage, a des fonctions graphiques intégrées pour afficher des points, des lignes, des images bitmap, des boutons volumétriques, des textes, etc.


Structure du système basée sur le contrôleur graphique FT800
La formation des images est basée sur un ensemble de commandes (liste d'affichage), qui sont transmises par le microcontrôleur de contrôle au FT800 via l'interface I2C ou SPI (dans Gameduino 2, la communication entre l'Arduino et le FT800 s'effectue via l'interface SPI). Les caractéristiques du FT800 soulagent considérablement le contrôleur hôte du système.

image

Par exemple, pour afficher un certain nombre de boutons, il suffit de transférer une commande au contrôleur graphique (quatre mots de 32 bits), et le FT800 formera indépendamment une image de ces boutons sur l'écran d'affichage TFT. Le jeu de commandes du contrôleur graphique FTDI comprend plus de 50 fonctions qui peuvent être utilisées pour afficher diverses images sur l'écran d'affichage avec divers effets.

Le manuel de programmation détaillé du contrôleur et des exemples de travail avec divers environnements de conception peuvent être trouvés dans les notes d'application sur le site Web FTDI .

En russe, une bonne description de la fonctionnalité, des principes généraux et des exemples de travail est ici .

Caractéristiques de Gameduino 2:
  • résolution d'écran de 480x272 pixels en couleur 24 bits;
  • un ensemble de commandes dans le style d'OpenGL;
  • jusqu'à 2000 sprites de toutes tailles;
  • 256 Ko de mémoire vidéo;
  • rotation et mise à l'échelle des sprites en douceur avec filtrage bilinéaire;
  • cercle lisse et motif linéaire dans le matériel - lissage 16x;
  • Décodage matériel JPEG;
  • Rendu intégré des dégradés, du texte, des cadrans et des boutons.

Le son est émis via une prise casque renforcée.
Le système prend en charge la sélection d'échantillons et d'outils intégrés.

La ROM du contrôleur est déjà câblée:

  • polices de haute qualité (6 tailles);
  • échantillons de 8 instruments de musique joués par une note MIDI;
  • échantillons de 10 sons de percussion.

Et, bien sûr, vous pouvez charger vos propres polices et extraits sonores dans 256 Ko de RAM.

L'utilisation de la plate-forme Arduino n'est pas une condition préalable: la carte Gameduino 2 peut être connectée à n'importe quel microcontrôleur ou carte de microcontrôleur avec une interface SPI.

En 2017, la troisième version de la carte a été publiée - Gameduino 3, qui ressemble presque à Gameduino 2. Au lieu de FT800, le nouveau contrôleur graphique FT810 est utilisé, qui a une compatibilité logicielle descendante avec FT800 (c'est-à-dire que tout le code pour Gameduino2 fonctionne sur Gameduino3). mais en même temps, il dispose de capacités de calcul 4 fois supérieures, telles que le décodage matériel JPEG plus rapide, le décodage vidéo, jusqu'à 1 Mo de RAM, etc.

Caractéristiques Gameduino 3:
  • décodeur vidéo pour vidéo plein écran à 30 ips;
  • 1 mégaoctet de RAM interne;
  • connecteurs pour cartes microSD et sortie audio;
  • Écran LCD 4,3 ”480x272 à contraste élevé avec écran tactile résistif;
  • prise en charge des cartes créées à l'aide de l'éditeur de cartes en mosaïque;
  • Téléchargez l'image PNG à partir de microSD;
  • décodage JPEG accéléré;
  • commutation matérielle portrait / paysage;
  • prise en charge d'Arduino, ESP8266 et Teensy 3.2;
  • des outils en ligne pour préparer des graphiques, des fichiers audio, des polices et des vidéos;


James Bowman a publié une bibliothèque pour son projet avec de nombreux exemples qui fonctionnent tout de suite. La bibliothèque actuelle que j'ai réussi à trouver est ici . Guide de programmation (en anglais), où tout est décrit en détail. Beaucoup d'informations utiles sur l'installation d'un IDE, etc., etc.

Programmation


Une fois, errant dans les étendues du Théâtre Internet du Bolchoï , je suis tombé sur un projet intéressant pour Arduino - le jeu logique "Colonnes" , écrit sous le traditionnel écran couleur chinois 128x160 pixels. Je voulais répéter ce jeu, mais sur ma carte mère, je l'appellerai FT810 (du nom du GPU), qui était déjà entre mes mains. J'ai déjà réussi à étudier le manuel de programmation et les exemples de la bibliothèque, donc mes mains ont juste «démangé» du désir d'écrire quelque chose de leur côté. Ce que j'ai immédiatement commencé.

La première chose que je devais faire était d'afficher le texte à l'écran.

Grâce à la présence de polices intégrées, la sortie de texte à l'écran est assez facile. Voici un croquis de démonstration de la bibliothèque (avec mes commentaires):

Croquis 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(); //         (   ). } 
En conséquence, nous obtenons ici un si beau texte:
image

Ensuite, il a fallu dessiner des formes géométriques, par exemple: des lignes.
Pour tracer des lignes, vous devez utiliser Begin (LINES) ou Begin (LINE_STRIP).
LINES joint chaque paire de sommets, tandis que LINE_STRIP joint tous les sommets ensemble.

Je vais donner le croquis de démonstration suivant de la bibliothèque (avec mes commentaires):

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

Lignes à l'écran:

image

Du dessin au trait, passons au dessin de rectangles.

Pour dessiner des rectangles, utilisez Begin (RECTS) et définissez les coins opposés du rectangle. L'ordre des deux angles n'a pas d'importance. Les rectangles sont dessinés avec des coins arrondis, en utilisant la largeur de la ligne actuelle comme rayon du coin. Les coins arrondis s'étendent au-delà des bordures du rectangle, donc l'augmentation du rayon de l'angle entraîne une augmentation du nombre de pixels. Cet exemple dessine trois fois un rectangle 420 × 20 avec un rayon d'angle croissant.

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

Résultat:
image

Passons maintenant au dessin d'un cercle - la base des futurs boutons tactiles. Revenons au premier exemple avec du texte et ajoutons quelques lignes à loop ().

Croquis avec dessin de cercles colorés
 #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(); //         (   ) } 

Résultat:



Pour travailler avec les boutons tactiles, il est nécessaire d'organiser le traitement de la pression sur l'écran tactile. Cela se fait comme suit. Je vais expliquer brièvement le principe. Chaque pixel (point) sur l'écran a une couleur. Il a également une valeur de balise invisible, qui peut être affectée à un point (ou à un objet entier tel qu'une ligne, un cercle, un rectangle, etc.) et utilisée en outre pour détecter les contacts de cet objet. L'esquisse suivante montre un exemple de définition de la valeur d'étiquette pour les cercles colorés sur 100 et 101.

Croquis d'écran tactile
 #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); //       “”  } 

Maintenant, lorsque le système détecte une touche sur un cercle, il rend compte de son code de capteur, dans ce cas 100 ou 101. Lorsque vous cliquez sur des objets (cercles colorés), les valeurs des étiquettes correspondant aux objets pressés seront affichées à l'écran dans la fenêtre du port série:



J'ai donné des exemples d'opérations de base, à l'aide desquelles il était déjà possible de procéder en toute sécurité à la création du jeu. Bien sûr, le jeu n'a pas été créé à partir de zéro, mais la base était un code de travail prêt à l'emploi qui a été adapté (tout en préservant les graphismes), de sorte que la version originale du jeu était très similaire à l'original.

La première version du jeu:



Après avoir joué pendant plusieurs jours, je voulais changer quelque chose dans la conception, par exemple, pour en ajouter un autre, inhabituel, au lieu d'un fond blanc. Et puis je me suis souvenu d'un exemple de la bibliothèque, dans lequel le ciel étoilé brillait en arrière-plan:

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

Vue:



Afin de ne pas plonger dans la nature, je n'ai pas commenté tout le code, mais seulement les lignes de code nécessaires que j'ai copiées dans mon croquis de travail.

Pour ajouter une image du ciel étoilé comme arrière-plan, j'ai dû faire ce qui suit: tout d'abord, changez la couleur noire des lignes et du texte en blanc (afin qu'ils soient visibles sur un fond noir), écrivez le fichier slotgag.gd2 sur la carte micro SD, qui stocke l'image ajoutez slotgag_assets.h au dossier du projet et ajoutez les 8 lignes de code nécessaires à l'esquisse.

En conséquence, le jeu a acquis cette forme:



Et bien sûr, quel genre de jeu est sans design sonore? Reste à ajouter des effets sonores, d'autant plus qu'ils sont présentés de bonne qualité et variés.

Gameduino 2/3 a deux systèmes de son. Le premier - un synthétiseur - peut générer un ensemble de sons fixes et de notes de musique. Un synthétiseur est utile pour ajouter rapidement du son à un projet, mais comme l'ensemble des sons est fixe, il n'est pas très flexible. Le second est la reproduction d'échantillons. Il reproduit le son échantillonné de la mémoire principale dans différents formats. Ce système est beaucoup plus flexible, mais vous devrez préparer et charger des échantillons dans la RAM.

J'ai utilisé un synthétiseur sonore fixe. Le synthétiseur fournit plusieurs sons courts de «percussion», principalement pour une utilisation dans les interfaces utilisateur. Pour jouer un son, vous devez appeler GD.play () avec un identifiant de son. Liste complète des sons disponibles:

Cliquez sur
COMMUTATEUR
Cloche
NOTCH
Hihat
Kickdrum
Pop
Clack
CHACK

Résumé


Le résultat a été une telle esquisse:

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

Je crois que j'ai terminé la tâche de créer mon premier croquis de travail pour ce conseil. J'espère qu'au moins une personne était intéressée à lire mon histoire. Les critiques et commentaires sont les bienvenus. Les plans ne s'arrêtent pas, ne progressent pas et, bien sûr, partagent l'expérience, les connaissances.

Pour démontrer le fonctionnement de la carte, je poste une vidéo avec son (Attention! Son fort!).
Merci de votre attention.

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


All Articles