Petit jeu du démineur / Airsoft, escape room (à adapter à vos besoins)

bonjour

Je partage ici un petit code de jeu de "déminage" où l'utilisateur doit "couper" les fils de la bonne couleur pour interrompre un décompte.

Pour éviter d'avoir à couper réellement des fils, le montage utilise une petite breadboard où les fils de couleurs sont fixes et un 'petit pont' en face du fil de couleur est l'élément à retirer pour déconnecter un fil donné

Il y a trois boutons en plus dans le montage: un bouton plus, un bouton moins et un bouton de validation / OK.

L'interface de ce prototype se fait dans le moniteur série ouvert à 115200 bauds.

Au lancement de l'application, vous verrez

Choisir la Durée: 20 s

Cette valeur provient de la variable dureeInitialequi est initialisée à 20s dans le setup.

Un click sur le bouton + augmente cette durée et sur le bouton - la diminue. Il s'agit de définir le temps disponible pour "couper" les bons fils.

Une fois ce temps défini, vous clickez sur le bouton de validation (OK). Par exemple si j'appuie 5 fois sur le bouton + et je valide, je vais avoir dans la console série :

Choisir la Durée: 20 s
Durée: 21 s
Durée: 22 s
Durée: 23 s
Durée: 24 s
Durée: 25 s
Choix effectué, Durée: 25 s

Ensuite le code génère un nombre de fils à couper et vous dit la solution. C'est défini dans la constante nbHotWiresToGuess et vaut 3 par défaut

Génération combinaison aléatoire
Vert
Rouge
Gris
Appuyez sur OK pour commencer

Cette partie de configuration du jeu donc est à effectuer par un administrateur.

Ensuite l'idée c'est d'avoir un jeu (sans l'arduino) qui permet aux joueurs de déterminer quels sont les trois fils choisis et dans quel ordre il faut les retirer.

Quand une équipe de joueurs pense avoir deviné, ils viennent tester leur choix sur l'arduino.

Un appui sur OK lance le décompte (qui s'affiche dans le moniteur série) et un beep audio qui accélère donne un retour Sonore au joueur.

Le joueur retire les petits strips, une erreur de couleur ou d'ordre et c'est la fin du jeu marqué par une musique adaptée et un message affiché dans le moniteur série:

C'est parti
Durée: 20 s
Durée: 19.00 s
Durée: 18.00 s
Durée: 17.00 s
Durée: 16.00 s
ERREUR: Gris débranché !!!
Vous avez PERDU...

Si les 3 fils sont enlevés dans le bon ordre, un autre musique marque le gain de la partie (et le moniteur série affiche aussi de l'information)


Le câblage est tout simple:

  • 8 fils de couleurs (pins 6 à 13) reliés à GND au travers du "petit pont" qui sert à couper la connexion

  • un piezo connecté en pin 5 (+) avec le (-) connecté à GND (par précaution je mets toujours une petite résistance de limitation d'appel de courant de 150Ω)

  • 3 boutons momentanés connectés en pins 2, 3 et 4 (pin -- bouton -- GND) qui seront configurés en INPUT_PULLUP donc pas besoin de résistance.

Au final ça ressemble à cela:


Voici le code qui nécessite la librairie de @bricoleau pour la gestion des 3 boutons.

/* ============================================
  KEEP THIS INFORMATION IF YOU USE THIS CODE

  code is placed under the MIT license
  Copyright (c) 2019 J-M-L For the Arduino Forum

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/

/* ********* PIN DEFINITION ************
  PINS
  0     Not connected (reserved for Serial Rx)
  1     Not connected (reserved for Serial Tx)
  2     OK buton (momentary button connected to GND)
  3     +  buton (momentary button connected to GND)
  4     -  buton (momentary button connected to GND)
  5     Piezo +, (- going to GND through 150Ω current limiting resistor)
  6     hotwire to GND (with removable strip)
  7     hotwire to GND (with removable strip)
  8     hotwire to GND (with removable strip)
  9     hotwire to GND (with removable strip)
  10    hotwire to GND (with removable strip)
  11    hotwire to GND (with removable strip)
  12    hotwire to GND (with removable strip)
  13    hotwire to GND (with removable strip)

  A0
  A1
  A2
  A3
  A4
  A5


  Libraries
  simpleBouton    http://forum.arduino.cc/index.php?topic=375232.0

*/

#include <simpleBouton.h>
#include <assert.h>

enum t_etape : byte {Reset, Reglage, Generation, Confirmation, Decompte, Victoire, Echec};

simpleBouton    boutonOK(2);
simpleBouton    boutonPlus(3);
simpleBouton    boutonMoins(4);

const byte      piezoPin = 5;

const int       totalHotWires = 8;
const byte      hotWirePin[] = {6, 7, 8, 9, 10, 11, 12, 13};
const char *    hotWireColor[] = {"Gris", "Violet", "Bleu", "Vert", "Jaune", "Orange", "Rouge", "Marron"};
const int       nbHotWiresToGuess = 3;

// Vérifications de cohérence
static_assert((totalHotWires == sizeof(hotWireColor) / sizeof(hotWireColor[0])), "hotWireColor: Nombre incorrect de couleurs");
static_assert((totalHotWires == sizeof(hotWirePin) / sizeof(hotWirePin[0])), "hotWirePin: Nombre incorrect de pins");
static_assert((totalHotWires >= nbHotWiresToGuess), "nbHotWiresToGuess: Trop de fils à enlever");

t_etape         etape = Reset;

uint32_t        deltaT = 1000;
int32_t         dureeInitiale;
int32_t         dureeRestante;
uint32_t        t0, lastChrono;

byte            solution[totalHotWires];
byte            hotWireState[totalHotWires];
int             wireIndexToGuess;

void fisherYatesShuffle(byte* tableau, byte n) {
  randomSeed(analogRead(A1));
  for (byte i = n - 1; i >= 1; --i) {
    byte alea = random(0, i + 1);
    byte tmp = tableau[i];
    tableau[i] = tableau[alea];
    tableau[alea] = tmp;
  }
}

inline void tictac() {
  static bool tic = false;
  tone(piezoPin, tic ? 1000 : 2000, 10);
  tic = !tic;
}

void melodiePerte() {
  for (int16_t f = 1500; f >= 1; f--) {
    tone(piezoPin, f);
    delay(1);
  }
  noTone(piezoPin);
}

void melodieGain() {
  uint16_t melody[][2] = {
    {262, 250}, {196, 125}, {196, 125}, {220, 250},
    {196, 250}, {  0, 250}, {247, 250}, {262, 250},
  };

  for (byte i = 0; i < 8; i++) {
    tone(piezoPin, melody[i][0], melody[i][1]);
    delay(melody[i][1] * 1.25);
  }
}

inline void readHotWireState() {
  for (byte i = 0; i < totalHotWires; i++) hotWireState[i] = digitalRead(hotWirePin[i]);
}

inline void afficherdureeRestante(byte precision = 0) {
  Serial.print(F("Durée: "));
  Serial.print(dureeRestante / 1000.0, precision);
  Serial.println(F(" s"));
}

void gestionEtat() {
  switch (etape) {
    case Reset:
      deltaT = 1000;
      dureeInitiale = 20000l; // par défaut 20 secondes
      dureeRestante = dureeInitiale;
      wireIndexToGuess = 0;
      for (byte i = 0; i < totalHotWires; i++) solution[i] = i;
      Serial.print(F("Choisir la "));
      afficherdureeRestante();
      etape = Reglage;
      break;

    case Reglage:
      boutonPlus.actualiser();
      if (boutonPlus.vientDEtreEnfonce()) {
        tone(piezoPin, 500, 20);
        dureeRestante += 1000;
        dureeInitiale = dureeRestante;
        afficherdureeRestante();
      }

      boutonMoins.actualiser();
      if (boutonMoins.vientDEtreEnfonce()) {
        tone(piezoPin, 300, 20);
        if (dureeRestante >= 2000) {
          dureeRestante -= 1000;
          dureeInitiale = dureeRestante;
        }
        afficherdureeRestante();
      }

      boutonOK.actualiser();
      if (boutonOK.vientDEtreEnfonce()) {
        tone(piezoPin, 2500, 20);
        Serial.print(F("Choix effectué, "));
        afficherdureeRestante();
        etape = Generation;
      }
      break;

    case Generation:
      Serial.println(F("Génération combinaison aléatoire"));
      fisherYatesShuffle(solution, totalHotWires);
      for (byte i = 0; i < nbHotWiresToGuess; i++) {
        Serial.write('\t');
        Serial.println(hotWireColor[solution[i]]);
      }
      Serial.println(F("Appuyez sur OK pour commencer"));
      etape = Confirmation;
      break;

    case Confirmation:
      boutonPlus.actualiser();
      boutonMoins.actualiser();
      boutonOK.actualiser();
      if (boutonPlus.vientDEtreEnfonce() || boutonMoins.vientDEtreEnfonce()) { // on ne veut pas valider
        tone(piezoPin, 500, 25); delay(25); tone(piezoPin, 300, 25);
        Serial.println(F("Choisir la durée"));
        afficherdureeRestante();
        etape = Reglage;
      } else if (boutonOK.vientDEtreEnfonce()) {
        tone(piezoPin, 2500, 20);
        t0 = millis();
        lastChrono = t0;
        Serial.println(F("C'est parti"));
        afficherdureeRestante();
        etape = Decompte;
      }
      break;

    case Decompte:
      {
        if (millis() - lastChrono >= deltaT) { // deltaT ms de passé
          dureeRestante -= deltaT;
          if (dureeRestante < 0) dureeRestante = 0;
          lastChrono += deltaT;
          afficherdureeRestante(2);
          tictac();
          if (dureeRestante == 0) {
            etape = Echec;
          } else if (dureeRestante <= dureeInitiale / 8) deltaT = 100;
          else if (dureeRestante <= dureeInitiale / 4) deltaT = 250;
          else if (dureeRestante <= dureeInitiale / 2) deltaT = 500;
        }

        // on regarde l'état des fils
        readHotWireState();

        // test si situation inchangée
        bool situationCorrecte = true;
        for (int i = 0; i < wireIndexToGuess ; i++) // ceux là doivent être HIGH (déconnnectés)
          if (hotWireState[solution[i]] == LOW) {
            Serial.print(F("\tERREUR: "));
            Serial.print(hotWireColor[solution[i]]);
            Serial.println(F(" rebranché !!!"));
            situationCorrecte = false;
            break;
          }
        if (situationCorrecte)
          for (int i = wireIndexToGuess + 1; i < totalHotWires; i++) // ceux là doivent être LOW (connectés)
            if (hotWireState[solution[i]] == HIGH) {
              Serial.print(F("\tERREUR: "));
              Serial.print(hotWireColor[solution[i]]);
              Serial.println(F(" débranché !!!"));
              situationCorrecte = false;
              break;
            }

        if (situationCorrecte) {
          if (hotWireState[solution[wireIndexToGuess]] == HIGH) { // on a déconecté le bon
            tone(piezoPin, 300); delay(20); tone(piezoPin, 400); delay(20); tone(piezoPin, 300, 20);
            Serial.write('\t');
            Serial.print(hotWireColor[solution[wireIndexToGuess]]);
            Serial.println(F(" est OK !!!"));
            wireIndexToGuess++;
            if (wireIndexToGuess >= nbHotWiresToGuess) {
              etape = Victoire;
            }
          }
        } else { // situation incorrecte
          etape = Echec;
        }
      }
      break;

    case Victoire:
      Serial.println(F("Vous avez GAGNE !!!"));
      melodieGain();
      etape = Reset;
      break;

    case  Echec:
      Serial.println(F("Vous avez PERDU..."));
      melodiePerte();
      etape = Reset;
      break;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(piezoPin, OUTPUT);
  for (byte i = 0; i < totalHotWires; i++) pinMode(hotWirePin[i], INPUT_PULLUP);
}

void loop() {
  gestionEtat();
}

Dans les modifications simples:

  • Vous pouvez modifier le code pour câbler en dur les 3 fils à enlever si vous voulez faire un jeu non aléatoire (à mettre l'index des pins dans le tableau solution)

  • vous pouvez enlever les boutons + et - et ne conserver que le bouton OK pour démarrer la partie

Bon amusement :wink:

Merci J-M-L, tu prévois le retour du confinement...?

:slight_smile:

il pleuvait et je faisais un peu de ménage dans mes vieux montages... mes petits neveux avaient eu besoin d'un code comme cela pour un jeu il y a 2 ans) et c'était resté monté... J'ai récupéré le UNO et le buzzer pour un autre projet, mais je me suis dit que je pourrais poster avant de tout démonter

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.