From f50a2febad0a9b626ec448a0cb052a235015889a Mon Sep 17 00:00:00 2001 From: Vegard Matthey Date: Fri, 15 Aug 2025 15:11:34 +0200 Subject: [PATCH] initial --- .gitignore | 1 + Makefile | 26 +++++ main.c | 324 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2521805 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +TicTacToe diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..51ad497 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +LDFLAGS = + +CFLAGS = -g -pedantic -Wall -Wextra -Wshadow -Wunused-macros + +RELEASE = -O3 -DNDEBUG + +TicTacToe: main.c + gcc $(CFLAGS) -o TicTacToe main.c $(LDFLAGS) + +.PHONY: run clean + +run: TicTacToe + ./TicTacToe + +release: + gcc $(LDFLAGS) $(RELEASE) -o TicTacToe main.c + ./VulkanApplication + +debug: + gcc $(CFLAGS) -o TicTacToe main.c $(LDFLAGS) -fsanitize=address + +grind: TicTacToe + valgrind --leak-check=yes ./TicTacToe + +clean: + rm -f TicTacToe diff --git a/main.c b/main.c new file mode 100644 index 0000000..bccd5d4 --- /dev/null +++ b/main.c @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BACKLOG 10 // how many pending connections queue will hold +#define MAX_DATA_SIZE 128 // max number of bytes we can get at once +#define SIZE 3 + +typedef enum { + Empty, + X, + O, +} Cell; + +typedef enum { + Offline, + OnlineFriend, + OnlineBot, +} GameMode; + +static const int YES = 1; + +static void sigchld_handler() { + while(waitpid(-1, NULL, WNOHANG) > 0); +} + +static void *get_in_addr(struct sockaddr *sa) { + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*)sa)->sin_addr); + } + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} + +static int connect_to_player_as_client(char* player_address, char* port) { + struct addrinfo hints, *servinfo, *p; + char s[INET6_ADDRSTRLEN]; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int rv; + if ((rv = getaddrinfo(player_address, port, &hints, &servinfo)) != 0) { + fprintf(stderr, "ERROR: getaddrinfo: %s\n", gai_strerror(rv)); + exit(1); + } + + int sockfd; + for(p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + fprintf(stderr, "ERROR: client: socket"); + continue; + } + + if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + fprintf(stderr, "ERROR: client: connect"); + continue; + } + + break; + } + + if (p == NULL) { + fprintf(stderr, "ERROR: client: failed to connect\n"); + exit(1); + } + + inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), + s, sizeof s); + printf("INFO: client: connecting to %s\n", s); + + freeaddrinfo(servinfo); // all done with this structure + return sockfd; +} + +static int connect_to_player_as_server(char* ip, char* port) { + struct addrinfo hints, *servinfo, *p; + char s[INET6_ADDRSTRLEN]; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // use my IP + + int rv = getaddrinfo(ip, port, &hints, &servinfo); + if (rv != 0) { + fprintf(stderr, "ERROR: getaddrinfo: %s\n", gai_strerror(rv)); + return 1; + } + + int sockfd; // listen on sock_fd, new connection on new_fd + for(p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { + fprintf(stderr, "ERROR: server: socket\n"); + continue; + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(int)) == -1) { + fprintf(stderr, "ERROR: setsockopt\n"); + exit(1); + } + + if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + fprintf(stderr, "ERROR: server: bind\n"); + continue; + } + + break; + } + + freeaddrinfo(servinfo); // all done with this structure + + if (p == NULL) { + fprintf(stderr, "ERROR: server: failed to bind\n"); + exit(1); + } + + if (listen(sockfd, BACKLOG) == -1) { + fprintf(stderr, "ERROR: listen\n"); + exit(1); + } + + struct sigaction sa; + sa.sa_handler = sigchld_handler; // reap all dead processes + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + fprintf(stderr, "ERROR: sigaction\n"); + exit(1); + } + + printf("INFO: server: waiting for connections...\n"); + + struct sockaddr_storage their_addr; + socklen_t sin_size = sizeof(their_addr); + int new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size); + if (new_fd == -1) { + fprintf(stderr, "ERROR: accept\n"); + exit(1); + } + + inet_ntop( + their_addr.ss_family, + get_in_addr((struct sockaddr *)&their_addr), + s, + sizeof(s) + ); + printf("INFO: server: got connection from %s\n", s); + + close(sockfd); // child doesn't need the listener + return new_fd; +} + +static Cell win(Cell board[SIZE][SIZE]) { + for (int i = 0; i < SIZE; i++) { + if (board[i][0] == board[i][1] && board[i][1] == board[i][2]) { + return board[i][0]; + } + if (board[0][i] == board[1][i] && board[1][i] == board[2][i]) { + return board[0][i]; + } + } + if (board[0][0] == board[1][1] && board[1][1] == board[2][2]) { + return board[0][0]; + } + if (board[2][0] == board[1][1] && board[1][1] == board[0][2]) { + return board[2][0]; + } + return Empty; +} + +static bool place(Cell board[SIZE][SIZE], int x, int y, Cell c) { + if (board[y][x] == Empty) { + board[y][x] = c; + return true; + } + return false; +} + +static void render(Cell board[SIZE][SIZE]) { + int str_len = (SIZE + 4) * SIZE + 1; + char str[str_len]; + str[str_len - 1] = 0; + int str_pos = 0; + for (int y = 2; y >= 0; y--) { + for (int x = 0; x < SIZE; x++) { + switch (board[y][x]) { + case Empty: str[str_pos] = ' '; break; + case X: str[str_pos] = 'X'; break; + case O: str[str_pos] = 'O'; break; + default: {fprintf(stderr, "ERROR: Invalid cell value\n"); exit(1);}; + } + str_pos++; + str[str_pos] = '|'; + str_pos++; + } + str[str_pos] = '\n'; + str_pos++; + } + str[str_len - 1] = '\0'; + printf("%s", str); +} + +static void single_device_input(int* x, int* y) { + while (1) { + char input[4] = {0}; + printf("Enter move: "); + if (!fgets(input, sizeof(input), stdin)) { + fprintf(stderr, "ERROR: Failed to get input from stdin\n"); + exit(1); + } + int c; while ((c = getchar()) != '\n' && c != EOF) {} // Discard rest of input + if (input[0] < '0' || input[0] > '2') { + printf("x coord invalid: '%c'\n", input[0]); + continue; + } + if (input[2] < '0' || input[2] > '2') { + printf("y coord invalid: '%c'\n", input[2]); + continue; + } + *x = input[0] - '0'; + *y = input[2] - '0'; + break; + } +} + +static void online_friend_input(int* x, int* y, bool my_turn, int fd) { + if (my_turn) { + single_device_input(x, y); + char input[2] = {*x, *y}; + if (send(fd, input, 2, 0) == -1) { + fprintf(stderr, "ERROR: send\n"); + exit(1); + } + } else { + printf("Wait for your turn\n"); + char buf[2]; + int packet_size = recv(fd, buf, 2, 0); + if (packet_size == -1) { + fprintf(stderr, "ERROR: receive\n"); + exit(1); + } + *x = buf[0]; + *y = buf[1]; + if (*x > 2 || *x < 0) { + fprintf(stderr, "ERROR: received invalid data\n"); + exit(1); + } + if (*y > 2 || *y < 0) { + fprintf(stderr, "ERROR: received invalid data\n"); + exit(1); + } + } +} + +int main() { + Cell board[SIZE][SIZE]; + memset(board, 0, SIZE * SIZE * sizeof(Cell)); + Cell turn = X; + Cell self = X; + + GameMode game_mode; + + int fd; + while (1) { + printf("What do you want to do?\n (0) - Play on single device\n (1) - Join game\n (2) - Create game\n (3) - Play against bot\nEnter answer: "); + char input[2] = {0}; + if (!fgets(input, sizeof(input), stdin)) { + fprintf(stderr, "ERROR: Failed to get input from stdin\n"); + exit(1); + } + int c; while ((c = getchar()) != '\n' && c != EOF) {} // Discard rest of input + if (input[0] < '0' || input[0] > '3') { + printf("Invalid input: '%c'\n", input[0]); + } else { + switch (input[0]) { + case '0': game_mode = Offline; break; + case '1': {fd = connect_to_player_as_client("127.0.0.1", "3490"); game_mode = OnlineFriend; self = O;} break; + case '2': {fd = connect_to_player_as_server("127.0.0.1", "3490"); game_mode = OnlineFriend;} break; + case '3': {game_mode = OnlineBot; fprintf(stderr, "ERROR: Not implemented\n"); exit(1);}; break; + default: {fprintf(stderr, "ERROR: Invalid cell value\n"); exit(1);}; + } + break; + } + } + + while (1) { + render(board); + Cell result = win(board); + switch (result) { + case Empty: break; + case X: printf("X won!\n"); return 0; + case O: printf("O won!\n"); return 0; + default: {fprintf(stderr, "ERROR: Invalid cell value\n"); exit(1);}; + } + + int x, y; + do { + bool my_turn = turn == self; + switch (game_mode) { + case Offline: single_device_input(&x, &y); break; + case OnlineFriend: online_friend_input(&x, &y, my_turn, fd); break; + case OnlineBot: {fprintf(stderr, "ERROR: Not implemented\n"); exit(1);}; break; + } + } while(!place(board, x, y, turn)); + + switch(turn) { + case X: turn = O; break; + case O: turn = X; break; + default: {fprintf(stderr, "ERROR: Invalid turn value\n"); exit(1);}; + } + } +}