diff --git a/Makefile b/Makefile index 6d85b74615d772d09c4e77a3011e49e0790fba21..bc8b3ac47d8df2b91aba7904fe132193722f2067 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,12 @@ CFLAGS = -g -Wall -Wextra -Wpedantic LDLIBS = -lm LDFLAGS = -fsanitize=address -fsanitize=leak -puissance4: puissance4.c src/board.o src/game.o +puissance4: puissance4.o src/board.o src/game.o $(CC) $(CFLAGS) $^ -o $@ $(LDLIBS) $(LDFLAGS) board.o: src/board.h game.o: src/game.h +puissance4.o: puissance4.h clean: @echo "this rule must clean everything up (including candidate files in testbed)" @@ -29,8 +30,13 @@ tests_rand_ai: puissance4 tests_smart_ai: puissance4 $(MAKE) -C testbed/smart_ai -generate_board_test: src/test_board.o src/board.o - $(CC) $(CFLAGS) $^ -o src/test_board $(LDLIBS) $(LDFLAGS) +tests_game: src/test_game.o src/game.o src/board.o + $(CC) $(CFLAGS) $^ -o tests_game $(LDLIBS) $(LDFLAGS) + ./tests_game -tests_board: generate_board_test - ./src/test_board \ No newline at end of file +tests_board: src/test_board.o src/board.o + $(CC) $(CFLAGS) $^ -o tests_board $(LDLIBS) $(LDFLAGS) + ./tests_board + +run_tests: tests_game tests_board + @echo "All tests executed successfully." \ No newline at end of file diff --git a/puissance4.c b/puissance4.c index 85b0e6b03b65c3836192ea7c11d062aeb817297e..f21647932014dfca9c3b2971c793d710655d792d 100644 --- a/puissance4.c +++ b/puissance4.c @@ -1,19 +1,16 @@ -#include <stdio.h> -#include <stdlib.h> -#include <stdbool.h> -#include "src/game.h" -#include "src/board.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); +#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]))) { @@ -26,10 +23,63 @@ int main(int argc, char const* argv[]) { } enum GAME_MODE game_mode = atoi(argv[1]); - int rows = atoi(argv[2]); - int cols = atoi(argv[3]); + int row_count = atoi(argv[2]); + int col_count = atoi(argv[3]); srand(AI_SEED); - return play_game(game_mode, rows, cols); + + int** board = create_board(row_count, col_count); + if (board == NULL) { + fprintf(stderr, "Error: Unable to allocate memory for the board.\n"); + return EXIT_FAILURE; + } + + fill_board(row_count, col_count, board); + printf("Board size is %dx%d (rows x col)", row_count, col_count); + print_board(row_count, col_count, board); + + int current_turn = 0; + struct coordinate empty_slot; + + while (true) { + int selected_column = get_selected_column(game_mode, current_turn, col_count, row_count, board); + if (!is_valid_column(selected_column, col_count)) { + continue; + } + + empty_slot = find_empty_slot(selected_column - 1, row_count, board); + place_token(current_turn % 2, empty_slot, board); + print_board(row_count, col_count, board); + + if (does_player_win(empty_slot, row_count, col_count, board)) { + announce_winner(game_mode, current_turn); + break; + } + + if (is_board_full(current_turn, row_count, col_count)) { + printf("\nIt is a draw.\n"); + break; + } + + current_turn++; + } + + if (destroy_board(row_count, board) == EXIT_FAILURE) { + fprintf(stderr, "Error: Unable to free memory for the board.\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +void announce_winner(enum GAME_MODE game_mode, int current_turn) { + const char* winner = (current_turn % 2 == 0) ? "Player one" : "Computer"; + + if (game_mode == PLAYER_VS_PLAYER) { + winner = (current_turn % 2 == 0) ? "one" : "two"; + printf("\nPlayer %s won!\n", winner); + } else { + printf("\n%s won!\n", winner); + } } bool is_valid_game_mode(int game_mode) { diff --git a/puissance4.h b/puissance4.h new file mode 100644 index 0000000000000000000000000000000000000000..ba2d9ccb9749cea6a931cdc432d516c0c4b53d85 --- /dev/null +++ b/puissance4.h @@ -0,0 +1,29 @@ +#ifndef _PUSSIANCE4_h_ +#define _PUSSIANCE4_h_ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include "src/game.h" +#include "src/board.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); +void flush_input(); + +#endif diff --git a/src/actual_output.txt b/src/actual_output.txt deleted file mode 100644 index 8885cb0d6e401e3a28108ea54c3f63c656356eea..0000000000000000000000000000000000000000 --- a/src/actual_output.txt +++ /dev/null @@ -1,15 +0,0 @@ - -┌─┬─┬─┬─┬─┬─┬─┐ -│ │ │ │ │ │ │ │ -├─┼─┼─┼─┼─┼─┼─┤ -│ │ │ │ │ │ │ │ -├─┼─┼─┼─┼─┼─┼─┤ -│ │ │ │ │ │ │ │ -├─┼─┼─┼─┼─┼─┼─┤ -│ │ │ │ │ │ │ │ -├─┼─┼─┼─┼─┼─┼─┤ -│ │ │ │ │ │ │ │ -├─┼─┼─┼─┼─┼─┼─┤ -│ │ │ │ │ │ │ │ -└─┴─┴─┴─┴─┴─┴─┘ - 1 2 3 4 5 6 7 \ No newline at end of file diff --git a/src/game.c b/src/game.c index b80875a4cf01a921d8426d1fa4b8b073e5512be7..530c90f372a503d68e8631658d165e188131abfd 100644 --- a/src/game.c +++ b/src/game.c @@ -23,60 +23,41 @@ struct coordinate find_empty_slot(int x, int rows, int** board) { return coordinate; } -int play_game(enum GAME_MODE game_mode, int rows, int cols) { - int** board = create_board(rows, cols); - if (board == NULL) { - fprintf(stderr, "Error: Unable to allocate memory for the board.\n"); - return EXIT_FAILURE; - } - - fill_board(rows, cols, board); - printf("Board size is %dx%d (rows x col)", rows, cols); - print_board(rows, cols, board); - - int player_selected_column, player = 0; - struct coordinate coordinate; +bool is_valid_column(int column, int col_count) { + return column >= 1 && column <= col_count; +} - while (true) { - if (game_mode == PLAYER_VS_PLAYER || player % 2 == 0) { - printf("\nColumn number? (starts at 1):"); - scanf("%d", &player_selected_column); - } else { - if (game_mode == PLAYER_VS_RANDOM_AI) { - player_selected_column = select_random_column(cols, rows, board); - } else if (game_mode == PLAYER_VS_SMART_AI) { - player_selected_column = select_smart_column(cols, rows, board); - } - } +int get_player_input() { + int column; + printf("\nColumn number? (starts at 1):"); + scanf("%d", &column); + return column; +} - if (player_selected_column < 1 || player_selected_column > cols) { - continue; - } +bool is_board_full(int current_turn, int row_count, int col_count) { + return current_turn + 1 == row_count * col_count; +} - coordinate = find_empty_slot(player_selected_column - 1, rows, board); - place_token(player % 2, coordinate, board); +int get_selected_column(enum GAME_MODE game_mode, int current_turn, int col_count, int row_count, int** board) { + if (is_human_turn(game_mode, current_turn)) { + return get_player_input(); + } + return get_ai_move(game_mode, col_count, row_count, board); +} - print_board(rows, cols, board); - if (does_player_win(coordinate, rows, cols, board)) { - if (game_mode == PLAYER_VS_PLAYER) { - printf("\nPlayer %s won!\n", player % 2 == 0 ? "one" : "two"); - } else { - printf("\n%s won!\n", player % 2 == 0 ? "Player one" : "Computer"); - } - break; - } - if (player + 1 == rows * cols) { - printf("\nIt is a draw.\n"); - break; - } +bool is_human_turn(enum GAME_MODE game_mode, int current_turn) { + return game_mode == PLAYER_VS_PLAYER || current_turn % 2 == 0; +} - player++; - } - if (destroy_board(rows, board) == EXIT_FAILURE) { - fprintf(stderr, "Error: Unable to free memory for the board.\n"); - return EXIT_FAILURE; +int get_ai_move(enum GAME_MODE game_mode, int col_count, int row_count, int** board) { + switch (game_mode) { + case PLAYER_VS_RANDOM_AI: + return select_random_column(col_count, row_count, board); + case PLAYER_VS_SMART_AI: + return select_smart_column(col_count, row_count, board); + default: + return -1; } - return EXIT_SUCCESS; } bool does_player_win(struct coordinate coordinate, int rows, int cols, int** board) { diff --git a/src/game.h b/src/game.h index f9dc934b3cfe154183c9bbd75e1d2d9bb178a2a6..75d431c01f90e91b85e815801471681b93969170 100644 --- a/src/game.h +++ b/src/game.h @@ -1,5 +1,5 @@ -#ifndef GAME_H -#define GAME_H +#ifndef _GAME_H +#define _GAME_H #include <stdbool.h> #include "board.h" @@ -15,9 +15,14 @@ struct coordinate { int y; }; -int play_game(enum GAME_MODE game_mode, int rows, int cols); struct coordinate find_empty_slot(int x, int rows, int** board); bool does_player_win(struct coordinate coordinate, int rows, int cols, int** board); void place_token(int player, struct coordinate coordinate, int** board); +bool is_valid_column(int column, int col_count); +bool is_board_full(int current_turn, int row_count, int col_count); +int get_selected_column(enum GAME_MODE game_mode, int current_turn, int col_count, int row_count, int** board); +bool is_human_turn(enum GAME_MODE game_mode, int current_turn); +int get_ai_move(enum GAME_MODE game_mode, int col_count, int row_count, int** board); +int get_player_input(); #endif diff --git a/src/test_board.c b/src/test_board.c index 050c064b11949e16bf50090020636f65816cfafd..018c4936d89f519b02371112cdf2f43fa3542ed3 100644 --- a/src/test_board.c +++ b/src/test_board.c @@ -4,23 +4,18 @@ #include <string.h> #include "../src/board.h" -#define EXPECTED_OUTPUT "src/expected_output.txt" -#define ACTUAL_OUTPUT "src/actual_output.txt" - #define GREEN "\033[1;32m" #define RED "\033[1;31m" #define RESET "\033[0m" void test_destroy_board(); void test_fill_board(); -// void test_print_board(); void test_create_board(); int main() { test_create_board(); test_fill_board(); test_destroy_board(); - // test_print_board(); return EXIT_SUCCESS; } @@ -38,43 +33,6 @@ void test_destroy_board() { printf(GREEN "test_destroy_board: PASSED\n" RESET); } -// void test_print_board() { -// int rows = 6; -// int cols = 7; -// int** board = create_board(rows, cols); -// assert(board != NULL && "Board creation failed"); - -// fill_board(rows, cols, board); - -// FILE* expected_output_file = fopen(EXPECTED_OUTPUT, "w"); -// assert(expected_output_file != NULL && "Failed to open expected output file"); -// freopen(EXPECTED_OUTPUT, "w", stdout); -// print_board(rows, cols, board); -// fclose(expected_output_file); - -// freopen("/dev/tty", "w", stdout); - -// FILE* actual_output_file = fopen(EXPECTED_OUTPUT, "r"); -// assert(actual_output_file != NULL && "Failed to open actual output file"); - -// char actual_output[1024] = { 0 }; -// fread(actual_output, sizeof(char), 1024, actual_output_file); -// fclose(actual_output_file); - -// FILE* expected_output_file_read = fopen(EXPECTED_OUTPUT, "r"); -// assert(expected_output_file_read != NULL && "Failed to open expected output file for reading"); - -// char expected_output[1024] = { 0 }; -// fread(expected_output, sizeof(char), 1024, expected_output_file_read); -// fclose(expected_output_file_read); - -// assert(strncmp(actual_output, expected_output, strlen(expected_output)) == 0 -// && "Output does not match expected output"); - -// printf(GREEN "test_print_board: PASSED\n" RESET); -// destroy_board(rows, board); -// } - void test_create_board() { int rows = 6; int cols = 7; diff --git a/src/test_game.c b/src/test_game.c new file mode 100644 index 0000000000000000000000000000000000000000..3cd6b996ae61696cbc15c3eac97b2fc91b94ad1a --- /dev/null +++ b/src/test_game.c @@ -0,0 +1,128 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.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 test_place_token() { + int rows = 6, cols = 7; + int** board = setup_board(rows, cols, EMPTY); + + // Player1 + struct coordinate coord = { 2, 3 }; + place_token(0, coord, board); + assert(board[2][3] == PLAYER1); + + // Player2 + coord.x = 4; + coord.y = 5; + place_token(1, coord, board); + assert(board[4][5] == PLAYER2); + + destroy_board(rows, board); +} + +void test_find_empty_slot() { + int rows = 6, cols = 7; + int** board = setup_board(rows, cols, EMPTY); + + // Search for an empty slot in a column + board[5][2] = PLAYER1; + struct coordinate coord = find_empty_slot(2, rows, board); + assert(coord.x == 4 && coord.y == 2); + + // Search for an empty slot in a column with multiple tokens + board[4][2] = PLAYER2; + coord = find_empty_slot(2, rows, board); + assert(coord.x == 3 && coord.y == 2); + + destroy_board(rows, board); +} + +void test_is_valid_column() { + int cols = 7; + + assert(is_valid_column(1, cols) == true); + assert(is_valid_column(7, cols) == true); + + assert(is_valid_column(0, cols) == false); + assert(is_valid_column(8, cols) == false); +} + +void test_is_board_full() { + int rows = 6, cols = 7; + + assert(is_board_full(41, rows, cols) == true); // Full board + assert(is_board_full(40, rows, cols) == false); // Not full board +} + +void test_does_player_win() { + int rows = 6, cols = 7; + int** board = setup_board(rows, cols, EMPTY); + + // Test horizontal win + board[5][0] = PLAYER1; + board[5][1] = PLAYER1; + board[5][2] = PLAYER1; + board[5][3] = PLAYER1; + struct coordinate coord = { 5, 3 }; + assert(does_player_win(coord, rows, cols, board) == true); + + // Test vertical win + fill_board(rows, cols, board); + board[5][2] = PLAYER2; + board[4][2] = PLAYER2; + board[3][2] = PLAYER2; + board[2][2] = PLAYER2; + coord.x = 2; + coord.y = 2; + assert(does_player_win(coord, rows, cols, board) == true); + + // Test diagonal win (left to right) + fill_board(rows, cols, board); + board[5][0] = PLAYER1; + board[4][1] = PLAYER1; + board[3][2] = PLAYER1; + board[2][3] = PLAYER1; + coord.x = 2; + coord.y = 3; + assert(does_player_win(coord, rows, cols, board) == true); + + // Test diagonal win (right to left) + fill_board(rows, cols, board); + board[5][3] = PLAYER2; + board[4][2] = PLAYER2; + board[3][1] = PLAYER2; + board[2][0] = PLAYER2; + coord.x = 2; + coord.y = 0; + assert(does_player_win(coord, rows, cols, board) == true); + + destroy_board(rows, board); +} + +int main() { + test_place_token(); + test_find_empty_slot(); + test_is_valid_column(); + test_is_board_full(); + test_does_player_win(); + + printf("All tests passed successfully.\n"); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests_board b/tests_board new file mode 100755 index 0000000000000000000000000000000000000000..74e2f07dd6600edda11e746f20c1c567898064ad Binary files /dev/null and b/tests_board differ