Cet article montre comment vous pouvez réaliser le célèbre jeu Tetris avec un microcontrôleur et un écran tactile. Le boîtier mural AZ-Touch avec un écran de 2,8 pouces ou le boîtier mural AZ-Touch MOD avec un écran de 2,4 pouces sont utilisés comme base. Un Dev-Kit C ESP32 ou un D1-Mini peut être utilisé comme microcontrôleur. Après avoir soudé les connecteurs femelles pour l'affichage et le contrôleur du côté des composants, l'affichage peut être monté. Conseil : ne soudez que les connecteurs femelles (également appelés connecteurs Caterpillar) qui sont nécessaires pour les MCU respectives.
Si le D1-Mini doit être utilisé comme contrôleur, il ne doit pas être équipé des simples en-têtes de broches, car les broches ne tiennent pas bien dans la prise. Il est préférable d'équiper le D1-Mini avec les en-têtes combinés femelle/mâle, car ces broches sont plus longues.
Si vous avez soudé les bandes Caterpillar pour les deux MCU, les broches de la bande ESP32 qui se trouvent sous le D1 mini doivent être recouvertes de ruban isolant afin qu'il n'y ait pas de connexion conductrice avec le boîtier de la prise USB du D1 mini. Voir image.
Matériel requis
numéro |
Composant |
annotation |
---|---|---|
1 |
|
|
ou 1 |
|
|
1 |
|
|
ou 1 |
|
Structure du programme
Les règles du jeu Tetris sont censées être connues. Dans cette mise en œuvre, sept parties différentes sont utilisées. Avec les versions tournées, cela donne 19 parties différentes.
Chaque partie est stockée comme une constante dans une matrice de 4 x 4 = 16 octets. Les sept différentes parties ont des couleurs différentes. Dans la matrice, les blocs inoccupés sont remplis avec 0, les blocs occupés avec l'index de la table des couleurs.
// couleurs pour les blocs
const uint16_t colorBlock [8] = {ILI9341_BLACK, ILI9341_YELLOW, ILI9341_RED, ILI9341_CYAN, ILI9341_GREEN, ILI9341_PURPLE, ILI9341_BLUE, ILI9341_ORANGE};
// motif de bits pour les pièces
// 0 = bloc non défini> 0 index de la couleur du bloc
pièce const uint8_t [20] [16] = {
{0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0 , 0,
0, 0, 0, 0},
{0, 0, 0, 0,
0, 0, 0, 0,
1, 1, 0 , 0,
1, 1, 0, 0},
{0, 2, 0, 0,
< / tr>
Le plateau de jeu lui-même comporte 16 lignes et 12 colonnes. L'indexation des colonnes est de gauche à droite, celle des lignes de bas en haut.
Dans ce qui suit, le déroulement du programme est montré pour une meilleure compréhension. Un jeu est lancé en supprimant le score et le terrain de jeu. Ensuite, une nouvelle pièce est insérée en haut. Le jeu se termine lorsqu'aucune nouvelle partie ne peut être insérée. L'organigramme montre le débit de la boucle principale.
Les déplacements vers la droite et la gauche ainsi que la rotation sont déclenchés de manière asynchrone par des événements tactiles. Après avoir vérifié si le changement est possible, il est exécuté. Si le changement n'est pas possible, tout reste inchangé.
Pour chaque nouvelle partie ajoutée, la note est augmentée de 4. Si une ligne complète peut être supprimée, le score est augmenté de 10. En fonction du score, le niveau et la vitesse de chute sont augmentés.
<1000
2
0,7 s / ligne
Score |
Niveau |
Vitesse de chute |
---|---|---|
<100 </ td> |
1 |
0,9 s / ligne |
<10 000 |
3 |
0,5 s / ligne |
<100 000 |
4 |
0,3 s / line </ p> |
De 100 000 |
5 |
0,1 s / ligne |
Le programme
En plus du paquet ESP32 ou ESP8266, les bibliothèques suivantes sont nécessaires :
- Adafruit ILI9341
- Bibliothèque Adafruit GFX
- XPT2046_Touchscreen
- TouchEvent </ li>
Le programme fait automatiquement la distinction entre ESP32 et D1-Mini et utilise les brochages correspondants. Cette version est pour l'affichage 2,8 pouces. Pour l'affichage de 2,4 pouces, la ligne
#define TOUCH_ROTATION = 3
doit être remplacée par
#define TOUCH_ROTATION = 1
.
# include < Adafruit_GFX . h > // Grafik Bibliothek # include < Adafruit_ILI9341 . h > // Afficher Treiber # include < XPT2046_Ecran tactile . h > // Treiber à écran tactile # include < TouchEvent . h > // Auswertung von Touchscreen Ereignissen // Aussehen # define FOND ILI9341_GREENYELLOW // Farbe des Rahmens # define TOPMARGIN 20 // Rand oben # define LEFTMARGIN 12 // Liens aléatoires et rechts # define COLONNES 12 // Anzahl der Spalten # define RANGÉES 16 // Anzahl der Zeilen # define BLOCKSIZE 18 // Blocs Größe eines en Pixel # define NOPIECE ILI9341_BLACK // Farb für das leere Spielfeld # define ALLON ILI9341_DARKGREY // Farbe für alle Blöcke ein # define BORDURE ILI9341_WHITE // Farbe für den Blockrand // Unterschiedliche Pin-Belegung für ESP32 und D1Mini # ifdef ESP32 # define TFT_CS 5 # define TFT_DC 4 # define TFT_RST 22 # define TFT_LED 15 # define TOUCH_CS 14 # define LED_ON 0 # endif # ifdef ESP8266 # define TFT_CS D1 # define TFT_DC D2 # define TFT_RST - 1 # define TFT_LED D8 # define TOUCH_CS 0 # define LED_ON 1 # endif # define TOUCH_ROTATION 3 // muss für 2.4 Zoll Display 1 und für 2.8 Zoll Display 3 sein // Instanzen der Bibliotheken Adafruit_ILI9341 tft = Adafruit_ILI9341 ( TFT_CS , TFT_DC , TFT_RST ) ; XPT2046_Ecran tactile touchez ( TOUCH_CS ) ; TouchEvent tevent ( touchez ) ; // Farben für die Blöcke const uint16_t colorBlock [ 8 ] = { ILI9341_BLACK , ILI9341_YELLOW , ILI9341_RED , ILI9341_CYAN , ILI9341_GREEN , ILI9341_PURPLE , ILI9341_BLUE , ILI9341_ORANGE }; // Bitmuster für die Teile // 0 = Block nicht gesetzt> 0 Index der Farbe für den Block const uint8_t pièce[20][16] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0}, {0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2}, {0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 3, 3, 0}, {0, 0, 0, 0, 0, 3, 0, 0, 3, 3, 0, 0, 3, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 4, 0, 0}, {0, 0, 0, 0, 4, 0, 0, 0, 4, 4, 0, 0, 0, 4, 0, 0}, {0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 5, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 5, 5, 5, 0}, {0, 0, 0, 0, 5, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 0, 5, 0, 0, 0}, {0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 6, 6, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 0, 0, 0, 6, 0}, {0, 0, 0, 0, 6, 6, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 6, 6, 6, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 7, 7, 7, 0}, {0, 0, 0, 0, 0, 7, 0, 0, 7, 7, 0, 0, 0, 7, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 7, 0, 0}, {0, 0, 0, 0, 0, 7, 0, 0, 0, 7, 7, 0, 0, 7, 0, 0} }; // Speicherplatz für das Spielfeld // 0 bedeutet Block frei> 0 Index der Farbe des belegten Blocks uint8_t terrain de jeux[LIGNES][COLONNES]; // Globale variablen uint8_t curPiece; // aktuelles Tetris Teil int8_t curCol; // aktuelle Spalte int8_t curRow; // aktuelle Zeile uint32_t But; // Score Aktueller uint8_t niveau; // Niveau aktueller uint16_t intervalle; // aktuelles Zeitintervall für die Abwärtsbewegung uint32_t dernier; // letzter Zeitstempel // Fuktion zeigt in der Kopfleiste den aktuellen Score und den Level an // Abhängig vom Score wird der Level hinaufgesetzt und das Intervall verringert annuler displayScore() { si (But < 10) {niveau = 1; intervalle = 900;} autre si (But < 100) {niveau = 2; intervalle = 700;} autre si (But < 1000) {niveau = 3; intervalle = 500;} autre si (But < 10000) {niveau = 4; intervalle = 300;} autre si (But < 100000) {niveau = 5; intervalle = 100;} tft.fillRect(0,0,240,20,CONTEXTE); tft.setTextSize(2); tft.setTextColor(ILI9341_BLACK); tft.setCursor(5,4); carboniser buf[50]; sprintf(buf,«SC:% 8i LV:% i»,But,niveau); tft.impression(buf); } // Funktion um ein Tetris-Teil zu drehen. Der Parameter ist die Nummer des // Teils das gedreht werden soll. Rückgabewert ist der Index des vgerehten // Teils uint8_t tourner(uint8_t pc) { uint8_t res = 0; changer (pc) { Cas 1: res = 1; Pause; Cas 2: res = 3; Pause; Cas 3: res = 2; Pause; Cas 4: res = 5; Pause; Cas 5: res = 4; Pause; Cas 6: res = 7; Pause; Cas 7: res = 6; Pause; Cas 8: res = 9; Pause; Cas 9: res = 10; Pause; Cas 10: res = 11; Pause; Cas 11: res = 8; Pause; Cas 12: res = 13; Pause; Cas 13: res = 14; Pause; Cas 14: res = 15; Pause; Cas 15: res = 12; Pause; Cas 16: res = 17; Pause; Cas 17: res = 18; Pause; Cas 18: res = 19; Pause; Cas 19: res = 16; Pause; } revenir res; } // Fonction teste si une ligne est pleine booléen rowComplete(int8_t rpg) { si ((rpg >= 0) && (rpg < LIGNES)) { booléen res = vrai; uint8_t c = 0; // si un bloc n'est pas utilisé (couleur 0), // la ligne n'est pas complète tandis que (res && (c < COLONNES)) { si (terrain de jeux[rpg][c] == 0) res = faux; c++; } revenir res; } } // Fonction vérifie s'il y a entre la ligne rpc de la pièce Tetris pc et // la ligne rpg du terrain de jeu à partir de la position cpg donne des collisions. // Lorsqu'une collision se produit ou la dernière ligne du terrain de jeu // a été atteint est renvoyé de manière incorrecte booléen checkRow(uint8_t pc, int8_t rpc, int8_t cpg, int8_t rpg) { booléen res = vrai; si (rpg >= LIGNES) revenir faux; si (rpg < 0) revenir vrai; pour (uint8_t je = 0; je<4; je++) { si (pièce[pc][rpc*4 + je]>0) { si (((cpg+je) < 0) || ((cpg+je) >= COLONNES)) { res = faux; }autre { si (terrain de jeux[rpg][cpg+je] > 0) res = faux; } } } revenir res; } // Fonction vérifie si la partie Tetris PC sur le terrain de jeu à la position // ligne rpg colonne cpg (coin inférieur gauche de la pièce) des collisions se produisent booléen checkPièce(uint8_t pc, int8_t cpg, int8_t rpg) { booléen res = vrai; uint8_t rpc = 0; tandis que (res && (rpc < 4)) { res = checkRow(pc,rpc,cpg,rpc+rpg-3); // Serial.printf ("vérifier% i =% i \ n", rpc + rpg-3, res); rpc++; } revenir res; } // La fonction montre un bloc du terrain de jeu dans la ligne y colonne x avec la couleur de la couleur // la couleur est la couleur au format 565 pour l'affichage annuler showBlock(uint8_t X, uint8_t y, uint16_t Couleur) { tft.fillRect(MARGE DE GAUCHE+X*TAILLE DE BLOC+2,MARGE SUPÉRIEURE+y*TAILLE DE BLOC+2,TAILLE DE BLOC-4,TAILLE DE BLOC-4,Couleur); tft.drawRect(MARGE DE GAUCHE+X*TAILLE DE BLOC+1,MARGE SUPÉRIEURE+y*TAILLE DE BLOC+1,TAILLE DE BLOC-2,TAILLE DE BLOC-2,FRONTIÈRE); } // La fonction remplit un bloc du terrain de jeu dans la ligne y colonne x avec la couleur d'arrière-plan annuler hideBlock(uint8_t X, uint8_t y) { tft.fillRect(MARGE DE GAUCHE+X*TAILLE DE BLOC,MARGE SUPÉRIEURE+y*TAILLE DE BLOC,TAILLE DE BLOC,TAILLE DE BLOC,NOPIÈCE); } // Fonction affiche la partie Tetris pc dans la ligne rpg, colonne cpg (coin inférieur gauche) // La couleur est tirée de la définition de la pièce Tetris annuler joyau(uint8_t pc, uint8_t cpg, uint8_t rpg) { uint8_t Couleur; pour (uint8_t r = 0; r<4; r++) { pour (uint8_t c = 0; c<4; c++) { Couleur = pièce[pc][r*4+c]; si ((Couleur > 0) && ((3-r+rpg) >= 0)) showBlock(cpg+c,rpg-3+r,bloc de couleur[Couleur]); } } } // La fonction remplit les blocs occupés de la partie Tetris pc en ligne rpg, // colonne cpg (coin inférieur gauche) avec couleur d'arrière-plan annuler cacherPièce(uint8_t pc, int8_t cpg, int8_t rpg) { uint8_t Couleur; pour (uint8_t r = 0; r<4; r++) { pour (uint8_t c = 0; c<4; c++) { Couleur = pièce[pc][r*4+c]; si ((Couleur > 0) && ((3-r+rpg) >= 0)) hideBlock(cpg+c,rpg-3+r); } } } // La fonction remplit la ligne de ligne du terrain de jeu avec une couleur d'arrière-plan et // supprime toutes les entrées de cette ligne dans la mémoire de champ annuler Supprimer la ligne(int8_t rangée) { tft.fillRect(MARGE DE GAUCHE,MARGE SUPÉRIEURE+rangée*TAILLE DE BLOC,COLONNES * TAILLE DE BLOC,TAILLE DE BLOC,NOPIÈCE); pour (uint8_t je =0; je<COLONNES; je++) terrain de jeux[rangée][je]=0; } // Fonction copie la ligne srcrow sur la ligne dstrow // L'affichage de la ligne cible est préalablement supprimé. Au // copier la ligne source s'affiche dans la ligne cible annuler copyRow(int8_t srcrow, int8_t dstrow) { uint8_t col; Supprimer la ligne(dstrow); si ((srcrow < dstrow) && (srcrow >=0) && (dstrow < LIGNES)) { pour (uint8_t c = 0; c < COLONNES; c++) { col = terrain de jeux[srcrow][c]; terrain de jeux[dstrow][c] = col; si (col > 0) showBlock(c,dstrow,bloc de couleur[col]); } } } // Fonction affiche tous les blocs du terrain de jeu avec la couleur ALLON. // Après une pause de 500 ms, le Sielfeld est complètement supprimé annuler clearBoard() { pour (uint8_t X = 0; X<COLONNES; X++) { pour (uint8_t y = 0; y<LIGNES; y++) { showBlock(X,y,ALLON); } } retard(500); pour (uint8_t je = 0; je<LIGNES; je++) { Supprimer la ligne(je); } } // Fonction transfère la pièce Tetris pc vers la mémoire de champ de la ligne // rpg dans la colonne cpg (coin inférieur gauche) annuler mettrePièce(uint8_t pc, int8_t cpg, int8_t rpg) { uint8_t Couleur; pour (uint8_t r = 0; r<4; r++) { pour (uint8_t c = 0; c<4; c++) { Couleur = pièce[pc][r*4+c]; si ((Couleur > 0) && ((3-r+rpg) >= 0)) terrain de jeux[rpg-3+r][cpg+c] = Couleur; } } } // Un nouveau morceau de tetris est inséré en haut du terrain de jeu. // Quelle partie et dans quelle colonne est déterminée comme un nombre aléatoire // Si la nouvelle partie n'a pas sa place sur le terrain de jeu, le jeu est terminé booléen nouveauPièce() { uint8_t pc = Aléatoire(1,20); uint8_t cpg = Aléatoire(0,COLONNES-4); booléen res = checkPièce(pc,cpg,3); curPiece=0; si (res) { curPiece = pc; curCol = cpg; curRow = 0; joyau(pc,cpg,0); But += 4; displayScore(); } autre { tft.setTextSize(3); tft.setCursor(MARGE DE GAUCHE+COLONNES*TAILLE DE BLOC/2-79,MARGE SUPÉRIEURE+LIGNES*TAILLE DE BLOC/2-10); tft.setTextColor(ILI9341_BLACK); tft.impression("JEU TERMINÉ"); tft.setCursor(MARGE DE GAUCHE+COLONNES*TAILLE DE BLOC/2-81,MARGE SUPÉRIEURE+LIGNES*TAILLE DE BLOC/2-12); tft.setTextColor(ILI9341_YELLOW); tft.impression("JEU TERMINÉ"); } } // La fonction détermine les lignes complètement remplies sur le terrain de jeu et les supprime // Les lignes ci-dessus sont déplacées vers le bas annuler removeComplete() { uint8_t s=LIGNES-1; int8_t ré= LIGNES-1; tandis que (ré >= 0) { si (rowComplete(ré)) { s--; But += 10; copyRow(s,ré); } autre { si ((s < ré) && (s >=0)) { En série.printf("copier% i vers% i \ n",s, ré); copyRow(s,ré); } s--; ré--; } } displayScore(); } // La fonction démarre un nouveau jeu. Le score est fixé à 0, le terrain de jeu // supprimé et démarré avec une nouvelle pièce Tetris annuler nouveau jeu() { But=0; displayScore(); clearBoard(); nouveauPièce(); } // Fonction de rappel pour le clic d'événement de l'écran tactile // Cette fonction est appelée à chaque fois que l'écran // est brièvement touché. p indique la position du point de contact annuler sur clic(TS_Point p) { si (p.y < 80) { // Cliquez dans le quart supérieur de l'affichage nouveau jeu(); } autre si (p.y > 240) { // Cliquez dans le quartier le plus bas uint8_t pc = curPiece; int8_t c = curCol; si (p.X < 80) { // Cliquez dans le tiers gauche -> faites glisser vers la gauche c--; } autre si (p.X <160) { // cliquez dans le tiers central -> tournez pc = tourner(pc); } autre { // cliquez dans le tiers droit -> faites glisser vers la droite c++; } // après changement de position, une collision est vérifiée // seulement si aucune collision ne se produit, le mouvement // réalisé si (checkPièce(pc,c,curRow)) { cacherPièce(curPiece,curCol,curRow); curPiece = pc; curCol = c; joyau(curPiece,curCol,curRow); } } } //Préparation annuler installer() { En série.commencer(115200); // allume le rétroéclairage pinMode(TFT_LED,PRODUCTION); digitalWrite(TFT_LED, CONDUIT SUR); En série.println("Commencer"); // initialise l'affichage tft.commencer(); tft.fillScreen(CONTEXTE); // Préparez l'écran tactile toucher.commencer(); toucher.setRotation(TOUCH_ROTATION); un événement.setRésolution(tft.largeur(),tft.la taille()); un événement.setDrawMode(faux); // Enregistrer la fonction de rappel un événement.registerOnTouchClick(sur clic); //tft.fillRect(LEFTMARGIN,TOPMARGIN,COLUMNS*BLOCKSIZE,ROWS*BLOCKSIZE,NOPIECE); clearBoard(); // newPiece (); // Rappelez-vous l'heure de début et supprimez le terrain de jeu charge = millis(); But=0; displayScore(); } // boucle principale annuler boucle() { // Vérifier les événements tactiles un événement.pollTouchScreen(); // Chaque fois que l'intervalle de temps est atteint, la partie Tetris actuelle devient // Déplacé d'une ligne vers le bas si cela est possible. // S'il y a collision ou si le bord inférieur est atteint, // pour que la pièce ne soit pas déplacée mais ancrée sur le terrain de jeu. // Les lignes complètes sont supprimées et une nouvelle partie en haut // marge insérée si ((curPiece > 0) && ((millis()-charge) > intervalle)) { charge = millis(); si (checkPièce(curPiece,curCol,curRow+1)) { cacherPièce(curPiece,curCol,curRow); curRow++; joyau(curPiece,curCol,curRow); } autre { mettrePièce(curPiece,curCol,curRow); removeComplete(); nouveauPièce(); } } }
Amusez-vous bien à jouer.
4 commentaires
Bernhard
Bei mir hat das Übertragen problemlos funktioniert. Display + Touch funktionieren. Wenn ich allerdings das Spiel starte kommt es zu einer Exception/Reboot. Ich kann keinen Fehler finden – weder in Code noch in meinem Setup :-(
Woran könnte es liegen?
ArduinoTouch 01-03 mit ESP8266, 2.4Zoll TFT
Gerald
Hallo TimQ,
die meisten Beispiele auch die im Smarthome-Buch arbeiten mit der AZ-Touch Version mit 2.4 Zoll Display und der alten Version der Platine, bei der ein Jumper zum Hochladen angeschlossen werden musste. Bei der neuen Platine wurde die Interruptleitung für den Touchscreen auf Pin27 geändert. Hier die notwendigen Änderungen damit die Smarthome-Zentrale aus dem Smarthome Buch funktioniert.
Das neue AZ-Touch MOD hat den Interrupt für den Touchscreen nicht mehr an Pin 2 sondern an Pin27.
Die entsprechende Definition am Anfang der Smarthome Sketches muss daher
#define TOUCH_IRQ 27
lauten.
Bei der Verwendung des 2.8 Zoll Displays muss außerdem in der Setup-Funktion nach
touch.begin();
touch.setRotation(3);
eingefügt werden.
Birger T
Dieses Projekt sollte der Beginn meiner Beschäftigung mit den ESP32 und ESP8266 aus einem AZ-Delivery Überraschungspaket sein. Nach dem Studium der diversen E-books habe ich auch irgendwo den Hinweis gefunden, wie weitere Bordverwalter URLs in die Arduino IDE zu ergänzen sind und somit die “Boards” installiert werden können. Doch der erste Versuch mit dem Blink-Sketch funktionierte nicht: Kompilierung abgebrochen, weil das im ESP-Tool einzubindende “Serial” nicht vorhanden ist.
Ich arbeite unter Ubuntu 20.04 – und wollte hier denjenigen Ubuntu Usern, die ebenfalls Probleme mit der ESP Programmierung in der Arduino IDE das Problem mit dem “Serial” haben, einen/den Link zur Lösung mitteilen: https://koen.vervloesem.eu/blog/fixing-the-arduino-ide-for-the-esp32esp8266-on-ubuntu-2004/
TimQ
Das ist das erste Beispiel für den AZ-Touch MOD 01-03 und ESP32, das funktioniert. Super !
Alle anderen Versuche mit den Beispielen aus den Blocks, Smarthome Buch oder den zahlreichen Foren sind bei mir gescheitert. Entweder blieb der Screen weiß oder dunkel. Ich hoffe es gibt bald mal eine wirklich funktionierende Beschreibung zur Smarthome Zentrale. Danke