Skip to content
Snippets Groups Projects
Commit 62438e0e authored by dario.genga's avatar dario.genga
Browse files

Merge branch 'develop' into main

parents 78dfd10b 8016548c
Branches
No related tags found
No related merge requests found
Showing
with 455 additions and 24 deletions
......@@ -78,4 +78,9 @@ dkms.conf
# End of https://www.toptal.com/developers/gitignore/api/c,visualstudiocode
# Custom gitignore for project
puissance
\ No newline at end of file
puissance4
testbed/2players/test*
testbed/rand_ai/test*
testbed/smart_ai/test*
cmake-build-debug
.idea
\ No newline at end of file
......@@ -17,7 +17,7 @@ Use this command to compile the project.
Use this command to clean the project.
### Run the tests
> `make test`
> `make tests`
>
> `./test`
......
......@@ -7,12 +7,62 @@
#include <stdlib.h>
#include "puissance.h"
int main() {
void print_help() {
printf("Usage: puissance4 <mode> <row> <col>\n");
printf(" mode specifies the mode:\n");
printf(" 1 = single player game (random),\n");
printf(" 2 = single player game (AI),\n");
printf(" 3 = two players game\n");
printf(" row specifies the number of rows (>= 4)\n");
printf(" col specifies the number of columns (>= 4)\n");
}
void play_game(puissance game) {
int selected_col_index = -1;
while(game.state == ONGOING) {
// First player action (always a human)
if (game.current_player == PLAYER_ONE || game.mode == TWO_PLAYERS) {
printf("\nColumn number? (starts at 1):");
do {
scanf("%d", &selected_col_index);
selected_col_index -= 1;
} while (manual_play(&game, selected_col_index) == false);
}
// Second player action (can be the computer)
else {
if (game.mode == RAND_AI) {
random_play(&game);
} else {
smart_play(&game);
};
}
print_game(game);
}
}
int main(int argc, char *argv[]) {
srand(0);
puissance game;
game_init(&game, RAND_AI, DEFAULT_ROW, DEFAULT_COL);
// Get game arguments
if (argc != 4) {
print_help();
return EXIT_FAILURE;
}
GameMode mode = atoi(argv[1]) - 1;
int rows = atoi(argv[2]);
int cols = atoi(argv[3]);
// Initialize the game
game_init(&game, mode, rows, cols);
printf("Board size is %dx%d (rows x col)", rows, cols);
print_game(game);
// Start playing
play_game(game);
// Free the memory and exit the program
game_destroy(&game);
return EXIT_SUCCESS;
}
LIB=-lm
CC=gcc -Wall -Wextra -g
puissance: puissance.o main.o
puissance4: puissance.o main.o
$(CC) $^ -fsanitize=address -fsanitize=leak -o $@ $(LIB)
puissance.o: puissance.c puissance.h
$(CC) -c $< $(LIB)
main.o: main.c
$(CC) -c $< $(LIB)
tests: puissance4
$(MAKE) -C testbed
clean:
rm -f *.o puissance test
\ No newline at end of file
rm -f *.o puissance4 test
$(MAKE) -C testbed clean
\ No newline at end of file
......@@ -5,45 +5,416 @@
#include "puissance.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void game_init(puissance *p, GameMode mode, int row, int col) {
p->mode = mode;
p->state = ONGOING;
p->current_player = PLAYER_ONE;
p->row = row;
p->col = col;
// Allocate memory for the data
p->data = malloc(col * sizeof(int*));
for (int i = 0; i < col; i++) {
p->data[i] = malloc(row * sizeof(int*));
}
// Set a default value for the data
for (int r = 0; r < row; r++) {
for (int c = 0; c < col; c++) {
p->data[r][c] = EMPTY_CELL_VALUE;
}
}
}
puissance game_copy(puissance *p) {
puissance copy;
game_init(&copy, p->mode, p->row, p->col);
copy.state = p->state;
copy.current_player = p->current_player;
for (int r = 0; r < copy.row; r++) {
for (int c = 0; c < copy.col; c++) {
copy.data[r][c] = p->data[r][c];
}
}
return copy;
}
void print_top(char *display, int col) {
// Print top
strcat(display, "\n┌");
for (int i = 1; i <= col * 2 - 1; i++) {
if (i % 2 == 1) {
strcat(display, "─");
} else {
strcat(display, "┬");
}
}
strcat(display, "┐\n");
}
void print_bot(char *display, int col) {
// Print bot
strcat(display, "└");
for (int i = 1; i <= col * 2 - 1; i++) {
if (i % 2 == 1) {
strcat(display, "─");
} else {
strcat(display, "┴");
}
}
strcat(display, "┘\n");
// Print col numbers
for (int i = 1; i <= col; i++) {
char i_has_string[256];
sprintf(i_has_string, "%d", i);
strcat(display, " ");
strcat(display, i_has_string);
}
}
void print_row_separator(char *display, int col) {
strcat(display, "├");
for (int i = 1; i <= col * 2 - 1; i++) {
if (i % 2 == 1) {
strcat(display, "─");
} else {
strcat(display, "┼");
}
}
strcat(display, "┤\n");
}
void print_game(puissance p) {
char display[1024];
// Ensure that the display string is empty before doing anything
display[0] = '\0';
// Print the top of the game
print_top(display, p.col);
// Print the rest of the game (except bottom)
for (int row_index = 0; row_index < p.row; row_index++) {
for (int col_index = 0; col_index < p.col; col_index++) {
// Print each row
strcat(display, "│");
if (p.data[row_index][col_index] == PLAYER_ONE) {
strcat(display, PLAYER_ONE_STRING);
}
else if (p.data[row_index][col_index] == PLAYER_TWO) {
strcat(display, PLAYER_TWO_STRING);
}
else {
strcat(display, " ");
}
// Close the row before going to the next line
if (col_index == p.col - 1) {
strcat(display, "│\n");
}
}
// Print the row separator (except when we are at the bottom)
if (row_index != p.row - 1) {
print_row_separator(display, p.col);
}
}
// Print the bottom then display the game
print_bot(display, p.col);
printf("%s", display);
// Clear the string to avoid SIGABRT
display[0] = '\0';
if (p.state != ONGOING) {
display_game_result(p);
}
}
void verify_game(puissance p) {
GameResult get_winning_player(puissance *p) {
// Return the player who have won.
if (p->current_player == PLAYER_ONE) {
p->state = PLAYER_ONE_WIN;
} else {
p->state = PLAYER_TWO_WIN;
}
return p->state;
}
GameResult vertical_game_check(puissance *p, int last_col_index_played) {
if (p->state != ONGOING) {
return p->state;
}
bool four_aligned = false;
// Get the row index
int last_row_index_played = get_available_row_index(p, last_col_index_played) + 1;
// Verify if we have enough vertical space.
if (last_row_index_played + NB_VERIFICATION_FOR_WIN < p->row) {
int last_played_value = p->data[last_row_index_played][last_col_index_played];
four_aligned = true;
// Verify if the aligned value are the same
for (int i = 1; i <= NB_VERIFICATION_FOR_WIN; i++) {
if (last_played_value != p->data[last_row_index_played + i][last_col_index_played]) {
four_aligned = false;
break;
}
}
if (four_aligned) {
return get_winning_player(p);
}
}
return ONGOING;
}
GameResult horizontal_game_check(puissance *p, int last_col_index_played) {
if (p->state != ONGOING) {
return p->state;
}
bool four_aligned = false;
// Get the row index
int last_row_index_played = get_available_row_index(p, last_col_index_played) + 1;
int last_played_value = p->data[last_row_index_played][last_col_index_played];
int same_aligned = 0;
for (int i = 0; i < p->col; i++) {
if (last_played_value == p->data[last_row_index_played][i]) {
same_aligned++;
} else {
same_aligned = 0;
}
if (same_aligned == NB_SAME_VALUE_ALIGNED_FOR_WIN) {
four_aligned = true;
break;
}
}
if (four_aligned) {
return get_winning_player(p);
}
return ONGOING;
}
bool diagonal_parse(puissance *p, int x_r, int y_c, int starting_row, int starting_col) {
bool four_aligned = true;
int last_played_value = p->data[starting_row][starting_col];
// Search the end position of the diagonal
do {
int x = -x_r + starting_row;
int y = -y_c + starting_col;
if (x > 0 && x < p->row && y > 0 && y < p->col && p->data[x][y] == last_played_value) {
starting_row = x;
starting_col = y;
} else {
break;
}
} while(true);
// Verify if we have the same value aligned enough times
for (int i = 1; i <= NB_VERIFICATION_FOR_WIN; i++) {
int r = x_r * i + starting_row;
int c = y_c * i + starting_col;
if (r < 0 || r >= p->row || c < 0 || c >= p->col) {
return false;
}
if (p->data[r][c] != last_played_value) {
four_aligned = false;
break;
}
}
return four_aligned;
}
GameResult diagonal_game_check(puissance *p, int last_col_index_played) {
if (p->state != ONGOING) {
return p->state;
}
// Get the row index
int last_row_index_played = get_available_row_index(p, last_col_index_played) + 1;
// down right
if (diagonal_parse(p, 1, 1, last_row_index_played, last_col_index_played)) {
return get_winning_player(p);
}
// down left
if (diagonal_parse(p, 1, -1, last_row_index_played, last_col_index_played)) {
return get_winning_player(p);
}
// up left
if (diagonal_parse(p, -1, -1, last_row_index_played, last_col_index_played)) {
return get_winning_player(p);
}
// up right
if (diagonal_parse(p, -1, 1, last_row_index_played, last_col_index_played)) {
return get_winning_player(p);
}
return ONGOING;
}
GameResult verify_space_remaining(puissance *p) {
if (p->state != ONGOING) {
return p->state;
}
bool space_available = false;
int top_row_index = 0;
// Verify that at least one cell at the top row is empty.
for (int i = 0; i < p->col; i++) {
if (p->data[top_row_index][i] == EMPTY_CELL_VALUE) {
space_available = true;
break;
}
}
// If no cells at top is empty, then the game can't continue, so it's a draw.
if (space_available == false) {
p->state = DRAW;
}
return p->state;
}
GameResult verify_game(puissance *p, int last_col_index_played) {
// Vertical check
vertical_game_check(p, last_col_index_played);
// Horizontal check
horizontal_game_check(p, last_col_index_played);
// Diagonal check
diagonal_game_check(p, last_col_index_played);
// Verify remaining space
verify_space_remaining(p);
return p->state;
}
GameResult display_game_result(puissance p) {
if (p.state == PLAYER_ONE_WIN) {
printf("\nPlayer one won!\n");
}
if (p.state == PLAYER_TWO_WIN) {
if (p.mode == TWO_PLAYERS) {
printf("\nPlayer two won!\n");
} else {
printf("\nComputer won!\n");
}
}
if (p.state == DRAW) {
printf("\nIt is a draw.\n");
}
return p.state;
}
int get_available_row_index(puissance *p, int selected_col_index) {
// Verify the index selected
if (selected_col_index >= p->col || selected_col_index < 0) {
return EMPTY_CELL_VALUE; // Bad index
}
for (int i = p->row - 1; i >= 0; i--) {
if (p->data[i][selected_col_index] == EMPTY_CELL_VALUE) {
return i;
}
}
return -1; // No remaining row
}
bool manual_play(puissance *p, int selected_col_index) {
bool valid_action = true;
// Get the available row index and verify that the column is not full.
int row_index = get_available_row_index(p, selected_col_index);
if (row_index < 0) {
return false;
}
// Play
p->data[row_index][selected_col_index] = p->current_player;
// Verify the game
GameResult result = verify_game(p, selected_col_index);
return valid_action;
// Switch the current player
if (result == ONGOING) {
if (p->current_player == PLAYER_ONE) {
p->current_player = PLAYER_TWO;
} else {
p->current_player = PLAYER_ONE;
}
}
// Valid the action
return true;
}
bool random_play(puissance *p) {
bool valid_action = true;
int max = p->col - 1;
int min = 0;
int random_index = -1;
do {
random_index = rand() % (max - min + 1) + min;
} while(manual_play(p, random_index) == false);
return true;
}
bool search_optimal_action(puissance *p, bool simulate_player_one) {
bool move_validated = false;
// Search the optimal action by simulating each possible action
// Not the must memory efficient but this method is easily implemented and should work without problems
for (int i = 0; i < p->col; i++) {
puissance copy = game_copy(p);
if (simulate_player_one) {
copy.current_player = PLAYER_ONE;
}
return valid_action;
if (manual_play(&copy, i)) {
if (copy.state != ONGOING) {
move_validated = manual_play(p, i);
}
}
game_destroy(&copy);
if (move_validated) {
return move_validated;
}
}
return move_validated;
}
bool smart_play(puissance *p) {
bool valid_action = true;
// Search for a wining action
if (search_optimal_action(p, false)) {
return true;
}
return valid_action;
// Try to prevent the user to win
if (search_optimal_action(p, true)) {
return true;
}
// If nothing has been done, do something random
return random_play(p);
}
void game_destroy(puissance *p) {
for (int i = 0; i < p->col; i++) {
free(p->data[i]);
}
free(p->data);
p = NULL;
}
......
......@@ -13,6 +13,11 @@
#define COL_MIN 4
#define DEFAULT_ROW 6
#define DEFAULT_COL 7
#define PLAYER_ONE_STRING "X"
#define PLAYER_TWO_STRING "O"
#define EMPTY_CELL_VALUE -2
#define NB_SAME_VALUE_ALIGNED_FOR_WIN 4
#define NB_VERIFICATION_FOR_WIN (NB_SAME_VALUE_ALIGNED_FOR_WIN - 1)
typedef enum {
RAND_AI,
......@@ -34,6 +39,7 @@ typedef enum {
typedef struct _puissance {
GameMode mode;
GameResult state;
Player current_player;
int row;
int col;
......@@ -41,11 +47,16 @@ typedef struct _puissance {
} puissance;
void game_init(puissance *p, GameMode mode, int row, int col);
puissance game_copy(puissance *p);
void print_game(puissance p);
void verify_game(puissance p);
GameResult verify_game(puissance *p, int last_col_index_played);
GameResult display_game_result(puissance p);
int get_available_row_index(puissance *p, int selected_col_index);
bool manual_play(puissance *p, int selected_col_index);
bool random_play(puissance *p);
bool smart_play(puissance *p);
void game_destroy(puissance *p);
// TODO: add get top of col
#endif
\ No newline at end of file
puissance4:
@echo "first rule which must create the puissance4 executable"
clean:
@echo "this rule must clean everything up (including candidate files in testbed)"
$(MAKE) -C testbed clean
tests: puissance4
$(MAKE) -C testbed
\ No newline at end of file
File deleted
File moved
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment