325 lines
8.4 KiB
C
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);};
|
|
}
|
|
}
|
|
}
|