transport.sh (24864B)
1 #!/usr/bin/env bash 2 # ============================================================================ 3 # lib/transport.sh -- De DONDE viene cada jugada (la "fuente de jugadas"). 4 # ============================================================================ 5 # 6 # Idea central del proyecto: el motor de ajedrez (las reglas) NO sabe ni le 7 # importa si jugas vos en el teclado, otra persona por red, o la computadora. 8 # Cada turno, el juego pregunta: "es mi turno?". Si lo es, pide una jugada a la 9 # interfaz (el menu visual). Si no, espera la jugada del otro lado. El mismo motor 10 # corre en las dos computadoras; lo unico que cambia es la FUENTE de la jugada. 11 # Este archivo es justamente esa "fuente de jugadas" y todo lo de la red. 12 # 13 # TRANSPORT_MODE : hotseat | p2p | server | ai (en que modo estamos jugando) 14 # LOCAL_COLOR : white | black | both (both = hotseat, manejamos los dos lados) 15 # 16 # Usa protocol.sh (mandar/recibir por red) y ui.sh (el menu para elegir jugada). 17 18 TRANSPORT_MODE=hotseat # por defecto, dos personas en la misma compu 19 LOCAL_COLOR=both # por defecto, manejamos ambos colores 20 21 # Datos de la conexion, que recordamos para poder RECONECTAR si se corta la red 22 # (ver transport_reconnect y el modo "rejoin"). 23 # NET_ROLE : host | join | server (que lado de la conexion somos) 24 # NET_HOST : direccion del otro / del relay al que nos conectamos 25 # NET_PORT : puerto donde escuchamos (host) o al que nos conectamos 26 # MY_COLOR : nuestro color, que se mantiene aunque nos reconectemos 27 NET_ROLE="" 28 NET_HOST="" 29 NET_PORT="" 30 MY_COLOR="" 31 32 # --- El reloj de ajedrez (compartido con ui.sh y con el bucle de ches.sh) ------ 33 # TC_ENABLED : 1 si hay reloj, 0 si se juega sin tiempo 34 # TC_BASE : tiempo base por jugador, en segundos 35 # TC_INC : incremento "Fischer" que se suma despues de cada jugada 36 # CLOCK_* : segundos que le quedan a cada lado 37 # El control de tiempo se serializa para la red como un texto: "off" o "BASE:INC". 38 TC_ENABLED=0 39 TC_BASE=0 40 TC_INC=0 41 CLOCK_white=0 42 CLOCK_black=0 43 44 # Se permiten retrocesos (deshacer la ultima jugada de comun acuerdo)? Lo elige 45 # quien arma la partida (con tb_pick) y viaja al otro dentro del estado (GAME_TB). 46 # El menu de pausa solo ofrece "retroceder" si esto esta activado. 47 ALLOW_TAKEBACK=0 48 49 # --- tc_pick: elegir el control de tiempo con un menu (fzf) ------------------ 50 # "fzf" es un programa que muestra una lista y deja elegir una opcion con las 51 # flechas y Enter. Le damos las opciones con "printf ... | fzf". Lo que la persona 52 # elige queda en "sel". (Los textos quedan en ingles: es la interfaz.) 53 tc_pick() { 54 local sel 55 sel=$(printf '%s\n' \ 56 "1 min" "3 min" "5 min" "10 min" "15 min" \ 57 "3 + 2 (3 min, 2s/move)" "5 + 5 (5 min, 5s/move)" \ 58 "10 + 5 (10 min, 5s/move)" "15 + 10 (15 min, 10s/move)" \ 59 "No time (unlimited)" "Custom..." \ 60 | fzf --reverse --border --height=60% --prompt="Time control: " \ 61 --header=$' Choose a clock for this game') 62 # Segun lo elegido, prendemos el reloj y fijamos el tiempo base y el incremento. 63 case "$sel" in 64 "1 min") TC_ENABLED=1; TC_BASE=60; TC_INC=0 ;; 65 "3 min") TC_ENABLED=1; TC_BASE=180; TC_INC=0 ;; 66 "5 min") TC_ENABLED=1; TC_BASE=300; TC_INC=0 ;; 67 "10 min") TC_ENABLED=1; TC_BASE=600; TC_INC=0 ;; 68 "15 min") TC_ENABLED=1; TC_BASE=900; TC_INC=0 ;; 69 "3 + 2"*) TC_ENABLED=1; TC_BASE=180; TC_INC=2 ;; 70 "5 + 5"*) TC_ENABLED=1; TC_BASE=300; TC_INC=5 ;; 71 "10 + 5"*) TC_ENABLED=1; TC_BASE=600; TC_INC=5 ;; 72 "15 + 10"*) TC_ENABLED=1; TC_BASE=900; TC_INC=10 ;; 73 "Custom..."*) _tc_custom ;; 74 *) TC_ENABLED=0; TC_BASE=0; TC_INC=0 ;; # Sin tiempo / cancelado 75 esac 76 } 77 78 # tb_pick: elegir si se permiten retrocesos. Deja ALLOW_TAKEBACK en 1 o 0. 79 tb_pick() { 80 local sel 81 sel=$(printf '%s\n' \ 82 "No — moves are final" \ 83 "Yes — allow takebacks (undo last move by agreement)" \ 84 | fzf --reverse --border --height=30% --prompt="Takebacks: " \ 85 --header=$' Allow taking moves back this game?') 86 [[ "$sel" == Yes* ]] && ALLOW_TAKEBACK=1 || ALLOW_TAKEBACK=0 87 } 88 89 # _tc_custom: control de tiempo a medida. Pregunta minutos e incremento por 90 # teclado. "read -p" muestra un texto y espera lo que escriban; "</dev/tty" lee 91 # directo del teclado real. 92 _tc_custom() { 93 local mins inc 94 read -r -p " Base minutes per side [5]: " mins </dev/tty 95 read -r -p " Increment seconds per move [0]: " inc </dev/tty 96 # "[[ "$mins" =~ ^[0-9]+$ ]]" chequea que sea un numero. Si no, usa el valor 97 # por defecto (5 minutos / 0 de incremento). 98 [[ "$mins" =~ ^[0-9]+$ ]] || mins=5 99 [[ "$inc" =~ ^[0-9]+$ ]] || inc=0 100 if (( mins == 0 && inc == 0 )); then TC_ENABLED=0; TC_BASE=0; TC_INC=0; return; fi 101 TC_ENABLED=1; TC_BASE=$(( mins * 60 )); TC_INC=$inc # pasamos minutos a segundos 102 } 103 104 # Convertir el control de tiempo a texto (para la red) y de vuelta. 105 tc_encode() { if (( TC_ENABLED )); then printf '%s:%s' "$TC_BASE" "$TC_INC"; else printf 'off'; fi; } 106 tc_decode() { 107 local tok=$1 108 # Si es "off", vacio, o no tiene ":", no hay reloj. 109 if [[ "$tok" == off || -z "$tok" || "$tok" != *:* ]]; then 110 TC_ENABLED=0; TC_BASE=0; TC_INC=0; return 111 fi 112 # "${tok%%:*}" es lo de antes del ":", "${tok##*:}" lo de despues. 113 TC_ENABLED=1; TC_BASE="${tok%%:*}"; TC_INC="${tok##*:}" 114 } 115 116 # _push_state_to_peer: el lider (o el sobreviviente de una caida) le manda al otro 117 # la configuracion + el estado actual. El otro juega el color contrario al nuestro. 118 _push_state_to_peer() { 119 net_send "HELLO host $(tc_encode)" 120 net_send "COLOR $(other_color "$MY_COLOR")" 121 net_send "STATE $(_state_encode)" 122 } 123 124 # _adopt_state_from_peer: el que se conecta espera el COLOR y el STATE que manda el 125 # lider, fija su color, y carga la partida en GAME_*. Devuelve 1 si se corto. 126 _adopt_state_from_peer() { 127 local got_color=0 got_state=0 line 128 # Seguimos esperando hasta tener AMBAS cosas (color y estado). 129 while (( !got_color || !got_state )); do 130 line=$(net_recv) || return 1 131 case "$line" in 132 "COLOR "*) LOCAL_COLOR="${line#COLOR }"; got_color=1 ;; 133 "STATE "*) _state_decode "${line#STATE }"; got_state=1 ;; 134 "HELLO host "*) tc_decode "${line#HELLO host }" ;; 135 esac 136 done 137 return 0 138 } 139 140 # _net_show_ip: imprime la IP de esta maquina en la red (para decirsela al otro). 141 # "hostname -I" lista las IP; "awk '{print $1}'" se queda con la primera. 142 _net_show_ip() { 143 local ip; ip=$(hostname -I 2>/dev/null | awk '{print $1}') 144 [[ -n "$ip" ]] && echo " Your IP: ${ip}" 145 } 146 147 # --- transport_init: preparar la partida segun el modo elegido -------------- 148 # Modos: 149 # hotseat dos personas en la misma compu 150 # ai contra la computadora (Stockfish) 151 # host <puerto> hacemos de servidor (jugamos blancas) 152 # join <host> <puerto> nos conectamos a un servidor (jugamos negras) 153 # server <host> <puerto> jugamos via un relay (el color se negocia) 154 # rejoin <host> <puerto> reconectarse a una partida en curso 155 transport_init() { 156 local mode=$1; shift 157 case "$mode" in 158 hotseat) 159 TRANSPORT_MODE=hotseat 160 LOCAL_COLOR=both 161 _leader_setup # reloj + posicion inicial o cargada 162 ;; 163 ai) 164 TRANSPORT_MODE=ai 165 # Sin el motor instalado no se puede jugar contra la maquina. 166 if ! ai_available; then 167 echo "Stockfish engine not found." >&2 168 echo "Install it with: ./bin/get-stockfish.sh (or: pacman -S stockfish)" >&2 169 exit 1 170 fi 171 ai_pick_color # vos = LOCAL_COLOR, el motor = AI_COLOR 172 ai_pick_level # dificultad -> nivel/Elo + tiempo de pensar 173 _leader_setup # reloj + retrocesos + posicion 174 ai_init || { echo "Could not start the engine." >&2; exit 1; } 175 echo "You are ${LOCAL_COLOR^^} vs Stockfish (${AI_LABEL})." 176 ;; 177 aivai) 178 # Modo espectador: Stockfish juega los dos bandos con el mismo nivel. 179 TRANSPORT_MODE=aivai 180 LOCAL_COLOR=none # ningun bando es "local"; transport_is_local_turn siempre devuelve falso 181 if ! ai_available; then 182 echo "Stockfish engine not found." >&2 183 echo "Install it with: ./bin/get-stockfish.sh (or: pacman -S stockfish)" >&2 184 exit 1 185 fi 186 ai_pick_level 187 ai_pick_spectate_speed 188 _leader_setup 189 ai_init || { echo "Could not start the engine." >&2; exit 1; } 190 echo "AI vs AI — Stockfish (${AI_LABEL}) plays both sides." 191 ;; 192 host) 193 TRANSPORT_MODE=p2p 194 local port=$1 195 NET_ROLE=host; NET_PORT=$port; MY_COLOR=white 196 _leader_setup # el host arma la partida para los dos 197 echo "Waiting for opponent to connect on port ${port}..." 198 # Arrancamos ncat ESCUCHANDO en el puerto, como un coproceso en paralelo. 199 coproc NET { ncat -l "$port"; } 200 NET_R=${NET[0]}; NET_W=${NET[1]} # guardamos los canales de lectura/escritura 201 # net_recv falla (EOF) si ncat no pudo "agarrar" el puerto, casi 202 # siempre porque ya hay otro juego usandolo. NO seguimos de largo hacia 203 # una partida fantasma para una sola persona: avisamos y cortamos. 204 if ! net_recv >/dev/null; then # esperamos el HELLO del que se conecta 205 echo "Could not listen on port ${port} — it may already be in use." >&2 206 echo "Pick another port, or stop the other game first (see: ss -ltnp | grep ${port})." >&2 207 transport_close; exit 1 208 fi 209 LOCAL_COLOR=white 210 _push_state_to_peer 211 echo "Opponent connected. You are WHITE." 212 ;; 213 join) 214 TRANSPORT_MODE=p2p 215 local host=$1 port=$2 216 NET_ROLE=join; NET_HOST=$host; NET_PORT=$port 217 echo "Connecting to ${host}:${port}..." 218 coproc NET { ncat "$host" "$port"; } # ncat CONECTANDOSE al host 219 NET_R=${NET[0]}; NET_W=${NET[1]} 220 net_send "HELLO join" 221 if ! _adopt_state_from_peer; then # recibimos COLOR + STATE del host 222 echo "Could not connect to ${host}:${port}." >&2 223 transport_close; exit 1 224 fi 225 MY_COLOR=$LOCAL_COLOR 226 echo "Connected. You are ${LOCAL_COLOR^^}." 227 ;; 228 rejoin) 229 # Reconectarse a un sobreviviente que esta esperando (ver transport_reconnect). 230 TRANSPORT_MODE=p2p 231 local host=$1 port=$2 232 NET_ROLE=join; NET_HOST=$host; NET_PORT=$port 233 echo "Rejoining game at ${host}:${port}..." 234 coproc NET { ncat "$host" "$port"; } 235 NET_R=${NET[0]}; NET_W=${NET[1]} 236 net_send "HELLO rejoin" 237 if ! _adopt_state_from_peer; then 238 echo "Could not rejoin ${host}:${port}." >&2 239 transport_close; exit 1 240 fi 241 MY_COLOR=$LOCAL_COLOR 242 echo "Rejoined. You are ${LOCAL_COLOR^^}." 243 ;; 244 server) 245 TRANSPORT_MODE=server 246 local host=$1 port=$2 247 NET_ROLE=server; NET_HOST=$host; NET_PORT=$port 248 echo "Connecting to relay ${host}:${port}..." 249 coproc NET { ncat "$host" "$port"; } 250 NET_R=${NET[0]}; NET_W=${NET[1]} 251 _server_handshake 252 MY_COLOR=$LOCAL_COLOR 253 _server_setup # las blancas eligen reloj + estado; las negras reciben 254 echo "Joined game. You are ${LOCAL_COLOR^^}." 255 ;; 256 *) 257 echo "Unknown transport mode: $mode" >&2; return 1 ;; 258 esac 259 } 260 261 # _server_handshake: en modo relay, ningun lado es "el servidor", asi que hay que 262 # negociar quien juega cada color. Cada uno se inventa un numero al azar y lo 263 # anuncia hasta escuchar el del otro; el numero mas alto juega blancas. 264 _server_handshake() { 265 local myid peerid="" line tries=0 266 myid=$(( RANDOM * 32768 + RANDOM )) # un numero grande al azar 267 while [[ -z "$peerid" ]]; do 268 net_send "HELLO $myid" 269 line=$(net_recv_timeout 1) || line="" 270 case "$line" in 271 "HELLO "*) 272 local pid="${line#HELLO }" 273 if [[ "$pid" == "$myid" ]]; then 274 myid=$(( RANDOM * 32768 + RANDOM )) # casualidad: mismo numero, lo cambiamos 275 else 276 peerid="$pid" 277 fi 278 ;; 279 esac 280 # Si pasamos 300 intentos sin respuesta, nos rendimos. 281 (( ++tries > 300 )) && { echo "Handshake timed out." >&2; exit 1; } 282 done 283 # Nos aseguramos de que el otro tambien nos escuche antes de seguir. 284 local i; for i in 1 2 3 4 5; do net_send "HELLO $myid"; done 285 if (( myid > peerid )); then LOCAL_COLOR=white; else LOCAL_COLOR=black; fi 286 } 287 288 # De donde arranca la partida, segun lo que haya pedido ches.sh antes de transport_init 289 # (desde el menu de "resume" o los comandos resume/fen/pgn). Si los tres estan 290 # vacios, es una partida nueva. 291 RESUME_FILE="" 292 RESUME_FEN="" 293 RESUME_PGN="" 294 295 # _leader_setup: arma la partida que va a jugar el lider (el que inicia el hotseat, 296 # el host, o las blancas en el relay): un PGN pegado, un FEN pegado, un archivo 297 # guardado, o si no, una partida nueva con reloj. Llena las variables GAME_*. 298 _leader_setup() { 299 if [[ -n "$RESUME_PGN" ]]; then 300 load_pgn_string "$RESUME_PGN" 301 echo "Started from pasted PGN." 302 elif [[ -n "$RESUME_FEN" ]]; then 303 load_fen_string "$RESUME_FEN" 304 echo "Started from pasted FEN." 305 elif [[ -n "$RESUME_FILE" ]]; then 306 load_game "$RESUME_FILE" # tambien fija el control de tiempo 307 echo "Resumed from ${RESUME_FILE}." 308 else 309 tc_pick 310 tb_pick 311 game_state_fresh 312 fi 313 } 314 315 # _server_setup (relay): las blancas (las decide el handshake) arman la partida y 316 # anuncian el tiempo y el estado; las negras se quedan esperando esos datos. 317 _server_setup() { 318 if [[ "$LOCAL_COLOR" == white ]]; then 319 _leader_setup 320 local i 321 # Lo mandamos 3 veces por las dudas (en un relay se puede perder algun mensaje). 322 for i in 1 2 3; do 323 net_send "TIME $(tc_encode)" 324 net_send "STATE $(_state_encode)" 325 done 326 else 327 local line tok got_time=0 got_state=0 tries=0 328 while (( !got_time || !got_state )); do 329 line=$(net_recv_timeout 2) || { (( ++tries > 120 )) && break; continue; } 330 case "$line" in 331 "TIME "*) read -r _ tok <<<"$line"; tc_decode "$tok"; got_time=1 ;; 332 "STATE "*) _state_decode "${line#STATE }"; got_state=1 ;; 333 esac 334 done 335 fi 336 } 337 338 # transport_is_local_turn: el que tiene que mover, se controla en ESTA compu? 339 # Es verdad en hotseat (manejamos los dos), o si el color que mueve es el nuestro. 340 transport_is_local_turn() { 341 [[ "$LOCAL_COLOR" == both || "$LOCAL_COLOR" == "$TURN" ]] 342 } 343 344 # _negotiate_draw: ofrecer tablas y avisar si el otro acepto (0) o rechazo (1). 345 # En hotseat se le pregunta a la otra persona ahi mismo; en red se manda DRAWOFFER 346 # y se espera el DRAWYES/DRAWNO. La parte de RED vive aca, no en la interfaz. 347 _negotiate_draw() { 348 [[ "$TRANSPORT_MODE" == ai ]] && return 1 # la maquina sigue jugando (la oferta no se ofrece) 349 if [[ "$LOCAL_COLOR" == both ]]; then 350 ui_confirm_draw; return $? # hotseat: preguntamos en esta compu 351 fi 352 net_send "DRAWOFFER" 353 local line 354 while line=$(net_recv); do 355 case "$line" in 356 DRAWYES) return 0 ;; 357 DRAWNO) return 1 ;; 358 esac 359 done 360 return 1 # el otro desaparecio 361 } 362 363 # _negotiate_takeback: pedir un retroceso y avisar si el otro acepto (0) o no (1). 364 # En hotseat (y contra la maquina) se acepta al toque; en red se manda TAKEBACK y 365 # se espera el TBYES/TBNO. 366 _negotiate_takeback() { 367 [[ "$LOCAL_COLOR" == both || "$TRANSPORT_MODE" == ai ]] && return 0 # local: hacelo nomas 368 net_send "TAKEBACK" 369 local line 370 while line=$(net_recv); do 371 case "$line" in 372 TBYES) return 0 ;; 373 TBNO) return 1 ;; 374 esac 375 done 376 return 1 # el otro desaparecio 377 } 378 379 # --- transport_local_move: nuestra jugada (la elegimos y la mandamos) ------- 380 # Pedimos la jugada a la interfaz (ui_pick_move) y la transmitimos. Devuelve la 381 # jugada, o una "palabra de control": RESIGN (rendirse), CLAIM50 (reclamar tablas), 382 # TIMEOUT (se acabo el tiempo), QUIT (salir), DRAWAGREE (tablas acordadas), 383 # TAKEBACK (retroceso). Si rechazan unas tablas o un retroceso, volvemos a pedir 384 # una jugada normal (no se pierde el turno). 385 transport_local_move() { 386 local m 387 while :; do # ":" es un comando que no hace nada; "while :" = bucle infinito 388 m=$(ui_pick_move) 389 case "$m" in 390 "") net_send "RESIGN"; echo "RESIGN"; return ;; # cancelar el menu = rendirse 391 CLAIM50) net_send "DRAW50"; echo "CLAIM50"; return ;; 392 # Se nos acabo el tiempo mientras elegiamos; avisamos al otro asi ve una 393 # victoria por tiempo y no una desconexion a secas. 394 TIMEOUT) net_send "TIMEOUT"; echo "TIMEOUT"; return ;; 395 # Salida prolija (por ejemplo despues de guardar). No es rendicion: le 396 # avisamos al otro para que tambien frene. 397 QUIT) net_send "QUIT"; echo "QUIT"; return ;; 398 DRAWOFFER) if _negotiate_draw; then echo "DRAWAGREE"; return; fi 399 continue ;; # rechazaron -> elegir una jugada de verdad 400 TAKEBACK) if _negotiate_takeback; then echo "TAKEBACK"; return; fi 401 continue ;; # rechazaron -> elegir una jugada de verdad 402 *) net_send "MOVE $m"; echo "$m"; return ;; # una jugada normal 403 esac 404 done 405 } 406 407 # --- transport_recv_move: esperar la jugada del otro (pantalla interactiva) -- 408 # Mientras esperamos, la pantalla sigue VIVA: cada ~1 segundo revisamos la red, 409 # repintamos el reloj, y miramos el teclado (cualquier tecla abre el menu de 410 # espera). Devuelve la jugada o una palabra de control: 411 # RESIGN / CLAIM50 / DRAWAGREE / TAKEBACK / QUIT 412 # TIMEOUT al otro se le acabo el tiempo (solo si pasamos un limite) 413 # RECONNECT se corto la conexion (EOF) -> intentar reconectar solo 414 # RECONNECT-MANUAL la persona declaro que el otro se cayo (sirve para una caida 415 # "dura", que NO genera EOF y por eso no se puede detectar sola) 416 # <deadline> es el tiempo que le queda al otro (+ un margen); se omite si no hay reloj. 417 transport_recv_move() { 418 local deadline_secs=$1 active=$TURN 419 local epoch="" line rc key 420 # Si hay limite, calculamos el momento (en segundos desde 1970, con "date +%s") 421 # en el que se le acabaria el tiempo al otro. 422 [[ -n "$deadline_secs" ]] && epoch=$(( $(date +%s) + deadline_secs )) 423 while :; do 424 # 1. La red: leemos con 1 segundo de limite, asi el bucle no se cuelga y el 425 # reloj y el teclado siguen respondiendo. 426 line=$(net_recv_timeout 1); rc=$? 427 if (( rc == 0 )); then 428 # Llego un mensaje: lo procesamos segun que sea. 429 case "$line" in 430 "MOVE "*) echo "${line#MOVE }"; return ;; 431 RESIGN) echo "RESIGN"; return ;; 432 DRAW50) echo "CLAIM50"; return ;; 433 TIMEOUT) echo "TIMEOUT"; return ;; 434 QUIT) echo "QUIT"; return ;; 435 DRAWOFFER) if ui_confirm_draw; then net_send "DRAWYES"; echo "DRAWAGREE"; return 436 else net_send "DRAWNO"; fi ;; 437 TAKEBACK) if ui_confirm_takeback; then net_send "TBYES"; echo "TAKEBACK"; return 438 else net_send "TBNO"; fi ;; 439 "HELLO "*|"TIME "*|"STATE "*|"COLOR "*) : ;; # ruido suelto del saludo: ignorar 440 esac 441 elif (( rc <= 128 )); then 442 echo "RECONNECT"; return # EOF: el otro cerro / se cayo su programa 443 fi 444 # (rc mayor a 128 = se cumplio el limite de 1 seg: seguimos al reloj y teclado.) 445 446 # 2. El reloj y la caida de bandera (se acabo el tiempo). 447 if [[ -n "$epoch" ]]; then 448 (( $(date +%s) >= epoch )) && { echo "TIMEOUT"; return; } 449 fi 450 _wait_repaint "$active" "$epoch" # repintamos la linea del reloj 451 452 # 3. El teclado: cualquier tecla abre el menu de espera. Es la UNICA forma de 453 # recuperarse de una caida "dura" (que nunca produce un EOF). 454 # "read -t 0.05 -N1" intenta leer 1 tecla con 50ms de paciencia. 455 if read -r -t 0.05 -N1 key </dev/tty 2>/dev/null; then 456 case "$(ui_wait_menu)" in 457 RECONNECT) echo "RECONNECT-MANUAL"; return ;; 458 RESIGN) net_send "RESIGN"; echo "RESIGN"; return ;; 459 QUIT) net_send "QUIT"; echo "QUIT"; return ;; 460 SAVE) _save_prompt ;; # guarda y sigue esperando 461 esac 462 fi 463 done 464 } 465 466 # --- transport_reconnect: volver a armar una conexion que se corto ---------- 467 # Se llama desde el bucle principal. El que sobrevive se vuelve el "punto de 468 # encuentro" (se pone a escuchar) y reenvia el estado real de la partida; el que 469 # vuelve lo adopta. En un cortecito pasajero (auto) donde el host sigue vivo, el 470 # "join" simplemente se reconecta. Devuelve 1 si no lo logra. Imprime el progreso 471 # en la pantalla (corre en el proceso principal, no dentro de un $( )). 472 transport_reconnect() { 473 local mode=$1 474 transport_close 475 stage_live_state # la posicion actual sera lo que reenviemos 476 if [[ "$NET_ROLE" == server ]]; then _reconnect_relay; return $?; fi 477 # Si la persona lo activo a mano, o si somos el host: nos volvemos el punto de 478 # encuentro. (Si se cayo el host, el "join" sobreviviente se asciende a host, 479 # porque el que sobrevive siempre tiene el estado real de la partida.) 480 if [[ "$mode" == manual || "$NET_ROLE" == host ]]; then 481 _reconnect_as_listener 482 else 483 _reconnect_as_dialer # cortecito pasajero, el host sigue escuchando 484 fi 485 } 486 487 # _reconnect_as_listener: nos ponemos a escuchar y le decimos al otro como volver. 488 _reconnect_as_listener() { 489 NET_ROLE=host 490 echo 491 echo " Waiting for the other player to reconnect on port ${NET_PORT}." 492 echo " On their machine, run: ./ches.sh rejoin <your-ip> ${NET_PORT}" 493 _net_show_ip 494 coproc NET { ncat -l "$NET_PORT"; } 495 NET_R=${NET[0]}; NET_W=${NET[1]} 496 net_recv >/dev/null || return 1 # esperamos su HELLO / HELLO rejoin 497 _push_state_to_peer # le reenviamos el estado real 498 echo " Reconnected. Resuming." 499 return 0 500 } 501 502 # _reconnect_as_dialer: reintentamos conectarnos al host (que sigue vivo). 503 _reconnect_as_dialer() { 504 local tries=0 505 echo 506 echo " Connection lost — reconnecting to ${NET_HOST}:${NET_PORT}..." 507 while :; do 508 coproc NET { ncat "$NET_HOST" "$NET_PORT"; } 509 NET_R=${NET[0]}; NET_W=${NET[1]} 510 net_send "HELLO rejoin" 511 if _adopt_state_from_peer; then 512 adopt_game_state; echo " Reconnected."; return 0 513 fi 514 transport_close 515 (( ++tries > 30 )) && return 1 # despues de 30 intentos, nos rendimos 516 sleep 1 # esperamos 1 segundo y reintentamos 517 done 518 } 519 520 # _reconnect_relay: en modo relay, volvemos a marcar al relay (que es el punto de 521 # encuentro), rehacemos el saludo, y el que es blancas reenvia el estado. 522 _reconnect_relay() { 523 echo 524 echo " Connection lost — re-dialing relay ${NET_HOST}:${NET_PORT}..." 525 coproc NET { ncat "$NET_HOST" "$NET_PORT"; } 526 NET_R=${NET[0]}; NET_W=${NET[1]} 527 _server_handshake || return 1 528 if [[ "$MY_COLOR" == white ]]; then 529 _push_state_to_peer 530 else 531 _adopt_state_from_peer && adopt_game_state || return 1 532 fi 533 echo " Reconnected." 534 return 0 535 } 536 537 # transport_close: cerrar las conexiones al terminar. "kill" le manda una senial 538 # al proceso para que termine. Cerramos tanto ncat (la red) como el motor (la IA). 539 transport_close() { 540 [[ -n "$NET_PID" ]] && kill "$NET_PID" 2>/dev/null 541 [[ -n "${AI_PID:-}" ]] && ai_close # apagamos tambien el coproceso del motor 542 NET_R=""; NET_W="" 543 }