Erstellen eines Logikspiels für eine Spieleplattform

Guten Tag.

Ich möchte meine Bekanntschaftsgeschichte mit der Gameduino 3-Gaming-Plattform sowie ein kleines Gespräch über die Programmierung des einfachsten Logikspiels für diese Plattform, das in Verbindung mit Arduino Uno verwendet wird, teilen.

Was ist Gameduino 3? Gameduino 3 ist ein Erweiterungsboard, mit dem Sie das Arduino in eine moderne Handheld-Spielekonsole (dh Größe) verwandeln können. Zu meiner Überraschung konnte ich im Hub keine detaillierten Informationen zu diesem Board finden. Ich möchte diese Lücke schließen, zumal der Vorstand meiner Meinung nach Aufmerksamkeit verdient.

Ein bisschen Geschichte


Der Autor des Projekts namens Gameduino ist James Bowman, der 2011 die erste Version des Boards erstellt hat. Dann wurde es als VGA-Modul für Arduino positioniert. Das Board hieß Gameduino und basierte auf der Xilinx Spartan-3A-Familie programmierbarer Logik-FPGAs. Auf der Platine wurden Anschlüsse zum Anschließen eines VGA-Monitors und von Stereolautsprechern installiert. Bild

Gameduino-Funktionen (1):
  • VGA-Videoausgang mit einer Auflösung von 400 x 300 Pixel, 512 Farben;
  • Der gesamte Farbumfang wird im FPGA mit einer Genauigkeit von 15 Bit verarbeitet.

Hintergrundgrafiken:

  • Bereich der symbolischen Hintergrundgrafiken 512x512 Pixel;
  • 256 Zeichen mit jeweils einer unabhängigen 4-Farben-Palette;
  • implementierte den Effekt des Umschließens von Textzeichenfolgen mit Pixelglättung;

Vordergrundgrafiken:

  • Jedes Sprite hat eine Auflösung von 16 x 16 Pixel.
  • Jedes Sprite kann eine 256-, 16- oder 4-Farben-Palette haben.
  • Unterstützung für 4-Wege-Rotations- und Horizontalrotationsalgorithmen;
  • 96 Sprites pro Rasterlinie, 1536 Texturelemente pro Rasterlinie;
  • Mechanismus zum Erkennen möglicher Schnittpunkte von Sprites;

Audioausgabe:

  • 12-Bit-Zweikanal-Frequenzsynthesizer;
  • 64-stimmige Polyphonie im Frequenzbereich von 10 - 8000 Hz.

Das Bild wird auf dem Bildschirm eines Standard-VGA-Monitors mit einer Auflösung von 400 x 300 Pixel angezeigt.
Die Kompatibilität mit allen Standard-VGA-Monitoren mit einer Auflösung von 800 x 600 Pixel bleibt erhalten.

2013 wurde die zweite Version des Boards veröffentlicht - Gameduino 2, in dem es im Gegensatz zur Vorgängerversion bereits einen resistiven 4,3-Zoll-Touchscreen mit einer Auflösung von 480 x 272, einen 3-Achsen-Beschleunigungsmesser, einen microSD-Speicherkartensteckplatz und einen Audioausgang für Kopfhörer gab.

Bild

Das „Herz“ der Platine war der Grafikcontroller EVE (Embedded Video Engine - auf Russisch übersetzt als „Embedded Video Module“) FT800, der über leistungsstarke Rechenfunktionen verfügt und mehrere Funktionen gleichzeitig kombiniert: Bilderzeugung und Ausgabe auf dem Bildschirm des TFT-Displays, Touchscreen-Verarbeitung, Klangerzeugung.

Funktionsplan des FT800-Grafikcontrollers
Bild

Die folgenden Funktionsblöcke sind in der Mikroschaltungsstruktur enthalten: Grafikcontroller, Audio-Controller, resistiver Touchpanel-Controller. Der FT800-Chip steuert Displays mit einer Auflösung von bis zu 512 x 512 Pixel. Der FT800 unterstützt auch LCD WQVGA (480 x 272) und QVGA (320 x 240). Die EVE (Embedded Video Engine) FT800 ist eine schlüsselfertige Lösung zum Erstellen einer grafischen Benutzeroberfläche. Die Mikroschaltung erzeugt Anzeigesteuerungssignale und verfügt über integrierte Grafikfunktionen zum Anzeigen von Punkten, Linien, Bitmap-Bildern, volumetrischen Tasten, Texten usw.


Systemstruktur Basierend auf dem FT800 Graphics Controller
Die Bilderzeugung basiert auf einer Reihe von Befehlen (Anzeigeliste), die vom Steuerungsmikrocontroller über die I2C- oder SPI-Schnittstelle an den FT800 übertragen werden (in Gameduino 2 erfolgt die Kommunikation zwischen dem Arduino und dem FT800 über die SPI-Schnittstelle). Die FT800-Funktionen entlasten den Host-Controller des Systems erheblich.

Bild

Um beispielsweise eine Reihe von Tasten anzuzeigen, reicht es aus, einen Befehl an den Grafikcontroller zu übertragen (vier 32-Bit-Wörter), und der FT800 bildet unabhängig das Bild dieser Tasten auf dem Bildschirm des TFT-Displays. Der Befehlssatz des FTDI-Grafikcontrollers enthält mehr als 50 Funktionen, mit denen verschiedene Bilder mit verschiedenen Effekten auf dem Bildschirm angezeigt werden können.

Ausführliche Programmieranleitungen für Steuerungen und Beispiele für die Arbeit mit verschiedenen Entwurfsumgebungen finden Sie in den Anwendungshinweisen auf der FTDI- Website.

Auf Russisch finden Sie hier eine gute Beschreibung der Funktionalität, der allgemeinen Prinzipien und der Arbeitsbeispiele.

Eigenschaften von Gameduino 2:
  • Bildschirmauflösung von 480 x 272 Pixel in 24-Bit-Farbe;
  • eine Reihe von Befehlen im Stil von OpenGL;
  • bis zu 2000 Sprites jeder Größe;
  • 256 KB Videospeicher;
  • sanfte Sprite-Rotation und Skalierung mit bilinearer Filterung;
  • glatter Kreis und lineares Muster in Hardware - 16x Glättung;
  • JPEG-Hardware-Decodierung;
  • Integriertes Rendering von Verläufen, Text, Wählscheiben und Schaltflächen.

Der Ton wird über eine verstärkte Kopfhörerbuchse ausgegeben.
Das System unterstützt die Auswahl integrierter Beispiele und Werkzeuge.

Das Controller-ROM ist bereits verkabelt:

  • hochwertige Schriftarten (6 Größen);
  • Samples von 8 Musikinstrumenten, die von einer MIDI-Note gespielt werden;
  • Samples von 10 Percussion Sounds.

Und natürlich können Sie Ihre eigenen Schriftarten und Soundbits in 256 KB RAM laden.

Die Verwendung der Arduino-Plattform ist keine Voraussetzung: Die Gameduino 2-Karte kann mit einem Mikrocontroller oder einer Mikrocontroller-Karte mit einer SPI-Schnittstelle verbunden werden.

2017 wurde die dritte Version des Boards veröffentlicht - Gameduino 3, das fast identisch mit Gameduino 2 aussieht. Anstelle von FT800 wird der neue Grafikcontroller FT810 verwendet, der mit FT800 abwärtskompatibel ist (d. H. Der gesamte Code für Gameduino2 funktioniert auf Gameduino3). Gleichzeitig verfügt es über viermal mehr Rechenfunktionen, z. B. schnellere Hardware-JPEG-Decodierung, Videodecodierung, bis zu 1 MB RAM usw.

Eigenschaften Gameduino 3:
  • Videodecoder für 30-fps-Vollbildvideo;
  • 1 Megabyte interner RAM;
  • Anschlüsse für microSD-Karten und Audioausgang;
  • 4,3 ”480x272 kontrastreiches LCD-Panel mit resistivem Touchscreen;
  • Unterstützung für Karten, die mit dem Kachelkarten-Editor erstellt wurden;
  • PNG-Bild von microSD herunterladen;
  • beschleunigte JPEG-Decodierung;
  • Hardware-Hoch- / Querformat-Umschaltung;
  • Unterstützung für Arduino, ESP8266 und Teensy 3.2;
  • Online-Tools zur Vorbereitung von Grafiken, Audio, Schrift und Video;


James Bowman hat für sein Projekt eine Bibliothek mit vielen Beispielen veröffentlicht, die sofort funktionieren. Die aktuelle Bibliothek, die ich gefunden habe, ist hier . Programmieranleitung (auf Englisch), in der alles ausführlich beschrieben wird. Viele nützliche Informationen zum Installieren einer IDE usw. usw.

Programmierung


Als ich durch die Weiten des Bolschoi- Internet- Theaters wanderte, stieß ich auf ein interessantes Projekt für Arduino - das logische Spiel „Columns“ , das unter der üblichen preiswerten chinesischen Farbanzeige von 128 x 160 Pixel geschrieben wurde. Ich wollte dieses Spiel wiederholen, aber auf meinem Board werde ich es FT810 (mit dem Namen der GPU) nennen, das zu diesem Zeitpunkt bereits in meinen Händen war. Ich habe es bereits geschafft, das Programmierhandbuch und Beispiele aus der Bibliothek zu studieren, und meine Hände „juckten“ nur vor dem Wunsch, etwas Eigenes zu schreiben. Was ich sofort angefangen habe.

Als erstes musste ich den Text auf dem Bildschirm anzeigen.

Dank der integrierten Schriftarten ist die Textausgabe auf dem Bildschirm recht einfach. Hier ist eine Demo-Skizze aus der Bibliothek (mit meinen Kommentaren):

Skizze 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(); //         (   ). } 
Als Ergebnis erhalten wir hier einen so schönen Text:
Bild

Als nächstes mussten geometrische Formen gezeichnet werden, zum Beispiel: Linien.
Um Linien zu zeichnen, müssen Sie Begin (LINES) oder Begin (LINE_STRIP) verwenden.
LINES verbindet jedes Scheitelpunktpaar, während LINE_STRIP alle Scheitelpunkte miteinander verbindet.

Ich werde die folgende Demo-Skizze aus der Bibliothek geben (mit meinen Kommentaren):

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

Linien auf dem Bildschirm:

Bild

Fahren wir vom Strichzeichnen mit dem Zeichnen von Rechtecken fort.

Verwenden Sie zum Zeichnen von Rechtecken Begin (RECTS) und legen Sie die gegenüberliegenden Ecken des Rechtecks ​​fest. Die Reihenfolge der beiden Winkel spielt keine Rolle. Rechtecke werden mit abgerundeten Ecken gezeichnet, wobei die Breite der aktuellen Linie als Radius der Ecke verwendet wird. Abgerundete Ecken erstrecken sich über die Ränder des Rechtecks ​​hinaus. Wenn Sie also den Radius des Winkels vergrößern, erhöht sich die Anzahl der Pixel. In diesem Beispiel wird mit zunehmendem Eckenradius dreimal ein 420 × 20-Rechteck gezeichnet.

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

Ergebnis:
Bild

Fahren wir mit dem Zeichnen eines Kreises fort - der Grundlage für zukünftige Touch-Schaltflächen. Kehren wir zum ersten Beispiel mit Text zurück und fügen Sie loop () einige Zeilen hinzu.

Skizze mit farbigen Kreisen
 #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(); //         (   ) } 

Ergebnis:



Um mit Touch-Tasten arbeiten zu können, muss die Verarbeitung des Drückens des Touchscreens organisiert werden. Dies geschieht wie folgt. Ich werde das Prinzip kurz erläutern. Jedes Pixel (Punkt) auf dem Bildschirm hat eine Farbe. Es hat auch einen unsichtbaren Tag-Wert, der einem Punkt (oder einem ganzen Objekt wie Linie, Kreis, Rechteck usw.) zugewiesen und zum Erkennen von Berührungen dieses Objekts verwendet werden kann. Die folgende Skizze zeigt ein Beispiel für das Festlegen des Tag-Werts für farbige Kreise auf 100 und 101.

Touchscreen-Skizze
 #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); //       “”  } 

Wenn das System erkennt, dass ein Kreis berührt wird, meldet es seinen Sensorcode, in diesem Fall 100 oder 101. Wenn Sie im Fenster der seriellen Schnittstelle auf dem Bildschirm auf Objekte (farbige Kreise) klicken, werden die Werte der Tags angezeigt, die den gepressten Objekten entsprechen:



Ich gab Beispiele für grundlegende Operationen, mit denen es bereits möglich war, sicher mit der Erstellung des Spiels fortzufahren. Natürlich wurde das Spiel nicht von Grund auf neu erstellt, aber die Basis war ein vorgefertigter Arbeitscode, der angepasst wurde (unter Beibehaltung der Grafiken), sodass die Originalversion des Spiels dem Original sehr ähnlich war.

Die erste Version des Spiels:



Nachdem ich einige Tage gespielt hatte, wollte ich etwas am Design ändern, zum Beispiel einen anderen, ungewöhnlichen anstelle eines weißen Hintergrunds. Und dann erinnerte ich mich an ein Beispiel aus der Bibliothek, in dem der Sternenhimmel im Hintergrund blitzte:

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

Ansicht:



Um nicht in die Wildnis einzutauchen, habe ich nicht den gesamten Code kommentiert, sondern nur die erforderlichen Codezeilen, die ich in meine Arbeitsskizze kopiert habe.

Um ein Bild des Sternenhimmels als Hintergrund hinzuzufügen, musste ich Folgendes tun: Ändern Sie zunächst die schwarze Farbe der Linien und des Texts in Weiß (so dass sie auf einem schwarzen Hintergrund sichtbar sind), schreiben Sie die Datei slotgag.gd2 auf die Micro-SD-Karte, auf der das Bild gespeichert ist. Fügen Sie slotgag_assets.h zum Projektordner hinzu und fügen Sie der Skizze die erforderlichen 8 Codezeilen hinzu.

Als Ergebnis erhielt das Spiel diese Form:



Und was ist ein Spiel ohne Sounddesign? Es bleiben Soundeffekte hinzuzufügen, zumal sie in guter Qualität und Vielfalt präsentiert werden.

Gameduino 2/3 verfügt über zwei Soundsysteme. Der erste - ein Synthesizer - kann eine Reihe von festen Klängen und Noten erzeugen. Ein Synthesizer ist nützlich, um einem Projekt schnell Sound hinzuzufügen. Da der Soundsatz jedoch festgelegt ist, ist er nicht sehr flexibel. Die zweite ist die Probenreproduktion. Es gibt gesampelten Sound aus dem Hauptspeicher in verschiedenen Formaten wieder. Dieses System ist viel flexibler, aber Sie müssen Samples vorbereiten und in den RAM laden.

Ich habe einen festen Soundsynthesizer verwendet. Der Synthesizer bietet mehrere kurze Percussion-Sounds, hauptsächlich zur Verwendung in Benutzeroberflächen. Um einen Sound abzuspielen, müssen Sie GD.play () mit einer Soundkennung aufrufen. Vollständige Liste der verfügbaren Sounds:

Klicken Sie auf
SCHALTER
Kuhglocke
NOTCH
Hihat
Kickdrum
Pop
Klack
CHACK

Zusammenfassung


Das Ergebnis war eine solche Skizze:

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

Ich glaube, ich habe die Aufgabe abgeschlossen, meine erste Arbeitsskizze für dieses Board zu erstellen. Ich hoffe, dass mindestens eine Person daran interessiert war, meine Geschichte zu lesen. Kritik und Kommentare sind willkommen. Die Pläne hören nicht auf, gehen weiter und teilen natürlich Erfahrung und Wissen.

Um die Funktionsweise des Boards zu demonstrieren, poste ich ein Video mit Ton (Achtung! Lauter Ton!).
Vielen Dank für Ihre Aufmerksamkeit.

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


All Articles