Plan du site  
pixel
pixel

Articles - Étudiants SUPINFO

Square game in C++ Part2

Par Ayoub BELMAHFOUD Publié le 12/07/2018 à 14:27:06 Noter cet article:
(0 votes)
Avis favorable du comité de lecture

Game development

As indicated in the end of the first part of this article, we will now move on to developing the most interesting part of this project wish is artificial intelligence.

AI

First What is AI? It's a set of algorithm and theories that will allow us to simulate intelligence.

Before tackling the code, I'd like to explain the logic behind the AI ​​that we will implement in this project.

In this of we can divide the moves played in three different moves:

Winning move: This is a move that will allow the player to draw the missing side of a square and thus to replay.

Losing move: This is a move in which the player draws a side of the square that will allow the opposing player in his turn to draw the missing side of the square. So a losing move is automatically followed by a winning move by the opponent (unless he didn't saw it).

Neutral move: This is a move in wish the side of the square that was drawn does not allows neither the player to finish a square nor the opposing player to finish it (So ​​a neutral move is never followed by a winning move).

In order for our AI to be effective, it must be able to distinguish between different types of moves. We should therefore set up algorithms that will allow our AI to differentiate the different moves and to choose the best move to play it.

Without further ado I'll show you what our class AI looks like and explain the different functions and algorithm of this class.

#pragma once
#include "Player.h"
#include "stdafx.h"
#include <iostream>
#include <vector>
using namespace std;
enum Direction { top, bottom, right, left };
enum Level { hard, veryHard };

class IA : public Player
{
public:
  IA();
  IA(Level lvl);
  ~IA();
  virtual vector<int> play(vector<vector<vector<char>>> &grid);
private:
  Level level;
  vector<int> moveChoice(vector<vector<vector<char>>> &grid);
  int sideSquareEmpty(vector<vector<vector<char>>> &grid, int x, int y, int z, Direction direction);
  void moveType(int emptySideNumber, vector<vector<int>> &squareMadeMove, vector<vector<int>> &squareGivenMove, vector<vector<int>> &noSquareMove, int x, int y, int z);
  void simulationLoosingMove(vector<vector<vector<char>>> &grid, int &nbrSquareMade , vector<vector<int>> &simulationAllcordinate);
  vector<int> getBestLoosingMove(vector<vector<vector<int>>> &allSquareGivenSimulationResultsquareGivenMove ,vector<int> &simulationNumberSquareResult);
};


  

We find in the top of our .h file two enum that will just allow us to better organize our code and make it more readable.

The main function of our class will be "moveChoice", we will explain this function as last because it includes all the other private functions and so to better understand the functioning of this function we will start by explaining it.

sideSquareEmpty

This function will allow the AI ​​to determine the empty side number of a square in case it decides to draw one of the four sides of that square. This function takes as parameter the game grid, the side of the square in question and a direction which will determine wish sqaure we will verify, the one located on the right, on the left, at the top or bottom of the side in parameter.

All this may not seem very clear, but as i explain to you the use of each function, you will understand the usefulness of each function.

moveType

This function will determine the type of move (winning, losing, neutral) if the AI ​​decides to draw a side. It takes in parameter an integer which will determine the number of empty side, this one will be deduced from the function seen previously, a table of winning moves, a table of neutral moves and a table of losing moves, I would return to the utility of these tables later and finally the coordinates of the side in question.

The operation of this function is simple, if the number of empty side is equal to 0 it would mean that drawing the side in question the AI ​​will complete a square and so it will be a winning move, if it is equal at 1 it will mean that by drawing this side the AI ​​will allow the opposing player to finish this square and therefore it will be a losing move, if it is greater than 1 it would mean that the square in question has several empty sides and so drawing a side of this square will neither allow the AI ​​to finish this square nor the player who will play after and so it will be a neutral shot. Once the type of shots is deducted, it will be classified in the respective table seen before.

simulationLoosingMove

Knowing that our AI aims to play the best move possible and therefore to focus on winning moves and avoid losing moves at all costs, the AI can end up in a situation where the only choice that will be presented to him will be to play a losing move among several others. What I have not explained to you yet is that there is a difference between losing moves, some may allow the opposing player to finish more squares than others. To better understand, below these 2 images.

We notice in the first image that we can only play a losing move, then whatever the side we draw the next player will finish one or more squares.

In this second image we can notice the difference between drawing the side in red which will generate only the formation of 2 squares, and drawing the side in blue which on the other hand will allow the opposing player to make 9 squares.

You may have guessed it but so that we can calculate the number of total squares that can be made at the end it will simulate the course of the game after playing the losing move until it's no longer possible to make a square. And so it will be a recursive function that will call itself as a square can be made. It will classify losing moves according to the number of squares made.

However, to be able to do all the calculations of the simulation, this function may be very expensive in terms of time and performance. This is why we will introduce a concept of dynamic programming that is based on the storage of the result of the function so we will not repeat the same calculations. To clarify things to you I will show you this example.

In this first image we notice that all possible moves are losing moves.

In the image above and the image below the result after drawing the blue side.

In the image above and the image below the result after drawing the sky blue side.

We find the same result regardless of the first side drawn, you'll understand if we started with any side in purple we would reach the same result, it would be useless to do the simulation starting with one side in purple because we will only repeat the same calculations which is not very wise. The solution will be to record all the sides drawn during a simulation and give them the same result.

We come back now to the functioning of the simulationLoosingMove function, it takes as parameter the game grid, an integer in which we will store the number of squares made in this simulation and finally a table in which we can store the coordinates of all sides we have drawn in this simulation to give them the same result and avoid making the same calculations as explained in the paragraph above.

This function will simulate the progression of the game after playing a losing move, each time a square is made, the integer representing the squares will be incremented and the coordinates of the drawn sides will be recorded in the table, the function will stop when no more square can be made.

getBestLoosingMove

This function is simple, it takes as parameter an array that includes all the coordinates traveled during a simulation and another table that contains the result of square made respectively. It only compares the different losing moves according to the number of square ends to finally choose the one that allows to make the least squares possible. Nothing complicated for this function.

moveChoice

Now place to the main function of this class which thanks to it, the AI ​​will determine the move it will play, this function will obviously use all functions seen previously.

This function takes as a single parameter, the grid of the game, we loop on this grid to be able to classify the different possible moves, each time we find ourselves on the coordinates of a side that has not been drawn, we check what type of move it will be, if it is a losing move we save the coordinates in a table of losing moves, if it is a neutral move we save the coordinates in a table of neutral moves and if it is a winning move, AI do not bother to keep going through the grid, we stop there and the AI ​​plays the coordinates of this side. If on the other hand after having traversed all the grid, and none winning move has been found, the AI will choose in a random way a neutral move among the various neutral moves stored in the appropriate table. And if, on the other hand, we do not find a winning move or a neutral move, we end up in the case where the AI ​​is forced to play a losing move, here is the difference between the two AIs we created (the HARD and VERY HARD). The HARD will be choose a move to play randomly a losing move among all the losing moves, sin the opposit the AI VERY HARD will make a simulation on all the losing moves to choose the one that results at least squares possible.

Without further ado I put you below the source code of the class IA.c with some comment to help you understand. Feel free to re-read this part to fully understand how the different algoritms work in this class.

#include "stdafx.h"
#include "IA.h"


IA::IA()
{
}


IA::~IA()
{
}

IA::IA(Level lvl) {
  this->level = lvl;
  if (lvl == Level::hard)
    this->name = "IA Hard";
  else
    this->name = "IA Very Hard";
}

//Function that returns the move that the AI ​​has chosen.
vector<int> IA::play(vector<vector<vector<char>>> &grid) {
  return moveChoice(grid);
}

//Function that chooses which move the AI ​​will play. To choose the right move to play the AI loop on the entire grid and does its calculations on the empty coordinates of the grid.
vector<int> IA::moveChoice(vector<vector<vector<char>>> &grid) {
  int x, y, z;
  int emptySideNumber = 0;
  int emptySideNumber2 = 0;
  int nbrSquareMade = 0;
  bool coordinateFound = false;
  vector<vector<vector<int>>> classifiedLoosingMoveSimulation; //Tabealu ou seront classé tout les coups perdant selon le nombre de carré qui seront fait en consequant.
  vector<vector<int>> loosingMoveSimulationResult; // Tableau qui va contenir toutes les coordonée que la fonction "loosingMooveSimulation" a parcouru pour éviter de relancer cette fonction avec ses cordonnées
  vector<int> simulationNumberSquareResult;
  vector<vector<vector<char>>> gridCopie;
  vector<vector<int>> winningMove;    //Tableau contenant l'enssemble des coordonée d'un coup gagnant.
  vector<vector<int>> loosingMove;  //Tableau contenant l'enssemble des coordonée d'un coup perdant.
  vector<vector<int>> neutralMove;    //Tableau contenant l'enssemble des coordonée d'un coup neutre.
  for (int x(0); x < grid.size(); x++) {
    for (int z(0); z < 2; z++) {
      for (int y(0); y < grid.size(); y++) {
        if (grid[x][y][z] != 'o') { // Vérifie que ces coordonées ne contiennent pas un coté.
          if (z == 0) {
            if (x == 0) {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::top);
              moveType(emptySideNumber, winningMove, loosingMove, neutralMove, x, y, z);
              emptySideNumber = 0;
            }
            else {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::top); // Vérifie combien de coté vide contient le carré au dessus de ses coordonées.
              emptySideNumber2 = sideSquareEmpty(grid, x, y, z, Direction::bottom);// Vérifie combien de coté vide contient le carré en dessous de ses coordonées.

              if (emptySideNumber > emptySideNumber2) 
                moveType(emptySideNumber2, winningMove, loosingMove, neutralMove, x, y, z);// On fait appel a la fonction qui détérmine de quel type de moove il s'agit
              else
                moveType(emptySideNumber, winningMove, loosingMove, neutralMove, x, y, z);

              emptySideNumber = 0;
              emptySideNumber2 = 0;
            }
          }
          else if (z == 1) {
            if (y == 0) {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::right);
              moveType(emptySideNumber, winningMove, loosingMove, neutralMove, x, y, z);
              emptySideNumber = 0;
            }
            else {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::right);
              emptySideNumber2 = sideSquareEmpty(grid, x, y, z, Direction::left);

              if (emptySideNumber > emptySideNumber2)
                moveType(emptySideNumber2, winningMove, loosingMove, neutralMove, x, y, z);
              else
                moveType(emptySideNumber, winningMove, loosingMove, neutralMove, x, y, z);

              emptySideNumber = 0;
              emptySideNumber2 = 0;
            }
          }
        }
        if (!winningMove.empty()) //Si il s'agit d'un coup gagnant on s'arrete la sans continuer le parcours du restant de la grille et on retourne ce coup
          return winningMove[0];      
      }
    }
  }
  //If we arrive here it means that no winning move was found.
   if (!neutralMove.empty()) { //Si il exisite un coup neutre l'IA en choisit un par hasard parmis tout les coups neutre.
    int x = rand() % neutralMove.size();
    return neutralMove[x];
  }
  //Here we are in the case where the AI ​​must play a losing move
  else {
    if (this->level == Level::hard) { //Si il s'agit de l'IA hard qui est l'IA la plus facile parmis les deux, elle choisit comme elle le fait pour les coup neutre un au hasard
      int x = rand() % loosingMove.size();
      return loosingMove[x];
    }
    else { //If it is the most difficult AI (veryHard) it uses a recursive function that checks how much square the opponent can make for each losing move and then AI will choose the losing move with the less square made.
      for (int i(0); i < loosingMove.size(); i++) { 
        gridCopie = grid;
        x = loosingMove[i][0];
        y = loosingMove[i][1];
        z = loosingMove[i][2];
        if (i != 0) {
          for (int i(0); i < classifiedLoosingMoveSimulation.size(); i++) {
            if (coordinateFound)
              break;
            for (int j(0); j < classifiedLoosingMoveSimulation[i].size(); j++) {
              if (classifiedLoosingMoveSimulation[i][j][0] == x && classifiedLoosingMoveSimulation[i][j][1] == y && classifiedLoosingMoveSimulation[i][j][2] == z) {
                coordinateFound = true;
                break;
              }

            }
          }
        }
        if (!coordinateFound) { // we check if the recursive function has already passed through these coordinates to not redo unnecessary calculations

          gridCopie[x][y][z] = 'o';
          simulationLoosingMove(gridCopie, nbrSquareMade, loosingMoveSimulationResult);
          classifiedLoosingMoveSimulation.push_back(loosingMoveSimulationResult);
          simulationNumberSquareResult.push_back(nbrSquareMade);
        }
        loosingMoveSimulationResult.clear();
        nbrSquareMade = 0;
        coordinateFound = false;
      }
      return getBestLoosingMove(classifiedLoosingMoveSimulation, simulationNumberSquareResult);
    }



  }

}

//Function that returns the number of empty sides of a square
int IA::sideSquareEmpty(vector<vector<vector<char>>> &grid, int x, int y, int z, Direction direction) {
  int emptySideNumber = 0;
  if (direction == Direction::top) { //carré au dessus des coordonées
    if (grid[x - 1][y][z + 1] != 'o')
      emptySideNumber++;
    if (grid[x - 1][y + 1][z + 1] != 'o')
      emptySideNumber++;
    if (grid[x - 1][y][z] != 'o')
      emptySideNumber++;
  }

  else if (direction == Direction::bottom) {//carré en dessous des coordonées
    if (grid[x][y][z + 1] != 'o')
      emptySideNumber++;
    if (grid[x][y + 1][z + 1] != 'o')
      emptySideNumber++;
    if (grid[x + 1][y][z] != 'o')
      emptySideNumber++;
  }
  else if (direction == Direction::right) {//carré à droite des coordonées
    if (grid[x][y][z - 1] != 'o')
      emptySideNumber++;
    if (grid[x + 1][y][z - 1] != 'o')
      emptySideNumber++;
    if (grid[x][y + 1][z] != 'o')
      emptySideNumber++;
  }
  else if (direction == Direction::left) {//carré à gauche des coordonées
    if (grid[x][y - 1][z - 1] != 'o')
      emptySideNumber++;
    if (grid[x + 1][y - 1][z - 1] != 'o')
      emptySideNumber++;
    if (grid[x][y - 1][z] != 'o')
      emptySideNumber++;
  }

  return emptySideNumber;
}

//Function that determines the type of the move according to the number of empty sides of a square
void IA::moveType(int emptySideNumber, vector<vector<int>> &winningMove, vector<vector<int>> &loosingMove, vector<vector<int>> &neutralMove, int x, int y, int z) {
  if (emptySideNumber == 0)
    winningMove.push_back({ x,y,z });
  else if (emptySideNumber == 1)
    loosingMove.push_back({ x,y,z });
  else                 
    neutralMove.push_back({ x,y,z });
}

//Function that will simulate a losing move to calculate the number of squares that can be made.
void IA::simulationLoosingMove(vector<vector<vector<char>>> &grid, int &nbrSquareMade , vector<vector<int>> &simulationAllcordinate) {
  int emptySideNumber = 0;
  int emptySideNumber2 = 0;
  for (int x(0); x < grid.size(); x++) {
    for (int z(0); z < 2; z++) {
      for (int y(0); y < grid.size(); y++) {
        if (grid[x][y][z] != 'o') {
          if (z == 0) {
            if (x == 0) {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::top);
              if (emptySideNumber == 0) {
                grid[x][y][z] = 'o';
                nbrSquareMade++;
                simulationAllcordinate.push_back({ x,y,z });
                simulationLoosingMove(grid, nbrSquareMade, simulationAllcordinate);
              }

            }
            else {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::top); 
              emptySideNumber2 = sideSquareEmpty(grid, x, y, z, Direction::bottom);
              if (emptySideNumber == 0) {       //Si un carre est fait elle s'apelle elle meme avec le nouveau coup perdant.
                if (emptySideNumber2 == 0)
                  nbrSquareMade++;
                grid[x][y][z] = 'o';
                nbrSquareMade++;
                simulationAllcordinate.push_back({ x,y,z });
                simulationLoosingMove(grid, nbrSquareMade, simulationAllcordinate); 
              }
              else if (emptySideNumber2 == 0) {
                grid[x][y][z] = 'o';
                nbrSquareMade++;
                simulationAllcordinate.push_back({ x,y,z });
                simulationLoosingMove(grid, nbrSquareMade, simulationAllcordinate);
              }

            }
          }
          else if (z == 1) {
            if (y == 0) {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::right);
              if (emptySideNumber == 0) {
                grid[x][y][z] = 'o';
                nbrSquareMade++;
                simulationAllcordinate.push_back({ x,y,z });
                simulationLoosingMove(grid, nbrSquareMade, simulationAllcordinate);
              }
            }
            else {
              emptySideNumber = sideSquareEmpty(grid, x, y, z, Direction::right);
              emptySideNumber2 = sideSquareEmpty(grid, x, y, z, Direction::left);
              if (emptySideNumber == 0) {
                if (emptySideNumber2 == 0)
                  nbrSquareMade++;
                grid[x][y][z] = 'o';
                nbrSquareMade++;
                simulationAllcordinate.push_back({ x,y,z });
                simulationLoosingMove(grid, nbrSquareMade, simulationAllcordinate);
              }

              else if (emptySideNumber2 == 0) {
                grid[x][y][z] = 'o';
                nbrSquareMade++;
                simulationAllcordinate.push_back({ x,y,z });
                simulationLoosingMove(grid, nbrSquareMade, simulationAllcordinate);
              }

            }
          }
        }
      }
    }
  }



}

//This function allow the AI to choose the best losing move among all the losing moves
vector<int> IA::getBestLoosingMove(vector<vector<vector<int>>> &classifiedLoosingMoveSimulation, vector<int> &loosingMoveSimulationResult) {

  vector<int> bestMoove = classifiedLoosingMoveSimulation[0][0];
  for (int i(1); i < loosingMoveSimulationResult.size(); i++) {
    if (loosingMoveSimulationResult[i] < loosingMoveSimulationResult[i - 1])
      bestMoove = classifiedLoosingMoveSimulation[i][0];
  }
  return bestMoove;
}
  

GameOfSquare

Now we'll see the "main" class of our program, nothing complicated, it is the class that will contain the loop of the game, we will have to set up our menu to choose the game mode that we wish either Player vs AI or AI vs AI we can choose the difficulty of the AI ​​and the size of the grid, and the possibility of replaying or not at the end of the game. Our menu will look like this:

Sans plus attendre je vous mets le code source de cette classe qui n’a rien de compliqué.

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <time.h>
#include "Player.h"
#include "Grid.h"
#include "IA.h"
#include <string>
using namespace std;

void generalMenu(int &modeChoice , int &difficultyChoice , int &sizeGrid , vector<Player*> &players);
void game(int sizeGrid, vector<Player*> &players);
bool rePlay();

int main()
{

  int sizeGrid;
  int difficultyChoice = 0;
  int modeChoice = 0;
  vector<Player*> players;
  srand(time(NULL));
      
  

  do {
    generalMenu(modeChoice, difficultyChoice, sizeGrid, players);
    game(sizeGrid, players);
  } while (rePlay());


  return 0;
}


//Function that deals with the general menu of the game namely the choice of the game mode, the choice of the size of the gridgénéral du jeu à savoir le choix du mode de jeu, le choix de la taille de la grille
void generalMenu(int &modeChoice, int &difficultyChoice, int &sizeGrid , vector<Player*> &players) {
  sizeGrid = 0;
  difficultyChoice = 0;
  modeChoice = 0;

  //Description of the game

  cout << " ******        Welcome to the game of squares               " << endl;
  cout << "the aim of the game is to make the maximum squares in the grid by drawing side by side. " << endl;
  cout << "To draw a side of a square, you need to choose 3 coordinates" << endl << endl;
  cout << "x : Corresponds to the line where you want to insert the side of the square." << endl;
  cout << "y : Corresponds to the column where you want to insert the side of the square." << endl;
  cout << "z : Corresponds to the dimension of the square side horizontal(-) z = 0 or vertical(| ) z = 1" << endl << endl << endl;

  //Game mode choice

  cout << "Please choose 1 of the 3 game mode : " << endl << endl;
  cout << "1 : Play against AI(by default)" << endl;
  cout << "2 : IA versus IA" << endl << endl;
  cout << "Your Choice : ";
  cin >> modeChoice;
  if (!int(modeChoice)) {
    cin.clear();
    cin.ignore(100, '\n');
    modeChoice = 1;
  }

  
  cout << endl << endl;
  if (modeChoice == 2) {      //If the choice is IA vs IA we update our player table with the 2IA.
    players.push_back(new IA(Level::hard));
    players.push_back(new IA(Level::veryHard));
  }
  else {              //If not we choose the difficulty of the AI ​​that play our human player.
    cout << "Please choose the difficulty of the AI : " << endl << endl;
    cout << "1 Difficult (par defaut)" << endl;
    cout << "2 Very difficult" << endl << endl;
    cout << "Votre choix : ";
    cin >> difficultyChoice;
    if (!int(difficultyChoice)) {
      cin.clear();
      cin.ignore(100, '\n');
      difficultyChoice = 1;
    }

    if (difficultyChoice == 2) {
      players.push_back(new Player());
      players.push_back(new IA(Level::veryHard));
    }
    else {
      players.push_back(new Player());
      players.push_back(new IA(Level::hard));
    }
    cout << endl << endl;
  }

  //Grid size choice
  do {
    cout << "Veuillez saisir la taille de la grille : ";
    cin >> sizeGrid;
    if (!int(sizeGrid)) {
      cin.clear();
      cin.ignore(100, '\n');
      sizeGrid = 0;
    }
    else if (sizeGrid < 3)
      cout << "Veuillez saisir une taille superieure ou egale a 3" << endl << endl;
  } while (sizeGrid < 3);
}


//this function is the main loop of the game, it is called after the general menu
void game(int sizeGrid, vector<Player*> &players) {
  
  Grid grid = Grid(sizeGrid);
  int nbrSquareCreated = 0;
  vector<int> move ;
  int playerNumber = 0;
  grid.showGrid();
  clock_t tStart = clock();

  do {
    do {  //We display the grid and the player in question must play, this loop is repeated as long as the entered coordinates are invalid.
      system("cls");
      grid.showGrid();
      cout << endl << players[playerNumber]->getName() << endl;
      
      auto gridMap = grid.getGrid();
      move = players[playerNumber]->play(gridMap);
    } while (!grid.IsCoordinateValid(move));


    grid.updateGrid(move);
    if (!grid.IsSquareCreated(move, nbrSquareCreated)) { // We update the score if a square is made. 

      if (playerNumber == 0)
        playerNumber = 1;
      else
        playerNumber = 0;
    }
    else                                                // otherwise we change the player
      players[playerNumber]->updateScore(nbrSquareCreated);

    //cout << "Score:   " << players[0]->getName() << ": " << players[0]->getScore() << "   " << players[1]->getName() << ": " << players[1]->getScore() << endl;

  } while (!grid.IsGameOver()); // Cette boucle s'arrete quand toutes les cases de la grille sont pleines.

  cout << "Score:   " << players[0]->getName() << ": " << players[0]->getScore() << "   " << players[1]->getName() << ": " << players[1]->getScore() << endl;
  grid.showGrid();

  double clockExec = (double)(clock() - tStart) / CLOCKS_PER_SEC;
  //We print the winner and the score of the game.
  cout << endl << "Partie terminee en   " << clockExec << " s"<<  endl << endl;

  if (players[0]->getScore() > players[1]->getScore())
    cout << players[0]->getName() << " a gagne la partie" << endl << endl;
  else if (players[0]->getScore()  < players[1]->getScore())
    cout << players[1]->getName() << " a gagne la partie" << endl << endl;
  else
    cout << "La partie s'est termine a egalite " << endl << endl;

  players.clear();
}

//Function in wish the player choose to replay or not.
bool rePlay() {
  int choice = 0;
  cout << "Veux tu continuer a jouer ? :" << endl << endl;
  cout << "1 Oui" << endl;
  cout << "2 Non (par defaut)" << endl << endl;
  cout << "Votre choix : ";
  cin >> choice;
  cout << endl << endl << endl;
  if (!int(choice)) {
    cin.clear();
    cin.ignore(100, '\n');
    choice = 2;
  }
  if (choice == 1)
    return true;

  return false;
}


  

Conclusion

The game of the square with an implemented AI is a good exercise to deepen your knowledge of algorithmic and programming. I hope this article pleases you and that you have put into practice concepts such as recursion, do not hesitate to reread the article and redo the game without seeing the correction to better assimilate some notion. On this I wish you a good proofreading and see you soon for a next console game 😃.

Bibliography

Link for the first part of the article: https://www.supinfo.com/articles/single/7129-square-game-in-c-part1

A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Toeic, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels