Dans ce projet on va voir comment faire un jeu de ping pong sur arduino. Vous pouvez jouer à deux joueurs. Chaque joueur à deux boutons, un pour déplacer la raquette vers le haut et l’autre vers le bas.

Un point est marqué quand l’adversaire n’a pas réussi à renvoyer la balle. Le premier joueur qui marque 10 points gagne.

Difficulté :

Matériel nécessaire

On va maintenant le matériel nécessaire pour le projet :

Schéma du projet

Simulation du projet

Voici la simulation du projet  sur tinkercad :

Programme du projet

#include <LiquidCrystal.h>
#include <avr/interrupt.h>


// On assigne les pins pour l'écran
#define RS 12
#define EN 11
#define D4 6
#define D5 7
#define D6 8
#define D7 9

// On définit les boutons qui font bouger les raquettes
#define Player_1_moveDownButton 10   // Bouton du bas pour le joueur 1
#define Player_1_moveUpButton 5     //  Bouton du haut pour le joueur 1
#define Player_2_moveDownButton 2   // Bouton du bas pour le joueur 2
#define Player_2_moveUpButton 3     // Bouton du haut pour le joueur 1

// On assigne le numéro à chaque joueur
#define Player_1 1
#define Player_2 2

// Temps d'attente entre deux mise à jour de la balle
#define DiagonalballUpdateTime 21
#define HorizontalballUpdateTime 15

//Départ de la position de la balle  
#define Start_X 35
#define Start_Y 7

#define Button_Pressed (p1_UpButState | p1_DownButState | p2_UpButState | p2_DownButState)

void(* resetFunc) (void) = 0;       

// Variable globale pour les interruptions
volatile boolean x_Up = true;
volatile boolean x_Down = true;

LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);    // On initialise l'écran LCD


class Paddle // Class pour la raquette
{
  public:
  // On stock la valeur de chaque ligne contenant la raquette
  uint8_t PaddleColArray[16] = {0,0,0,0,0,4,4,4,0,0,0,0,0,0,0,0};
  
  uint8_t PaddlePos = 6;     // On enregistre le centre de la raquette comme référence pour la faire bouger
  uint8_t Score = 0;         // Score de chaque joueur

  void MovePaddleUp()   // Quand le joueur appuie sur le bouton haut
  {
    //On vérifie que la raquette ne sorte pas de l'écran
    if(PaddlePos != 1)
    {
      PaddlePos--;
      PaddleColArray[PaddlePos+2]=0;
      PaddleColArray[PaddlePos-1]=4;
    }
  }

  // Quand le joueur appuie sur le bouton du bas
  void MovePaddleDown()
  {
    if(PaddlePos != 14)
    {
      PaddlePos++;
      PaddleColArray[PaddlePos-2]=0;
      PaddleColArray[PaddlePos+1]=4;
    }
  }

  // Afficher les raquettes avec le numéro des joueurs
  void PrintPaddles(uint8_t Player_Num)
  {
    if(Player_Num == 2)
    {
      // Chaque joueur a un unique numéro à afficher
      lcd.createChar(0, PaddleColArray);
      lcd.createChar(1, PaddleColArray+8);

      lcd.setCursor(14, 0);
      lcd.write(byte(0));
      
      lcd.setCursor(14, 1);
      lcd.write(byte(1));
    }
  
     else
    {
      lcd.createChar(2, PaddleColArray);
      lcd.createChar(3, PaddleColArray+8);
  
      lcd.setCursor(1, 0);
      lcd.write(byte(2));

      lcd.setCursor(1, 1);
      lcd.write(byte(3));
    }
  }
};

// On créer les deux raquettes 
Paddle p1, p2;

class Print_Game
{
  public:

  // On affiche le texte quand le jeu démarre
  void Start_Game()
  {
    lcd.print(F("   PING PONG "));
    
    //On se met sur la deuxième ligne
    lcd.setCursor(0, 1);
    lcd.print(F("Appuyez sur un bouton ! "));

    // Variable poru récupérer si un bouton est appuyé
    uint8_t p1_UpButState = 0;
    uint8_t p1_DownButState = 0;
    uint8_t p2_UpButState = 0;
    uint8_t p2_DownButState = 0;
    
    // On attend qu'un bouton soit appuyé
    while(!(Button_Pressed))
    {
      p1_UpButState = ((PIND & (1 << Player_1_moveUpButton)) );
      p1_DownButState = ((PINB & (1 << (Player_1_moveDownButton-8))) );
      p2_UpButState = ((PIND & (1 << Player_2_moveUpButton)) );
      p2_DownButState = ((PIND & (1 << Player_2_moveDownButton)) );
    }
    lcd.clear();        //On efface l'écran pour commencer le jeu
  }

  // On affiche le score de chaque joueur
  void Print_Score()
  {
    // On efface l'écran LCD pour afficher les scores
    lcd.clear();
    lcd.print(F("Joueur_1  Joueur_2"));
    lcd.setCursor(3 ,1);
    lcd.print(p1.Score);
    lcd.setCursor(12 ,1);
    lcd.print(p2.Score);

    // Les scores s'affcihent pendant 2 secondes
    delay(2000);

    // On efface l'écran pour continuer le jeu
    lcd.clear();
  }

  // On affiche le gagnant
  void Print_Winner(int Player_Num)
  {
    lcd.setCursor(0 ,0);
    lcd.print(F("   PERDU   "));
    lcd.setCursor(1, 1);
    lcd.print(F("Joueur "));
    lcd.print(Player_Num);
    lcd.setCursor(11 ,1);
    lcd.print(F("a gagne"));

    // Le texte reste pendant 5 secondes
    delay(5000);

    // On reintialise le jeu
    resetFunc();
  }
};

Print_Game g;

class Ball // Fonction pour la balle
{
  private:

  //Pour reintialiser la balle et les raquettes qaund un point est marqué Flag to reset the ball and paddles when a point is scored
  uint8_t Point_Scored = 0;
  
  uint8_t ballYDir = 0;       
  uint8_t ballXDir = -1;    

  // La localisation de la balle
  uint8_t ballY = Start_Y;
  uint8_t ballX = Start_X;
  
  uint8_t ballCharArray[8] = {0, 0, 0, 0, 0, 0, 0, 16};

  public:

  void GenerateBallArray();

  void PrintBall();

  void UpdateBall(uint8_t , uint8_t);

  void AwardAPoint();
};


void Ball :: GenerateBallArray() // Generation de la balle
{
  for(uint8_t i=0; i<8; i++)
    {
    if(i == (ballY % 8))
    {
      ballCharArray[i] = 2 << (4 - (ballX % 5));
    }
    else
    {
      ballCharArray[i] = 0;
    }
  }
}

void Ball :: PrintBall() // On affiche la balle
{
   uint8_t LCDCol = ballX / 5;
  uint8_t LCDRow = (ballY <= 7) ? 0 : 1;
  lcd.createChar(4, ballCharArray);
  lcd.setCursor(LCDCol,LCDRow);
  lcd.write(byte(4));
}

// Mise à jour de la position de la balle
void Ball :: UpdateBall(uint8_t P1_PaddlePos, uint8_t P2_PaddlePos)
{
  // Petite pause avant la mise à jour
  // La balle à la même vitesse quand cela va en diagonale
  if(ballYDir)          // Quand la balle va en diagonale 
  {
    delay(DiagonalballUpdateTime);
  }
  else                  // Quand la balle va à l'horizontale
  {
    delay(HorizontalballUpdateTime);
  }

  //Calculer la prochaine position de la balle
  
  //Si la balle sort de la raquette
  if((ballX <= 6) || (ballX >= 73))
  {
    AwardAPoint();
  }

 // Si la balle touche la raquette du joueur 2
  else if(ballX == 72)
  {
    // si la balle touche au milieu de la raquette
    if(ballY == P2_PaddlePos)
    {
      ballXDir = -1;
    }

    // Si la balle touche le bas de la raquette
    else if(ballY == (P2_PaddlePos + 1))
    {
      ballXDir = -1;
      if(ballY == 15)    // Si la balle touche le bas de la raquette au bout de l'écran
      {
        ballYDir = -1;
      }
      else
      {
        ballYDir = 1;
      }
    }

    // Si la balle touche la haut de la raquette du joueur 2
    else if(ballY == (P2_PaddlePos - 1)){
      ballXDir = -1;
      if(ballY == 0)    // Si la balle touche le haut de la raquette et le haut de l'écran
      {
        ballYDir = 1;
      }
      else
      {
        ballYDir = -1;
      }
    }
  }

  //si la balle est au milieu de la raquette
  else if(ballX == 7)
  {
    if(ballY == P1_PaddlePos)
    {
      ballXDir = 1;
    }
        // Si la balle touche le bas de la raquette
    else if(ballY == (P1_PaddlePos + 1)){
      ballXDir = 1;
      if(ballY == 15)   // Si la balle touche le bas de la raquette au bout de l'écran
      {
        ballYDir = -1;
      }
      else
      {
        ballYDir = 1;
      }
    }

     // Si la balle touche la haut de la raquette du joueur 1
    else if(ballY == (P1_PaddlePos - 1))
    { 
      ballXDir = 1;
      if(ballY == 0)    // Si la balle touche le haut de la raquette et le haut de l'écran
      {
        ballYDir = 1;
      }
      else
      {
        ballYDir = -1;
      }
    }
  }

  // Si la balle touche un bord de l'écran
  else if((ballY == 0) || (ballY == 15))
  {
    ballYDir *= -1;
  }
    
  //On reintialise la ballee t les raquettes si un point a été gagné 
  if(Point_Scored == 1)
  {
    // On reintialise la balle
    ballX = Start_X;
    ballY = Start_Y;
    ballXDir *= -1;
    ballYDir = 0;

    // On reintialise les raquettes
    p1.PaddlePos = 6;
    p2.PaddlePos = 6;
    
    for(uint8_t i=0; i<16; i++)
    {
      if((i==5) || (i==6) || (i==7))
      {
        p1.PaddleColArray[i] = 4;
        p2.PaddleColArray[i] = 4;
      }
      else
      {
        p1.PaddleColArray[i] = 0;
        p2.PaddleColArray[i] = 0;
      }
    }
    
    Point_Scored = 0;    // On reintialise le score
  }

  //  On efface la balle de sa position initiale
  uint8_t LCDCol = ballX / 5;
  uint8_t LCDRow = (ballY <= 7) ? 0 : 1;
  lcd.setCursor(LCDCol, LCDRow);
  lcd.print(" ");
  
  // On change la position de la balle basé sur les directions de X et Y  
  ballX += ballXDir;
  ballY += ballYDir;

  GenerateBallArray();

  // On affiche la balle après avoir calculé son nouvel emplacement
  PrintBall();
}

// On augmente le score des joueurs une fois un point marqué
void Ball :: AwardAPoint()
{
  if(ballX <= 8)    // Quand le joueur 2 marque un point 
  {
    p2.Score++;
  }
    
  else              // Quand le joueur 1 marque un point 
  {
    p1.Score++;
  }
  
  // Quand un des joueurs arrivent à 10 points
  
  if(p1.Score == 10)            // Si le joueur 1 a atteint 10 points
  {
    g.Print_Winner(Player_1);   //On affiche joueur 1 a gagné
  }

  else if(p2.Score == 10)       // Si le joueur 2 a atteint 10 points
  {
    g.Print_Winner(Player_2);   //On affiche joueur 2 a gagné
  }

   // On affiche le score des deux joueurs
  g.Print_Score();
    
  Point_Scored = 1;        
}

Ball b;


void setup()
{
  lcd.begin(16, 2); // On initialize l'écran LCD
  
  // On initalize les boutons
  DDRD &= ~(1<<Player_1_moveUpButton);
  DDRD &= ~(1<<Player_1_moveDownButton);
  DDRD &= ~(1<<Player_2_moveUpButton);
  DDRD &= ~(1<<Player_2_moveDownButton);
  
  g.Start_Game();
  
  // On affiche les raquettes
  p1.PrintPaddles(Player_1);
  p2.PrintPaddles(Player_2);
  
  // On affiche la balle sur l'écran LCD
  b.PrintBall();


  PCMSK2 |= (1 << PCINT21);
  PCICR |= (1 << PCIE2);

  PCMSK0 |= (1 << PCINT2);
  PCICR |= (1 << PCIE0);

  sei();
  
  attachInterrupt(digitalPinToInterrupt(Player_2_moveDownButton), P2_Move_Down, RISING);
  attachInterrupt(digitalPinToInterrupt(Player_2_moveUpButton), P2_Move_Up, RISING);
}


void loop()
{
  //On affiche les deux raquettes
  p1.PrintPaddles(Player_1);
  p2.PrintPaddles(Player_2);
  
  // Mise à jour de la position de la balle
  b.UpdateBall(p1.PaddlePos, p2.PaddlePos);
}

void P2_Move_Down()
{
  p2.MovePaddleDown();
}

void P2_Move_Up()
{
  p2.MovePaddleUp();
}

ISR(PCINT2_vect)
{
  if(x_Up)
  {
    p1.MovePaddleUp();
    x_Up = false;
  }
  else
  {
    x_Up = true;
  }
}

ISR(PCINT0_vect)
{
  if(x_Down)
  {
    p1.MovePaddleDown();
    x_Down = false;
  }
  else
  {
    x_Down = true;
  }
}

Gestion du bord de l'écran

Lorsque la balle atteint un bord de l’écran celle-ci est renvoyé comme avec un mur et ne passe pas de l’autre côté de l’écran.