diff --git a/Makefile b/Makefile index bc8b3ac47d8df2b91aba7904fe132193722f2067..727325a4aba71a99ddd63bece287b12c0d560cc7 100644 --- a/Makefile +++ b/Makefile @@ -3,17 +3,18 @@ CFLAGS = -g -Wall -Wextra -Wpedantic LDLIBS = -lm LDFLAGS = -fsanitize=address -fsanitize=leak -puissance4: puissance4.o src/board.o src/game.o +puissance4: puissance4.o src/board.o src/computer.o src/game.o $(CC) $(CFLAGS) $^ -o $@ $(LDLIBS) $(LDFLAGS) board.o: src/board.h -game.o: src/game.h -puissance4.o: puissance4.h +game.o: src/game.h src/computer.h +computer.o: src/computer.h src/board.h +puissance4.o: puissance4.h src/game.h src/board.h clean: @echo "this rule must clean everything up (including candidate files in testbed)" $(MAKE) -C testbed clean - $(RM) -rf puissance4 *.o *.cand + $(RM) -rf puissance4 *.o tests_game tests_board tests_computer run: puissance4 ./$< 3 5 6 @@ -30,7 +31,7 @@ tests_rand_ai: puissance4 tests_smart_ai: puissance4 $(MAKE) -C testbed/smart_ai -tests_game: src/test_game.o src/game.o src/board.o +tests_game: src/test_game.o src/board.o src/game.o src/computer.o $(CC) $(CFLAGS) $^ -o tests_game $(LDLIBS) $(LDFLAGS) ./tests_game @@ -38,5 +39,13 @@ tests_board: src/test_board.o src/board.o $(CC) $(CFLAGS) $^ -o tests_board $(LDLIBS) $(LDFLAGS) ./tests_board -run_tests: tests_game tests_board +tests_computer: src/test_computer.o src/computer.o src/board.o src/game.o + $(CC) $(CFLAGS) $^ -o tests_computer $(LDLIBS) $(LDFLAGS) + ./tests_computer + +run_tests: tests_game tests_board tests_computer + @echo "Running all tests..." + ./tests_game + ./tests_board + ./tests_computer @echo "All tests executed successfully." \ No newline at end of file diff --git a/puissance4.c b/puissance4.c index f21647932014dfca9c3b2971c793d710655d792d..9f100df8d814161087f7d18539bc571ba7256613 100644 --- a/puissance4.c +++ b/puissance4.c @@ -1,17 +1,5 @@ #include "puissance4.h" -// #define AI_SEED 0 -// #define MIN_COLUMN 4 -// #define MIN_ROW 4 - -// void flush_input(); -// bool is_valid_game_mode(int game_mode); -// bool is_valid_board_size(int rows, int cols); -// bool is_column_full(int col, int rows, int** board); -// int select_random_column(int number_of_columns, int number_of_rows, int** board); -// int select_smart_column(int number_of_columns, int number_of_rows, int** board); -// void announce_winner(enum GAME_MODE game_mode, int current_turn); - int main(int argc, char const* argv[]) { if (argc != 4 || !is_valid_game_mode(atoi(argv[1])) || !is_valid_board_size(atoi(argv[2]), atoi(argv[3]))) { printf("Usage: %s <mode> <row> <col>\n", argv[0]); @@ -90,71 +78,6 @@ bool is_valid_board_size(int rows, int cols) { return rows >= MIN_ROW && cols >= MIN_COLUMN; } -int select_random_column(int number_of_columns, int number_of_rows, int** board) { - int robot_selected_column = -999; - do { - robot_selected_column = rand() % number_of_columns + 1; - } while (is_column_full(robot_selected_column, number_of_rows, board)); - - return robot_selected_column; -} - -int select_smart_column(int number_of_columns, int number_of_rows, int** board) { - int robot_selected_column = -999; - - // Check if the AI can win in the next move, if so, play it - for (int col = 0; col < number_of_columns; col++) { - if (!is_column_full(col + 1, number_of_rows, board)) { - struct coordinate temp_coordinate = find_empty_slot(col, number_of_rows, board); - - board[temp_coordinate.x][temp_coordinate.y] = PLAYER2; - if (does_player_win(temp_coordinate, number_of_rows, number_of_columns, board)) { - robot_selected_column = col + 1; - board[temp_coordinate.x][temp_coordinate.y] = EMPTY; - break; - } - board[temp_coordinate.x][temp_coordinate.y] = EMPTY; - } - - } - - if (robot_selected_column != -999) { - return robot_selected_column; - } - - // Check if the player can win in the next move, if so, block it - for (int col = 0; col < number_of_columns; col++) { - if (!is_column_full(col + 1, number_of_rows, board)) { - struct coordinate temp_coordinate = find_empty_slot(col, number_of_rows, board); - - // Check if the player can win in the next move - board[temp_coordinate.x][temp_coordinate.y] = PLAYER1; - if (does_player_win(temp_coordinate, number_of_rows, number_of_columns, board)) { - robot_selected_column = col + 1; - board[temp_coordinate.x][temp_coordinate.y] = EMPTY; - break; - } - board[temp_coordinate.x][temp_coordinate.y] = EMPTY; - } - } - - // If no winning move is found, select a random column - if (robot_selected_column == -999) { - robot_selected_column = select_random_column(number_of_columns, number_of_rows, board); - } - - return robot_selected_column; -} - -bool is_column_full(int col, int rows, int** board) { - for (int i = 0; i < rows; i++) { - if (board[i][col - 1] == EMPTY) { - return false; - } - } - return true; -} - void flush_input() { int c; while ((c = getchar()) != '\n' && c != EOF); diff --git a/puissance4.h b/puissance4.h index ba2d9ccb9749cea6a931cdc432d516c0c4b53d85..7ebd43755a5b51549332615c6af887d8ba1e5548 100644 --- a/puissance4.h +++ b/puissance4.h @@ -7,23 +7,18 @@ #include <stdbool.h> #include "src/game.h" #include "src/board.h" +#include "src/computer.h" #define AI_SEED 0 #define MIN_COLUMN 4 #define MIN_ROW 4 -#include <stdio.h> -#include <stdlib.h> -#include <stdbool.h> -#include "src/game.h" -#include "src/board.h" - void announce_winner(enum GAME_MODE game_mode, int current_turn); bool is_valid_game_mode(int game_mode); bool is_valid_board_size(int rows, int cols); -int select_random_column(int number_of_columns, int number_of_rows, int** board); -int select_smart_column(int number_of_columns, int number_of_rows, int** board); -bool is_column_full(int col, int rows, int** board); +// int select_random_column(int number_of_columns, int number_of_rows, int** board); +// int select_smart_column(int number_of_columns, int number_of_rows, int** board); +// bool is_column_full(int col, int rows, int** board); void flush_input(); #endif diff --git a/src/computer.c b/src/computer.c new file mode 100644 index 0000000000000000000000000000000000000000..6f22569671b0a0b9d6dd87e103f75e1e15d29e77 --- /dev/null +++ b/src/computer.c @@ -0,0 +1,67 @@ + +#include "computer.h" + +int select_smart_column(int number_of_columns, int number_of_rows, int** board) { + int robot_selected_column = -999; + + // 1. Check if the AI can win in the next move, if so, play it + for (int col = 0; col < number_of_columns; col++) { + if (!is_column_full(col + 1, number_of_rows, board)) { + struct coordinate temp_coordinate = find_empty_slot(col, number_of_rows, board); + + board[temp_coordinate.x][temp_coordinate.y] = PLAYER2; + if (does_player_win(temp_coordinate, number_of_rows, number_of_columns, board)) { + robot_selected_column = col + 1; + board[temp_coordinate.x][temp_coordinate.y] = EMPTY; + break; + } + board[temp_coordinate.x][temp_coordinate.y] = EMPTY; + } + + } + + if (robot_selected_column != -999) { + return robot_selected_column; + } + + // 2. Check if the player can win in the next move, if so, block it + for (int col = 0; col < number_of_columns; col++) { + if (!is_column_full(col + 1, number_of_rows, board)) { + struct coordinate temp_coordinate = find_empty_slot(col, number_of_rows, board); + + // Check if the player can win in the next move + board[temp_coordinate.x][temp_coordinate.y] = PLAYER1; + if (does_player_win(temp_coordinate, number_of_rows, number_of_columns, board)) { + robot_selected_column = col + 1; + board[temp_coordinate.x][temp_coordinate.y] = EMPTY; + break; + } + board[temp_coordinate.x][temp_coordinate.y] = EMPTY; + } + } + + // 3. If no winning move is found, select a random column + if (robot_selected_column == -999) { + robot_selected_column = select_random_column(number_of_columns, number_of_rows, board); + } + + return robot_selected_column; +} + +int select_random_column(int number_of_columns, int number_of_rows, int** board) { + int robot_selected_column = -999; + do { + robot_selected_column = rand() % number_of_columns + 1; + } while (is_column_full(robot_selected_column, number_of_rows, board)); + + return robot_selected_column; +} + +bool is_column_full(int col, int rows, int** board) { + for (int i = 0; i < rows; i++) { + if (board[i][col - 1] == EMPTY) { + return false; + } + } + return true; +} \ No newline at end of file diff --git a/src/computer.h b/src/computer.h new file mode 100644 index 0000000000000000000000000000000000000000..491015b112e2ed3db8f0d97eaeea36ea471518ff --- /dev/null +++ b/src/computer.h @@ -0,0 +1,14 @@ +#ifndef _COMPUTER_H +#define _COMPUTER_H + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include "computer.h" +#include "game.h" +#include "board.h" + +int select_random_column(int number_of_columns, int number_of_rows, int** board); +int select_smart_column(int number_of_columns, int number_of_rows, int** board); +bool is_column_full(int col, int rows, int** board); +#endif diff --git a/src/game.h b/src/game.h index 75d431c01f90e91b85e815801471681b93969170..4e31cb1decdde29713252ec82b2b94da858a93c9 100644 --- a/src/game.h +++ b/src/game.h @@ -3,6 +3,7 @@ #include <stdbool.h> #include "board.h" +#include "computer.h" enum GAME_MODE { PLAYER_VS_RANDOM_AI = 1, diff --git a/src/test_board.c b/src/test_board.c index 018c4936d89f519b02371112cdf2f43fa3542ed3..5e351ae08b21cc5c71f69632abf2f51983e9ac33 100644 --- a/src/test_board.c +++ b/src/test_board.c @@ -2,7 +2,7 @@ #include <stdlib.h> #include <assert.h> #include <string.h> -#include "../src/board.h" +#include "board.h" #define GREEN "\033[1;32m" #define RED "\033[1;31m" diff --git a/src/test_computer.c b/src/test_computer.c new file mode 100644 index 0000000000000000000000000000000000000000..63a111d38d9fb2c1342d6db62edc0a1ea707f0aa --- /dev/null +++ b/src/test_computer.c @@ -0,0 +1,99 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include "computer.h" +#include "game.h" + +int** setup_board(int rows, int cols, int default_value) { + int** board = create_board(rows, cols); + if (board == NULL) { + fprintf(stderr, "Error: Unable to allocate memory for the board.\n"); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + board[i][j] = default_value; + } + } + + return board; +} + +void reset_board(int rows, int cols, int** board) { + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + board[i][j] = EMPTY; + } + } +} + +void test_is_column_full() { + int rows = 6, cols = 7; + int** board = setup_board(rows, cols, EMPTY); + + // An empty column should not be considered full + assert(is_column_full(1, rows, board) == false); + + // Fill a column completely and test again + for (int i = 0; i < rows; i++) { + board[i][0] = PLAYER1; + } + assert(is_column_full(1, rows, board) == true); + + destroy_board(rows, board); +} + +void test_select_smart_column() { + int rows = 6, cols = 7; + int** board = setup_board(rows, cols, EMPTY); + + // Simulate a situation where the AI can win immediately + board[5][0] = PLAYER2; + board[4][0] = PLAYER2; + board[3][0] = PLAYER2; + + assert(select_smart_column(cols, rows, board) == 1); + reset_board(rows, cols, board); + + // Simulate a situation where the player can win on the next move + board[5][1] = PLAYER1; + board[4][1] = PLAYER1; + board[3][1] = PLAYER1; + assert(select_smart_column(cols, rows, board) == 2); + + destroy_board(rows, board); +} + +void test_select_random_column() { + int rows = 6, cols = 7; + int** board = setup_board(rows, cols, EMPTY); + + // Select a random column on an empty board + int col = select_random_column(cols, rows, board); + + assert(col == 2); // The expected value based on the random seed + + // Fill all columns except one + for (int j = 0; j < cols - 1; j++) { + for (int i = 0; i < rows; i++) { + board[i][j] = PLAYER1; + } + } + + // The AI should select the only non-full column, which is column 7 (the last one) + col = select_random_column(cols, rows, board); + assert(col == 7); // The last remaining column + + destroy_board(rows, board); +} + + +int main() { + test_is_column_full(); + test_select_smart_column(); + test_select_random_column(); + + printf("All computer tests passed successfully.\n"); + return EXIT_SUCCESS; +} diff --git a/tests_board b/tests_board index 74e2f07dd6600edda11e746f20c1c567898064ad..4963c645ce1583ee18bdcea9857c70d6a42b9165 100755 Binary files a/tests_board and b/tests_board differ diff --git a/tests_computer b/tests_computer new file mode 100755 index 0000000000000000000000000000000000000000..a1c01814781908237943a909f10aeff832497a61 Binary files /dev/null and b/tests_computer differ diff --git a/tests_game b/tests_game new file mode 100755 index 0000000000000000000000000000000000000000..f1cfa60e87bb4232589ffc7d78236e1434260775 Binary files /dev/null and b/tests_game differ