From 3bd9e5dd4d237b3dc3e021051caf2dfbc6e6aad7 Mon Sep 17 00:00:00 2001 From: Adam Majerik Date: Sat, 11 Jan 2025 19:42:40 +0100 Subject: [PATCH 1/2] implemented inter-process communication --- client/client.c | 122 +++++++++++++++++++++++++++++++-------- server/server.c | 146 ++++++++++++++++++++++++++++++++++++++++++----- source/board.c | 17 +++--- source/board.h | 4 +- source/message.h | 13 +++++ 5 files changed, 252 insertions(+), 50 deletions(-) create mode 100644 source/message.h diff --git a/client/client.c b/client/client.c index 46b5672..9fd20fa 100644 --- a/client/client.c +++ b/client/client.c @@ -8,15 +8,18 @@ #include "../source/board.h" #include "../source/menu.h" #include "../source/utils.h" +#include "../source/message.h" -#define SERVER_IP "127.0.0.1" //localhost +#define SERVER_IP "127.0.0.1" // localhost -int initialize_client(int port) { +int initialize_client(int port) +{ int sock; struct sockaddr_in server_addr; // Create socket - if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { perror("IC: Socket creation failed"); exit(EXIT_FAILURE); } @@ -25,13 +28,16 @@ int initialize_client(int port) { server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); - if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) { + //TODO Adam: do we need this? + if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) + { perror("IC: Invalid address or address not supported"); exit(EXIT_FAILURE); } // Connect to server - if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) + { perror("IC: Connection failed"); exit(EXIT_FAILURE); } @@ -39,53 +45,119 @@ int initialize_client(int port) { return sock; } -void play_game(int sock) { +void play_game(int sock) +{ board b_own; board b_enemy; - + bool game_over = false; + bool my_turn = false; + Message msg; + // Initialize game boards board_init(&b_own, 10); board_init(&b_enemy, 10); - + // Place ships place_ships(&b_own); - + // Game loop - bool requestGameEnd = false; - while (!requestGameEnd) { - // TODO Adam: Implement game loop with server communication - // Display initial board state - // board_display(&b_own, &b_enemy); + while (!game_over) + { + // receive message from server + if (recv(sock, &msg, sizeof(Message), 0) <= 0) { + perror("Play Game: Connection lost"); + break; + } + + switch(msg.type) { + case MSG_WAIT_PLAYER: + show_message("Waiting for other player..."); + break; + + case MSG_START_GAME: + clear_screen(); + show_message("Game started!"); + my_turn = true; // First player starts + break; + + case MSG_YOUR_TURN: + my_turn = true; + break; + + case MSG_SHOT: + // Received shot from opponent + int hit =receive_shot(msg.x, msg.y, &b_own); + + // Send result back + Message result = { + .type = MSG_RESULT, + .x = msg.x, + .y = msg.y, + .hit = hit + }; + send(sock, &result, sizeof(Message), 0); + break; + + case MSG_RESULT: + //Result of our shot + mark_hit(msg.x,msg.y, msg.hit, &b_enemy); + my_turn = true; //our turn again + break; + + case MSG_GAME_OVER: + game_over = true; + break; + } + + if (my_turn) { + board_display(&b_own, &b_enemy); + char* shot = shoot(&b_enemy); + // TODO refactor? + int x = shot[0] - 'A'; + int y = shot[1] - '0'; + + // Send shot + Message msg = { + .type = MSG_SHOT, + .x = x, + .y = y + }; + send(sock, &msg, sizeof(Message), 0); + my_turn = false; + } } - + // Cleanup board_destroy(&b_own); board_destroy(&b_enemy); } -int main(int argc, char** argv) { +int main(int argc, char **argv) +{ int mode; - + // Handle menu mode = handle_menu(); - if (mode == 0) // Quit + if (mode == 0) // Quit return 0; - + clear_screen(); - - if (mode == 1) { // Computer game mode + + if (mode == 1) // Computer game mode + { show_message("Computer mode not yet implemented"); sleep(3); clear_screen(); - //TODO Waffle: implement + // TODO Waffle: implement return 0; } - + int port = 8536; - if(argc >= 2) + if (argc >= 2) port = atoi(argv[1]); - if (mode == 2) { // Human vs Human (network) mode + if (mode == 2) // Human vs Human (network) mode + { show_message("Please wait, connecting..."); sleep(1); int sock = initialize_client(port); diff --git a/server/server.c b/server/server.c index 8b35245..706acdd 100644 --- a/server/server.c +++ b/server/server.c @@ -7,16 +7,43 @@ #include #include #include +#include "../source/message.h" #define MAX_PLAYERS 2 atomic_int player_count = 0; // Atomic variable to track connected players +typedef struct { + int socket; + int opponent_socket; + bool has_opponent; +} Player; + +Player players[MAX_PLAYERS]; +pthread_mutex_t players_mutex = PTHREAD_MUTEX_INITIALIZER; + +int find_player_slot(int socket) { + for(int i = 0; i < MAX_PLAYERS; i++) { + if(players[i].socket == socket) { + return i; + } + } + return -1; +} + +int find_empty_slot() { + for(int i = 0; i < MAX_PLAYERS; i++) { + if(players[i].socket == 0) { + return i; + } + } + return -1; +} + int initialize_server(int port) { int server_fd; struct sockaddr_in address; - int opt = 1; // Create socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) @@ -49,29 +76,119 @@ int initialize_server(int port) void *handle_client(void *arg) { + //Dereference the pointer to get the socket int client_socket = *(int *)arg; - free(arg); // Free memory allocated for client_socket + + // Register player + pthread_mutex_lock(&players_mutex); + int player_slot = find_empty_slot(); + if(player_slot == -1) { + pthread_mutex_unlock(&players_mutex); + close(client_socket); + return NULL; + } + + players[player_slot].socket = client_socket; + players[player_slot].has_opponent = false; + + //Try to find opponent, assign opponent socket and set has_opponent to true + for(int i = 0; i < MAX_PLAYERS; i++) { + if(i != player_slot && players[i].socket != 0 && !players[i].has_opponent) { + players[player_slot].opponent_socket = players[i].socket; + players[player_slot].has_opponent = true; + players[i].opponent_socket = client_socket; + players[i].has_opponent = true; + break; + } + } + pthread_mutex_unlock(&players_mutex); + + // Wait for opponent + while(!players[player_slot].has_opponent) { + Message msg ={.type = MSG_WAIT_PLAYER}; + send(client_socket, &msg, sizeof(Message), 0); + sleep(1); + + pthread_mutex_lock(&players_mutex); + if(atomic_load(&player_count) < 2) { + pthread_mutex_unlock(&players_mutex); + continue; + } + pthread_mutex_unlock(&players_mutex); + } - char buffer[1024] = {0}; + // Start game + Message start_msg = {.type = MSG_START_GAME}; + send(client_socket, &start_msg, sizeof(Message), 0); + + // First player gets first turn + if(player_slot == 0) { + Message turn_msg = {.type = MSG_YOUR_TURN}; + send(client_socket, &turn_msg, sizeof(Message), 0); + } + + // Game loop + while(true) { + Message msg; + if(recv(client_socket, &msg, sizeof(Message), 0) <= 0) { + break; + } - // Read message from client - read(client_socket, buffer, 1024); - printf("HC: Message from client: %s\n", buffer); + pthread_mutex_lock(&players_mutex); + int opponent_socket = players[player_slot].opponent_socket; + pthread_mutex_unlock(&players_mutex); + + switch(msg.type) { + case MSG_SHOT: + // Forward shot to opponent + send(opponent_socket, &msg, sizeof(Message), 0); + break; + + case MSG_RESULT: + // Forward result to opponent + send(opponent_socket, &msg, sizeof(Message), 0); + // Send turn message to opponent + Message turn_msg = {.type = MSG_YOUR_TURN}; + send(opponent_socket, &turn_msg, sizeof(Message), 0); + break; + } + } - // Send response to client - const char* message = "Hello from server"; - send(client_socket, message, strlen(message), 0); - printf("HC: Message sent to client\n"); + pthread_mutex_lock(&players_mutex); + int opponent_socket = players[player_slot].opponent_socket; + + // TODO Adam: check if player receives game over message + // Send game over to opponent if they're still connected + if(players[player_slot].has_opponent) { + Message game_over = {.type = MSG_GAME_OVER}; + send(opponent_socket, &game_over, sizeof(Message), 0); + } + + // Destroy player slot + players[player_slot].socket = 0; + players[player_slot].opponent_socket = 0; + players[player_slot].has_opponent = false; + + // destroy opponent's data + for(int i = 0; i < MAX_PLAYERS; i++) { + if(players[i].socket == opponent_socket) { + players[i].opponent_socket = 0; + players[i].has_opponent = false; + break; + } + } + pthread_mutex_unlock(&players_mutex); - // Decrement the player count atomically atomic_fetch_sub(&player_count, 1); - close(client_socket); return NULL; } int main(int argc, char** argv) { + // Initialize players array with 0s + memset(players, 0, sizeof(players)); + int port = 8536; if(argc >= 2) port = atoi(argv[1]); @@ -107,13 +224,14 @@ int main(int argc, char** argv) if (pthread_create(&thread_id, NULL, handle_client, new_socket) != 0) { perror("Server: Thread creation failed"); - atomic_fetch_sub(&player_count, 1); // Decrement count if thread creation fails + atomic_fetch_sub(&player_count, 1); close(*new_socket); free(new_socket); } else { - pthread_detach(thread_id); // Detach the thread to avoid memory leaks + // Launch the thread but dont wait for it to finish + pthread_detach(thread_id); } } else diff --git a/source/board.c b/source/board.c index 1bd3819..fa2feaf 100644 --- a/source/board.c +++ b/source/board.c @@ -276,7 +276,7 @@ bool check_destroyed(int x, int y, board* b) } // Output will probably be sent to server -bool receive_shot(int x, int y, board* b) +int receive_shot(int x, int y, board* b) { if (b->board_[x][y] == SHIP) { @@ -286,13 +286,13 @@ bool receive_shot(int x, int y, board* b) b->destroyed_++; if (b->destroyed_ == 5) { - // Probably gonna want to send the server an "im dead" message + return -1; // Game over } } - return true; + return 1; } b->board_[x][y] = HIT_WATER; - return false; + return 0; } void get_shot(char* shot, board* b_enemy) @@ -337,12 +337,11 @@ char* shoot(board* b_enemy) get_shot(shot, b_enemy); return shot; } -// Adam, dont forget to save the last shot before you send it to server, youll need it for this -void mark_hit(char* shot, bool destroyed, board* b_enemy) + +// Adam, dont forget to save the last shot before you send it to server, you'll need it for this +void mark_hit(int x, int y, int hit, board* b_enemy) { - int x = shot[0] - 'A'; - int y = shot[1] - '0'; - if (destroyed) + if (hit == 1) { b_enemy->board_[x][y] = HIT_SHIP; return; diff --git a/source/board.h b/source/board.h index 5d2b370..6cf119f 100644 --- a/source/board.h +++ b/source/board.h @@ -39,10 +39,10 @@ void place_ships(board* b); bool check_destroyed(int x, int y, board* b); -bool receive_shot(int x, int y, board* b); +int receive_shot(int x, int y, board* b); void get_shot(char* shot, board* b_enemy); char* shoot(board* b_enemy); -void mark_hit(char* shot, bool destroyed, board* b_enemy); \ No newline at end of file +void mark_hit(int x, int y, int hit, board* b_enemy); \ No newline at end of file diff --git a/source/message.h b/source/message.h new file mode 100644 index 0000000..e10b62d --- /dev/null +++ b/source/message.h @@ -0,0 +1,13 @@ +#define MSG_WAIT_PLAYER 'W' // Wait for other player +#define MSG_START_GAME 'S' // Start the game +#define MSG_YOUR_TURN 'T' // Your turn +#define MSG_SHOT 'H' // Shot coordinates +#define MSG_RESULT 'R' // Shot result +#define MSG_GAME_OVER 'G' // Game over + +typedef struct { + char type; // Message type + int x; // X coordinate + int y; // Y coordinate + int hit; // 1 if hit, 0 if miss, -1 if game over +} Message; \ No newline at end of file From d53a4528f41e8a5eba8c8e1ca1103d82d3513e1a Mon Sep 17 00:00:00 2001 From: Adam Majerik Date: Sat, 11 Jan 2025 21:26:17 +0100 Subject: [PATCH 2/2] fixed bugs in game logic --- client/client.c | 7 +++---- server/server.c | 19 ++++++++++++++----- source/board.c | 11 ++++++----- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/client/client.c b/client/client.c index 9fd20fa..8fe8634 100644 --- a/client/client.c +++ b/client/client.c @@ -77,7 +77,6 @@ void play_game(int sock) case MSG_START_GAME: clear_screen(); show_message("Game started!"); - my_turn = true; // First player starts break; case MSG_YOUR_TURN: @@ -99,9 +98,8 @@ void play_game(int sock) break; case MSG_RESULT: - //Result of our shot - mark_hit(msg.x,msg.y, msg.hit, &b_enemy); - my_turn = true; //our turn again + // Result of our shot + mark_hit(msg.x, msg.y, msg.hit, &b_enemy); break; case MSG_GAME_OVER: @@ -115,6 +113,7 @@ void play_game(int sock) // TODO refactor? int x = shot[0] - 'A'; int y = shot[1] - '0'; + free(shot); // Add this to prevent memory leak // Send shot Message msg = { diff --git a/server/server.c b/server/server.c index 706acdd..4714138 100644 --- a/server/server.c +++ b/server/server.c @@ -105,7 +105,7 @@ void *handle_client(void *arg) // Wait for opponent while(!players[player_slot].has_opponent) { - Message msg ={.type = MSG_WAIT_PLAYER}; + Message msg = {.type = MSG_WAIT_PLAYER}; send(client_socket, &msg, sizeof(Message), 0); sleep(1); @@ -121,10 +121,16 @@ void *handle_client(void *arg) Message start_msg = {.type = MSG_START_GAME}; send(client_socket, &start_msg, sizeof(Message), 0); - // First player gets first turn + // Wait a moment to ensure both players have received START_GAME + usleep(100000); // 100ms + + // First player gets first turn, but only after both are ready if(player_slot == 0) { Message turn_msg = {.type = MSG_YOUR_TURN}; send(client_socket, &turn_msg, sizeof(Message), 0); + } else { + Message wait_msg = {.type = MSG_WAIT_PLAYER}; + send(client_socket, &wait_msg, sizeof(Message), 0); } // Game loop @@ -140,16 +146,19 @@ void *handle_client(void *arg) switch(msg.type) { case MSG_SHOT: + Message wait_msg = {.type = MSG_WAIT_PLAYER}; + send(client_socket, &wait_msg, sizeof(Message), 0); + // Forward shot to opponent send(opponent_socket, &msg, sizeof(Message), 0); break; case MSG_RESULT: - // Forward result to opponent + // Forward result to shooter send(opponent_socket, &msg, sizeof(Message), 0); - // Send turn message to opponent + // Send turn message to the player who just got shot at Message turn_msg = {.type = MSG_YOUR_TURN}; - send(opponent_socket, &turn_msg, sizeof(Message), 0); + send(client_socket, &turn_msg, sizeof(Message), 0); break; } } diff --git a/source/board.c b/source/board.c index fa2feaf..83841c1 100644 --- a/source/board.c +++ b/source/board.c @@ -297,12 +297,11 @@ int receive_shot(int x, int y, board* b) void get_shot(char* shot, board* b_enemy) { - bool accepted = false; - while (!accepted) + while (true) { - int c; - while ((c = getchar() != '\n') && c != EOF); - if (fgets(shot, sizeof(char) * 2, stdin) == NULL) { + char* result = fgets(shot, sizeof(char) * 3, stdin); + while (getchar() != '\n'); + if (result == NULL) { printf("An error occured, try again\n"); continue; } @@ -321,7 +320,9 @@ void get_shot(char* shot, board* b_enemy) if(b_enemy->board_[x][y] != NOT_HIT) { printf("You have already shot there, try again\n"); + continue; } + break; } printf("Shot accepted!\n"); }