movegen.sh (11694B)
1 #!/usr/bin/env bash 2 # ============================================================================ 3 # lib/movegen.sh -- Generar las jugadas POSIBLES de cada pieza. 4 # ============================================================================ 5 # 6 # Este archivo se encarga de una sola cosa: dado el tablero, listar todas las 7 # jugadas que puede hacer la persona a la que le toca mover, segun como se mueve 8 # cada pieza. 9 # 10 # OJO con la palabra "pseudo-legal": 11 # Aca generamos jugadas que respetan COMO se mueve cada pieza (la torre en 12 # linea recta, el alfil en diagonal, etc.), pero todavia NO chequeamos una 13 # regla importante: que no podes hacer una jugada que deje a TU PROPIO rey en 14 # jaque. Ese filtro final lo hace otro archivo (lib/rules.sh). Separar las dos 15 # cosas hace el codigo mas simple. A las jugadas de aca, sin ese filtro, se las 16 # llama "pseudo-legales". 17 # 18 # COMO ESCRIBIMOS UNA JUGADA: 19 # Como un texto de 4 letras "desde+hasta", por ejemplo "e2e4" (mover de e2 a 20 # e4). Si un peon corona, agregamos una 5ta letra con la pieza nueva, por 21 # ejemplo "e7e8q" (corona dama). El enroque se escribe como el movimiento del 22 # rey dos casillas, por ejemplo "e1g1". 23 # Cada funcion IMPRIME (con echo) una jugada por linea. Quien la llama despues 24 # junta todas esas lineas. 25 26 # --- Piezas que se "deslizan": alfil, torre, dama --------------------------- 27 # Estas piezas avanzan en linea recta hasta chocar con algo. Les pasamos el 28 # casillero de origen ($1) y despues pares de numeros (dr, dc) que indican las 29 # DIRECCIONES en las que se mueven: dr = cuanto cambia la fila, dc = cuanto 30 # cambia la columna. Por ejemplo (-1,-1) es "una fila para arriba y una columna 31 # para la izquierda" = diagonal hacia arriba-izquierda. 32 _gen_slide() { 33 local from=$1; shift # guardamos el origen y "shift" descarta ese argumento, 34 # asi los que quedan son solo los pares de direccion. 35 local r=$(( from / 8 )) c=$(( from % 8 )) # fila y columna del origen 36 local color; color=$(piece_color "${BOARD[$from]}") # color de la pieza que mueve 37 # "$#" es CUANTOS argumentos quedan. Mientras queden al menos 2 (un par dr,dc): 38 while (( $# >= 2 )); do 39 local dr=$1 dc=$2; shift 2 # tomamos el par y lo descartamos (shift 2) 40 local nr=$(( r + dr )) nc=$(( c + dc )) # primer casillero en esa direccion 41 # Avanzamos mientras sigamos DENTRO del tablero (filas y columnas 0..7). 42 while (( nr >= 0 && nr < 8 && nc >= 0 && nc < 8 )); do 43 local tidx=$(( nr * 8 + nc )) tp tcolor 44 tp="${BOARD[$tidx]}"; tcolor=$(piece_color "$tp") 45 if [[ "$tcolor" == none ]]; then 46 # Casillero vacio: jugada valida, y podemos SEGUIR avanzando. 47 echo "$(idx_to_alg "$from")$(idx_to_alg "$tidx")" 48 elif [[ "$tcolor" == "$color" ]]; then 49 # Hay una pieza NUESTRA: no podemos ir ahi ni seguir. Cortamos. 50 break 51 else 52 # Hay una pieza RIVAL: la podemos capturar (jugada valida), pero 53 # despues frenamos (no se puede saltar piezas). 54 echo "$(idx_to_alg "$from")$(idx_to_alg "$tidx")" 55 break 56 fi 57 nr=$(( nr + dr )); nc=$(( nc + dc )) # un paso mas en la misma direccion 58 done 59 done 60 } 61 62 # --- Piezas que dan un solo "paso": caballo y rey --------------------------- 63 # A diferencia de las anteriores, estas se mueven UNA vez en cada direccion (no 64 # avanzan en linea). El caballo salta en "L" y el rey una casilla en cualquier 65 # direccion. Mismo formato: origen y pares (dr,dc). 66 _gen_step() { 67 local from=$1; shift 68 local r=$(( from / 8 )) c=$(( from % 8 )) 69 local color; color=$(piece_color "${BOARD[$from]}") 70 while (( $# >= 2 )); do 71 local dr=$1 dc=$2; shift 2 72 local nr=$(( r + dr )) nc=$(( c + dc )) 73 if (( nr >= 0 && nr < 8 && nc >= 0 && nc < 8 )); then # dentro del tablero? 74 local tidx=$(( nr * 8 + nc )) tcolor 75 tcolor=$(piece_color "${BOARD[$tidx]}") 76 # Vale ir si el casillero NO tiene una pieza nuestra (esta vacio o hay 77 # un rival para capturar). "\" al final corta la linea: el comando 78 # sigue en la linea de abajo. 79 [[ "$tcolor" != "$color" ]] && \ 80 echo "$(idx_to_alg "$from")$(idx_to_alg "$tidx")" 81 fi 82 done 83 } 84 85 # --- El peon: la pieza con mas reglas raras --------------------------------- 86 # El peon: avanza derecho (no captura de frente), captura en diagonal, puede 87 # avanzar 2 casillas la primera vez, captura "al paso" (en passant) y "corona" 88 # (se transforma en otra pieza) al llegar a la ultima fila. 89 _gen_pawn() { 90 local from=$1 91 local r=$(( from / 8 )) c=$(( from % 8 )) 92 local color; color=$(piece_color "${BOARD[$from]}") 93 # Los peones blancos van hacia arriba (filas internas que BAJAN, dir=-1) y los 94 # negros hacia abajo (dir=1). Tambien definimos desde que fila pueden avanzar 95 # 2 casillas y en que fila coronan. 96 local dir start_rank promo_rank 97 if [[ "$color" == white ]]; then dir=-1; start_rank=6; promo_rank=0 98 else dir=1; start_rank=1; promo_rank=7; fi 99 100 local from_alg; from_alg=$(idx_to_alg "$from") 101 102 # Avanzar una casilla (y, si corresponde, dos). 103 local nr=$(( r + dir )) 104 if (( nr >= 0 && nr < 8 )); then 105 local one=$(( nr * 8 + c )) 106 if [[ "${BOARD[$one]}" == "." ]]; then # la casilla de adelante esta libre 107 _emit_pawn "$from_alg" "$(idx_to_alg "$one")" "$nr" "$promo_rank" 108 # Si el peon esta en su fila inicial, puede avanzar 2 (si tambien esta libre). 109 if (( r == start_rank )); then 110 local two=$(( (r + 2*dir) * 8 + c )) 111 [[ "${BOARD[$two]}" == "." ]] && \ 112 echo "${from_alg}$(idx_to_alg "$two")" 113 fi 114 fi 115 fi 116 117 # Capturas en diagonal (incluyendo la captura "al paso"). 118 local dc 119 for dc in -1 1; do # mira la diagonal izquierda (-1) y la derecha (1) 120 local ncol=$(( c + dc )) 121 # Si nos saldriamos del tablero, salteamos esta diagonal ("continue" pasa 122 # a la siguiente vuelta del for). 123 (( ncol < 0 || ncol > 7 || nr < 0 || nr > 7 )) && continue 124 local tidx=$(( nr * 8 + ncol )) 125 local talg; talg=$(idx_to_alg "$tidx") 126 local tcolor; tcolor=$(piece_color "${BOARD[$tidx]}") 127 if [[ "$tcolor" != none && "$tcolor" != "$color" ]]; then 128 # Hay una pieza rival en la diagonal: la podemos capturar. 129 _emit_pawn "$from_alg" "$talg" "$nr" "$promo_rank" 130 elif [[ "$talg" == "$EP" ]]; then 131 # Captura "al paso": la casilla destino es la marcada en EP (ver 132 # board.sh). Es una jugada especial donde capturamos un peon que 133 # acaba de avanzar dos, comiendolo "como si" hubiese avanzado una. 134 echo "${from_alg}${talg}" 135 fi 136 done 137 } 138 139 # _emit_pawn: imprime una jugada de peon. Si el peon llega a la fila de coronar, 140 # imprime CUATRO jugadas (una por cada pieza en la que se puede convertir: dama, 141 # torre, alfil, caballo). Si no, imprime la jugada normal. 142 _emit_pawn() { 143 local from_alg=$1 to_alg=$2 to_rank=$3 promo_rank=$4 144 if (( to_rank == promo_rank )); then 145 local p 146 for p in q r b n; do echo "${from_alg}${to_alg}${p}"; done 147 else 148 echo "${from_alg}${to_alg}" 149 fi 150 } 151 152 # --- El enroque ------------------------------------------------------------- 153 # El enroque es la jugada especial donde el rey y una torre se mueven juntos. 154 # Tiene varias condiciones: que rey y torre no se hayan movido (eso lo guarda 155 # CASTLE), que las casillas del medio esten vacias, y que el rey no este en jaque 156 # ni pase por (ni termine en) una casilla atacada por el rival. 157 # Usa is_square_attacked() (definida en lib/rules.sh) para chequear los ataques. 158 _gen_castle() { 159 local color=$TURN enemy 160 enemy=$(other_color "$color") 161 local rank king_e 162 if [[ "$color" == white ]]; then rank=7; else rank=0; fi 163 king_e=$(( rank * 8 + 4 )) # el rey arranca en la columna "e" 164 165 # El rey tiene que estar en su casa y no estar en jaque. "return" sale de la 166 # funcion sin generar ningun enroque. 167 local king_char; [[ "$color" == white ]] && king_char=K || king_char=k 168 [[ "${BOARD[$king_e]}" != "$king_char" ]] && return 169 is_square_attacked "$king_e" "$enemy" && return 170 171 # Vemos que enroques siguen permitidos segun CASTLE. "$CASTLE" == *K* pregunta 172 # si el texto CASTLE CONTIENE una "K" (los * son comodines: cualquier cosa 173 # antes y despues). K/Q = enroques de las blancas, k/q = de las negras. 174 local kside qside rook_char 175 if [[ "$color" == white ]]; then 176 [[ "$CASTLE" == *K* ]] && kside=1; [[ "$CASTLE" == *Q* ]] && qside=1 177 rook_char=R 178 else 179 [[ "$CASTLE" == *k* ]] && kside=1; [[ "$CASTLE" == *q* ]] && qside=1 180 rook_char=r 181 fi 182 183 # Indices de las casillas de esta fila que nos interesan (columnas a..h). 184 local f=$(( rank*8+5 )) g=$(( rank*8+6 )) h=$(( rank*8+7 )) 185 local d=$(( rank*8+3 )) c2=$(( rank*8+2 )) b=$(( rank*8+1 )) a=$(( rank*8+0 )) 186 187 # Enroque corto (del lado del rey): torre en "h", casillas f y g vacias, y el 188 # rey no pasa por f ni termina en g atacado. 189 if [[ -n "$kside" && "${BOARD[$h]}" == "$rook_char" \ 190 && "${BOARD[$f]}" == "." && "${BOARD[$g]}" == "." ]]; then 191 if ! is_square_attacked "$f" "$enemy" && ! is_square_attacked "$g" "$enemy"; then 192 echo "$(idx_to_alg "$king_e")$(idx_to_alg "$g")" 193 fi 194 fi 195 # Enroque largo (del lado de la dama): torre en "a", casillas d, c y b vacias, 196 # y el rey no pasa por d ni termina en c atacado. (La casilla b solo necesita 197 # estar vacia; no hace falta que este libre de ataque, porque el rey no la pisa.) 198 if [[ -n "$qside" && "${BOARD[$a]}" == "$rook_char" \ 199 && "${BOARD[$d]}" == "." && "${BOARD[$c2]}" == "." && "${BOARD[$b]}" == "." ]]; then 200 if ! is_square_attacked "$d" "$enemy" && ! is_square_attacked "$c2" "$enemy"; then 201 echo "$(idx_to_alg "$king_e")$(idx_to_alg "$c2")" 202 fi 203 fi 204 } 205 206 # --- Juntar TODO: todas las jugadas pseudo-legales del que mueve ------------ 207 # Recorre los 64 casilleros; por cada pieza del color al que le toca, llama a la 208 # funcion generadora que corresponda segun el TIPO de pieza. 209 pseudo_moves() { 210 local i piece color 211 for (( i=0; i<64; i++ )); do 212 piece="${BOARD[$i]}" 213 [[ "$piece" == "." ]] && continue # casillero vacio: saltear 214 color=$(piece_color "$piece") 215 [[ "$color" != "$TURN" ]] && continue # pieza del rival: no es su turno 216 # "case" elige que hacer segun un valor (como un "switch" de otros 217 # lenguajes). "${piece,,}" pasa la letra a MINUSCULA, asi una "N" o una 218 # "n" caen las dos en el caso "n)". Los numeros que siguen son los pares 219 # (dr,dc) con todas las direcciones de esa pieza. 220 case "${piece,,}" in 221 p) _gen_pawn "$i" ;; 222 n) _gen_step "$i" -2 -1 -2 1 -1 -2 -1 2 1 -2 1 2 2 -1 2 1 ;; # caballo (8 saltos en L) 223 b) _gen_slide "$i" -1 -1 -1 1 1 -1 1 1 ;; # alfil (4 diagonales) 224 r) _gen_slide "$i" -1 0 1 0 0 -1 0 1 ;; # torre (4 rectas) 225 q) _gen_slide "$i" -1 -1 -1 1 1 -1 1 1 -1 0 1 0 0 -1 0 1 ;; # dama (diagonales + rectas) 226 k) _gen_step "$i" -1 -1 -1 0 -1 1 0 -1 0 1 1 -1 1 0 1 1 ;; # rey (8 casillas alrededor) 227 esac 228 done 229 _gen_castle # y por ultimo, los enroques (que no dependen de un casillero suelto) 230 }