Tours de Hanoi

Bonjour à tous,

je réalise actuellement une version électronique des Tours de Hanoi sur base d'un arduino UNO et de composants électroniques simples. La tour est, dans ma réalisation, dite inversée car la base de la tour se trouve en haut.

Je partage ici mon code commenté pour ceux que ça intéresse.

Le principe technique est le suivant : quinze LED (3x cinq couleurs différentes) sont disposées et branchés en sortie et trois boutons sont disposés et branchés en entrée comme ceci :

5   5   5
4   4   4
3   3   3
2   2   2
1   1   1

b   b   b

1 = rouge, 2 = orange, 3 = jaune, 4 = vert, 5 = bleu, b = bouton

Le principe logique est le suivant : lorsque l'on appuie sur un bouton, on sélectionne la pièce disponible (allumée) la plus éloignée de la base de la tour correspondante sauf si celle-ci est vide. Ensuite, on sélectionne la tour de destination avec le bouton correspondant et on pose la pièce sélectionnée dessus. On ne peut pas poser une pièce sur une colonne où une autre pièce, plus éloignée de la base, se trouve déjà.

Voici quelques exemples de déplacements :

5  x  x    5  x  x    5  x  x    5  x  x    5  x  x    5  x  x    5  x  x    5  x  x
4  x  x    4  x  x    4  x  x    4  x  x    4  x  x    4  x  x    4  x  x    4  x  x
3  x  x    3  x  x    3  x  x    3  x  x    x  3  x    x  3  x    x  3  x    x  3  x
2  x  x    2  x  x    x  x  2    x  x  2    x  x  2    x  x  2    x  2  x    x  2  x
1  x  x    x  1  x    x  1  x    x  x  1    x  x  1    1  x  x    1  x  x    x  1  x

sens de lecture = de gauche à droite
1 = rouge, 2 = orange, 3 = jaune, 4 = vert, 5 = bleu, x = led éteinte

Et voici le code final :

/*             
 *                5 
 * TOUR DE HANOI  4
 *                  3 
 *                    2
 *                1
 *
 * copyleft anonyme
 * anarchie vaincra
 *
 */

#define PIECES 5 // nombre de couleurs
#define DEPART 1 // tour de départ

const int pinButton[3] = {14,15,16}; // entrées boutons sur A0, A1, A2
const int pinLed[PIECES][3] = {0,1,2,3,4,5,6,7,8,9,10,11,17,18,19}; // sorties LED
const int pinTone = 13; // sortie son

unsigned long lastMillis = 0; // chrono du clignotement

int thisRow, thisColor, selectedRow, selectedColor; // mémoires cycliques et de sélection
int maxColor[3]; // couleur max des tours

boolean buttonState[3], lastButtonState[3]; // états des boutons pour détecteur de bord
boolean selectMode = false; // mode : sélection ou mouvement
boolean Blink; // permutateur pour le clignotement
boolean ledState[PIECES][3]; // états des LED

void setup() { // configuration et initialisation

  pinMode(pinTone, OUTPUT); // sortie son
  
  for (thisRow = 0 ; thisRow < 3 ; thisRow++) { // pour chaque bouton

    pinMode(pinButton[thisRow], INPUT_PULLUP); // active les broches des boutons
    
    if (thisRow != DEPART) maxColor[thisRow] = PIECES+1 ; // initialise le max de départ
    else maxColor[thisRow] = 0 ;

    for (thisColor = 0 ; thisColor < PIECES ; thisColor++) { // pour chaque couleur

      pinMode(pinLed[thisColor][thisRow], OUTPUT); // active les broches des LED
      
      if (thisRow != DEPART) ledState[thisColor][thisRow] = LOW; // pour la tour de départ
      else {

        ledState[thisColor][thisRow] = HIGH; // enregistre les états
        digitalWrite(pinLed[thisColor][thisRow], HIGH); // allume la tour
      }
    }
  }
}

void loop() { // boucle principale
  
  delay(10); // stabilisation
  
  if (selectMode == true) doBlink(selectedColor, selectedRow); // clignote si demandé
  
  for (thisRow = 0 ; thisRow < 3 ; thisRow++) { // pour chaque bouton

    buttonState[thisRow] = digitalRead(pinButton[thisRow]); // lit l'entrée
    
    if (buttonState[thisRow] != lastButtonState[thisRow]){ // décèle un changement

      lastButtonState[thisRow] = buttonState[thisRow]; // réinitialise l'état
      
      if (buttonState[thisRow] == LOW){ // lorsque le bouton est enfoncé

        if (selectMode == false){ // si aucune sélection n'est en cours

          if (maxColor[thisRow] == PIECES+1) tone(pinTone, 220, 80); // tour vide
          else {
          
            selectedRow = thisRow; // enregistre la colonne
            selectedColor = maxColor[thisRow]; // enregistre la couleur

            selectMode =! selectMode; // permute le mode
          }
        }
        
        else { // si une sélection est faite

          if (maxColor[selectedRow] > maxColor[thisRow]) tone(pinTone, 220, 80); // mouvement invalide
          else {
          
            ledState[selectedColor][selectedRow] = LOW; // retire du registre
            ledState[selectedColor][thisRow] = HIGH; // ajoute au registre
            
            maxColor[thisRow] = maxColor[selectedRow]; // transfère le max
            maxColor[selectedRow] = refreshMax(selectedRow); // actualise le max originel

            digitalWrite(pinLed[selectedColor][selectedRow], LOW); // éteind
            digitalWrite(pinLed[selectedColor][thisRow], HIGH); // allume
            
            selectMode =! selectMode; // permute le mode
          }
        }
      }
    }
  }
}

void doBlink(int color, int row) { // fonction clignotement

  if(millis() - lastMillis > 80) { // si 0,08s sont écoulées

    Blink =! Blink; // permute l'état
    digitalWrite(pinLed[color][row], Blink); // écrit l'état
    lastMillis = millis(); // enregistre le temps
  }
}

int refreshMax(int row) { // fonction rafraichissement du max

  for (int color = 0 ; color < PIECES ; color++) { // pour chaque couleur

    if (ledState[color][row] == HIGH) return color; // teste à partir de 0 et renvoit si allumé
  }
  return PIECES+1; // si rien n'est allumé, renvoit le code "tour vide"
}

En espérant que cela permette à d'autres de saisir la remarquable opportunité qu'offre l'arduino à ceux qui souhaiteraient approfondir leurs connaissances en électronique et en programmation sur des bases saines.

Il est aussi à noter que ce jeu est utilisé dans le domaine de la psychologie cognitive et est très agréable pour les enfants (que nous avons ou que nous sommes).