void setup() {
  Serial.begin(115200); // port debug
  Serial2.begin(1200, SERIAL_7E1); // port minitel
}

void loop() {
  // redirection debug -> minitel
  while (Serial.available() > 0) {
    Serial2.write(Serial.read());
  }
 // redirection minitel -> debug
  while (Serial2.available() > 0) {
    Serial.write(Serial2.read());
  }
}

// libraries
#include <WiFi.h>
#include <WebSocketsClient.h>
#include <Minitel1B_Hard.h>

// minitel config
#define MINITEL_PORT Serial2
#define MINITEL_BAUD 4800
#define MINITEL_DISABLE_ECHO true

// wifi credentials
const char* ssid     = "mySsid";
const char* password = "myPassword";

// host parameters
char* host = "home.teletel.org";
int port = 9001;
char* path = "/";

WebSocketsClient webSocket;
Minitel minitel(MINITEL_PORT);

void setup() {

  // minitel set-up
  int baud = minitel.searchSpeed();
  if (baud != MINITEL_BAUD)
    baud = minitel.changeSpeed(MINITEL_BAUD);
  if (MINITEL_DISABLE_ECHO)
    minitel.echo(false);

  // WiFi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
    delay(10);

  // Websocket connection and callback
  webSocket.begin(host, port, path);
  webSocket.onEvent(webSocketEvent);

}

void loop() {

  // Websocket -> Minitel
  webSocket.loop();

// Minitel -> Websocket
  uint32_t key = minitel.getKeyCode(false);
  if (key != 0) {
    // prepare data to send over websocket
    uint8_t payload[4];
    size_t len = 0;
    for (len = 0; key != 0 && len < 4; len++) {
      payload[3-len] = uint8_t(key);
      key = key >> 8;
    }
    webSocket.sendTXT(payload+4-len, len);
  }
}

void webSocketEvent(WStype_t type, uint8_t * payload, size_t len) {
  if (type == Wstype_TEXT) {
      if (len > 0) {
        for (size_t i = 0; i < len; i++)
          minitel.writeByte(payload[i]);
      }
  }
}

void MiniChess::drawChessBoard() {
  // Fonction d'affichage du plateau de jeu seul
  
  // on se place en haut à gauche sur la case A8 qui est blanche
  minitel.moveCursorXY(BOARD_LEFT, BOARD_TOP);
  bool dark = false; //couleur de la case
  int cy = 8; // numero de case en ordonnées
  // on boucle sur les cases de haut en bas
  while (cy > 0) {
    int row = 1;
    // on boucle sur les 3 lignes constituant une case
    while (row <= CASE_HEIGHT) {
      int cx = 1; // numero de case en abscisses
      // on boucle sur les cases de gauche à droite
      while (cx < 9) {
        // on passe en mode texte pour afficher le nom de la case
        minitel.textMode();
        // on règle la couleur de caractère que l'on passe en arrière plan ensuite
        if (dark) minitel.attributs(CARACTERE_BLEU);
        else minitel.attributs(CARACTERE_VERT);
        minitel.attributs(INVERSION_FOND);
        if (row == 1) minitel.printChar(cx+64);    // A-H
        if (row == 2) minitel.printChar(cy+48);    // 1-8
        if (row == 3) minitel.printChar(SP);       // ESPACE
        // on passe en mode semi-graphique
        minitel.graphicMode();
        // on règle la couleur de la case
        if (dark) minitel.attributs(FOND_BLEU);
        else minitel.attributs(FOND_VERT);
        // on dessine un carcatère vide
        minitel.graphic(0b000000);
        // qu'on répète 2 fois
        minitel.repeat(CASE_WIDTH - 2);
        dark = !dark; // changement de couleur de case
        cx++; // case suivante
      }
      minitel.moveCursorLeft(CASE_WIDTH*8);
      minitel.moveCursorDown(1);
      row++; // ligne suivante
    }
  dark = !dark; // changement de couleur de case
  cy--; // rangée suivante
  }
}

#define COLOR_MASK 0b1000
#define PIECE_MASK 0b0111

    // Identification des pièces de jeux (PIECE+COLOR)
    enum { VOID, PAWN, ROOK, KNIGHT, BISHOP, QUEEN, KING}; // PIECES
    enum {_BLACK = 0b0000, _WHITE = 0b1000}; // COLORS
    
    // Pieces en 3*3 caractères semi-graphiques décrits dans le sens de la lecture
    byte piece[7][PIECE_WIDTH*PIECE_HEIGHT] = { 
      {0b000000, 0b000000, 0b000000, 0b000000, 0b000000, 0b000000, 0b000000, 0b000000, 0b000000}, // VOID
      {0b000000, 0b000000, 0b000000, 0b000101, 0b101111, 0b000000, 0b000100, 0b101100, 0b000000}, // PAWN
      {0b000010, 0b000010, 0b000010, 0b110101, 0b111101, 0b100000, 0b011100, 0b011100, 0b001000}, // ROOK
      {0b000000, 0b000111, 0b000010, 0b011110, 0b011101, 0b101010, 0b001100, 0b111100, 0b001000}, // KNIGHT
      {0b000001, 0b001011, 0b000000, 0b111111, 0b101111, 0b101010, 0b011100, 0b111100, 0b001000}, // BISHOP
      {0b001001, 0b000011, 0b001000, 0b000111, 0b101111, 0b000010, 0b111100, 0b011100, 0b101000}, // QUEEN
      {0b000001, 0b001011, 0b000000, 0b000111, 0b101111, 0b000010, 0b111100, 0b011100, 0b101000} // KING
    };

[...]

void MiniChess::drawPiece(int cx, int cy, byte pc) {
  // Fonction pour afficher une pièce dans une case donnée
  // avec pc: l'identifiant de la pièce et de la couleur
  
  // on détermine les coordonnées du curseur du minitel
  int x = cx * CASE_WIDTH + BOARD_LEFT;
  int y = cy * CASE_HEIGHT + BOARD_TOP;
  
  // on récupère la couleur et la pièce
  byte color = pc & COLOR_MASK;
  byte p = pc & PIECE_MASK;
  
  // on passe en mode graphique et on règle les couleurs
  minitel.graphicMode();
  if (color == _WHITE) {
    // effet lignage pour améliorer la netteté
    minitel.attributs(DEBUT_LIGNAGE);
    minitel.attributs(CARACTERE_BLANC);
  }
  else { // _BLACK
    minitel.attributs(CARACTERE_NOIR);
  }
  // on détermine la couleur de la case
  if ((cx+cy)%2 == 1) minitel.attributs(FOND_BLEU);
  else minitel.attributs(FOND_VERT);
  // on dessine la pièce caractère par caractère
  for (int j = 0; j < PIECE_HEIGHT; j++) {
    minitel.moveCursorXY(x+1,y+j);
    for (int i = 0; i < PIECE_WIDTH; i++) {
      minitel.graphic(piece[p][i+j*PIECE_WIDTH]);
    }
  }
  if (color == _WHITE) {
    minitel.attributs(FIN_LIGNAGE);
  }
}

void MiniChess::markCase(int cx, int cy, bool mark, bool force_erase) {
  // Fonction pour marqué la case courante dans le coin bas-gauche
  // Si mark = true, la marque de position ou de selection est affichée
  // Si mark = false, la marque est effacée sauf s’il s’agit de la selection
  // auquel cas il faut mettre force_erase = true pour l’effacer

  // on place le curseur du minitel au bon endroit
  int x = cx*CASE_WIDTH + BOARD_LEFT;
  int y = cy*CASE_HEIGHT + BOARD_TOP + 2;
  minitel.moveCursorXY(x,y);
  // on se met en mode graphique
  minitel.graphicMode();
  // on détermine si on est sur une case sombre ou claire
  bool dark = false;
  if ((cx+cy)%2 == 1) dark = true;
  // on ajuste la couleur de fond
  if (dark) minitel.attributs(FOND_BLEU);
  else minitel.attributs(FOND_VERT);
  // on détermine si on est sur la case sélectionnée 
  bool selected = (cx == scx && cy == scy);
  if (mark) {
    // on détermine la couleur à mettre
    byte color = CARACTERE_BLANC;
    if (selected && (player == _WHITE))
      color = CARACTERE_NOIR;
    if (!selected && (player == _BLACK))
      color = CARACTERE_NOIR;
    // on affiche la marque
    minitel.attributs(color);
    minitel.graphic(0b111111);
  }
  else { // mark = false
    if (!selected || force_erase)
      // on efface la marque
      minitel.graphic(0b000000);
  }
}

void MiniChess::moveUp() {
  // Fonction pour déplacer la postion courante vers le haut
  if (cy > 0) {
    markCase(cx,cy, false);
    cy--;
    markCase(cx,cy, true);
  }
}

unsigned long MiniChess::getKeyboardInput() {
  // Fonction pour rediriger les saisies du clavier
  
  unsigned long key = minitel.getKeyCode();
  switch (key) {
    
    // Annulation de la sélection en cours
    case CORRECTION:
    case ANNULATION:
    case RETOUR:
    case ESC:
    case DEL:
                               return CAN;   break;
    
    // Sélection
    case ENVOI:
    case SP:
                               return CR;    break;
    
    // Touches inhibées
    case CONNEXION_FIN:
    case SOMMAIRE:
    case REPETITION:
    case GUIDE:
    case SUITE:
                               return 0;     break;
    
    // Autres touches
    default:
                               return key;   break;
    
  }
}

void MiniChess::getMove() {
  // Fonction pour acquérir le prochain coup
  bool gotMove = false;
  
  while (!gotMove) {
    // Saisie clavier
    unsigned long c = getKeyboardInput();
    if (c != 0) {
      switch (c) {
        // Mouvement du curseur
        case TOUCHE_FLECHE_HAUT:   moveUp();    break;
        case TOUCHE_FLECHE_BAS:    moveDown();  break;
        case TOUCHE_FLECHE_GAUCHE: moveLeft();  break;
        case TOUCHE_FLECHE_DROITE: moveRight(); break;
        // Selection
        case CR:
          // Case de départ
          if (scx == -1) {
            if (validateSelection()) {
                markCase(cx, cy, true); // marqueur de selection
            }
          }
          // Case d'arrivée
          else {
            if (validateMove()) {
              gotMove = true;
            }
          }
          break;
        // Annulation
        case CAN:
          // S'il y a une selection en cours
          if (scx != -1) {
            // on efface le marqueur de selection
            markCase(scx, scy, false, true);
            // on affiche le marqueur de position si besoin
            if (scx == cx && scy == cy)
              markCase(cx, cy, true);
            scx = -1; scy = -1;
          }
          break;
      }
    }
  }
}

void MiniChess::makeMove() {
  // Fonction pour afficher le mouvement de pièces
  // en gérant les mouvements spéciaux
  drawMove();
  byte b = board[cx][cy];
  // Petit roque  et Grand roque
  if ((b & PIECE_MASK) == KING) {
    if ((b & COLOR_MASK) == _BLACK) {
      Serial.println("BLACK");
      if (scx == 4 && scy == 0) {
        if (cx == 1 && cy == 0) {
          // roque des noirs à gauche
          markCase(cx, cy, false);
          scx = 0; scy = 0;
          cx = 2; cy = 0;
          drawMove();
        }
        if (cx == 6 && cy == 0) {
          // roque des noirs à droite
          markCase(cx, cy, false);
          scx = 7; scy = 0;
          cx = 5; cy = 0;
          drawMove();
        }
      }
    }
    if ((b & COLOR_MASK) == _WHITE) {
      if (scx == 4 && scy == 7) {
        if (cx == 1 && cy == 7) {
          // roque des blancs à gauche
          markCase(cx, cy, false);
          scx = 0; scy = 7;
          cx = 2; cy = 7;
          drawMove();
        }
        if (cx == 6 && cy == 7) {
          // roque des blancs à droite
          markCase(cx, cy, false);
          scx = 7; scy = 7;
          cx = 5; cy = 7;
          drawMove();
        }
      }
    }
  }
  // Promotion de pion en Reine
  if ((b & PIECE_MASK) == PAWN) {
    if ((b & COLOR_MASK) == _BLACK) {
      if (cy == 7) {
        // promotion d'un pion noir
        byte pc = QUEEN + _BLACK;
        board[cx][cy] = pc;
        drawPiece(cx, cy, pc);
      }
    }
    if ((b & COLOR_MASK) == _WHITE) {
      if (cy == 0) {
        // promotion d'un pion blanc
        byte pc = QUEEN + _WHITE;
        board[cx][cy] = pc;
        drawPiece(cx, cy, pc);
      }
    }
  }
  // on reinitialise la selection
  scx = -1; scy = -1;
}

[…]

void MiniChess::drawMove() {
  // Fonction pour afficher un mouvement de pièce
  byte b = board[scx][scy];
  board[cx][cy] = b;
  board[scx][scy] = VOID;
  // on efface la pièce de la case d'origine
  erasePiece(scx, scy);
  // on retire le marqueur de sélection
  markCase(scx, scy, false, true);
  // on dessine la pièce à son nouvel emplacement
  drawPiece(cx, cy, board[cx][cy]);
  // on retire le marqueur de position
  markCase(cx, cy, false);
}

#include "Minichess.h"
#include <Minitel1B_Hard.h>

#define MINITEL_PORT Serial2 // For ESP32 dev board
//#define MINITEL_PORT Serial1 // For ATMEGA32u4
#define BAUDS 4800

MiniChess chess(MINITEL_PORT);

void setup() {
  // on attend que le minitel démarre
  delay(500);
  // réglage port série et config minitel
  chess.initializeMinitel(BAUDS); 

  // initialisation du jeu
  chess.drawChessBoard();     // affichage plateau vide
  chess.initPiecesPosition(); // placement des pieces
  chess.drawAllPieces();      // affichage des pièces
}

void loop() {
  while (true) {
    chess.getMove();
    chess.makeMove();
    chess.changePlayer();
  }
}