board.sh (10501B)
1 #!/usr/bin/env bash 2 # ============================================================================ 3 # lib/board.sh -- El TABLERO: como guardamos la posicion y como la leemos. 4 # ============================================================================ 5 # 6 # Este archivo NO se ejecuta solo. Es una "libreria": un archivo lleno de 7 # variables y funciones que otros archivos cargan (con el comando "source") para 8 # usar. Aca vive todo lo basico sobre el tablero. 9 # 10 # COMO REPRESENTAMOS EL TABLERO (la idea mas importante de todo el proyecto): 11 # Un tablero de ajedrez tiene 64 casilleros (8 filas x 8 columnas). En la 12 # computadora lo guardamos como una LISTA de 64 elementos, uno por casillero. 13 # En programacion a esa lista se la llama "arreglo" o "array". Cada elemento 14 # tiene un numero de posicion (un "indice") que va del 0 al 63. 15 # 16 # Decidimos numerar los casilleros asi: el indice 0 es la esquina de arriba a 17 # la izquierda (la casilla "a8"), y vamos contando hacia la derecha y hacia 18 # abajo, fila por fila, hasta el indice 63 que es "h1" (abajo a la derecha): 19 # 20 # columna: a b c d e f g h 21 # fila 8: 0 1 2 3 4 5 6 7 22 # fila 7: 8 9 10 11 12 13 14 15 23 # fila 6: 16 17 18 19 20 21 22 23 24 # fila 5: 24 25 26 27 28 29 30 31 25 # fila 4: 32 33 34 35 36 37 38 39 26 # fila 3: 40 41 42 43 44 45 46 47 27 # fila 2: 48 49 50 51 52 53 54 55 28 # fila 1: 56 57 58 59 60 61 62 63 29 # 30 # Cada elemento del arreglo guarda UNA letra que dice que hay en ese casillero: 31 # "." = casillero vacio 32 # mayusculas P N B R Q K = piezas BLANCAS (Peon, kNight=caballo, Bishop=alfil, 33 # Rook=torre, Queen=dama, King=rey) 34 # minusculas p n b r q k = piezas NEGRAS (las mismas, pero del otro color) 35 # Usamos las letras en ingles porque es el estandar internacional del ajedrez. 36 37 # --- El estado del juego: un puniado de "variables globales" ---------------- 38 # Una variable es una cajita con nombre donde guardamos un valor. "Global" 39 # quiere decir que cualquier funcion del programa la puede ver y modificar. 40 # Entre todas estas variables describen COMPLETAMENTE la partida en un momento 41 # dado (es lo que el ajedrez llama una posicion, en formato "FEN", ver mas abajo). 42 43 declare -a BOARD # "declare -a" crea un arreglo (la lista de 64 casilleros) 44 TURN="white" # de quien es el turno: "white" (blancas) o "black" (negras) 45 CASTLE="KQkq" # quien todavia puede enrocar (ver reglas mas adelante) 46 EP="-" # casilla de captura "al paso" (en passant), o "-" si no hay 47 HALFMOVE=0 # contador de medias jugadas para la regla de 50 movimientos 48 FULLMOVE=1 # numero de jugada completa (sube despues de que mueven las negras) 49 50 # La posicion INICIAL del ajedrez, escrita en formato FEN (lo explicamos abajo). 51 START_FEN="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" 52 53 # --- Ayudantes de coordenadas ----------------------------------------------- 54 # A veces es mas comodo pensar en (fila, columna) en vez de en un indice de 0 a 55 # 63. Usamos "r" (de "rank"/fila) que va de 0 a 7 de ARRIBA hacia abajo (r=0 es 56 # la fila 8 del ajedrez) y "c" (de "column"/columna) de 0 a 7 de IZQUIERDA a 57 # derecha (c=0 es la columna "a"). 58 # 59 # Nota sobre bash: "$(( ... ))" hace CUENTAS con numeros enteros. "$1" es el 60 # primer argumento que recibe la funcion, "$2" el segundo. "echo" imprime el 61 # resultado para que quien llama a la funcion lo pueda capturar. 62 rc_to_idx() { echo $(( $1 * 8 + $2 )); } # de (fila,columna) al indice 0..63 63 idx_rank() { echo $(( $1 / 8 )); } # del indice a la fila (0..7, 0 = fila 8) 64 idx_file() { echo $(( $1 % 8 )); } # del indice a la columna (0..7, 0 = "a") 65 # "/" es division entera y "%" es el RESTO de la division. Por ejemplo, el indice 66 # 10: 10/8 = 1 (fila), 10%8 = 2 (columna) -> esta en la fila r=1, columna c=2. 67 68 # idx_to_alg: convierte un indice (0..63) al nombre de casilla del ajedrez, como 69 # "e4" o "a8". A eso se le dice notacion "algebraica". 70 idx_to_alg() { 71 local files=(a b c d e f g h) # arreglo con las letras de las columnas 72 # "${files[N]}" agarra el elemento N del arreglo. La columna es (indice % 8). 73 # La fila del ajedrez es 8 menos la fila interna (indice / 8). 74 echo "${files[$(( $1 % 8 ))]}$(( 8 - ($1 / 8) ))" 75 } 76 77 # alg_to_idx: lo inverso. Convierte un nombre como "e4" al indice 0..63. 78 alg_to_idx() { 79 # "${1:0:1}" toma del argumento 1 caracter, empezando en la posicion 0: o sea 80 # la PRIMERA letra ("e"). El truco "'e" en printf '%d' da el codigo numerico 81 # de la letra en la tabla ASCII. La 'a' vale 97, asi que restando 97 a la 82 # letra obtenemos: a->0, b->1, ..., h->7 (la columna). 83 local f=$(( $(printf '%d' "'${1:0:1}") - 97 )) 84 # "${1:1:1}" toma 1 caracter desde la posicion 1: el SEGUNDO (el numero de 85 # fila del ajedrez). "8 - ese numero" nos da la fila interna (fila 8 -> r=0). 86 local r=$(( 8 - ${1:1:1} )) 87 echo $(( (r * 8) + f )) # juntamos fila y columna en un solo indice 88 } 89 90 # --- Preguntas sobre una pieza ---------------------------------------------- 91 # Estas funciones contestan verdadero/falso (en bash, "exito" = verdadero). 92 # "[[ "$1" =~ [A-Z] ]]" pregunta si el texto coincide con una expresion regular: 93 # aca, si el caracter es una letra MAYUSCULA (de la A a la Z) -> es pieza blanca. 94 is_white() { [[ "$1" =~ [A-Z] ]]; } # es una pieza blanca? 95 is_black() { [[ "$1" =~ [a-z] ]]; } # es una pieza negra (minuscula)? 96 is_empty() { [[ "$1" == "." ]]; } # el casillero esta vacio? 97 98 # piece_color: dado el caracter de una pieza, imprime "white", "black" o "none". 99 # "if/elif/else/fi" es la estructura "si... sino si... sino...". En bash el "if" 100 # se cierra con "fi" (if al reves). Pregunta por la condicion ejecutando una 101 # funcion: si esa funcion da exito, entra al "then". 102 piece_color() { 103 if is_white "$1"; then echo white 104 elif is_black "$1"; then echo black 105 else echo none 106 fi 107 } 108 109 # other_color: dado un color, devuelve el contrario. "A && B || C" se lee como 110 # "si A es verdadero hace B, sino hace C" (un if cortito en una sola linea). 111 other_color() { [[ "$1" == white ]] && echo black || echo white; } 112 113 # --- Leer un FEN (texto) y cargarlo en el tablero --------------------------- 114 # QUE ES UN FEN: es la forma estandar de escribir una posicion de ajedrez en una 115 # sola linea de texto. Tiene 6 partes separadas por espacios. Ejemplo, la 116 # posicion inicial: 117 # rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 118 # 1) Las piezas, fila por fila (de la 8 a la 1), separadas por "/". Las letras 119 # son piezas; un numero significa "esa cantidad de casillas vacias seguidas". 120 # 2) "w" o "b": a quien le toca mover. 121 # 3) Derechos de enroque (KQkq) o "-" si nadie puede. 122 # 4) Casilla "al paso" o "-". 123 # 5) Contador de la regla de 50 movimientos. 124 # 6) Numero de jugada completa. 125 # 126 # setup_fen agarra ese texto y completa nuestras variables (BOARD, TURN, etc.). 127 setup_fen() { 128 local fen="$1" 129 local placement turn castle ep half full 130 # "read" parte el texto en pedazos usando los espacios y mete cada pedazo en 131 # una variable. "<<< "$fen"" le da ese texto como entrada (here-string). 132 read -r placement turn castle ep half full <<< "$fen" 133 134 # "${placement//\//}" reemplaza TODAS las barras "/" por nada: nos deja solo 135 # las 64 posiciones seguidas, sin los separadores de fila. 136 local clean="${placement//\//}" 137 local idx=0 i char 138 BOARD=() # arrancamos con el tablero vacio 139 # Recorremos caracter por caracter el texto de las piezas. 140 for (( i=0; i<${#clean}; i++ )); do # "${#clean}" es la cantidad de caracteres 141 char="${clean:$i:1}" # el caracter en la posicion i 142 if [[ "$char" =~ [1-8] ]]; then 143 # Si es un numero del 1 al 8, son casillas vacias: ponemos esa 144 # cantidad de puntos "." en el tablero. 145 local j 146 for (( j=0; j<char; j++ )); do BOARD[idx]="."; ((idx++)); done 147 else 148 # Si es una letra, es una pieza: la guardamos tal cual. 149 BOARD[idx]="$char"; ((idx++)) 150 fi 151 done 152 153 # Las otras 5 partes del FEN pueden venir o no (a veces nos pasan solo las 154 # piezas). "[[ -n "$turn" ]]" pregunta si la variable NO esta vacia; solo en 155 # ese caso la usamos. Convertimos "w"/"b" a "white"/"black". 156 [[ -n "$turn" ]] && { [[ "$turn" == w ]] && TURN=white || TURN=black; } 157 [[ -n "$castle" ]] && CASTLE="$castle" 158 [[ -n "$ep" ]] && EP="$ep" 159 [[ -n "$half" ]] && HALFMOVE="$half" 160 [[ -n "$full" ]] && FULLMOVE="$full" 161 } 162 163 # --- Generar un FEN (lo contrario: del tablero al texto) -------------------- 164 # board_to_placement arma solo la PRIMERA parte del FEN (las piezas). 165 board_to_placement() { 166 local out="" r c run idx piece 167 for (( r=0; r<8; r++ )); do # por cada fila, de la 8 (r=0) a la 1 168 run=0 # contador de casillas vacias seguidas 169 for (( c=0; c<8; c++ )); do # por cada columna 170 idx=$(( r * 8 + c )) 171 piece="${BOARD[$idx]}" 172 if [[ "$piece" == "." ]]; then 173 ((run++)) # otra vacia: sumamos al contador 174 else 175 # Si veniamos contando vacias, primero escribimos ese numero y 176 # reiniciamos el contador; despues escribimos la pieza. 177 [[ $run -gt 0 ]] && out+="$run" && run=0 178 out+="$piece" 179 fi 180 done 181 [[ $run -gt 0 ]] && out+="$run" # vacias que quedaron al final de la fila 182 [[ $r -lt 7 ]] && out+="/" # separador entre filas (menos en la ultima) 183 done 184 echo "$out" 185 } 186 187 # get_fen: arma el FEN COMPLETO (las 6 partes) del estado actual del juego. 188 get_fen() { 189 local turn_char; [[ "$TURN" == white ]] && turn_char=w || turn_char=b 190 echo "$(board_to_placement) $turn_char $CASTLE $EP $HALFMOVE $FULLMOVE" 191 } 192 193 # position_key: una "huella" de la posicion para detectar repeticiones. Por las 194 # reglas FIDE, dos posiciones son "la misma" si coinciden las piezas, de quien es 195 # el turno, los enroques disponibles y la casilla al paso. Por eso la huella usa 196 # esas 4 cosas (y NO los contadores de jugadas). La usamos para la regla de 197 # repeticion (la misma posicion varias veces = tablas). 198 position_key() { 199 local turn_char; [[ "$TURN" == white ]] && turn_char=w || turn_char=b 200 echo "$(board_to_placement) $turn_char $CASTLE $EP" 201 }