/// ETML /// Autheur : Théophile BORBOËN /// Date : 30.10.2024 /// Description : Puissance 4 en programmation orientée objet en C# namespace P4 { // Création d'un enum pour les jetons public enum Token { None, FirstPlayer, SecondPlayer, } /// /// Classe programme c'est la classe qui est exécutée par le programme /// public class Program { // Déclaration des couleurs des joueurs public readonly ConsoleColor FIRSTPLAYERCOLOR = ConsoleColor.Red; public readonly ConsoleColor SECONDPLAYERCOLOR = ConsoleColor.Yellow; // Déclaration des couleurs de la console const ConsoleColor BACKGROUND_COLOR = ConsoleColor.Black; const ConsoleColor FOREGROUND_COLOR = ConsoleColor.White; /// /// Méthode pour changer les couleurs de la console /// /// couleur de fond /// couleur du texte public void SetColor(ConsoleColor backgroundColor = BACKGROUND_COLOR, ConsoleColor foregroundColor = FOREGROUND_COLOR) { Console.ForegroundColor = foregroundColor; Console.BackgroundColor = backgroundColor; } /// /// Méthode Main la méthode qui s'exécute au lancement du programme /// /// tableau des arguments données au programme static void Main(string[] args) { // on définit les couleurs par défaut de la console new Program().SetColor(); // on vide la console Console.Clear(); // on dessine le titre new Draw().DrawTitle(); // on enlève le curseur Console.CursorVisible = false; // on récupère la taille du plateau int[] boardSize = new Board().BoardSize(); // on crée un tableau de jetons Token[,] board = new Token[boardSize[0], boardSize[1]]; // on lance le jeu new Game().Start(board); // on demande si les joueurs veulent rejouer new Game().Replay(boardSize); } } /// /// Classe Game qui contient toutes les méthodes pour jouer /// public class Game { // on crée des instances d'autres classes private Draw _draw = new Draw(); private Program _program = new Program(); /// /// Méthode pour demander si les joueurs veulent rejouer et pour relancer le jeu si c'est le cas /// public void Replay(int[] boardSize) { Console.SetCursorPosition(0, _draw.topSpacing + boardSize[0] * 2 + 4); // on demande si l'utilisateur veut rejouer Console.Write("Voulez-vous rejouer ? (o/n): "); // on regarde quelle touche a été pressée switch (Console.ReadKey().Key) { case ConsoleKey.O: // si c'est oui on relance le jeu new Program().SetColor(); Console.Clear(); new Draw().DrawTitle(); boardSize = new Board().BoardSize(); Token[,] board = new Token[boardSize[0], boardSize[1]]; new Game().Start(board); new Game().Replay(boardSize); break; case ConsoleKey.N: // si c'est non on quitte le programme return; default: Console.SetCursorPosition(0, _draw.topSpacing + boardSize[0] * 2 + 5); // sinon on demande de réessayer Console.Write("Entrée invalide"); Replay(boardSize); break; } } /// /// Méthode pour lancer la boucle principale du jeu /// /// tableau de jetons en 2 dimensions public void Start(Token[,] board) { // initialisation des variables int currentPosition = 0; // position actuelle Token currentPlayer = Token.FirstPlayer; // joueur actuel bool placed = false; // vérification s'il on peut placer un jeton // on vide la console Console.Clear(); // on efface le curseur au cas où la taille de la fenêtre aurait changé Console.CursorVisible = false; // on dessine le titre l'espace de jeu le plateau et l'aide _draw.DrawTitle(_draw.leftSpacing); _draw.DrawPlayGroundBorder(board); _draw.DrawBorder(board); _draw.DrawHelp(board); // boucle principale du jeu while (!CheckWin(board)) { // on vérifie s'il y a égalité if (CheckDraw(board)) break; // on dessine les jetons dans l'espace de jeu et le plateau _draw.DrawBoard(board); _draw.DrawPlayGround(board, currentPlayer, currentPosition); // on récupère la touche pressée ConsoleKey key = Console.ReadKey(true).Key; // on regarde si la touche est escape if (key == ConsoleKey.Escape) { // s'il le joueur appuie sur escape il n'y a pas de gagnant donc on set le currentPlayer à None currentPlayer = Token.None; break; } // on regarde les actions des joueurs switch (key) { case ConsoleKey.RightArrow: // on incrémente la position actuelle si elle est inférieure à la taille du plateau if (currentPosition < board.GetLength(0) - 1) currentPosition++; else // on loop si on est à la fin du plateau currentPosition = 0; break; case ConsoleKey.LeftArrow: // on decremente la position actuelle si elle est supérieure à 0 if (currentPosition > 0) currentPosition--; else // on loop si on est à le début du plateau currentPosition = board.GetLength(0) - 1; break; case ConsoleKey.Enter: case ConsoleKey.Spacebar: // on place le jeton et on change de joueur placed = PlaceToken(board, currentPlayer, currentPosition); if (placed && currentPlayer == Token.FirstPlayer) currentPlayer = Token.SecondPlayer; else if (placed && currentPlayer == Token.SecondPlayer) currentPlayer = Token.FirstPlayer; break; } } // on dessine les derniers jetons _draw.DrawBoard(board); _draw.DrawPlayGround(board, currentPlayer, currentPosition); // on reset les couleurs _program.SetColor(); // on place le curseur en bas du tableau avec le bon spacing Console.SetCursorPosition(0, board.GetLength(1) * 2 + _draw.topSpacing + 4); // on verifie s'il y a une égalité if (CheckDraw(board)) Console.WriteLine("il y a eu une égalité"); else if (currentPlayer == Token.FirstPlayer) { // si le joueur actuel est le joueur 1 cela signifie que le joueur 2 a gagné Console.Write("Le "); _program.SetColor(foregroundColor: _program.SECONDPLAYERCOLOR); Console.Write("joueur 2"); _program.SetColor(); Console.WriteLine(" a gagné"); } else if (currentPlayer == Token.SecondPlayer) { // si le joueur actuel est le joueur 2 cela signifie que le joueur 1 a gagné Console.Write("Le "); _program.SetColor(foregroundColor: _program.FIRSTPLAYERCOLOR); Console.Write("joueur 1"); _program.SetColor(); Console.WriteLine(" a gagné"); } else if (currentPlayer == Token.None) { // si le joueur actuel est None cela signifie que la partie a été arrêtée Console.WriteLine("La partie a été arrêtée"); } // on affiche le nombre de tours Console.WriteLine("La partie a duré " + Turn(board) + " tours"); } /// /// Méthode pour compter le nombre de jetons sur le plateau /// /// tableau de jetons /// nombre de jetons sur le plateau int Turn(Token[,] board) { int count = 0; // compteur for (int y = 0; y < board.GetLength(1); y++) { for (int x = 0; x < board.GetLength(0); x++) if (board[x, y] != 0) { // ajoute 1 si le jeton n'est pas égal à 0 count++; } } return count; } /// /// Méthode pour vérifier s'il y a une égalité /// /// tableau de jetons /// return false t'en qu'il y a au moins 1 0 sur le plateau private bool CheckDraw(Token[,] board) { for (int y = 0; y < board.GetLength(1); y++) { for (int x = 0; x < board.GetLength(0); x++) { if (board[x, y] == 0) { return false; } } } return true; } /// /// Méthode qui vérifie si un joueur a gagné /// /// tableau de jetons /// return true s'il y a une combinaison gagnante private bool CheckWin(Token[,] board) { // tableau des joueurs Token[] players = {Token.FirstPlayer, Token.SecondPlayer}; // boucle qui alterne entre les joueurs 1 et 2 foreach (Token j in players) { // boucle qui fait toutes les colonnes for (int y = 0; y < board.GetLength(1); y++) { // boucle qui fait toute la valeur des colonnes for (int x = 0; x < board.GetLength(0); x++) { // on check les bordures horizontales if (x + 3 < board.GetLength(0)) { // on check les 4 prochains jetons dans le sens horizontal if (board[x, y] == j && board[x + 1, y] == j && board[x + 2, y] == j && board[x + 3, y] == j) { // on retourne vrai si la condition est respectée return true; } } // on check les bordure verticales if (y + 3 < board.GetLength(1)) { // on check les 4 prochains jetons dans le sens vertical if (board[x, y] == j && board[x, y + 1] == j && board[x, y + 2] == j && board[x, y + 3] == j) { // on retourne vrai si la condition est respectée return true; } } // on check les bordures horizontales et verticales if (y + 3 < board.GetLength(1) && x + 3 < board.GetLength(0)) { // on check les 4 prochains jetons dans le sens horizontal et vertical if (board[x, y] == j && board[x + 1, y + 1] == j && board[x + 2, y + 2] == j && board[x + 3, y + 3] == j) { // on retourne vrai si la condition est respectée return true; } } // on check les bordures horizontales et verticales if (y - 3 > 0 && x + 3 < board.GetLength(0)) { // on check les 4 prochains jetons dans le sens horizontal et vertical if (board[x, y] == j && board[x + 1, y - 1] == j && board[x + 2, y - 2] == j && board[x + 3, y - 3] == j) { // on retourne vrai si la condition est respectée return true; } } } } } // si rien n'a été trouvé, on retourne false return false; } /// /// Méthode qui place un jeton sur le plateau /// /// tableau de jetons /// joueur actuel /// position du joueur /// private bool PlaceToken(Token[,] board, Token currentPlayer, int currentPosition) { for (int i = board.GetLength(1) - 1; i >= 0; i--) { // si la case est déjà occupée on continue if (board[currentPosition, i] != Token.None) continue; // sinon on place le jeton board[currentPosition, i] = currentPlayer; // on revoie vrai s'il on a placé le jeton return true; } // on revoie faux s'il on n'a pas placé le jeton return false; } } /// /// Classe Board contient les méthodes pour la création du tableau /// public class Board { private Program _program = new Program(); public int[] BoardSize() { int[] boardSize = { 0, 0 }; // initialisation de la taille string[] input = { "", "" }; // initialisation de l'entrée utilisateur // écriture des instructions en couleur grâce à la méthode WriteColor Console.SetCursorPosition(0, 4); Console.Write("La taille doit être comprise entre "); _program.SetColor(foregroundColor: ConsoleColor.Red); Console.Write("5x6"); _program.SetColor(); Console.Write(" et "); _program.SetColor(foregroundColor: ConsoleColor.Red); Console.WriteLine("13x16"); _program.SetColor(); Console.WriteLine("exemple: 6x7 (6 lignes et 7 colonnes)"); Console.SetCursorPosition(0, 6); Console.Write(" "); Console.SetCursorPosition(0, 6); Console.Write("taille: "); // récupération de l'entrée utilisateur dans une liste grâce à un split input = Console.ReadLine().Split('x'); // on essaye de Parse en int les entrées, sinon on fait une récursion try { boardSize[0] = int.Parse(input[1]); boardSize[1] = int.Parse(input[0]); } catch { Console.WriteLine("La taille est invalide. réessayer"); return BoardSize(); } // on check si les entrées sont incorrectes si oui on fait une récursion if (boardSize[1] <= 5 || boardSize[1] >= 13 || boardSize[0] <= 6 || boardSize[0] >= 16) { Console.WriteLine("La taille est invalide. réessayer"); return BoardSize(); } // et si tous les checks sont passés, on retourne la taille Console.WriteLine("taille valide"); return boardSize; } } /// /// Classe Draw qui contient toutes les méthodes pour render /// public class Draw { // on instancie la classe Program private Program _program = new Program(); // on définit les espacements public int topSpacing = 4; public int leftSpacing = 10; /// /// Méthode DrawPlayGroundBorder qui dessine le terrain de jeu /// /// tableau de jetons public void DrawPlayGroundBorder(Token[,] board) { for (int y = 0; y < 3; y++) for (int x = 0; x < board.GetLength(0) * 4 + 1; x++) { // on met la position partout où l'on va écrire un caractère Console.SetCursorPosition(x + leftSpacing, y + topSpacing); // case 0, 0 if (x == 0 && y == 0) Console.Write('╔'); // case 0, 2 if (x == 0 && y == 2) Console.Write('╚'); // toutes 4 case de la 2eme ligne if (x % 4 == 0 && y == 1) Console.Write('║'); // tout le reste if (x % 4 != 0 && y != 1) Console.Write('═'); // dernière case de la première rangée if (x == board.GetLength(0) * 4 && y == 0) Console.Write('╗'); // dernière case de la première rangée if (x == board.GetLength(0) * 4 && y == 2) Console.Write('╝'); // tous les case 4 case de la première ligne if (x != board.GetLength(0) * 4 && x % 4 == 0 && y == 0) Console.Write('╦'); // tous les case 4 case de la dernière ligne if (x != board.GetLength(0) * 4 && x % 4 == 0 && y == 2) Console.Write('╩'); } } /*public void DrawPlayGroundBorder(Token[,] board) { // on place le curseur en haut du terrain de jeu après le titre Console.SetCursorPosition(leftSpacing, topSpacing); // on dessine la première ligne du terrain de jeu Console.Write("╔═══"); for (int x = 0; x < board.GetLength(0) - 1; x++) { Console.Write("╦═══"); } Console.Write("╗"); // on dessine les autres lignes du terrain de jeu for (int y = 0; y < board.GetLength(1); y++) { for (int x = 0; x < board.GetLength(0); x++) { Console.SetCursorPosition(leftSpacing + x * 4, y * 2 + topSpacing + 1); Console.Write("║"); } Console.Write(" ║"); } // on dessine la dernière ligne du terrain de jeu Console.SetCursorPosition(leftSpacing, topSpacing + 2); Console.Write("╚═══"); for (int x = 0; x < board.GetLength(0) - 1; x++) { Console.Write("╩═══"); } Console.Write("╝"); }*/ /// /// Méthode DrawPlayGround qui dessine les jetons dans le terrain de jeu /// /// tableau de jetons /// joueur actuel /// position du joueur actuel public void DrawPlayGround(Token[,] board, Token currentPlayer, int currentPosition) { for (int x = 0; x < board.GetLength(0); x++) { // on place le curseur dans chaque case du terrain de jeu Console.SetCursorPosition(leftSpacing + x * 4+2, topSpacing + 1); // on dessine le jeton du joueur actuel si la position actuelle est égale à la position x sinon on ne dessine rien if (currentPosition == x) Console.Write(DrawToken(currentPlayer)); else Console.Write(DrawToken(Token.None)); } } /// /// Méthode DrawBorder qui dessine les bordures du plateau /// /// tableau de jetons public void DrawBorder(Token[,] board) { for (int y = 0; y < board.GetLength(1) * 2 + 1; y++) for (int x = 0; x < board.GetLength(0) * 4 + 1; x++) { // on place le curseur Console.SetCursorPosition(x + leftSpacing, y + topSpacing + 3); // la première case if (x == 0 && y == 0) Console.Write('╔'); // une case sur deux en y et une case sur quatre en y if (x % 4 == 0 && y % 2 == 1) Console.Write('║'); // toutes les case qui ne sont pas toutes les 4 case en x et toutes les 2 case en y if (x % 4 != 0 && y % 2 == 0) Console.Write('═'); // dans la première colonne toutes les deux cases if (x == 0 && y == board.GetLength(1) * 2) Console.Write('╚'); // la dernière case de la première ligne if (x == board.GetLength(0) * 4 && y == 0) Console.Write('╗'); // la dernière case de la dernière ligne if (x == board.GetLength(0) * 4 && y == board.GetLength(1) * 2) Console.Write('╝'); // toutes les 4 case de la première ligne sauf la première et la dernière if (x != 0 && x != board.GetLength(0) * 4 && x % 4 == 0 && y == 0) Console.Write('╦'); // toutes les 4 case de la dernière ligne sauf la première et la dernière if (x != 0 && x != board.GetLength(0) * 4 && x % 4 == 0 && y == board.GetLength(1) * 2) Console.Write('╩'); // toutes les 2 case d'une ligne sur deux dans la dernière colonne sauf la première et la dernière if (y != 0 && y != board.GetLength(1) * 2 && x == board.GetLength(0) * 4 && y % 2 == 0) Console.Write('╣'); // toutes les 2 case d'une ligne sur deux dans la première colonne sauf la première et la dernière if (y != 0 && y != board.GetLength(1) * 2 && x == 0 && y % 2 == 0) Console.Write('╠'); // toutes les cases au milieu if (x != 0 && x != board.GetLength(0) * 4 && y != 0 && y != board.GetLength(1) * 2 && x % 4 == 0 && y % 2 == 0) Console.Write('╬'); } } /*public void DrawBorder(Token[,] board) { // déclaration de l'espacement du plateau int playgroundSpacing = topSpacing + 3; // on place le curseur en haut du plateau Console.SetCursorPosition(leftSpacing, playgroundSpacing); // on dessine la première ligne du plateau Console.Write("╔═══"); for (int x = 0; x < board.GetLength(0) - 1; x++) { Console.Write("╦═══"); } Console.Write("╗"); // on dessine les autres lignes du plateau for (int y = 0; y < board.GetLength(1); y++) { for (int x = 0; x < board.GetLength(0); x++) { Console.SetCursorPosition(leftSpacing + x * 4, y * 2 + playgroundSpacing + 1); Console.Write("║"); } Console.SetCursorPosition(leftSpacing + board.GetLength(0) * 4, y * 2 + playgroundSpacing + 1); Console.Write("║"); } // on dessine les interligne du plateau for (int y = 0; y < board.GetLength(1) - 1; y++) { Console.SetCursorPosition(leftSpacing, y * 2 + playgroundSpacing + 2); Console.Write("╠═══"); for (int x = 1; x < board.GetLength(0); x++) { Console.SetCursorPosition(leftSpacing + x * 4, y * 2 + playgroundSpacing + 2); Console.Write("╬═══"); } Console.Write("╣"); } // on dessine la dernière ligne du plateau Console.SetCursorPosition(leftSpacing, board.GetLength(1) * 2 + playgroundSpacing); Console.Write("╚═══"); for (int x = 0; x < board.GetLength(0) - 1; x++) { Console.Write("╩═══"); } Console.Write("╝"); }*/ /// /// Méthode DrawBoard qui dessine les jetons sur le plateau /// /// tableau de jetons public void DrawBoard(Token[,] board) { // déclaration de l'espacement du plateau int playgroundSpacing = topSpacing + 3; for (int y = 0; y < board.GetLength(1); y++) for (int x = 0; x < board.GetLength(0); x++) { // on place le curseur dans chaque case du plateau Console.SetCursorPosition(leftSpacing + x * 4 + 2, y * 2 + playgroundSpacing + 1); // on dessine le jeton qui correspond à la case Console.Write(DrawToken(board[x, y])); } } /// /// Méthode DrawToken qui transforme un jeton en string /// /// jeton à transformer /// jeton transformé public string DrawToken(Token token) { switch (token) { case Token.FirstPlayer: // on set la couleur du premier joueur _program.SetColor(foregroundColor: _program.FIRSTPLAYERCOLOR); // on retourne le jeton return "■"; case Token.SecondPlayer: // on set la couleur du deuxième joueur _program.SetColor(foregroundColor: _program.SECONDPLAYERCOLOR); // on retourne le jeton return "■"; case Token.None: // on set la couleur par défaut _program.SetColor(foregroundColor: ConsoleColor.DarkGray); // on retourne le jeton return "≡"; default: // revoye le rest au cas où return default; } } /// /// Méthode DrawTitle qui dessine le titre du jeu /// /// espacement entre le bord et le titre public void DrawTitle(int spacing = 0) { // on met le curseur à la bonne position avant d'écrire chaques lignes Console.SetCursorPosition(spacing, 0); Console.WriteLine("╔═══════════════════════════════════╗"); Console.SetCursorPosition(spacing, 1); Console.WriteLine("║ Bienvenue dans le jeu Puissance 4 ║"); Console.SetCursorPosition(spacing, 2); Console.WriteLine("║ Réalisé par Théophile Borboën ║"); Console.SetCursorPosition(spacing, 3); Console.WriteLine("╚═══════════════════════════════════╝"); } /// /// Méthode DrawHelp qui dessine l'aide du jeu /// /// tableau de jetons public void DrawHelp(Token[,] board) { // on dessine l'aide relative au plateau et qu'avec des positions WriteHelp(board, 0, 0, "Mode d'utilisation"); WriteHelp(board, 0, 1, "-------------------"); WriteHelp(board, 4, 2, "Déplacement"); WriteHelp(board, 18, 2, "Touches directionnelles"); WriteHelp(board, 4, 3, "Tir"); WriteHelp(board, 18, 3, "Spacebar ou Enter"); WriteHelp(board, 4, 4, "Quitter"); WriteHelp(board, 18, 4, "Escape"); _program.SetColor(foregroundColor: _program.FIRSTPLAYERCOLOR); WriteHelp(board, 4, 6, "Joueur 1: "); _program.SetColor(_program.FIRSTPLAYERCOLOR); WriteHelp(board, 14, 6, " "); _program.SetColor(foregroundColor: _program.SECONDPLAYERCOLOR); WriteHelp(board, 18, 6, "Joueur 2: "); _program.SetColor(_program.SECONDPLAYERCOLOR); WriteHelp(board, 28, 6, " "); } /// /// Méthode WriteHelp qui écrit les phrases de l'aide /// /// tableau de jetons /// position x /// position y /// text à écrire à cette position private void WriteHelp(Token[,] board, int x, int y, string text) { Console.SetCursorPosition(leftSpacing + board.GetLength(0) * 4 + 8 + x, topSpacing + y); Console.Write(text); } } }