juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

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 }