// SAKKOM: sakk a konzolban király! online multiplayer // chess.com alternatíva, ami netcatet használ mint kliens // // AJEDREZ: servidor de ajedrez multijugador en línea // alternativo a chess.com con un cliente para netcat. // Hispanizado para texto-plano.xyz // // do what you want // boa #include #include // -lpthread #include #include #include #include #include #include #include #include #ifdef __BSD_VISIBLE #include #include #endif #define PRINT_ALL(f_, ...) \ do { \ dprintf(w_fd, (f_), ##__VA_ARGS__); \ dprintf(b_fd, (f_), ##__VA_ARGS__); \ } while (0) typedef enum mode { mode_ascii, mode_unicode } mode; mode modes[2]; typedef enum color { c_w = 0, c_b = 1 } color; typedef enum castle { castle_no, castle_long, castle_short } castle; int colors[] = {39, 30}; char ascii_colors[] = {' ', '*'}; int abs(int a) { if (a >= 0) return a; else return -1 * a; } char symbols[7][7] = {" ", "♟︎", "♜", "♞", "♝", "♛", "♚"}; char ascii_symbols[7] = {' ', 'P', 'R', 'N', 'B', 'Q', 'K'}; typedef enum result { r_no, r_win, r_stalemate } result; typedef enum piece_type { p_no, // vacío p_pawn, // peón p_rook, // torre p_knight, // caballo p_bishop, // alfil p_queen, // reina p_king // rey } piece_type; typedef struct pos { int x; int y; } pos; typedef struct piece { piece_type p; color c; } piece; typedef struct move { pos f; // pos. origen pos t; // pos. destino piece p; // pieza movida piece e; // enemigo tomado } move; typedef struct en_passant { bool valid; pos p; piece *to_remove; } en_passant; en_passant enp; piece table[8][8]; int w_fd = -1, b_fd = -1; int *c_fd = &w_fd; // current fd bool moved_king[2] = {false, false}; bool moved_rook_l[2] = {false, false}; bool moved_rook_r[2] = {false, false}; int remaining_time[2] = {30 * 60, 30 * 60}; int game_number = 1; void do_move(move m); bool is_move_legal(color c, move m); bool in_check(color c, int cx, int cy); void print_table(); void setup_table(); void play_game(); bool is_same_pos(pos p1, pos p2) { return p1.x == p2.x && p1.y == p2.y; } bool is_empty(piece p) { return p.p == p_no; } bool is_friend_of(color c, piece p) { return !is_empty(p) && p.c == c; } bool is_enemy_of(color c, piece p) { return !is_empty(p) && p.c != c; } bool is_inside(int i) { return i >= 0 && i < 8; } bool is_move_inside(move m) { return is_inside(m.f.x) && is_inside(m.f.y) && is_inside(m.t.x) && is_inside(m.t.y); } void print_table() { if (modes[c_w] == mode_unicode) { dprintf(w_fd, "\n \e[43m\x1B[30m\033[1m NEGRO \x1B[0m\n\n "); for (int x = 0; x < 8; x++) { dprintf(w_fd, " %c ", 'A' + x); } dprintf(w_fd, "\n \e[43m┏━━━┳━━━┳━━━┳━━━┳━━━┳━━━┳━━━┳━━━┓\x1B[0m\n"); for (int y = 7; y >= 0; y--) { if (y != 7) { dprintf(w_fd, "\x1B[0m " "\e[43m┣━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━┫\x1B[0m\n"); } dprintf(w_fd, " %d \e[43m┃", y + 1); for (int x = 0; x < 8; x++) dprintf(w_fd, "\e[43m\x1B[%dm \033[1m%s \x1B[0m\e[43m┃", colors[table[x][y].c], symbols[table[x][y].p]); dprintf(w_fd, "\x1B[0m %d\x1B[0m\n", y + 1); } dprintf(w_fd, " \e[43m┗━━━┻━━━┻━━━┻━━━┻━━━┻━━━┻━━━┻━━━┛\x1B[0m\n "); for (int x = 0; x < 8; x++) { dprintf(w_fd, " %c ", 'A' + x); } dprintf( w_fd, "\n\n \e[43m\x1B[39m\033[1m BLANCO \x1B[0m\n\n\x1B[0m"); } else { dprintf(w_fd, "\n [ NEGRO ]\n\n "); for (int x = 0; x < 8; x++) { dprintf(w_fd, " %c ", 'A' + x); } dprintf(w_fd, "\n +---+---+---+---+---+---+---+---+\n"); for (int y = 7; y >= 0; y--) { if (y != 7) { dprintf(w_fd, " +---+---+---+---+---+---+---+---+\n"); } dprintf(w_fd, " %d |", y + 1); for (int x = 0; x < 8; x++) dprintf(w_fd, "%c%c |", ascii_colors[table[x][y].c], ascii_symbols[table[x][y].p]); dprintf(w_fd, " %d\n", y + 1); } dprintf(w_fd, " +---+---+---+---+---+---+---+---+\n "); for (int x = 0; x < 8; x++) { dprintf(w_fd, " %c ", 'A' + x); } dprintf(w_fd, "\n\n [ BLANCO ]\n\n"); } if (modes[c_b] == mode_unicode) { dprintf(b_fd, "\n \e[43m\x1B[39m\033[1m BLANCO \x1B[0m\n\n "); for (int x = 0; x < 8; x++) { dprintf(b_fd, " %c ", 'A' + 7 - x); } dprintf(b_fd, "\n \e[43m┏━━━┳━━━┳━━━┳━━━┳━━━┳━━━┳━━━┳━━━┓\x1B[0m\n"); for (int y = 0; y < 8; y++) { if (y != 0) { dprintf(b_fd, "\x1B[0m " "\e[43m┣━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━╋━━━┫\x1B[0m\n"); } dprintf(b_fd, " %d \e[43m┃", y + 1); for (int x = 7; x >= 0; x--) dprintf(b_fd, "\e[43m\x1B[%dm \033[1m%s \x1B[0m\e[43m┃", colors[table[x][y].c], symbols[table[x][y].p]); dprintf(b_fd, "\x1B[0m %d\n", y + 1); } dprintf(b_fd, " \e[43m┗━━━┻━━━┻━━━┻━━━┻━━━┻━━━┻━━━┻━━━┛\x1B[0m\n "); for (int x = 0; x < 8; x++) { dprintf(b_fd, " %c ", 'A' + 7 - x); } dprintf( b_fd, "\n\n \e[43m\x1B[30m\033[1m NEGRO \x1B[0m\n\n\x1B[0m"); } else { dprintf(b_fd, "\n [ BLANCO ]\n\n "); for (int x = 0; x < 8; x++) { dprintf(b_fd, " %c ", 'A' + 7 - x); } dprintf(b_fd, "\n +---+---+---+---+---+---+---+---+\n"); for (int y = 0; y < 8; y++) { if (y != 0) { dprintf(b_fd, " +---+---+---+---+---+---+---+---+\n"); } dprintf(b_fd, " %d |", y + 1); for (int x = 7; x >= 0; x--) dprintf(b_fd, "%c%c |", ascii_colors[table[x][y].c], ascii_symbols[table[x][y].p]); dprintf(b_fd, " %d\n", y + 1); } dprintf(b_fd, " +---+---+---+---+---+---+---+---+\n "); for (int x = 0; x < 8; x++) { dprintf(b_fd, " %c ", 'A' + 7 - x); } dprintf(b_fd, "\n\n [ NEGRO ]\n\n"); } } bool in_check(color c, int cx, int cy) { move m; m.t.x = cx; m.t.y = cy; m.e = table[cx][cy]; for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { if (cx == x && cy == y) continue; m.f.x = x; m.f.y = y; m.p = table[x][y]; if (table[x][y].c != c && is_move_legal(m.p.c, m)) { return true; } } } return false; } bool in_check_sim(move m) { do_move(m); bool res = false; for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { if (table[x][y].p == p_king && table[x][y].c == m.p.c) { res = in_check(m.p.c, x, y); goto out; } } } out: table[m.f.x][m.f.y] = m.p; table[m.t.x][m.t.y] = m.e; return res; } void setup_table() { table[0][0] = (piece){p_rook, c_w}; table[1][0] = (piece){p_knight, c_w}; table[2][0] = (piece){p_bishop, c_w}; table[3][0] = (piece){p_king, c_w}; table[4][0] = (piece){p_queen, c_w}; table[5][0] = (piece){p_bishop, c_w}; table[6][0] = (piece){p_knight, c_w}; table[7][0] = (piece){p_rook, c_w}; for (int x = 0; x < 8; x++) table[x][1] = (piece){p_pawn, c_w}; table[0][7] = (piece){p_rook, c_b}; table[1][7] = (piece){p_knight, c_b}; table[2][7] = (piece){p_bishop, c_b}; table[3][7] = (piece){p_king, c_b}; table[4][7] = (piece){p_queen, c_b}; table[5][7] = (piece){p_bishop, c_b}; table[6][7] = (piece){p_knight, c_b}; table[7][7] = (piece){p_rook, c_b}; for (int x = 0; x < 8; x++) table[x][6] = (piece){p_pawn, c_b}; } pos find_king(color c) { for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++) if (table[x][y].p == p_king && table[x][y].c == c) return (pos){x, y}; return (pos){-1, -1}; } bool is_stalemate(color c) { pos kp = find_king(c); if (in_check(c, kp.x, kp.y)) return false; move m; for (m.f.y = 0; m.f.y < 8; m.f.y++) { for (m.f.x = 0; m.f.x < 8; m.f.x++) { if (table[m.f.x][m.f.y].c == c) { m.p = table[m.f.x][m.f.y]; for (m.t.y = 0; m.t.y < 8; m.t.y++) { for (m.t.x = 0; m.t.x < 8; m.t.x++) { if (m.t.x == m.f.x && m.t.y == m.f.y) continue; m.e = table[m.t.x][m.t.y]; if (is_move_legal(c, m)) { return false; } } } } } } return true; } bool is_checkmate(color c) { // probably the most simple and least efficient way to solve this problem move m; for (m.f.y = 0; m.f.y < 8; m.f.y++) { for (m.f.x = 0; m.f.x < 8; m.f.x++) { if (table[m.f.x][m.f.y].c == c) { m.p = table[m.f.x][m.f.y]; for (m.t.y = 0; m.t.y < 8; m.t.y++) { for (m.t.x = 0; m.t.x < 8; m.t.x++) { m.e = table[m.t.x][m.t.y]; if (is_move_legal(c, m)) { return false; } } } } } } return true; } int move_num = 1; void print_move(move m) { char buf[20]; int n = 0; buf[n++] = ascii_symbols[m.p.p]; buf[n++] = m.f.x + 'a'; buf[n++] = m.f.y + '1'; buf[n++] = ' '; if (m.e.p != p_no) buf[n++] = ascii_symbols[m.p.p]; buf[n++] = m.t.x + 'a'; buf[n++] = m.t.y + '1'; buf[n++] = '\n'; buf[n++] = '\0'; if (m.p.c == c_w) { PRINT_ALL("%d. blanco: ", move_num); } else { PRINT_ALL("%d. negro: ", move_num); move_num++; } PRINT_ALL(buf); } move parse_move(char buf[50]) { move m; m.f.x = buf[0] - 'a'; m.f.y = buf[1] - '1'; m.t.x = buf[3] - 'a'; m.t.y = buf[4] - '1'; m.p = table[m.f.x][m.f.y]; m.e = table[m.t.x][m.t.y]; return m; } castle is_castle(color c, move m) { int dx = m.t.x - m.f.x, dy = m.t.y - m.f.y; if (m.p.p == p_king) { if (!moved_king[c] && dy == 0) { if (!moved_rook_l[c] && dx == -2 && table[m.f.x - 1][m.f.y].p == p_no && table[m.f.x - 3][m.f.y].p == p_rook && table[m.f.x - 3][m.f.y].c == c && !in_check(c, m.f.x - 1, m.f.y) && !in_check(c, m.f.x, m.f.y)) { return castle_short; } else if (!moved_rook_r[c] && dx == 3 && table[m.f.x + 1][m.f.y].p == p_no && table[m.f.x + 2][m.f.y].p == p_no && table[m.f.x + 4][m.f.y].p == p_rook && table[m.f.x + 4][m.f.y].c == c && !in_check(c, m.f.x + 1, m.f.y) && !in_check(c, m.f.x + 2, m.f.y) && !in_check(c, m.f.x, m.f.y)) { return castle_long; } } } return castle_no; } bool is_move_legal(color c, move m) { if (!is_move_inside(m) || is_empty(m.p) || is_enemy_of(c, m.p) || is_friend_of(c, m.e)) return false; int dx = m.t.x - m.f.x, dy = m.t.y - m.f.y; int v_x, v_y, l; switch (m.p.p) { case p_pawn: if ((dx != 0 && ((dx != 1 && dx != -1) || (dy != 1 && c == c_w) || (dy != -1 && c == c_b) || (is_empty(m.e) && is_same_pos(m.t, enp.p)))) || dy < -2 || dy > 2 || (dy == 1 && c != c_w) || (dy == 2 && (c != c_w || m.f.y != 1 || table[m.f.x][m.f.y + 1].p != p_no)) || (dy == -1 && c != c_b) || (dy == -2 && (c != c_b || m.f.y != 6 || table[m.f.x][m.f.y - 1].p != p_no)) || (dx == 0 && !is_empty(m.e))) return false; break; case p_rook: if (dx * dy != 0) return false; v_x = (dx != 0) ? ((dx > 0) ? 1 : -1) : 0; v_y = (dy != 0) ? ((dy > 0) ? 1 : -1) : 0; for (int n = 1; n < abs(dx + dy); n++) if (!is_empty(table[m.f.x + n * v_x][m.f.y + n * v_y])) return false; break; case p_bishop: if (abs(dx) != abs(dy)) return false; v_x = dx ? ((dx > 0) ? 1 : -1) : 0; v_y = dy ? ((dy > 0) ? 1 : -1) : 0; for (int n = 1; n < abs(dx); n++) if (!is_empty(table[m.f.x + n * v_x][m.f.y + n * v_y])) return false; break; case p_queen: if (dx * dy != 0 && abs(dx) != abs(dy)) return false; v_x = dx ? ((dx > 0) ? 1 : -1) : 0; v_y = dy ? ((dy > 0) ? 1 : -1) : 0; l = (dx * dy == 0) ? abs(dx + dy) : abs(dx); for (int n = 1; n < l; n++) if (!is_empty(table[m.f.x + n * v_x][m.f.y + n * v_y])) return false; break; case p_king: if (abs(dx) > 3 || abs(dy) > 1) return false; if (in_check(c, m.t.x, m.t.y)) return false; if (abs(dx) > 1) { if (!moved_king[c] && dy == 0) { if (!moved_rook_l[c] && dx == -2 && table[m.f.x - 1][m.f.y].p == p_no && table[m.f.x - 3][m.f.y].p == p_rook && table[m.f.x - 3][m.f.y].c == c && !in_check(c, m.f.x - 1, m.f.y) && !in_check(c, m.f.x, m.f.y)) { return true; } else if (!moved_rook_r[c] && dx == 3 && table[m.f.x + 1][m.f.y].p == p_no && table[m.f.x + 2][m.f.y].p == p_no && table[m.f.x + 4][m.f.y].p == p_rook && table[m.f.x + 4][m.f.y].c == c && !in_check(c, m.f.x + 1, m.f.y) && !in_check(c, m.f.x + 2, m.f.y) && !in_check(c, m.f.x, m.f.y)) { return true; } } return false; } break; case p_knight: if (!((abs(dx) == 2 && abs(dy) == 1) || (abs(dx) == 1 && abs(dy) == 2))) return false; break; case p_no: return false; break; } if (in_check_sim(m)) return false; return true; } void do_move(move m) { table[m.t.x][m.t.y] = table[m.f.x][m.f.y]; table[m.f.x][m.f.y].p = p_no; } piece_type choose_type(int fd) { char cbuf[10] = {0}; while (true) { dprintf(fd, "¿qué hacer?\n"); dprintf(fd, "> "); read(fd, cbuf, 10); switch (cbuf[0]) { case 'P': return p_pawn; case 'R': return p_rook; case 'N': return p_knight; case 'B': return p_bishop; case 'Q': return p_queen; case 'K': return p_king; } } } color c_c; bool is_printing = false; void *run_timer(void *vargp) { while (true) { if (!is_printing) { dprintf(*c_fd, "\0337\r%02d:%02d > \0338", remaining_time[c_c] / 60, remaining_time[c_c] % 60); remaining_time[c_c]--; if (remaining_time[c_c] <= 0) { if (c_c == c_w) PRINT_ALL("blanco se ha quedado sin tiempo. ¡negro gana!\n"); else PRINT_ALL("negro se ha quedado sin tiempo. ¡blanco gana!\n"); raise(SIGTERM); } } sleep(1); } return NULL; } void play_game() { PRINT_ALL("¿interfaz (a)scii o (u)nicode? [u] "); char input_str[50]; read(w_fd, input_str, 50); if (input_str[0] == 'a') modes[c_w] = mode_ascii; else modes[c_w] = mode_unicode; read(b_fd, input_str, 50); if (input_str[0] == 'a') modes[c_b] = mode_ascii; else modes[c_b] = mode_unicode; c_c = c_w; // current color is_printing = true; pthread_t thread_id; pthread_create(&thread_id, NULL, run_timer, NULL); print_table(); for (;;) { is_printing = true; if (c_c == c_w) dprintf(b_fd, "blanco está pensando...\n"); else dprintf(w_fd, "negro está pensando...\n"); no_print: if (is_checkmate(c_c)) { if (c_c == c_w) PRINT_ALL("\n¡jaque mate! ¡negro gana!\n"); else PRINT_ALL("\n¡jaque mate! ¡blanco gana!\n"); break; } else if (is_stalemate(c_c)) { PRINT_ALL("\n¡empate!\n"); break; } dprintf(*c_fd, "\n%02d:%02d > ", remaining_time[c_c] / 60, remaining_time[c_c] % 60); is_printing = false; char buf[50] = {0}; read(*c_fd, buf, 50); is_printing = true; if (buf[0] == 's') goto skip; if (buf[0] == ':') { if (c_c == c_w) dprintf(b_fd, buf); else dprintf(w_fd, buf); goto no_print; } move m = parse_move(buf); if (is_move_legal(c_c, m)) { do_move(m); castle ct = is_castle(c_c, m); if (ct == castle_short) { table[m.f.x - 1][m.f.y] = table[m.f.x - 3][m.f.y]; table[m.f.x - 3][m.f.y].p = p_no; } else if (ct == castle_long) { table[m.f.x + 2][m.f.y] = table[m.f.x + 4][m.f.y]; table[m.f.x + 4][m.f.y].p = p_no; } if (m.p.p == p_rook && ((m.f.x == 7 && m.f.y == 0) || (m.f.x == 7 && m.f.y == 7))) moved_rook_r[c_c] = true; else if (m.p.p == p_rook && ((m.f.x == 0 && m.f.y == 0) || (m.f.x == 0 && m.f.y == 7))) moved_rook_l[c_c] = true; else if (m.p.p == p_king) moved_king[c_c] = true; // en passant if (enp.valid && is_same_pos(enp.p, m.t) && m.p.p == p_pawn) enp.to_remove->p = p_no; enp.valid = false; if (m.p.p == p_pawn && abs(m.f.y - m.t.y) == 2) { enp.valid = true; enp.to_remove = &table[m.t.x][m.t.y]; enp.p.x = m.t.x; enp.p.y = m.t.y + (c_c == c_w ? -1 : 1); } // trade pawn if ((m.t.y == 0 || m.t.y == 7) && m.p.p == p_pawn) table[m.t.x][m.t.y].p = choose_type(*c_fd); print_table(); print_move(m); } else { dprintf(*c_fd, "error\n"); goto no_print; } skip: if (c_fd == &w_fd) { c_fd = &b_fd; c_c = c_b; } else { c_fd = &w_fd; c_c = c_w; } } } int main() { setup_table(); // stolen from: https://git.sr.ht/~martijnbraam/among-sus fd_set rfds, afds; uint16_t port = 1234; int listen_fd, listen6_fd, new_fd, i; socklen_t client_size; struct sockaddr_in listen_addr = {0}, client_addr = {0}; struct sockaddr_in6 listen6_addr = {0}; if ((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("IPv4 socket"); exit(EXIT_FAILURE); } if ((listen6_fd = socket(PF_INET6, SOCK_STREAM, 0)) == -1) { perror("IPv6 socket"); exit(EXIT_FAILURE); } i = 1; if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i))) { perror("IPv4 setsockopt"); exit(EXIT_FAILURE); } if (setsockopt(listen6_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i))) { perror("IPv6 setsockopt"); exit(EXIT_FAILURE); } listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); listen_addr.sin_port = htons(port); listen6_addr.sin6_family = AF_INET6; listen6_addr.sin6_addr = in6addr_any; listen6_addr.sin6_port = htons(port); if (setsockopt(listen6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &i, sizeof(i))) { perror("setsockopt"); exit(EXIT_FAILURE); } if (setsockopt(listen6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &i, sizeof(i))) { perror("setsockopt"); exit(EXIT_FAILURE); } if (bind(listen_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0) { perror("ipv4 bind"); return -1; } if (bind(listen6_fd, (struct sockaddr *)&listen6_addr, sizeof(listen6_addr)) < 0) { perror("ipv6 bind"); return -1; } listen(listen_fd, 5); listen(listen6_fd, 5); printf("Escuchando en :%d\n", port); FD_ZERO(&afds); FD_SET(listen_fd, &afds); FD_SET(listen6_fd, &afds); while (1) { rfds = afds; if (select(FD_SETSIZE, &rfds, NULL, NULL, NULL) < 0) { perror("select"); exit(EXIT_FAILURE); } for (i = 0; i < FD_SETSIZE; ++i) { if (FD_ISSET(i, &rfds)) { if (i == listen_fd || i == listen6_fd) { client_size = sizeof(client_addr); new_fd = accept(i, (struct sockaddr *)&client_addr, &client_size); if (new_fd < 0) { perror("accept"); exit(EXIT_FAILURE); } printf("Nueva conexión desde %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); FD_SET(new_fd, &afds); if (w_fd == -1) { dprintf(new_fd, "tú eres las blancas\n"); w_fd = new_fd; } else if (b_fd == -1) { dprintf(new_fd, "tú eres las negras\n"); dprintf(w_fd, "ha entrado las negras, ¡comenzamos!\n"); b_fd = new_fd; if (fork() == 0) { play_game(); return 0; } else { w_fd = -1; b_fd = -1; game_number++; } } else { // write(new_fd, "nincs hely!\n", strlen("nincs hely!\n")); } } else { close(i); FD_CLR(i, &afds); } } } } return 0; }