/** * @file client.c * @author Tobias Eidelpes * @date 2018-03-21 * * @brief Client for OSUE exercise 1B `Battleship'. */ #include "common.h" /** * @details Constant which holds the port number to connect to. */ static const char *port = DEFAULT_PORT; /** * @details Struct which holds info for the socket. */ static struct addrinfo *ai = NULL; /** * @details Global variable for the hostname to connect to. */ static char *hostname = NULL; /** * @details Global variable for the socket file descriptor (easy cleanup). */ static int sockfd = -1; /** * @details Global variable for the program name for error messages. */ static char* pname; /** * @details Global variable for tracking where we have already fired a shot. */ static int field[MAP_SIZE][MAP_SIZE]; /** * @details Global variable for the x coordinate. */ static uint8_t x; /** * @details Global variable for the y coordinate. */ static uint8_t y; /** * @details Variable which get set on signal received. */ volatile sig_atomic_t quit = 0; /** * @details Signal handler. * @param signal The number of the received signal. * @return Function has no return value. */ static void handle_signal(int signal) { quit = 1; } /** * @details Prints the program usage. * @param void Function takes no arguments. * @return Always non-zero. */ static void usage(void) { fprintf(stderr, "[%s] Usage:\n\tSYNOPSIS\n\t\t [-h HOSTNAME] [-p PORT]\n\tEXAMPLE\n\tclient -h localhost -p 1280\n", pname); exit(EXIT_FAILURE); } /** * @details Sets even parity for the supplied message. * @param msg The encoded coordinates in 2 bytes. * @return Returns the message with even parity set. */ static uint16_t setParity(uint16_t msg) { uint16_t v = msg; v &= 0x7FF; uint8_t parity = 0; while (v) { parity = !parity; v = v & (v - 1); } if (parity) msg += 0x8000; return msg; } /** * @details Parses the commandline arguments. * @param argc Count of commandline arguments. * @param argv Array of commandline arguments. * @return Function has no return value. */ static void parse(int argc, char *argv[]) { if (argc == 1) { hostname = NULL; port = "1280"; } else { int opt_index = 0; while ((opt_index = getopt(argc, argv, "h:p:")) != -1) { switch (opt_index) { case 'h': hostname = optarg; break; case 'p': port = optarg; break; case '?': usage(); default: assert(0); } } } } /** * @details Function for convenient cleanup of resources. * @param void Function takes no arguments. * @return 0 on success. Non-zero on failure. */ static int cleanup(void) { if (close(sockfd) < 0) { perror(pname); return 1; } free(ai); return 0; } /** * @details Calls target mode by testing adjacent fields. * @param void Function takes no arguments. * @return Function has no return value. */ static void markSunk(void) { // check north for (int i = y; i >= 0; i--) { if (field[x][i] == 2) field[x][i] = 3; else break; } // check south for (int i = y; i < MAP_SIZE; i++) { if (field[x][i] == 2) field[x][i] = 3; else break; } // check east for (int i = x; i >= 0; i--) { if (field[i][y] == 2) field[i][y] = 3; else break; } // check west for (int i = x; i < MAP_SIZE; i++) { if (field[i][y] == 2) field[i][y] = 3; else break; } } /** * @details Function checks the north tile and sets x and y if not already hit. * @param void Function takes no arguments. * @return 1 if field north not hit. 0 otherwise. */ static int checkNorth(void) { int j = y - 1; for (; j >= 0; --j) { if (field[x][j] == 0) { y = j; return 1; } else { return 0; } } return 0; } /** * @details Function checks the south tile and sets x and y if not already hit. * @param void Function takes no arguments. * @return 1 if field north not hit. 0 otherwise. */ static int checkSouth(void) { int j = y + 1; for (; j < MAP_SIZE; ++j) { if (field[x][j] == 0) { y = j; return 1; } else { return 0; } } return 0; } /** * @details Function checks the eastern tile and sets x and y if not already hit. * @param void Function takes no arguments. * @return 1 if field north not hit. 0 otherwise. */ static int checkEast(void) { int i = x - 1; for (; i >= 0; --i) { if (field[i][y] == 0) { x = i; return 1; } else { return 0; } } return 0; } /** * @details Function checks the western tile and sets x and y if not already hit. * @param void Function takes no arguments. * @return 1 if field north not hit. 0 otherwise. */ static int checkWest(void) { int i = x + 1; for (; i < MAP_SIZE; ++i) { if (field[i][y] == 0) { x = i; return 1; } else { return 0; } } return 0; } /** * @details Searches for a tile not already hit and calls adjacent hit functions * when a ship has been hit but not sunk. * @param void Functions takes no arguments. * @return Function has no return value. */ static void searchHit(void) { for (int j = 0; j < MAP_SIZE; j++) { for (int i = 0; i < MAP_SIZE; i++) { if (field[i][j] == 2) { x = i; y = j; if (checkNorth()) { return; } if (checkSouth()) { return; } if (checkEast()) { return; } if (checkWest()) { return; } } } } x = rand() % 10; y = rand() % 10; while(field[x][y] != 0) { x = rand() % 10; y = rand() % 10; } } /** * @details Reads x and y and contructs the encoded message in 2 bytes. Then * sends the bytes on by one to the server and waits for response. Marks * the tile hit accordingly. * @param void Function takes no arguments. * @return Function has no return value. */ static void makeHit(void) { uint8_t buffer[1]; uint8_t status; uint8_t hit; uint16_t msg; msg = x; msg <<= 6; msg += y; msg = setParity(msg); uint16_t v = msg; v &= 0x00FF; buffer[0] = v; send(sockfd, buffer, 1, 0); v = msg; v &= 0xFF00; v >>= 8; buffer[0] = v; send(sockfd, buffer, 1, 0); recv(sockfd, buffer, 1, 0); status = buffer[0]; status &= 0x0C; hit = buffer[0]; hit &= 0x03; switch (status) { case 2: fprintf(stderr, "[%s] Parity error\n", pname); cleanup(); exit(2); case 3: fprintf(stderr, "[%s] Invalid coordinate\n", pname); cleanup(); exit(3); default: break; } if (status == 1 && (hit == 0 || hit == 1 || hit == 2)) { fprintf(stdout, "[%s] Game lost\n", pname); cleanup(); exit(EXIT_SUCCESS); } else if (status == 1 && hit == 3) { fprintf(stdout, "[%s] I win :)\n", pname); cleanup(); exit(EXIT_SUCCESS); } if (hit == 0) field[x][y] = 1; if (hit == 1) field[x][y] = 2; if (hit == 2) markSunk(); } /** * The main entry point of the program. * @deails Sets up signal handler. Parses the commandline arguments. Connects to * the server and starts the game. * @param argc Count of commandline arguments. * @param argv Array of commandline arguments. * @return 0 on success. Non-zero on failure. */ int main(int argc, char *argv[]) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handle_signal; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); pname = argv[0]; parse(argc, argv); struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; int res = getaddrinfo(hostname, port, &hints, &ai); if (res < 0) { fprintf(stderr, "[%s] ERROR: Failed to get address info: %s\n", pname, gai_strerror(res)); exit(EXIT_FAILURE); } sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sockfd < 0) { perror(pname); exit(EXIT_FAILURE); } if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) { close(sockfd); perror(pname); exit(EXIT_FAILURE); } srand(time(NULL)); while(quit == 0) { searchHit(); makeHit(); } cleanup(); exit(EXIT_SUCCESS); }