Cyclone - das Spiel

En la estación fría y húmeda, busca algo más que hacer por sus hijos o quiere demostrar sus habilidades nerd a sus amigos. Encontré el juego Cyclone, que significa Tornado en alemán, en Internet. Este juego me fascinó a mí y a mis hijos. Sin embargo, no me gustó ninguna de las variantes que se muestran, por lo que se desarrolló rápidamente una variante separada.

De que trata el juego Cyclone

Si ve el video, el principio se explica rápidamente.


Tienen un anillo LED con un punto LED en funcionamiento y un marcador de objetivo LED. El punto de jugador corre a una velocidad definida a través del anillo y debes intentar presionar una tecla tan pronto como el punto de jugador sea congruente con el marcador de destino. A diferencia del video, no desarrollé un nivel, sino un juego continuo que termina cuando el marcador objetivo no es alcanzado. Además, se instala una pantalla LCD, que muestra la puntuación más alta, la puntuación actual y la vuelta actual. La velocidad se elige al azar con cada nueva vuelta.

El hardware

Con el hardware que necesita, solo necesita unas pocas piezas. Dado que el código está escrito de tal manera que puede reemplazar el anillo LED WS2812B de Arduino con una tira WS2812B, aquí se guardan dos listas de piezas.

Para la variante que se presenta en esta publicación de blog, necesita los componentes que están en tabla 1 están listados.

Articulo número Componente
1 1 Nano V3.0 con Atmega328 CH340
o
Nano V3.0 con chip FT232RL y ATmega328
2 1 Anillo LED 5V RGB WS2812B 12 bits 50mm
3 1 Botón
4 1 Módulo LCD con interfaz I2C
5 1 Protoboard y puente
(Aquí en un juego con adaptador de fuente de alimentación)

Tabla 1: Piezas de hardware para Cyclone con WS2812B-LEDring

Sin embargo, si desea utilizar una tira, necesita los componentes Tabla 2. No se tienen en cuenta los demás componentes para construir la tira en un marco o similar.

Articulo número Componente
1 1 Nano V3.0 con Atmega328 CH340
o
Nano V3.0 con chip FT232RL y ATmega328
2 1 Tira de LED WS2812B
3 1 Botón
4 1 Módulo LCD con interfaz I2C
5 1

Protoboard y puente (Aquí en un juego con adaptador de fuente de alimentación)

6 1 Fuente de alimentación para LED y nano

Tabla 2: Piezas de hardware para Cyclone con WS2812B-Strip

Debe mencionarse aquí mismo que cuantos más LED tenga su tira WS2812B, más corriente tendrá que suministrar la fuente de alimentación. Para 60 LED, necesita un poco menos de 4A si todos los LED se encienden.

Software requerido

El software requerido para este proyecto es manejable:

La estructura

Para ensamblar con el anillo WS2812B, los componentes deben ser como se muestra en ilustración 1 se muestra que están conectados entre sí. Si tiene una tira WS2812B, debe conectar la fuente de alimentación y la conexión de entrada de datos correctamente.

 

Figura 1: Cableado de los componentes individuales

Lo siguiente se aplica a las dos variantes de WS2812B: rojo es la fase (5 voltios), negro es la tierra (GND) y gris es la conexión de entrada de datos. En la mayoría de los casos, tiene cuatro conexiones en las tiras WS2812B, por lo que debe verificar de antemano cuál es el pin de entrada de datos (DI) correcto. El pin DO (= Data OUT) es para la conexión opcional de más módulos WS2812B (conexión en serie).

El código fuente

Copie el código aquí del blog, consulte Codigo 1, o puede usar el Repositorio de Github del autor descargar.

//-----------------------------------------------------
// Juego "CYCLONE" para Arduino
// Autor: Joern Weise
// Licencia: GNU GPl 3.0
// Creado: 20 de septiembre de 2020
// Actualización: 25 de septiembre de 2020
//-----------------------------------------------------
// Incluir bibliotecas
#include
#include
#include
#include

// Define
#define NUMPIXELS 12 // Tamaño de anillo de NeoPixel popular o edite el número de LED
#define PIN 2 // Data-Pin para sonar o quitar
#define PINBTN 6 // Pin para botón de jugador
#define PINSCORERST 9 // Pin para restablecer la puntuación durante la primera ejecución

#define DISABLEWINDOW 3 // Las rondas antes del LED antes y después del objetivo ya no son válidas

// La velocidad de Player-Dot define
#define STARTINTERVAL 250 // Movimiento "normal"
#define MAXINTERVAL 500 // Movimiento muy lento
#define MININTERVAL 50 // Movimiento muy rápido

// Crea objetos
LiquidCrystal_I2C lcd (0x27,16,2); // establecer la dirección de la pantalla LCD
Adafruit_NeoPixel píxeles (NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); // Iniciar objeto NeoPixel

bool bFirstRun, bSecureWindow;
int iState = 1;
int iTargetPos, iPlayerPos, iStoredHighscore, iRound, iScore, iInterval; // Vars para el juego
int iLastButtonPressed, iButtonState, iDebounceButton; // Vars al botón antirrebote
unsigned long iLastPlayerMove, ulLastDebounceTime; // Botón de temporizador para debouce
unsigned long ulDebounceButton = 10; // Tiempo de rebote

configuración vacía () {
Serial.begin (115200);
Serial.println ("Iniciar comunicación serie: HECHO");

// Iniciar init para WS218B-ring o -strip
pixels.begin (); // INICIALIZAR el objeto de tira de NeoPixel (OBLIGATORIO)
píxeles.clear (); // Establecer todos los píxeles en "desactivado"
pixels.setBrightness (20); // Establecer brillo al 20%
pixels.show (); // Envía los colores de píxeles actualizados al hardware.
Serial.println ("Init WS218B-ring: DONE");

// Comenzar la visualización de inicio
lcd.init ();
LCD luz de fondo();
lcd.clear ();
Serial.println ("Pantalla LCD inicial: HECHO");

randomSeed (analogRead (0)); // Haz randome más randome
Serial.println ("Hacer randome más randome: HECHO");

// Leer la última puntuación más alta de EEPROM
iStoredHighscore = EEPROM.read (0);
Serial.println ("Última puntuación más alta almacenada:" + String (iStoredHighscore));

// Botón de inicio con resistencia pullup interna
pinMode (PINBTN, INPUT_PULLUP); // JuegoBTN
pinMode (PINSCORERST, INPUT_PULLUP); // Reset-Pin para puntuación

// Inicia algunas vars básicas
bFirstRun = verdadero; // Habilitar firstrun
iLastButtonPressed = digitalRead (PINBTN); // Iniciar iLastButtonPressed
iButtonState = digitalRead (PINBTN); // Iniciar iButtonstate
}

bucle vacío () {
int iDebounceButton = DebounceButton (); // Botón de verificación y eliminación de rebotes

si (! bFirstRun)
  {
if (iState == 1) // Pantalla de inicio
    {
bSecureWindow = verdadero;
iRound = 1;
iScore = 0;
iInterval = STARTINTERVAL;
lcd.clear ();
casa lcd ();
lcd.print ("Puntaje alto:" + Cadena (iStoredHighscore));
lcd.setCursor (0,1);
lcd.print ("Presione el botón ...");
iState = 2;
    }
if (iState == 2) // Obtener botón presionado
    {
si (iDebounceButton == BAJO)
      {
if (iRound == 1) // Mostrar solo una vez durante el juego
Serial.println ("-------- Nuevo juego --------");
lcd.clear ();
casa lcd ();
lcd.print ("Botón de liberación");
lcd.setCursor (0,1);
lcd.print ("para comenzar");
iState = 3;
      }
    }
if (iState == 3) // Iniciar la siguiente ronda
    {
si (iDebounceButton == HIGH)
      {
lcd.clear ();
        casa lcd ();
        lcd.print ("Ronda:" + Cadena (iRound));
        Serial.println ("Ronda:" + Cadena (iRound));
        lcd.setCursor (0,1);
        lcd.print ("Puntuación:" + Cadena (iScore));
        Serial.println ("Puntuación:" + Cadena (iScore));
        iTargetPos = aleatorio (0, NUMPIXELS-1);
        Serial.println ("Nueva posición de destino:" + String (iTargetPos));
        iPlayerPos = aleatorio (0, NUMPIXELS-1);
        while (iTargetPos == iPlayerPos)
          iPlayerPos = aleatorio (0, NUMPIXELS-1);
        Serial.println ("Posición de inicio del reproductor:" + Cadena (iPlayerPos));
        iState = 4;
      }
    }
if (iState == 4) // Dibuja el objetivo y juega el punto
    {
DrawNextTarget (iTargetPos, bSecureWindow); // Dibujar un nuevo objetivo
DrawPlayer (iPlayerPos); // Dibujar el punto del jugador
iLastPlayerMove = millis (); // Actualizar el temporizador para moverse
iState = 5;
    }
if (iState == 5) // Espere presionando el botón y mueva el punto del jugador
    {
si (iDebounceButton == BAJO)
      {
iState = 6;
      }
demás
      {
currentMillis largo sin firmar = millis ();
si (currentMillis - iLastPlayerMove> iInterval)
        {
iPlayerPos ++;
          si (iPlayerPos> = NUMPIXELS)
            iPlayerPos = 0;
          DrawNextTarget (iTargetPos, bSecureWindow);
          DrawPlayer (iPlayerPos);
          iLastPlayerMove = currentMillis;
        }
      }
    }
if (iState == 6) // Comprueba si el jugador gana
    {
if (CheckPlayerPos ()) // ¿Ganador o perdedor?
      {
iScore ++; // Actualizar puntuación
        iRound ++; // Actualizar rondas
        iState = 2; // Volver al botón de liberación
        if (iRound> DISABLEWINDOW) // Solo objetivo
        {
          bSecureWindow = falso;
          iInterval = aleatorio (MININTERVAL, MAXINTERVAL);
        }
        demás
          iInterval = aleatorio (STARTINTERVAL-50, MAXINTERVAL);
        Serial.println ("Nuevo intervalo:" + String (iInterval));
      }
      demás
        iState = 90;
    }
if (iState == 90) // El juego termina
    {
Serial.println ("Fin del juego");
lcd.clear ();
casa lcd ();
iDebounceButton = HIGH;
iLastButtonPressed = ALTO;
iButtonState = HIGH;
if (iScore> iStoredHighscore) // ¿Nueva puntuación más alta?
      {
lcd.print ("Nueva puntuación más alta");
lcd.setCursor (0,1);
lcd.print ("Nueva puntuación:" + String (iScore));
Serial.println ("La nueva puntuación más alta es" + String (iScore));
EEPROM.write (0, iScore); // Almacenar la nueva puntuación más alta en EEPROM
iStoredHighscore = iScore;
      }
else // Perdedor
      {
lcd.print ("Juego terminado");
lcd.setCursor (0,1);
lcd.print ("Usted pierde");
Serial.println ("¡Pierdes!");
      }
Serial.println ("-------- Fin del juego --------");
retraso (2000);
iState = 1;
    }
  }
demás
InitFirstRun (); // Iniciar Firstrun para comprobar LCD y WS218B-ring
}

// Función para hacer la primera ejecución
void InitFirstRun ()
{
if (digitalRead (PINSCORERST) == LOW) // Sobrescribe EEPROM con "0"
  {
Serial.println ("Restablecer puntuación más alta");
para (int iCnt = 0; iCnt EEPROM.write (iCnt, 0);
  }
Serial.println ("---- Iniciar init ----");
casa lcd ();
lcd.print ("Juego Cyclone");
Serial.println ("Game Cyclone");
lcd.setCursor (0,1);
lcd.print ("(c) M3taKn1ght");
Serial.print ("(c) M3taKn1ght");
retraso (1000);
lcd.clear ();
casa lcd ();
lcd.print ("Para entrega AZ");
Serial.println ("Para entrega AZ");
lcd.setCursor (0,1);
lcd.print ("Anillo de prueba ...");
Serial.println ("Anillo de prueba ...");
retraso (1000);
píxeles.clear ();
// Compruebe cada LED
para (int i = 0; i <= 255; i + = 51)
  {
InitRingTest (i, 0,0);
retraso (50);
  }
píxeles.clear ();
para (int i = 0; i <= 255; i + = 51)
  {
InitRingTest (0, i, 0);
retraso (50);
  }
píxeles.clear ();
para (int i = 0; i <= 255; i + = 51)
  {
InitRingTest (0,0, i);
retraso (50);
  }
píxeles.clear ();
pixels.show ();
Serial.println ("---- Fin init ----");
bFirstRun = falso;
Serial.println ("bFirstRun:" + String (bFirstRun));
Serial.println ("Activar estado para el juego");
}

// Función sencilla para comprobar LED-Ring uno por uno
vacío InitRingTest (int iRed, int iGreen, int iBlue)
{
Serial.println ("R:" + Cadena (iRed) + "G:" + Cadena (iGreen) + "B:" + Cadena (iBlue));
para (int iPixel = 0; iPixel <NUMPIXELS; iPixel ++)
  {
pixels.setPixelColor (iPixel, pixels.Color (iRed, iGreen, iBlue));
pixels.show ();
retraso (50);
  }
}

// Función para dibujar un objetivo en un área segura para el jugador
vacío DrawNextTarget (int iPos, bool bArea)
{
píxeles.clear ();
pixels.setPixelColor (iPos, pixels.Color (0, 255, 0));
si (bArea)
  {
si (iPos - 1 <0)
píxeles.setPixelColor (NUMPIXELS - 1, píxeles.Color (255, 136, 0));
demás
pixels.setPixelColor (iPos - 1, pixels.Color (255, 136, 0));

si (iPos + 1> = NUMPIXELS)
píxeles.setPixelColor (0, píxeles.Color (255, 136, 0));
demás
pixels.setPixelColor (iPos + 1, pixels.Color (255, 136, 0));
  }
}

// Función para dibujar jugadores LED
vacío DrawPlayer (int iPos)
{
if (iPos == iTargetPos) // objetivo y jugador-punto es igual
pixels.setPixelColor (iPos, pixels.Color (0, 0, 255)); // El color del punto será azul
demás
pixels.setPixelColor (iPos, pixels.Color (255, 0, 0)); // De lo contrario rojo
pixels.show ();
}

// Función para verificar después de presionar el botón, si el usuario golpea el objetivo
bool CheckPlayerPos ()
{
if (iTargetPos == iPlayerPos) // ¿El jugador alcanzó el objetivo?
devuelve verdadero;
demás
  {
if (bSecureWindow) // LED antes y después del objetivo activo?
    {
int iBeforeTarget = iTargetPos - 1;
int iAfterTarget = iTargetPos + 1;
si (iBeforeTarget <0)
iBeforeTarget = NUMPIXELS - 1;
if (iAfterTarget> = NUMPIXELS)
iAfterTarget = 0;
if (iBeforeTarget == iPlayerPos || iAfterTarget == iPlayerPos)
devuelve verdadero;
demás
falso retorno;
    }
demás
falso retorno;
  }
}

// Función de botón antirrebote
int DebounceButton ()
{
int iCurrentButtonState = digitalRead (PINBTN);
if (iCurrentButtonState! = iLastButtonPressed)
ulLastDebounceTime = millis ();

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

Código 1: Código zum Spiel "Cyclone"

Sie können an dieser Stelle einfach den Code a través de Arduino IDE hochladen, jedoch sollen noch einige Stellen vom Code näher erläutert werden.

Damit Sie das Display und die WS2812B-LED ansteuern können, müssen zunächst für beide ein entsprechendes Objekt angelegt werden. Dies sehen Sie direkt am Anfang von Codigo 1, hinter dem Kommentar „Crear objeto“. Direkt danach werden noch einige globale Variablen erzeugt und teilweise, sofern das nicht direkt danach in der setup () - Funktion geschieht, initialisiert.

Für Sie vielleicht interessant ist die Zeile „iStoredHighscore = EEPROM.read (0);“ bei welcher der letzte gespeicherte Wert vom Highscore aus dem EEPROM, also dem Speicher, der seine Werte im ausgeschalteten Zustand oder bei Reset nicht verliert, gelesen wird. Sofern der Highscore während eines Spiels überboten wurde, wird in der loop () - Funktion mittels der Zeile „EEPROM.write (0, iScore);“, der neue Highscore in den EEPROM geschrieben.

Die InitFirstRun () - Funktion wird in dem Code nur dann aufgerufen, wenn der Arduino neu gestartet wird. Wollen Sie einen Highscore oder alte Werte aus dem EEPROM löschen, so geschieht das in dieser Funktion. Verbinden Sie dazu noch vor dem Start des Nanos den Digitalpin 9 mit GND. Durch dieses Vorgehen ist der EEPROM komplett auf null gesetzt, bevor alle Farben von allen LEDs geprüft werden. Für diesen Test der LEDs ist es wichtig, dass Sie eine geeignete Spannungsversorgung für Ihre Schaltung haben.

Die loop () - Funktion ist das Herzstück des Spiels. Zum einen wird direkt am Anfang der aktuelle Zustand vom Drucktaster ermittelt und bei einer Betätigung dieser entprellt. Entprellt bedeutet, dass für eine definierte Zeit, in diesem Fall for 10 ms, ein eindeutiger Signalwechsel vorhanden sein muss. Mehr zum Thema Entprellen könne Sie hier nachlesen. Direkt danach wird der Ablauf des Spiels gesteuert. Welche Ausgaben müssen auf dem Screen visualisiert und ggf. Welche LEDs auf dem WS2812B-Ring angezeigt werden.

La visualización del punto de destino y el punto del jugador se ha implementado a través de las funciones DrawNextTarget(int iPos, bool bArea) o DrawPlayer(int iPos). Estas funciones se llaman con la posición LED tan pronto como se alcanza el tiempo para el siguiente paso desde el punto del jugador. Para asegurarse de que los LED antes y después del punto de destino todavía se muestran en las primeras rondas, se pasa una marca bool de la función DrawNextTarget(int iPos, bool bArea). Si el jugador ahora presiona el botón pulsador y el debounce está completo, se llama a la función bool CheckPlayerPos(). Esta función comprueba si el jugador presionó el botón en el momento adecuado o no.

En las primeras rondas, incluso si el LED antes y después del punto de destino sigue siendo válido, la zona de tolerancia se sigue teniendo en cuenta. Si el jugador ha atrapado al objetivo, se aumenta la puntuación actual, se determina una nueva velocidad aleatoria y se establece la posición desde el punto objetivo y la posición inicial desde el punto de jugador. No puede suceder que el punto de partida y el punto de destino sean idénticos justo al principio del juego.

Sin embargo, si el jugador ha presionado en el momento equivocado, se comprobará si se ha superado la puntuación más alta y se muestra una pantalla "Juego sobre". Si se supera la puntuación más alta, la nueva partitura se escribe directamente en el EEPROM.

Se han agregado muchos comentarios para ayudarle a comprender el código más rápidamente y, si es necesario, implementar modificaciones para una tira WS2818B. Por supuesto, también puede programar otros efectos para el anillo WS2812B antes del juego o durante el "Game Over". El código debe ser primero la base para las personalizaciones individuales.

Te deseo mucha diversión con la réplica.

Este y otros proyectos se pueden encontrar en GitHub en https://github.com/M3taKn1ght/Blog-Repo.

Para arduino

3 comentarios

zauBAERer

zauBAERer

Hallo,
ich hab das Spiel mit meiner Tochter nachgebaut. Wir haben uns für eine alte Wurstdose als Gehäuse entschieden. Das Display war natürlich zu groß daher wurde ein anderer Typ (auch von AZ) verwendet.
Damit es mehr “Automatenspielcharakter” hat haben wir noch einen Piezo eingebaut und und das Ganze mit ein Paar Sounds erweitert.

https://hackaday.io/project/181393-tin-can-cyclone hier noch ein paar Bilder davon

Grüße zauBAERer

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

Deja un comentario

Todos los comentarios son moderados antes de ser publicados

Artículos de blog

  1. Ahora instalamos el esp32 a través de la administración.
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. Transporte Aéreo - programación de ESP mediante redes locales inalámbricas