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 }