#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ut_state { char tiles[9][9]; char boards[3][3]; int playBoard; char player; }; const struct ut_state ut_initial = { .boards = {0}, .tiles = {0}, .playBoard = -1, .player = 'X', }; const char HELP_TEXT[] = "\ Usage: uttt (--local | --host | --join)\n\ \n\ --local play non-network game\n\ --host host game at 127.0.0.1:6669\n\ --join join game at 127.0.0.1:6669\n\ "; const char arg_local[] = "--local"; const char arg_host[] = "--host"; const char arg_join[] = "--join"; const char PLAY_BOARDS[][14] = { "top left", "top middle", "top right", "middle left", "middle", "middle right", "bottom left", "bottom middle", "bottom right", "all boards", }; char ut_turn(char player) { switch(player) { case 'X': return 'O'; case 'O': return 'X'; default: return '\0'; } } #define T(r, c) (tiles[offset + stride * r + c]) char ut_winner(const char *tiles, int offset, int stride) { // whoreizontal wins for (int y = 0; y < 3; y++) { char tile = T(y, 0); if (tile == '\0') {continue;} if (tile == T(y, 1) && tile == T(y, 2)) {return tile;} } // vertical wins for (int x = 0; x < 3; x++) { char tile = T(0, x); if (tile == '\0') {continue;} if (tile == T(1, x) && tile == T(2, x)) {return tile;} } // diagonalz char tile = T(1, 1); if (tile != '\0') { if (tile == T(0, 0) && tile == T(2, 2)) {return tile;} if (tile == T(2, 0) && tile == T(0, 2)) {return tile;} } for(int y = 0; y < 3; y++) { for(int x = 0; x < 3; x++) { if(T(y, x) == '\0') {return '\0';} // in progress } } return ' '; // draw } int ut_move(struct ut_state *new_state, const struct ut_state *old_state, int row, int col) { // bad move - out of bounds if(row < 0 || row >= 9 || col < 0 || col >= 9) {return 1;} // state->playBoard == -1 means all boards playable; otherwise only one board is playable if (old_state->playBoard != -1 && old_state->playBoard != 3 * (row / 3) + (col / 3)) {return 1;} // bad move - tile is occupied if(old_state->tiles[row][col] != '\0') {return 1;} // bad move - board is finished if(old_state->boards[row / 3][col / 3] != '\0') {return 1;} // copy old_state->{tiles,boards} to new_state->{tiles,boards} memmove(new_state->tiles, old_state->tiles, sizeof(old_state->tiles)); memmove(new_state->boards, old_state->boards, sizeof(old_state->boards)); // do move new_state->tiles[row][col] = old_state->player; new_state->boards[row / 3][col / 3] = ut_winner((char *)new_state->tiles, 27 * (row / 3) + 3 * (col / 3), 9); // next play board - if next play board is not playable, play any board new_state->playBoard = new_state->boards[row % 3][col % 3] == '\0' ? 3 * (row % 3) + (col % 3) : -1; new_state->player = ut_turn(old_state->player); return 0; } int curs_line; void ut_curserase(void) { curs_line = 0; erase(); } void ut_cursprint(const char *str, int n) { mvaddnstr(curs_line, 0, str, n); int y, x; getyx(stdscr, y, x); curs_line = y; } int ut_writefill(int fd, const char *x, size_t l) { while(l > 0) { int r = write(fd, x, l); if(r < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {continue;} else {return 1;} } x += r; l -= r; } return 0; } #define MAX_LINE 128 int ut_cursprintf(const char *restrict format, ...) { char line[MAX_LINE]; va_list ap; va_start(ap, format); int n = vsnprintf(line, MAX_LINE, format, ap); va_end(ap); if(n < 0 || n > MAX_LINE - 1) {return -1;} ut_cursprint(line, n); return n; } int ut_dprintf(int fd, const char *restrict format, ...) { char line[MAX_LINE]; va_list ap; va_start(ap, format); int n = vsnprintf(line, MAX_LINE, format, ap); va_end(ap); if(n < 0 || n > MAX_LINE - 1) {return -1;} if(ut_writefill(fd, line, n)) {return -1;} return n; } // TODO highlight last placed void ut_drawBoard(void (*mvch)(void*, int, int, char), void *arg, const char *tiles, int offset, int stride, int iy, int ix, char winner, bool highlight) { // 7x7 board display /* * X|X|X * -+-+- * X|X|X * -+-+- * X|X|X */ for(int r = 0; r < 3; r++) // tiles for(int c = 0; c < 3; c++) mvch(arg, iy + 2 * r + 1, ix + 2 * c + 1, T(r, c) != '\0' ? T(r, c) : ' '); //char info = winner != '\0' ? winner : highlight ? '*' : ' '; for(int r = 0; r < 3; r++) // | for(int c = 0; c < 2; c++) mvch(arg, iy + 2 * r + 1, ix + 2 * c + 1 + 1, winner != '\0' ? winner : '|'); for(int r = 0; r < 2; r++) // - for(int c = 0; c < 3; c++) mvch(arg, iy + 2 * r + 1 + 1, ix + 2 * c + 1, winner != '\0' ? winner : '-'); for(int r = 0; r < 2; r++) // + for(int c = 0; c < 2; c++) mvch(arg, iy + 2 * r + 1 + 1, ix + 2 * c + 1 + 1, winner != '\0' ? winner : highlight ? '*' : '+'); for(int r = 0; r < 7; r++) // | boundary for(int c = 0; c < 2; c++) mvch(arg, iy + r, ix + 6 * c, ' '); for(int r = 0; r < 2; r++) // - boundary for(int c = 0; c < 7; c++) mvch(arg, iy + 6 * r, ix + c, ' '); } #define DTILES_Y 21 #define DTILES_X 21 void ut_drawTiles(void (*mvch)(void*, int, int, char), void *arg, const struct ut_state *state, bool numbers) { for(int r = 0; r < 3; r++) for(int c = 0; c < 3; c++) ut_drawBoard(mvch, arg, (char *)state->tiles, 27 * r + 3 * c, 9, 7 * r, 7 * c, state->boards[r][c], state->playBoard == -1 || state->playBoard == 3 * r + c); if(numbers) { for(int b = 0; b < 3; b++) for(int c = 0; c < 3; c++) mvch(arg, 0, 7 * b + 2 * c + 1, '0' + 3 * b + c); for(int b = 0; b < 3; b++) for(int r = 0; r < 3; r++) mvch(arg, 7 * b + 2 * r + 1, 0, '0' + 3 * b + r); } } #define DBOARDS_Y 7 #define DBOARDS_X 7 void ut_drawBoards(void (*mvch)(void*, int, int, char), void *arg, const struct ut_state *state) { ut_drawBoard(mvch, arg, (char *)state->boards, 0, 3, 0, 0, '\0', false); } void ut_cursmvch(void *arg, int y, int x, char c) { mvaddch(curs_line + y, x, c); } void ut_tmvch(void *arg, int y, int x, char c) { ((char (*)[DTILES_X])arg)[y][x] = c; } void ut_bmvch(void *arg, int y, int x, char c) { ((char (*)[DBOARDS_X])arg)[y][x] = c; } #define DTILES_LINE 1 void ut_cursdraw(const struct ut_state *state) { ut_cursprintf("Turn: %c Play board: %d\n", (int)state->player, state->playBoard); ut_drawTiles(ut_cursmvch, NULL, state, false); curs_line += DTILES_Y; //ut_drawBoards(ut_cursmvch, NULL, state); } void ut_sockdraw(const struct ut_state *state, int fd) { ut_dprintf(fd, "Turn: %c Play board: %d\n", (int)state->player, state->playBoard); char dtiles[DTILES_Y][DTILES_X]; ut_drawTiles(ut_tmvch, dtiles, state, true); for(int r = 0; r < DTILES_Y; r++) { ut_dprintf(fd, "%.*s\n", DTILES_X, dtiles[r]); } } void ut_show(const struct ut_state *state, int fd) { #define tiles state->tiles ut_dprintf(fd, "Turn: %c\nPlay board: %s\n", (int)state->player, PLAY_BOARDS[(state->playBoard + 10) % 10]); int play_board_row = -1; int play_board_col = -1; if (state->playBoard != -1) { play_board_row = state->playBoard / 3; play_board_col = state->playBoard % 3; } ut_dprintf(fd, " 012 345 678\n"); for(int y = 0; y < 9; y++) { if (y == 3 || y == 6) { ut_dprintf(fd, " ---+---+---\n"); } ut_dprintf(fd, "%d ", y); for(int x = 0; x < 9; x++) { if (x == 3 || x == 6) { ut_writefill(fd, "|", 1); } ut_writefill(fd, tiles[y][x] ? &tiles[y][x] : " ", 1); } if (y / 3 == play_board_row || play_board_row == -1) { ut_writefill(fd, "<", 1); } ut_writefill(fd, "\n", 1); } if (play_board_col == -1) { ut_dprintf(fd, " ^^^ ^^^ ^^^"); } else { ut_dprintf(fd, " "); for (int i = 0; i < play_board_col; i++) { ut_dprintf(fd, " "); } ut_dprintf(fd, "^^^"); } ut_writefill(fd, "\n", 1); #undef tiles }*/ /*void ut_show_boards(const struct ut_state *state) { for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) { putchar(state->boards[j][i] ? state->boards[j][i] : ' '); } putchar('\n'); } }*/ int ut_readfill(int fd, char *x, size_t l) { while(l > 0) { int r = read(fd, x, l); if(r < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {continue;} else {return 1;} } else if(r == 0) {return 2;} x += r; l -= r; } return 0; } int ut_cursgetpos(const struct ut_state *state, int *r, int *c) { #define P(r, c, d) ((state->playBoard == -1 || state->playBoard == 3 * (r / 3) + (c / 3)) && \ (state->boards[r / 3][c / 3] == '\0' || d % 3 == 1)) MEVENT event; ut_cursprintf("Select move with arrow keys or mouse.\n"); if(*r < 0 || *r >= 9 || *c < 0 || *c >= 9) { *r = 4; *c = 4; } else if(state->playBoard == -1) { *r = 3 * (*r / 3) + 1; *c = 3 * (*c / 3) + 1; } else { *r = 3 * (state->playBoard / 3) + 1; *c = 3 * (state->playBoard % 3) + 1; } for(;;) { /*if(state->boards[*r / 3][*c / 3] != '\0') { *r = 3 * (*r / 3) + 1; *c = 3 * (*c / 3) + 1; }*/ move(DTILES_LINE + (7 * (*r / 3) + 1) + (2 * (*r % 3)), (7 * (*c / 3) + 1) + (2 * (*c % 3))); refresh(); switch(getch()) { case KEY_MOUSE: if(getmouse(&event) == OK && (event.bstate & BUTTON1_CLICKED)) { event.y -= DTILES_LINE; if(event.y < 0 || event.y >= 21) {break;} if(event.x < 0 || event.x >= 21) {break;} *r = (event.y % 7 - 1) / 2 + 3 * (event.y / 7); *c = (event.x % 7 - 1) / 2 + 3 * (event.x / 7); if(((event.y % 7) - 1) % 2 != 0) {break;} if(((event.x % 7) - 1) % 2 != 0) {break;} return 0; } break; case 'k': /* FALLTHROUGH */ case 'w': /* FALLTHROUGH */ case KEY_UP: //*r = (((*r - 1) % 9) + 9) % 9; for(int i = 0; i < 9 + 1; i++) { *r = (*r + 9 - 1) % 9; if(P(*r, *c, *r)) {break;} } break; case 'h': /* FALLTHROUGH */ case 'a': /* FALLTHROUGH */ case KEY_LEFT: //*c = (((*c - 1) % 9) + 9) % 9; for(int i = 0; i < 9 + 1; i++) { *c = (*c + 9 - 1) % 9; if(P(*r, *c, *c)) {break;} } break; case 'j': /* FALLTHROUGH */ case 's': /* FALLTHROUGH */ case KEY_DOWN: //*r = (*r + 1) % 9; for(int i = 0; i < 9 + 1; i++) { *r = (*r + 1) % 9; if(P(*r, *c, *r)) {break;} } break; case 'l': /* FALLTHROUGH */ case 'd': /* FALLTHROUGH */ case KEY_RIGHT: //*c = (*c + 1) % 9; for(int i = 0; i < 9 + 1; i++) { *c = (*c + 1) % 9; if(P(*r, *c, *c)) {break;} } break; case ' ': /* FALLTHROUGH */ case '\r': /* FALLTHROUGH */ case KEY_ENTER: return 0; case ERR: /* FALLTHROUGH */ default: break; } } #undef P } /*bool getpos(const struct ut_state *state, int *x, int *y) { while (true) { printf("Place token %c at position x,y: ", state->player); char line[5] = {0}; if (fgets(line, 5, stdin) == NULL) return false; // line was too short if (line[3] == '\0') continue; // line was too long if (line[3] != '\n') { // consume rest of line while (true) { int garbage = getchar(); if (garbage == EOF) return false; if (garbage == '\n') break; } continue; } int sscanf_result = sscanf(line, "%d,%d", x, y); if (sscanf_result == EOF) return false; if (sscanf_result != 2) continue; break; } return true; }*/ int ut_ignore_line(int sock) { char byte; for (int i = 0; i < 128; i++) { if (ut_readfill(sock, &byte, 1)) { return 1; } if (byte == '\n') {return 0;} } return 2; } int ut_sockgetpos(const struct ut_state *state, int sock, int *x, int *y, bool readable) { // TODO int index = 0; char byte; while (true) { int r = ut_readfill(sock, &byte, 1); if (r < 0) {return r;} if (r == 0) { printf("Connection closed.\n"); return -1; } if (index == 0 && byte == '|') { if (ut_ignore_line(sock)) {return -1;} } else if (index == 0) { *x = byte - 0x30; } else if (index == 2) { *y = byte - 0x30; } else if (index == 1 && byte != ',' || index == 3 && byte != '\n') { printf("Partner sent malformed coords - retrying\n"); if (ut_ignore_line(sock)) {return -1;} if (readable) { ut_dprintf(sock, "Invalid coordinates. Try again: "); } index = 0; continue; } index++; if (index == 4) {break;} } printf("x=%d y=%d\n", *x, *y); return 0; } int ut_local_game(struct ut_state *state) { int x, y; for(;;) { int r = 4, c = 4; ut_curserase(); ut_cursdraw(state); char w = ut_winner((char *)state->boards, 0, 3); if(w) { if(w == ' ') { ut_cursprintf("Draw!\n"); } else { ut_cursprintf("%c Wins!\n", (int)w); } break; } if(ut_cursgetpos(state, &r, &c)) {continue;} if(ut_move(state, state, r, c)) {continue;} } return 0; } int ut_network_game(struct ut_state *state, int sock, char player, bool readable) { // TODO int x, y; //printf("You play as %c.\n", player); while (true) { //ut_show(state, 1, false); //ut_show_boards(state); ut_show(state, sock); if (state->player == player) { if (readable) {ut_dprintf(sock, "Waiting for game partner ...\n");} bool ok = ut_cursgetpos(state, &x, &y); if (!ok) {continue;} ut_dprintf(sock, "%d,%d\n", x, y); } else { printf("Waiting for game partner ...\n"); if (readable) { // line feed aligns game board each turn ut_dprintf(sock, "\nPlace token %c in position x,y: ", ut_turn(player)); } int err = ut_sockgetpos(state, sock, &x, &y, readable); if (err == -1) { printf("Connection closed.\n"); return 1; } } int err = ut_move(state, state, y, x); if (err) {continue;} ut_dprintf(sock, "\n"); char w = ut_winner((char *)state->boards, 0, 3); if(w) { if(w == ' ') { printf("\nDraw!\n"); if (readable) {ut_dprintf(sock, "\nDraw!\n");} close(sock); } else { printf("\n%c wins!\n", (int)w); if (readable) {ut_dprintf(sock, "\n%c wins!\n", (int)w);} close(sock); } break; } } return 0; } char ut_random_player(void) { char player; int random = open("/dev/urandom", O_RDONLY); if (read(random, &player, 1) < 0) {return -1;} player = (player % 2 == 0) ? 'X' : 'O'; player = 'X'; // testing return player; } int ut_host_game(struct ut_state *state) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { printf("error %d\n", errno); return 1; } const struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(6669), .sin_addr = { .s_addr = htonl(0x7f000001) }, }; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { printf("error %d\n", errno); return 1; } if (listen(sock, 128) == -1) { printf("error %d\n", errno); return 1; } printf("Waiting for game partner at 127.0.0.1:6669 ...\n"); int conn = accept(sock, NULL, NULL); if (conn == -1) { printf("error %d\n", errno); return 1; } // decide X or O char player; if ((player = ut_random_player()) < 0) {return 1;} // check to determine readable char byte; ut_dprintf(conn, "| Press enter to start: "); if (ut_readfill(conn, &byte, 1)) { printf("Connection closed.\n"); return 1; } bool readable = byte != '\0'; if (byte != '\n') { int err = ut_ignore_line(sock); if (err == 1) { printf("Connection closed.\n"); return 1; } else if (err == 2) { printf("Partner sent too much data - exiting\n"); return 1; } } // tell partner X or O ut_dprintf(conn, "%c\n", ut_turn(player)); if (readable) { ut_dprintf(conn, "You play as: %c\n\n", ut_turn(player)); } return ut_network_game(state, conn, player, readable); } int ut_join_game(struct ut_state *state) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { printf("error %d\n", errno); return 1; } const struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(6669), .sin_addr = { .s_addr = htonl(0x7f000001) }, }; printf("Connecting to game host at 127.0.0.1:6669 ...\n"); if(connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { printf("error %d\n", errno); return 1; } // tell host to deactivate readable ut_dprintf(sock, "\0\n"); // host decides X or O char player_buf[2]; char *player = (char*)&player_buf; int r = ut_readfill(sock, player_buf, 2); if (r == 2) {printf("Connection closed.\n");} if (r != 0) {return 1;} return ut_network_game(state, sock, *player, false); } void finish(int sig) { // putchar('\n'); endwin(); // other cleanup exit(0); } void waitfinish(int sig) { ut_cursprintf("\nPress any key to exit."); refresh(); mousemask(0, NULL); getch(); finish(sig); } void begin(void) { signal(SIGINT, finish); initscr(); keypad(stdscr, TRUE); nonl(); // \r instead of \r\n cbreak(); noecho(); mousemask(BUTTON1_CLICKED, NULL); } int main(int argc, char **argv) { struct ut_state state = ut_initial; if (argc != 2) { printf(HELP_TEXT); return 1; } else if (strncmp(argv[1], arg_local, sizeof(arg_local)) == 0) { begin(); waitfinish(ut_local_game(&state)); } else if (strncmp(argv[1], arg_host, sizeof(arg_host)) == 0) { begin(); waitfinish(ut_host_game(&state)); } else if (strncmp(argv[1], arg_join, sizeof(arg_join)) == 0) { begin(); waitfinish(ut_join_game(&state)); } else { printf(HELP_TEXT); return 1; } return 0; }