Files
tic-tac-toe/main.c
2025-08-15 15:11:34 +02:00

325 lines
8.4 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdbool.h>
#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);};
}
}
}