initial
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
TicTacToe
|
||||
26
Makefile
Normal file
26
Makefile
Normal file
@@ -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
|
||||
324
main.c
Normal file
324
main.c
Normal file
@@ -0,0 +1,324 @@
|
||||
#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);};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user