diff --git a/client/client.c b/client/client.c index 0162a15..096033b 100644 --- a/client/client.c +++ b/client/client.c @@ -1,16 +1,4 @@ -#include -#include -#include -#include -#include -#include -#include -#include "../source/board.h" -#include "../source/menu.h" -#include "../source/utils.h" -#include "../source/message.h" - -#define SERVER_IP "127.0.0.1" // localhost +#include "client.h" int initialize_client(int port) { @@ -56,8 +44,6 @@ void play_game(int sock) // Initialize game boards board_init(&b_own, 10); board_init(&b_enemy, 10); - - // Place ships place_ships(&b_own); // Game loop @@ -117,11 +103,12 @@ void play_game(int sock) 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'; - free(shot); // Add this to prevent memory leak + char* shot = calloc(3, sizeof(char)); + shoot(shot, &b_enemy); + int x; + int y; + parse_input(shot, &x, &y, NULL); + free(shot); // Send shot Message msg = { @@ -142,6 +129,9 @@ void play_game(int sock) int main(int argc, char **argv) { int mode; + int port = DEFAULT_PORT; + if (argc >= 2) + port = atoi(argv[1]); // Handle menu mode = handle_menu(); @@ -150,24 +140,29 @@ int main(int argc, char **argv) clear_screen(); - if (mode == 1) // Computer game mode + if (mode == 1) // Computer singleplayer game mode { - show_message("Computer mode not yet implemented"); - sleep(3); - clear_screen(); - // TODO Waffle: implement + show_message("Connecting to computer opponent..."); + sleep(1); + int sock = initialize_client(port); + + Message mode_msg = {.type = MSG_SINGLE_PLAYER}; + send(sock, &mode_msg, sizeof(Message), 0); + + play_game(sock); + close(sock); return 0; } - int port = 8536; - if (argc >= 2) - port = atoi(argv[1]); - - if (mode == 2) // Human vs Human (network) mode + if (mode == 2) // Human vs Human multiplayer mode { show_message("Please wait, connecting..."); sleep(1); int sock = initialize_client(port); + + Message mode_msg = {.type = MSG_MULTI_PLAYER}; + send(sock, &mode_msg, sizeof(Message), 0); + play_game(sock); close(sock); } diff --git a/client/client.h b/client/client.h new file mode 100644 index 0000000..11d31a4 --- /dev/null +++ b/client/client.h @@ -0,0 +1,17 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../source/board.h" +#include "../source/menu.h" +#include "../source/utils.h" +#include "../source/message.h" + +#define SERVER_IP "127.0.0.1" // localhost + +int initialize_client(int port); + +void play_game(int sock); \ No newline at end of file diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index b42b4be..c570c98 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,2 +1,5 @@ # Project: Server -add_executable(server server.c) \ No newline at end of file +add_executable(server server.c bot_board.c) +target_include_directories(server PRIVATE ../source) +target_link_libraries(server PRIVATE board) +target_link_libraries(server PRIVATE utils) \ No newline at end of file diff --git a/server/bot_board.c b/server/bot_board.c new file mode 100644 index 0000000..5d98ea4 --- /dev/null +++ b/server/bot_board.c @@ -0,0 +1,42 @@ +#include "bot_board.h" + +void generate_board(board* b) +{ + int placed = 0; + int size = 5; + char* position = calloc(3, sizeof(char)); + while(placed < 5) + { + position[0] = 'A' + rand() % b->size_; + position[1] = '0' + rand() % b->size_; + position[2] = rand() % 2 ? 'd' : 'r'; + if (validate_position(position, size, b)) + { + finalise_placement(position, size, b); + placed++; + if (placed != 3) + { + size--; + } + } + } + free(position); +} + +void generate_shot(char* shot, board* b_enemy) +{ + while (true) + { + shot[0] = 'A' + rand() % b_enemy->size_; + shot[1] = '0' + rand() % b_enemy->size_; + shot[2] = '\0'; + int x; + int y; + parse_input(shot, &x, &y, NULL); + if (b_enemy->board_[x][y] != NOT_HIT) + { + continue; + } + break; + } +} \ No newline at end of file diff --git a/server/bot_board.h b/server/bot_board.h new file mode 100644 index 0000000..098b0ce --- /dev/null +++ b/server/bot_board.h @@ -0,0 +1,5 @@ +#include "../source/board.h" + +void generate_board(board* b); + +void generate_shot(char* shot, board* b_enemy); \ No newline at end of file diff --git a/server/server.c b/server/server.c index d59fad9..634dccb 100644 --- a/server/server.c +++ b/server/server.c @@ -1,24 +1,7 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../source/message.h" - -#define MAX_PLAYERS 2 +#include "server.h" 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; @@ -79,7 +62,13 @@ void *handle_client(void *arg) //Dereference the pointer to get the socket int client_socket = *(int *)arg; - // Register player + // Receive game mode from client + Message mode_msg; + if (recv(client_socket, &mode_msg, sizeof(Message), 0) <= 0) { + close(client_socket); + return NULL; + } + pthread_mutex_lock(&players_mutex); int player_slot = find_empty_slot(); if(player_slot == -1) { @@ -90,19 +79,39 @@ void *handle_client(void *arg) 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].player_type = PLAYER_TYPE_HUMAN; + + if (mode_msg.type == MSG_SINGLE_PLAYER) { + // Create bot player + int bot_slot = find_empty_slot(); + if(bot_slot != -1) { + players[bot_slot].socket = -1; + players[bot_slot].player_type = PLAYER_TYPE_BOT; + players[bot_slot].bot_state = malloc(sizeof(BotState)); + board_init(&players[bot_slot].bot_state->b_own, 10); + board_init(&players[bot_slot].bot_state->b_enemy, 10); + generate_board(&players[bot_slot].bot_state->b_own); + + // Connect bot and human player + players[player_slot].opponent_socket = -1; players[player_slot].has_opponent = true; - players[i].opponent_socket = client_socket; - players[i].has_opponent = true; - break; + players[bot_slot].opponent_socket = client_socket; + players[bot_slot].has_opponent = true; + } + } else if (mode_msg.type == MSG_MULTI_PLAYER) { + //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}; @@ -121,10 +130,10 @@ void *handle_client(void *arg) Message start_msg = {.type = MSG_START_GAME}; send(client_socket, &start_msg, sizeof(Message), 0); - // Wait a moment to ensure both players have received START_GAME + // Wait to ensure both players have received START_GAME usleep(100000); // 100ms - // First player gets first turn, but only after both are ready + // First player gets first turn after both are ready if(player_slot == 0) { Message turn_msg = {.type = MSG_YOUR_TURN}; send(client_socket, &turn_msg, sizeof(Message), 0); @@ -146,23 +155,72 @@ 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); + if (players[find_player_slot(opponent_socket)].player_type == PLAYER_TYPE_BOT) + { + // Handle bot response + int bot_slot = find_player_slot(opponent_socket); + BotState* bot_state = players[bot_slot].bot_state; + + // Process player's shot + int hit = receive_shot(msg.x, msg.y, &bot_state->b_own); + + // Send result back to player + Message result = { + .type = MSG_RESULT, + .x = msg.x, + .y = msg.y, + .hit = hit + }; + send(client_socket, &result, sizeof(Message), 0); - // Forward shot to opponent - send(opponent_socket, &msg, sizeof(Message), 0); + if (hit == -1) + { + Message game_over = {.type = MSG_GAME_OVER}; + send(client_socket, &game_over, sizeof(Message), 0); + break; + } + char shot[3]; + generate_shot(shot, &bot_state->b_enemy); + int x; + int y; + parse_input(shot, &x, &y, NULL); + Message bot_shot = { + .type = MSG_SHOT, + .x = x, + .y = y + }; + send(client_socket, &bot_shot, sizeof(Message), 0); + } + else + { + Message wait_msg = {.type = MSG_WAIT_PLAYER}; + send(client_socket, &wait_msg, sizeof(Message), 0); + send(opponent_socket, &msg, sizeof(Message), 0); + } break; case MSG_RESULT: // Forward result to shooter - send(opponent_socket, &msg, sizeof(Message), 0); + if (players[find_player_slot(opponent_socket)].player_type == PLAYER_TYPE_BOT) + { + int bot_slot = find_player_slot(opponent_socket); + BotState* bot_state = players[bot_slot].bot_state; + mark_hit(msg.x, msg.y, msg.hit, &bot_state->b_enemy); + } + else + { + send(opponent_socket, &msg, sizeof(Message), 0); + } // Send turn message to the player who just got shot at Message turn_msg = {.type = MSG_YOUR_TURN}; send(client_socket, &turn_msg, sizeof(Message), 0); break; case MSG_GAME_OVER: - send(opponent_socket, &msg, sizeof(Message), 0); + if (players[find_player_slot(opponent_socket)].player_type != PLAYER_TYPE_BOT) + { + send(opponent_socket, &msg, sizeof(Message), 0); + } break; } } @@ -185,6 +243,12 @@ void *handle_client(void *arg) // destroy opponent's data for(int i = 0; i < MAX_PLAYERS; i++) { if(players[i].socket == opponent_socket) { + if (players[i].player_type == PLAYER_TYPE_BOT && players[i].bot_state != NULL) { + board_destroy(&players[i].bot_state->b_own); + board_destroy(&players[i].bot_state->b_enemy); + free(players[i].bot_state); + players[i].bot_state = NULL; + } players[i].opponent_socket = 0; players[i].has_opponent = false; break; @@ -202,7 +266,7 @@ int main(int argc, char** argv) // Initialize players array with 0s memset(players, 0, sizeof(players)); - int port = 8536; + int port = DEFAULT_PORT; if(argc >= 2) port = atoi(argv[1]); diff --git a/server/server.h b/server/server.h new file mode 100644 index 0000000..e3d173b --- /dev/null +++ b/server/server.h @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../source/message.h" +#include "bot_board.h" +#include "../source/utils.h" + +#define MAX_PLAYERS 2 +#define PLAYER_TYPE_HUMAN 0 +#define PLAYER_TYPE_BOT 1 + +typedef struct { + board b_own; + board b_enemy; +} BotState; + +typedef struct { + int socket; + int opponent_socket; + bool has_opponent; + int player_type; + BotState* bot_state; +} Player; + +int find_player_slot(int socket); + +int find_empty_slot(); + +int initialize_server(int port); + +void* handle_client(void* arg); \ No newline at end of file diff --git a/source/board b/source/board deleted file mode 100755 index afe226f..0000000 Binary files a/source/board and /dev/null differ diff --git a/source/board.c b/source/board.c index 50990be..5a7de0b 100644 --- a/source/board.c +++ b/source/board.c @@ -53,6 +53,16 @@ void board_display(board* b_own, board* b_enemy) } } +void parse_input(char* input, int* x, int* y, bool* down) +{ + *x = input[0] - 'A'; + *y = input[1] - '0'; + if (down != NULL) + { + *down = input[2] == DOWN; + } +} + bool validate_coords(char* coords) { return((coords[0] >= 'A') && (coords[0] <= 'J') && (coords[1] >= '0') && (coords[1] <= '9')); @@ -74,10 +84,10 @@ bool validate_tile(int x, int y, board* b) bool validate_position(char* position, int size, board* b) { - int x = position[0] - 'A'; - int y = position[1] - '0'; - bool down = true; - if (position[2] == 'r') down = false; + int x; + int y; + bool down; + parse_input(position, &x, &y, &down); if (down) { if (y + size > b->size_) return false; @@ -136,10 +146,10 @@ void get_ship(char* position, int size, board* b) void finalise_placement(char* position, int size, board* b) { - int x = position[0] - 'A'; - int y = position[1] - '0'; - bool down = true; - if (position[2] == 'r') down = false; + int x; + int y; + bool down; + parse_input(position, &x, &y, &down); if (down) { for (int i = y; i < y + size ; i++) @@ -197,43 +207,42 @@ bool check_destroyed(int x, int y, board* b) { int checking; if (y > 0) { - checking = y - 1; - do + checking = y; + while ((checking >= 0) && (b->board_[x][checking] == HIT_SHIP)) { - if (b->board_[x][checking] == SHIP) return false; - checking -= 1; - } while ((checking >= 0) && (b->board_[x][checking] == HIT_SHIP)); + if (b->board_[checking][y] == SHIP) return false; + checking--; + } } if (y < b->size_ - 1) { - checking = y + 1; - do + checking = y; + while ((checking < b->size_) && (b->board_[x][checking] == HIT_SHIP)) { - if (b->board_[x][checking] == SHIP) return false; - checking += 1; - } while ((checking < b->size_) && (b->board_[x][checking] == HIT_SHIP)); + if (b->board_[checking][y] == SHIP) return false; + checking++; + } } if (x > 0) { - checking = x - 1; - do + checking = x; + while ((checking >= 0) && (b->board_[checking][y] == HIT_SHIP)) { if (b->board_[checking][y] == SHIP) return false; - checking -= 1; - } while ((checking >= 0) && (b->board_[checking][y] == HIT_SHIP)); + checking--; + } } if (x < b->size_ - 1) { - checking = x + 1; - do + checking = x; + while ((checking < b->size_) && (b->board_[checking][y] == HIT_SHIP)) { if (b->board_[checking][y] == SHIP) return false; - checking += 1; - } while ((checking < b->size_) && (b->board_[checking][y] == HIT_SHIP)); + checking++; + } } return true; } -// Output will probably be sent to server int receive_shot(int x, int y, board* b) { if (b->board_[x][y] == SHIP) @@ -273,8 +282,9 @@ void get_shot(char* shot, board* b_enemy) printf("Input not valid, try again\n"); continue; } - int x = shot[0] - 'A'; - int y = shot[1] - '0'; + int x; + int y; + parse_input(shot, &x, &y, NULL); if(b_enemy->board_[x][y] != NOT_HIT) { printf("You have already shot there, try again\n"); @@ -285,19 +295,15 @@ void get_shot(char* shot, board* b_enemy) printf("Shot accepted!\n"); } -// The output of this is probably just gonna get sent to server -char* shoot(board* b_enemy) +void shoot(char* shot, board* b_enemy) { printf("Shots are given as \"A2\"\n"); printf("X coordinate: A-J\n"); printf("Y coordinate: 0-9\n"); printf("Where do you want to shoot?\n"); - char* shot = calloc(3, sizeof(char)); get_shot(shot, b_enemy); - return shot; } -// 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) { if (hit == 1) diff --git a/source/board.h b/source/board.h index 6cf119f..786679d 100644 --- a/source/board.h +++ b/source/board.h @@ -25,6 +25,8 @@ void board_destroy(board* b); void board_display(board* b_own, board* b_enemy); +void parse_input(char* input, int* x, int* y, bool* down); + bool validate_coords(char* coords); bool validate_rotation(char rotation); @@ -43,6 +45,6 @@ int receive_shot(int x, int y, board* b); void get_shot(char* shot, board* b_enemy); -char* shoot(board* b_enemy); +void shoot(char* shot, board* b_enemy); 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 index e10b62d..89b098f 100644 --- a/source/message.h +++ b/source/message.h @@ -4,6 +4,8 @@ #define MSG_SHOT 'H' // Shot coordinates #define MSG_RESULT 'R' // Shot result #define MSG_GAME_OVER 'G' // Game over +#define MSG_SINGLE_PLAYER 'B' // Single player mode +#define MSG_MULTI_PLAYER 'M' // Multi player mode typedef struct { char type; // Message type diff --git a/source/utils.h b/source/utils.h index d503728..5a2a4c2 100644 --- a/source/utils.h +++ b/source/utils.h @@ -1,4 +1,6 @@ #include +#define DEFAULT_PORT 8536 + // Clears the screen using ANSI escape sequence void clear_screen(); \ No newline at end of file