Bonjour,
J’ai réalisé la première partie du TP Puissance 4 du cours sur le langage C (sans IA donc). J’aimerais avoir des avis sur le programme que j’ai écrit et qui est découpé en 3 fichiers présentés ci-dessous.
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "connect_four.h"
int main(void)
{
init_board();
const struct square *played_square;
do
{
display_board();
played_square = play_move();
update_game_state(played_square);
} while (strncmp(game_state, "ONGOING", sizeof game_state) == 0);
display_board();
if (strncmp(game_state, "VICTORY", STATE_LEN) == 0)
{
if (printf("Victoire du joueur %d !\n", turn) < 0)
{
fputs("printf failed when displaying the victorious player.\n", stderr);
return EXIT_FAILURE;
}
}
else
{
if (puts("Match nul.") == EOF)
{
fputs("puts failed when displaying the draw.\n", stderr);
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
connect_four.h
#ifndef CONNECT_FOUR_H
#define CONNECT_FOUR_H
#include <stdbool.h>
#include <stdint.h>
#define LINES 6
#define COLUMNS 7
#define STATE_LEN 8
#define P1_TOKEN 'O'
#define P2_TOKEN 'X'
struct square
{
uint_fast8_t line, column;
char value;
};
extern struct square board[LINES][COLUMNS];
extern char game_state[STATE_LEN];
extern uint_fast8_t turn;
extern void init_board(void);
extern void display_column_numbers(void);
extern void display_board(void);
extern const struct square *play_move(void);
extern uint_fast8_t select_column(void);
extern bool playable_column(const uint_fast8_t column);
extern bool filled_column(const uint_fast8_t column);
extern bool filled_board(void);
extern void update_game_state(const struct square *const LAST_PLAYED_SQUARE);
extern bool four_tokens_aligned(const struct square *const LAST_PLAYED_SQUARE);
extern bool four_ascending_diag_aligned(const struct square *const LAST_PLAYED_SQUARE);
extern bool four_descending_diag_aligned(const struct square *const LAST_PLAYED_SQUARE);
extern bool four_horizontal_aligned(const struct square *const LAST_PLAYED_SQUARE);
extern bool four_vertical_aligned(const struct square *const LAST_PLAYED_SQUARE);
extern void clean_stdin(void);
#endif
connect_four.c
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "connect_four.h"
struct square board[LINES][COLUMNS];
char game_state[STATE_LEN] = "ONGOING";
uint_fast8_t turn = 1;
void init_board(void)
{
for (uint_fast8_t i = 0; i < LINES; i++)
for (uint_fast8_t j = 0; j < COLUMNS; j++)
board[i][j] = (struct square){.line = i, .column = j, .value = ' '};
}
void display_column_numbers(void)
{
for (uint_fast8_t j = 1; j < COLUMNS; j++)
if (printf(" %" PRIuFAST8 " ", j) < 0)
{
fputs("printf failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
if (printf(" %d\n", COLUMNS) < 0)
{
fputs("printf failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
}
void display_board(void)
{
if (putchar('\n') == EOF)
{
fputs("putchar failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
display_column_numbers();
for (uint_fast8_t j = 0; j < COLUMNS; j++)
if (fputs("+---", stdout) == EOF)
{
fputs("fputs failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
if (puts("+") == EOF)
{
fputs("puts failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
for (uint_fast8_t i = LINES; i > 0; i--)
{
for (uint_fast8_t j = 0; j < COLUMNS; j++)
if (printf("| %c ", board[i - 1][j].value) < 0)
{
fputs("printf failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
if (puts("|") == EOF)
{
fputs("puts failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
for (uint_fast8_t j = 0; j < COLUMNS; j++)
if (fputs("+---", stdout) == EOF)
{
fputs("fputs failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
if (puts("+") == EOF)
{
fputs("puts failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
}
display_column_numbers();
if (putchar('\n') == EOF)
{
fputs("putchar failed when displaying the board.\n", stderr);
exit(EXIT_FAILURE);
}
}
const struct square *play_move(void)
{
uint_fast8_t i = 0;
const uint_fast8_t selected_column = select_column();
while (board[i][selected_column].value != ' ')
i++;
board[i][selected_column].value = (turn == 1) ? P1_TOKEN : P2_TOKEN;
return &board[i][selected_column];
}
uint_fast8_t select_column(void)
{
uint_fast8_t selected_column;
while (1)
{
if (printf("Joueur %d : ", turn) < 0)
{
fputs("printf failed when displaying prompt to the player.\n", stderr);
exit(EXIT_FAILURE);
}
const int scanf_res = scanf("%" SCNuFAST8, &selected_column);
clean_stdin();
if (scanf_res == EOF)
{
fputs("scanf failed when reading player’s input.\n", stderr);
exit(EXIT_FAILURE);
}
if (scanf_res != 1)
{
if (puts("Saisie incorrecte, veuillez saisir un chiffre entre 1 et 7 inclus.") == EOF)
{
fputs("puts failed when displaying information to the player.\n", stderr);
exit(EXIT_FAILURE);
}
}
else
{
selected_column--;
if (!playable_column(selected_column))
{
if (puts("Saisie incorrecte, veuillez saisir un chiffre entre 1 et 7 inclus.") == EOF)
{
fputs("puts failed when displaying information to the player.\n", stderr);
exit(EXIT_FAILURE);
}
}
else if (filled_column(selected_column))
{
if (printf("La colonne %" PRIuFAST8 " est remplie, veuillez en choisir une autre.\n",
selected_column + 1) < 0)
{
fputs("printf failed when displaying information to the player.\n", stderr);
exit(EXIT_FAILURE);
}
}
else
{
if (putchar('\n') == EOF)
{
fputs("putchar failed when displaying newline after player input.\n", stderr);
exit(EXIT_FAILURE);
}
return selected_column;
}
}
}
}
bool playable_column(const uint_fast8_t column)
{
return (column < COLUMNS) ? true : false;
}
bool filled_column(const uint_fast8_t column)
{
return (board[LINES - 1][column].value != ' ') ? true : false;
}
bool filled_board(void)
{
for (uint_fast8_t i = 0; i < COLUMNS; i++)
if (!filled_column(i))
return false;
return true;
}
void update_game_state(const struct square *const SQUARE)
{
if (four_tokens_aligned(SQUARE))
strcpy(game_state, "VICTORY");
else if (filled_board())
strcpy(game_state, "DRAW");
else
turn = (turn == 1) ? 2 : 1;
}
bool four_tokens_aligned(const struct square *const SQUARE)
{
return (four_ascending_diag_aligned(SQUARE) ||
four_descending_diag_aligned(SQUARE) ||
four_horizontal_aligned(SQUARE) ||
four_vertical_aligned(SQUARE));
}
bool four_ascending_diag_aligned(const struct square *const SQUARE)
{
uint_fast8_t counter = 1;
for (uint_fast8_t i = SQUARE->line, j = SQUARE->column; i > 0 && j > 0; i--, j--)
if (board[i - 1][j - 1].value == SQUARE->value)
{
if (++counter == 4)
return true;
}
else
break;
for (uint_fast8_t i = SQUARE->line + 1, j = SQUARE->column + 1; i < LINES && j < COLUMNS; i++, j++)
if (board[i][j].value == SQUARE->value)
{
if (++counter == 4)
return true;
}
else
break;
return false;
}
bool four_descending_diag_aligned(const struct square *const SQUARE)
{
uint_fast8_t counter = 1;
for (uint_fast8_t i = SQUARE->line + 1, j = SQUARE->column; i < LINES && j > 0; i++, j--)
if (board[i][j - 1].value == SQUARE->value)
{
if (++counter == 4)
return true;
}
else
break;
for (uint_fast8_t i = SQUARE->line, j = SQUARE->column + 1; i > 0 && j < COLUMNS; i--, j++)
if (board[i - 1][j].value == SQUARE->value)
{
if (++counter == 4)
return true;
}
else
break;
return false;
}
bool four_horizontal_aligned(const struct square *const SQUARE)
{
uint_fast8_t counter = 1;
for (uint_fast8_t j = SQUARE->column; j > 0; j--)
if (board[SQUARE->line][j - 1].value == SQUARE->value)
{
if (++counter == 4)
return true;
}
else
break;
for (uint_fast8_t j = SQUARE->column + 1; j < COLUMNS; j++)
if (board[SQUARE->line][j].value == SQUARE->value)
{
if (++counter == 4)
return true;
}
else
break;
return false;
}
bool four_vertical_aligned(const struct square *const SQUARE)
{
if (SQUARE->line < 3)
return false;
uint_fast8_t counter = 1;
for (uint_fast8_t i = SQUARE->line; i > 0; i--)
if (board[i - 1][SQUARE->column].value == SQUARE->value)
{
if (++counter == 4)
return true;
}
else
break;
return false;
}
void clean_stdin(void)
{
int c;
do
{
c = getchar();
} while (c != '\n' && c != EOF);
if (ferror(stdin))
{
fputs("getchar failed while cleaning stdin.\n", stderr);
exit(EXIT_FAILURE);
}
}
Quelques remarques préliminaires.
- La fonction
main()
contient peut-être encore « trop » de logique, j’hésite à ajouter une fonctionplay()
séparée, mais je ne sais pas si cela améliore vraiment le découpage ou non. Si c’est le cas, quelle serait la fonction demain()
? - Vous remarquerez sans doute que j’« abuse » de vérifications sur le retour des fonctions d’affichage sur la sortie standard. Je l’ai fait à titre d’exercice pour évaluer la lourdeur que cela engendre (et c’est effectivement très lourd à faire) mais une question m’est venue à l’esprit en le faisant : existe-t-il des cas (mêmes rares) où la sortie standard serait indisponible mais pas l’erreur standard ?
- Je me demande si la structure
struct square
est vraiment la plus adéquate ici ? J’ai lu d’autres messages sur le forum commentant des TP de Puissance 4 d’autres membres, et j’ai vu que l’on suggérait notamment d’utiliser plutôt une structure position. Quels en serait les avantages ? Je pense que cela serait utile si l’on cherchait à échanger de place deux jetons, mais ce n’est pas pertinent pour ce jeu. - Je pense que les fonctions vérifiant s’il y a 4 jetons alignés sont simplifiables, mais je ne sais pas vraiment comment m’y prendre. Je les trouve un peu compliquées et redondantes (même leurs noms ne me satisfont pas).
- D’ailleurs, dans les fonctions vérifiant l’alignement de 4 jetons en diagonales, j’utilise l’opérateur
,
dans les bouclesfor
pour parcourir les lignes et les colonnes en même temps. Je pense que le résultat est lisible, mais il y a peut-être une meilleure alternative ? - Je ne suis pas sûr de bien utiliser le mot-clé
extern
dans l’entêteconnect_four.h
. En particulier, je me dis que je pourrais contrôler plus finement la portée des variables et fonctions déclarées à l’intérieur, de sorte que seules celles effectivement utilisées parmain.c
lui soient visibles, tout en gardant les déclarations des fonctions « internes » àconnect_four.c
dans l’entête. - J’ai décidé en outre de rendre les variables stockant le plateau du jeu, l’état de la partie et le tour de la partie globales, car il me semble que cela simplifie grandement l’écriture des fonctions et que c’est plus adéquat (il n’y a bien qu’un seul plateau dans tout le programme). Y voyez-vous un inconvénient ? En bonus, je me suis demandé s’il existait une fonctionnalité d’un langage qui permette de préciser que telle fonction n’a pas accès aux variables globales ? Je ne pense pas que ce soit le cas dans le langage C, mais je me posais la question aussi pour les autres langages de programmation.
- Dans le fichier
connect_four.c
à la ligne 198, je modifie la variableturn
pour représenter le changement de tour de jeu (et ainsi que c’est à l’autre joueur de jouer). Je suis quasiment sûr qu’il est possible de faire cela sans utiliser de condition, étant donné que je représente le tour de jeu par 1 (c’est au joueur 1 de jouer) ou 2 (c’est au joueur 2 de jouer), mais je n’arrive pas à trouver comment. Pouvez-vous m’aider ?
+0
-0