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

notation.sh (6568B)


      1 #!/usr/bin/env bash
      2 # ============================================================================
      3 #  lib/notation.sh  --  Traducir jugadas a la notacion estandar (SAN).
      4 # ============================================================================
      5 #
      6 # Adentro del programa, una jugada se escribe "desde+hasta", como "e2e4". Eso se
      7 # llama notacion UCI. Pero las personas y el resto del mundo del ajedrez usan otra
      8 # notacion, la SAN (Standard Algebraic Notation), que escribe la misma jugada como
      9 # "e4", o "Nf3" (caballo a f3), "Bxc6" (alfil captura en c6), "O-O" (enroque
     10 # corto), "e8=Q" (peon corona dama), con un "+" si da jaque y "#" si es mate.
     11 #
     12 # La funcion principal, move_to_san, traduce de UCI a SAN. La calculamos SOBRE LA
     13 # POSICION ACTUAL (hay que llamarla ANTES de aplicar la jugada). La misma funcion
     14 # sirve para dos cosas: exportar partidas a PGN (formato estandar) y para
     15 # importar PGN (comparando cada jugada pegada contra las jugadas legales).
     16 # Usa board.sh, movegen.sh y rules.sh. A proposito no depende de la interfaz.
     17 
     18 # _move_is_capture: la jugada captura algo? (incluye la captura al paso).
     19 _move_is_capture() {
     20     local m=$1
     21     # OJO: esta linea va aparte de la anterior a proposito. En bash, si escribis
     22     # "local m=$1 frm=${m:0:2}" en UNA sola linea, los valores de la derecha se
     23     # calculan TODOS antes de asignar, asi que "frm" veria una "m" todavia vacia.
     24     # Separando las declaraciones, "m" ya tiene su valor cuando armamos "frm".
     25     local frm=${m:0:2} to=${m:2:2}
     26     local ti; ti=$(alg_to_idx "$to")
     27     # Si en el destino hay una pieza, es captura.
     28     [[ "${BOARD[$ti]}" != "." ]] && return 0
     29     # Si no, todavia puede ser captura al paso: un peon que va en diagonal
     30     # (cambia de columna) hacia la casilla marcada como "al paso" (EP).
     31     local fi; fi=$(alg_to_idx "$frm")
     32     [[ "${BOARD[$fi],,}" == p && "${frm:0:1}" != "${to:0:1}" && "$to" == "$EP" ]]
     33 }
     34 
     35 # _san_suffix: devuelve "+" si la jugada da jaque, "#" si da mate, o "" si nada.
     36 # Para saberlo, aplica la jugada sobre una COPIA del estado, mira el resultado, y
     37 # despues deja todo como estaba (no debe tener efectos secundarios, porque se la
     38 # llama mientras todavia estamos decidiendo).
     39 _san_suffix() {
     40     local uci=$1
     41     # Guardamos una copia de TODO el estado (el tablero entero con "${BOARD[@]}",
     42     # mas las 5 variables sueltas) para poder restaurarlo despues.
     43     local _b=("${BOARD[@]}") _t=$TURN _c=$CASTLE _e=$EP _h=$HALFMOVE _f=$FULLMOVE
     44     apply_move "$uci"
     45     local key; key=$(position_key)
     46     local suf=""
     47     if in_check "$TURN"; then
     48         # El rival quedo en jaque. Si ademas no le quedan jugadas legales, es mate.
     49         [[ -z "$(legal_moves)" ]] && suf="#" || suf="+"
     50     fi
     51     # Restauramos el estado original.
     52     BOARD=("${_b[@]}"); TURN=$_t; CASTLE=$_c; EP=$_e; HALFMOVE=$_h; FULLMOVE=$_f
     53     # apply_move habia sumado 1 a la cuenta de esta posicion; lo deshacemos.
     54     POSCOUNT[$key]=$(( ${POSCOUNT[$key]:-1} - 1 ))
     55     (( POSCOUNT[$key] <= 0 )) && unset 'POSCOUNT[$key]'   # si quedo en 0, la borramos
     56     printf '%s' "$suf"
     57 }
     58 
     59 # move_to_san: la traduccion principal, de UCI (e2e4) a SAN (e4), en la posicion actual.
     60 move_to_san() {
     61     local uci=$1
     62     local frm=${uci:0:2} to=${uci:2:2} promo=${uci:4:1}
     63     local fi; fi=$(alg_to_idx "$frm")
     64     local piece="${BOARD[$fi]}" pl="${BOARD[$fi]}"; pl="${pl,,}"
     65     local fc=$(( fi%8 )) tc=$(( $(alg_to_idx "$to")%8 ))   # columna de origen y destino
     66     local cap=""; _move_is_capture "$uci" && cap="x"       # "x" si es captura
     67     local san=""
     68 
     69     # Enroque: el rey se corre dos columnas. Se escribe O-O (corto) u O-O-O (largo).
     70     if [[ "$pl" == k ]]; then
     71         local d=$(( tc - fc )); (( d<0 )) && d=$(( -d ))
     72         if (( d == 2 )); then
     73             (( tc == 6 )) && san="O-O" || san="O-O-O"
     74             printf '%s%s' "$san" "$(_san_suffix "$uci")"; return
     75         fi
     76     fi
     77 
     78     if [[ "$pl" == p ]]; then
     79         # PEON: si captura, se escribe la columna de origen + "x" (ej: "exd5"). El
     80         # destino siempre va. Si corona, se agrega "=" y la pieza nueva en mayuscula.
     81         [[ -n "$cap" ]] && san="${frm:0:1}x"
     82         san+="$to"
     83         [[ -n "$promo" ]] && san+="=${promo^^}"
     84     else
     85         # OTRAS PIEZAS: empiezan con su letra (N, B, R, Q, K).
     86         local letter="${piece^^}"
     87         # "Desambiguacion": si OTRA pieza del mismo tipo tambien puede ir al mismo
     88         # destino, hay que aclarar cual de las dos. Por ejemplo, si dos caballos
     89         # pueden ir a d2, no alcanza con "Nd2"; se escribe "Nbd2" o "N1d2".
     90         # Recorremos todas las jugadas legales para detectar ese choque.
     91         local m sameFile=0 sameRank=0 other=0 oi oidx
     92         while IFS= read -r m; do
     93             [[ -z "$m" ]] && continue
     94             [[ "${m:2:2}" == "$to" ]] || continue          # va al mismo destino?
     95             [[ "${m:0:2}" == "$frm" ]] && continue         # es esta misma jugada? saltear
     96             oi="${m:0:2}"; oidx=$(alg_to_idx "$oi")
     97             [[ "${BOARD[$oidx]^^}" == "$letter" ]] || continue   # es del mismo tipo de pieza?
     98             other=1                                        # si: hay ambiguedad
     99             [[ "${oi:0:1}" == "${frm:0:1}" ]] && sameFile=1  # comparten columna?
    100             [[ "${oi:1:1}" == "${frm:1:1}" ]] && sameRank=1  # comparten fila?
    101         done < <(legal_moves)
    102         local disamb=""
    103         if (( other )); then
    104             # Regla SAN: se aclara con la columna; si comparten columna, con la
    105             # fila; si comparten ambas (raro), con la casilla completa.
    106             if   (( ! sameFile )); then disamb="${frm:0:1}"
    107             elif (( ! sameRank )); then disamb="${frm:1:1}"
    108             else                        disamb="$frm"; fi
    109         fi
    110         san="${letter}${disamb}${cap}${to}"
    111     fi
    112     printf '%s%s' "$san" "$(_san_suffix "$uci")"   # agregamos el "+"/"#" si toca
    113 }
    114 
    115 # san_to_uci: lo inverso. Recibe una jugada en SAN (como "Nf3") y devuelve la
    116 # jugada en UCI (como "g1f3"), buscando cual de las jugadas legales, al
    117 # traducirla con move_to_san, da ese mismo SAN. Ignora adornos como +, #, !, ?
    118 # (los simbolos de jaque/mate/comentario), tanto en la entrada como al comparar.
    119 san_to_uci() {
    120     local want=${1//[+#!?]/} m   # "${1//[+#!?]/}" borra esos simbolos del texto
    121     while IFS= read -r m; do
    122         [[ -z "$m" ]] && continue
    123         local s; s=$(move_to_san "$m"); s=${s//[+#!?]/}
    124         if [[ "$s" == "$want" ]]; then printf '%s' "$m"; return 0; fi
    125     done < <(legal_moves)
    126     return 1   # ninguna jugada legal coincide (SAN invalido para esta posicion)
    127 }