Cyclone - das Spiel

Pendant la saison froide et humide, vous cherchez quelque chose à faire pour vous ou vos enfants ou vous voulez prouver vos talents de geek à vos copains. Sur Internet, je suis tombé sur le jeu Cyclone, qui s'appelle Tornade. Ce jeu nous a fascinés, mes enfants et moi. Mais je n'ai pas aimé toutes les variantes présentées, alors j'ai développé ma propre variante sans plus attendre.

Quel est le sujet du jeu Cyclone

Si vous regardez la vidéo, le principe est rapidement expliqué.  


Vous avez un anneau LED avec un point LED en cours d'exécution et un marqueur de cible LED. Le point de joueur traverse le ring à une vitesse définie et vous devez essayer d'appuyer sur un bouton dès que le point de joueur est congruent avec le marqueur cible. Contrairement à la vidéo, je n'ai pas créé de niveaux, mais un jeu continu qui se termine si la cible n'est pas atteinte une fois. En outre, un écran LCD intégré indique le meilleur score, le score actuel et le tour en cours. La vitesse est choisie au hasard pour chaque nouveau tour.

Le matériel

Pour le matériel dont vous avez besoin, vous n'avez besoin que de quelques pièces. Mais comme le code est écrit de telle manière que vous pouvez également remplacer l'anneau LED WS2812B d'Arduino par une bande WS2812B, deux listes de pièces sont conservées ici.

Pour la variante présentée dans ce blogpost, vous aurez besoin des éléments énumérés dans le tableau 1.

Article numéro Composant
1 1 Nano V3.0 avec Atmega328 CH340
ou
Nano V3.0 avec puce FT232RL et ATmega328
2 1 Anneau LED 5V RGB WS2812B 12 bits 50mm
3 1 Touche
4 1 Module LCD avec interface I2C
5 1 Breadboard et jumper
(Ici dans un ensemble avec adaptateur secteur)

Tableau 1 : Pièces de matériel pour Cyclone avec anneau LED WS2812B

Toutefois, si vous souhaitez utiliser une bande, vous avez besoin des composants du tableau 2. Les autres composants pour le montage de la bande dans un cadre ou similaire ne sont pas pris en compte ici.

Article numéro Composant
1 1 Nano V3.0 avec Atmega328 CH340
ou
Nano V3.0 avec puce FT232RL et ATmega328
2 1 Bande LED WS2812B
3 1 Touche
4 1 Module LCD avec interface I2C
5 1

Breadboard et jumper (Ici dans un ensemble avec adaptateur secteur)

6 1 Alimentation pour LED et nano

Tableau 2 : Pièces de matériel pour Cyclone avec bande WS2812B

Il convient de mentionner ici que plus votre bande WS2812B comporte de LED, plus l'alimentation électrique doit fournir de courant. Pour 60 LED, vous avez besoin d'un peu moins de 4A lorsque toutes les LED sont allumées.

Logiciels requis

Le logiciel requis pour ce projet est gérable :

La structure

Pour la configuration avec l'anneau WS2812B, les composants doivent être connectés ensemble comme indiqué dans la figure 1. Si vous disposez d'une bande WS2812B, vous devez connecter correctement l'alimentation électrique et le connecteur Data-IN.

 

Figure 1 : Câblage des différents composants

Pour les deux variantes du WS2812B, le rouge est la phase (5 volts), le noir est la masse (GND) et le gris est le connecteur Data-IN. Dans la plupart des cas, vous avez quatre connexions sur les bandes WS2812B, vous devez donc vérifier à l'avance quelle est la bonne broche Data-IN (DI). La broche DO (=Data OUT) est destinée à la connexion optionnelle d'autres modules WS2812B (connexion en série).

Le code source

Vous pouvez soit copier le code ici à partir du blog, voir le code 1, soit le télécharger à partir du dépôt Github de l'auteur.

//-----------------------------------------------------
// Jeu "CYCLONE" pour Arduino
// Auteur: Joern Weise
// Licence: GNU GPl 3.0
// Créé: 20 sept. 2020
// Mise à jour: 25 septembre 2020
//-----------------------------------------------------
// Inclure les bibliothèques
#include
#include
#include
#include

// Définit
#define NUMPIXELS 12 // Taille de l'anneau NeoPixel populaire ou modifiez le nombre de LED
#define PIN 2 // Data-Pin pour sonner ou supprimer
#define PINBTN 6 // Pin pour le bouton du joueur
#define PINSCORERST 9 // Épingle pour réinitialiser le score lors de la première exécution

#define DISABLEWINDOW 3 // Arrondit avant que la LED avant et après la cible ne soit plus valide

// La vitesse Player-Dot définit
#define STARTINTERVAL 250 // Déplacement "normal"
#define MAXINTERVAL 500 // Mouvement très lent
#define MININTERVAL 50 // Déplacement très rapide

// Créer des objets
LCD LiquidCrystal_I2C (0x27,16,2); // définir l'adresse LCD
Adafruit_NeoPixel pixels (NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); // Objet Init NeoPixel

booléen bFirstRun, bSecureWindow;
int iState = 1;
int iTargetPos, iPlayerPos, iStoredHighscore, iRound, iScore, iInterval; // Vars pour le jeu
int iLastButtonPressed, iButtonState, iDebounceButton; // Bouton Vars pour anti-rebond
unsigned long iLastPlayerMove, ulLastDebounceTime; // Bouton de minuterie de débouce
non signé long ulDebounceButton = 10; // Temps de réponse

void setup() {
Serial.begin (115200);
Serial.println ("Init la communication série: DONE");

// Commencez init pour WS218B-ring ou -strip
pixels.begin (); // INITIALISER l'objet de bande NeoPixel (OBLIGATOIRE)
pixels.clear (); // Définit tous les pixels sur "off"
pixels.setBrightness (20); // Règle la luminosité sur 20%
pixels.show (); // Envoie les couleurs de pixels mises à jour au matériel.
Serial.println ("Init WS218B-ring: DONE");

// Commencer l'affichage d'initialisation
lcd.init ();
LCD rétro-éclairage();
lcd.clear ();
Serial.println ("Affichage LCD Init: DONE");

randomSeed (analogRead (0)); // Rendre Randome plus Randome
Serial.println ("Rendre plus aléatoire: DONE");

// Lire le dernier highscore de l'EEPROM
iStoredHighscore = EEPROM.read (0);
Serial.println ("Dernier meilleur score stocké:" + String (iStoredHighscore));

// Bouton Init avec résistance pullup interne
pinMode (PINBTN, INPUT_PULLUP); // GameBTN
pinMode (PINSCORERST, INPUT_PULLUP); // Reset-Pin pour le score

// Initier quelques variables de base
bFirstRun = vrai; // Activer firstrun
iLastButtonPressed = digitalRead (PINBTN); // Init iLastButtonPressed
iButtonState = digitalRead (PINBTN); // Init iButtonstate
}

boucle void () {
int iDebounceButton = DebounceButton (); // Bouton Vérifier et anti-rebond

si (! bFirstRun)
  {
if (iState == 1) // Startscreen
    {
bSecureWindow = vrai;
iRound = 1;
iScore = 0;
iInterval = STARTINTERVAL;
lcd.clear ();
lcd.home ();
lcd.print ("Highscore:" + String (iStoredHighscore));
lcd.setCursor (0,1);
lcd.print ("Appuyez sur le bouton ...");
iState = 2;
    }
if (iState == 2) // Obtenir le bouton enfoncé
    {
si (iDebounceButton == LOW)
      {
if (iRound == 1) // Ne s'affiche qu'une seule fois pendant le jeu
Serial.println ("-------- Nouveau jeu --------");
lcd.clear ();
lcd.home ();
lcd.print ("Bouton de libération");
lcd.setCursor (0,1);
lcd.print ("pour démarrer");
iState = 3;
      }
    }
if (iState == 3) // Init au tour suivant
    {
if (iDebounceButton == HIGH)
      {
lcd.clear ();
        lcd.home ();
        lcd.print ("Round:" + String (iRound));
        Serial.println ("Round:" + String (iRound));
        lcd.setCursor (0,1);
        lcd.print ("Score:" + String (iScore));
        Serial.println ("Score:" + String (iScore));
        iTargetPos = aléatoire (0, NUMPIXELS-1);
        Serial.println ("Nouvelle position cible:" + String (iTargetPos));
        iPlayerPos = aléatoire (0, NUMPIXELS-1);
        while (iTargetPos == iPlayerPos)
          iPlayerPos = aléatoire (0, NUMPIXELS-1);
        Serial.println ("Player start pos:" + String (iPlayerPos));
        iState = 4;
      }
    }
if (iState == 4) // Dessine la cible et joue le point
    {
DrawNextTarget (iTargetPos, bSecureWindow); // Dessine une nouvelle cible
DrawPlayer (iPlayerPos); // Dessine le point du joueur
iLastPlayerMove = millis (); // Mettre à jour le minuteur pour le déplacement
iState = 5;
    }
if (iState == 5) // Attendez en appuyant sur le bouton et déplacez le point du joueur
    {
si (iDebounceButton == LOW)
      {
iState = 6;
      }
autre
      {
non signé long currentMillis = millis ();
if (currentMillis - iLastPlayerMove> iInterval)
        {
iPlayerPos ++;
          si (iPlayerPos> = NUMPIXELS)
            iPlayerPos = 0;
          DrawNextTarget (iTargetPos, bSecureWindow);
          DrawPlayer (iPlayerPos);
          iLastPlayerMove = currentMillis;
        }
      }
    }
if (iState == 6) // Vérifie si le joueur gagne
    {
if (CheckPlayerPos ()) // Gagnant ou perdant?
      {
iScore ++; // Mettre à jour le score
        iRound ++; // Mettre à jour les tours
        iState = 2; // Revenir au bouton de libération
        if (iRound> DISABLEWINDOW) // Cible uniquement
        {
          bSecureWindow = false;
          iInterval = aléatoire (MININTERVAL, MAXINTERVAL);
        }
        autre
          iInterval = aléatoire (STARTINTERVAL-50, MAXINTERVAL);
        Serial.println ("Nouvel intervalle:" + String (iInterval));
      }
      autre
        iState = 90;
    }
if (iState == 90) // La partie se termine
    {
Serial.println ("Le jeu se termine");
lcd.clear ();
lcd.home ();
iDebounceButton = HAUT;
iLastButtonPressed = HIGH;
iButtonState = HAUT;
if (iScore> iStoredHighscore) // Nouveau meilleur score?
      {
lcd.print ("Nouveau meilleur score");
lcd.setCursor (0,1);
lcd.print ("Nouveau score:" + String (iScore));
Serial.println ("Le nouveau meilleur score est" + String (iScore));
EEPROM.write (0, iScore); // Stocke le nouveau highscore dans l'EEPROM
iStoredHighscore = iScore;
      }
else // Perdant
      {
lcd.print ("Game Over");
lcd.setCursor (0,1);
lcd.print ("Vous perdez");
Serial.println ("Vous perdez!");
      }
Serial.println ("-------- Fin de partie --------");
retard (2000);
iState = 1;
    }
  }
autre
InitFirstRun (); // Initier Firstrun pour vérifier l'écran LCD et l'anneau WS218B
}

// Fonction pour faire la première exécution
void InitFirstRun ()
{
if (digitalRead (PINSCORERST) == LOW) // Écraser l'EEPROM avec "0"
  {
Serial.println ("Réinitialiser le meilleur score");
pour (int iCnt = 0; iCnt EEPROM.write (iCnt, 0);
  }
Serial.println ("---- Démarrer init ----");
lcd.home ();
lcd.print ("Game Cyclone");
Serial.println ("Game Cyclone");
lcd.setCursor (0,1);
lcd.print ("(c) M3taKn1ght");
Serial.print ("(c) M3taKn1ght");
retard (1000);
lcd.clear ();
lcd.home ();
lcd.print ("Pour AZ-Delivery");
Serial.println ("Pour AZ-Delivery");
lcd.setCursor (0,1);
lcd.print ("Test de l'anneau ...");
Serial.println ("Test de l'anneau ...");
retard (1000);
pixels.clear ();
// Vérifiez chaque LED
pour (int i = 0; i <= 255; i + = 51)
  {
InitRingTest (i, 0,0);
retard (50);
  }
pixels.clear ();
pour (int i = 0; i <= 255; i + = 51)
  {
InitRingTest (0, i, 0);
retard (50);
  }
pixels.clear ();
pour (int i = 0; i <= 255; i + = 51)
  {
InitRingTest (0,0, i);
retard (50);
  }
pixels.clear ();
pixels.show ();
Serial.println ("---- End init ----");
bFirstRun = faux;
Serial.println ("bFirstRun:" + String (bFirstRun));
Serial.println ("Activer l'état pour le jeu");
}

// Fonction simple pour vérifier l'anneau LED un par un
void InitRingTest (int iRed, int iGreen, int iBlue)
{
Serial.println ("R:" + String (iRed) + "G:" + String (iGreen) + "B:" + String (iBlue));
pour (int iPixel = 0; iPixel <NUMPIXELS; iPixel ++)
  {
pixels.setPixelColor (iPixel, pixels.Color (iRed, iGreen, iBlue));
pixels.show ();
retard (50);
  }
}

// Fonction pour dessiner la cible une zone sécurisée pour le joueur
void DrawNextTarget (int iPos, bool bArea)
{
pixels.clear ();
pixels.setPixelColor (iPos, pixels.Color (0, 255, 0));
si (bArea)
  {
si (iPos - 1 <0)
pixels.setPixelColor (NUMPIXELS - 1, pixels.Color (255, 136, 0));
autre
pixels.setPixelColor (iPos - 1, pixels.Color (255, 136, 0));

si (iPos + 1> = NUMPIXELS)
pixels.setPixelColor (0, pixels.Couleur (255, 136, 0));
autre
pixels.setPixelColor (iPos + 1, pixels.Color (255, 136, 0));
  }
}

// Fonction pour dessiner la LED des joueurs
vide DrawPlayer (int iPos)
{
if (iPos == iTargetPos) // target et player-dot sont égaux
pixels.setPixelColor (iPos, pixels.Color (0, 0, 255)); // La couleur du point sera bleue
autre
pixels.setPixelColor (iPos, pixels.Color (255, 0, 0)); // Sinon rouge
pixels.show ();
}

// Fonction à vérifier après avoir appuyé sur le bouton, si l'utilisateur atteint la cible
booléen CheckPlayerPos ()
{
if (iTargetPos == iPlayerPos) // Le joueur atteint la cible?
retourne vrai;
autre
  {
if (bSecureWindow) // LED avant et après la cible active?
    {
int iBeforeTarget = iTargetPos - 1;
int iAfterTarget = iTargetPos + 1;
si (iBeforeTarget <0)
iBeforeTarget = NUMPIXELS - 1;
if (iAfterTarget> = NUMPIXELS)
iAfterTarget = 0;
if (iBeforeTarget == iPlayerPos || iAfterTarget == iPlayerPos)
retourne vrai;
autre
retourner faux;
    }
autre
retourner faux;
  }
}

// Fonction de bouton anti-rebond
int DebounceButton ()
{
int iCurrentButtonState = digitalRead (PINBTN);
if (iCurrentButtonState! = iLastButtonPressed)
ulLastDebounceTime = millis ();

if ((millis () - ulLastDebounceTime)> ulDebounceButton)
  {
if (iCurrentButtonState! = iButtonState)
iButtonState = iCurrentButtonState;
  }
iLastButtonPressed = iCurrentButtonState;
return iButtonState;
}

Code 1 : Code pour le jeu "Cyclone

À ce stade, vous pouvez simplement télécharger le code via Arduino IDE, mais certaines parties du code devraient être expliquées plus en détail.

Afin de pouvoir contrôler l'affichage et la WS2812B-LED, un objet correspondant doit d'abord être créé pour les deux. Vous pouvez le voir directement au début du code 1, derrière le commentaire "Créer un objet". Juste après, certaines variables globales sont créées et partiellement initialisées, si cela n'est pas fait directement après dans la fonction setup().


Vous pourriez être intéressé par la ligne "iStoredHighscore = EEPROM.read(0) ;" où la dernière valeur stockée du highscore est lue à partir de l'EEPROM, c'est-à-dire la mémoire qui ne perd pas ses valeurs lorsqu'elle est éteinte ou réinitialisée. Si le highscore a été battu pendant une partie, le nouveau highscore est écrit dans la fonction EEPROM dans la boucle() en utilisant la ligne "EEPROM.write(0,iScore) ;".

La fonction InitFirstRun() n'est appelée dans le code que lorsque l'Arduino est redémarré. Si vous souhaitez supprimer un high-score ou d'anciennes valeurs de l'EEPROM, cela se fait dans cette fonction. Pour ce faire, connectez la broche numérique 9 à GND avant de démarrer le nano. Par cette procédure, l'EEPROM est complètement mise à zéro avant que toutes les couleurs de toutes les LED soient testées. Pour ce test des LED, il est important que vous disposiez d'une alimentation électrique appropriée pour votre circuit.

La fonction loop() est le cœur du jeu. D'une part, l'état actuel du bouton-poussoir est déterminé directement au début et débloqué lorsqu'il est enfoncé. Le débat signifie que pendant un temps défini, dans ce cas pendant 10 ms, un changement de signal clair doit être présent. Vous pouvez en savoir plus sur le débat ici. Directement après, le déroulement du jeu est contrôlé. Quelles sorties doivent être visualisées à l'écran et, si nécessaire, quelles LED doivent être affichées sur l'anneau WS2812B.

La visualisation du point cible et du point joueur a été implémentée en utilisant les fonctions DrawNextTarget(int iPos, bool bArea) et DrawPlayer(int iPos) respectivement. Ces fonctions sont appelées avec la position de la LED dès que le temps pour l'étape suivante à partir du point de joueur est atteint. Pour s'assurer que les LEDs sont toujours affichées avant et après le point cible dans les premiers tours, un drapeau bool est passé à la fonction DrawNextTarget(int iPos, bool bArea). Si le joueur appuie maintenant sur le bouton poussoir et que le rebondissement est terminé, la fonction bool CheckPlayerPos() est appelée. Cette fonction permet de vérifier si le joueur a appuyé sur le bouton-poussoir au bon moment ou non.


Lors des premiers tours, lorsque la LED devant et derrière le point cible est encore valide, la zone de tolérance est toujours prise en compte. Si le joueur a atteint la cible, le score actuel est augmenté, une nouvelle vitesse aléatoire est déterminée et la position du point cible et la position de départ du point du joueur sont fixées. Il ne peut pas arriver que le point de départ et le point d'arrivée soient identiques au début du jeu.

Toutefois, si le joueur a appuyé au mauvais moment, on vérifie si le meilleur score a été battu et un écran "Game over" s'affiche. Si le score le plus élevé est battu, le nouveau score est écrit directement dans l'EEPROM.

Pour vous aider à comprendre le code plus rapidement et, si nécessaire, à mettre en œuvre les modifications pour une bande WS2818B, de nombreux commentaires ont été inclus. Bien sûr, vous pouvez aussi programmer d'autres effets pour la bague WS2812B avant le jeu ou à "Game Over". Le code doit d'abord servir de base à vos ajustements individuels.

J'espère que vous vous amuserez avec la réplique.

Ce projet et d'autres peuvent être trouvés sur GitHub à https://github.com/M3taKn1ght/Blog-Repo.

Pour arduino

2 commentaires

Jörn Weise

Jörn Weise

Hallo WinTiger,
Danke für das Feedback und das sie so Spaß haben. Zu ihren Problem fallen mir vier Lösungsvorschläge ein:
1. Haben sie SDA und SCL richtig angeschlossen?
2. Hat das Display den richtigen Kontrast? Stellschraube auf der Rückseite.
3. Stimmt die Spannungsversorgung, da sonst das Display bzw. die Buchstaben zu hell sind.
4. Ggf. Stimmt die I2C Adresse nicht, dann müssen sie diese mit dem I2C-Skript aus dem Wire.h- Beispielen ermitteln

Ich hoffe ich konnte helfen. Am Rande, mein aktueller Rekord liegt bei 55,vllt. Haben sie ihm ja schon überboten.

Gruß
Weise

WinTIger

WinTIger

Hallo,
ich bedanke mich zunächst erstmal für dieses interessante Spiel.
Ich habe es nachgebaut und macht einen riesen Spaß. Nur das Display zeigt nichts an bei mir. Es leuchtet, aber es wird nichts drauf geschrieben. Ich freue mich sehr, wenn ihr mir Anregungen geben könnt. Ich habe alle Komponenten von AZ-Delivery und verwende den LED-Ring.

Mit freundlichen Grüßen
WinTiger

Laisser un commentaire

Tous les commentaires sont modérés avant d'être publiés

Messages de blogs recommandés

  1. Installez maintenant ESP32 via l'administrateur de la carte
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA-Over the Air-ESP Programmation par WiFi

Produits recommandés