Acceder a un terminal mediante SSH inverso

Una de las cosas mas útiles que he deseado tener disponible, es el acceso SSH a mi computadora persona, desde cualquier parte del mundo. He conocido herramientas como ngrok y localtunnel que sirven, mas o menos, para lo que necesito, pero quería intentar con una herramienta que no dependa de servidores externos y ajenos a mi poder, sea porque no quiero compartir mis datos con terceros, o porque simplemente quería aprender a hacerlo por mi mismo.

Luego de mucho investigar, he encontrado algo que existe desde hace mucho tiempo y que probablemente es conocido por todo el mundo, pero recién aprendí a hacerlo por mi cuenta; SSH permite hacer túneles inversos.

Esquemas de control remoto

Desde el periodo de confinación por el COVID-19, todo tipo de servicios se hicieron sumamente populares, como zoom para las conferencias, y AnyDesk para trabajo remoto.

Aunque es de conocimiento común, se puede controlar un equipo utilizando el modelo Cliente-Servidor, donde el cliente se conecta al servidor y el servidor responde a sus peticiones.

Modelo Cliente-Servidor
Modelo Cliente-Servidor

En este modelo, el equipo a controlar debe ser el servidor, es decir, debe mantenerse escuchando para poder responder las peticiones del cliente, es decir, el controlador.

Este modelo es sencillo si hay conexión directa entre el cliente y el servidor, por lo que se puede utilizar la siguiente combinación de programas para el acceso remoto:

  • SSH – SSHd Acceso mediante consola (y montones de trucos mas).
  • RDP/XRDP – Escritorio remoto de Windows/Remina(Linux) Acceso mediante escritorio remoto usando el protocolo RDP de Windows.
  • Vino-Vinagre Acceso mediante VNC que admite múltiples sistemas operativos.

El problema entonces es que todo esto funciona en una red local, pero no a través de internet.

La gran nube

La red de redes
La red de redes

Podríamos definir a internet como la red que une a muchas redes pequeñas mediante un mapa de rutas complejas trazado por algoritmos aun mas complejos de entender.

Inicialmente, los proveedores de internet buscan conectar un solo dispositivo a internet, pero en una casa, esto no es lo ideal. Hay muchos dispositivos y cada día parecen haber mas, por lo que una sola conexión no abastecería.

Para solucionar esto, se crea una pequeña red local mediante routers que guiaran todas las conexiones hacia el exterior.

Es probable que por seguridad el proveedor de internet haya limitado las conexiones del router a solo salientes, por lo que no hay forma de conectarse desde afuera.

También es probable que el mismo proveedor haya creado una infraestructura compleja de subredes anidadas que dificultan aun mas encontrar el camino hacia un equipo en especifico desde el exterior. Casi cualquier dispositivo que no esté en la red local, estará en la misma situación, por lo que la comunicación entre ambos es virtualmente imposible.

La Solución

Internet es una maraña de dispositivos conectados, pero si alejamos lo suficiente nuestra perspectiva, nos daremos cuenta de que en realidad, parece una gigantesca red local.

Esto significa que es posible conectarse a algún nodo que tenga conexión directa y publica a internet, utilizando lo que es llamado IP Pública

Algunos proveedores de internet ofrecen el servicio de IP publica a sus clientes, por lo que ajustando algunas configuraciones en el router, ya se puede acceder desde cualquier parte del mundo a nuestro equipo, pero esto es innecesariamente peligroso, por cuanto internet es un lugar plagado de atacantes buscando vulnerabilidades en cada IP que puedan encontrar.

Para resolver esto y evitarle a los clientes todo tipo de pesadillas, las empresas proveedoras de servicios de control remoto, crearon servidores de conexión inversa, que sigue un modelo de Cliente – Servidor – Cliente.

Modelo Cliente Servidor Cliente
Modelo Cliente Servidor Cliente

Para este ejercicio, vamos a imitar el modelo que utilizan las grandes empresas y crearemos un puente para alcanzar algún equipo de nuestra red local, desde cualquier parte de internet.

Desarrollo de la Solución

Aunque técnicamente se puede realizar con solo dos dispositivos, lo mas común es que se necesiten tres, así que enumeraremos los requisitos a continuación:

  • Dispositivo de destino: Se encuentra aislado en una red local. y es incapaz de recibir conexiones entrantes debido a la configuración de su red y su proveedor de internet, pero puede ejecutar conexiones salientes a cualquier parte de internet.
    • Servidor SSH Puede ser cualquiera, como OpenSSH o Dropbear. Nota importante: el dispositivo debe tener instalado algún servidor SSHd. He hecho pruebas con termux en Android y, aunque tiene uno, su problema esta relacionado con que no permite el login de usuario y contraseña, para lo cual se deben usar otros métodos de autenticación.
    • Cliente SSH Puede ser cualquiera.
  • Servidor: Se encuentra en un lugar accesible de internet y permite conexiones entrantes o salientes libremente. Generalmente es un servidor, pero puede ser cualquier dispositivo mientras tenga una IP pública.
  • Dispositivo de salida: Generalmente está en la misma situación que el dispositivo de destino, pero en otra red. lo importante es que tenga conexión saliente hacia el servidor.

Seguramente ya dispones de los dispositivos de destino y de salida, pero el servidor es algo menos probable, así que, por que no arriesgarse con un droplet de Digital Ocean?

DigitalOcean Referral Badge

Utilizando este enlace puedes crear una cuenta con un crédito inicial de 100 dólares, totalmente sin costo durante 60 días. Úsalo para aprender y practicar con servidores Linux y cuando se acabe el periodo de prueba, puedes comprar Droplets desde 5 dólares mensuales. Recuerda administrar responsablemente tus VPS para evitar sorpresas al final del mes.

Ahora vamos a empezar a aplicar la configuración de los distintos dispositivos implicados:

Dispositivo de Destino

Este dispositivo, que se encuentra atrapado en una red que no permite conexiones entrantes, va a permanecer conectado todo el tiempo al servidor para que el túnel se mantenga vivo. Eso significa que la ventana del terminal tendrá que mantenerse abierta en todo momento o de lo contrario el túnel morirá.

En las aplicaciones de control remoto esto también sucede, pero es mas transparente al usuario debido a que utilizan servicios para mantener vivo el cliente y revivir el túnel cada que sea necesario. En nuestro caso se puede realizar algo similar, enviando la conexión al fondo mediante screen u otras herramientas.

En el dispositivo de destino, hay que escribir lo siguiente

ssh -R 16789:localhost:22 usuario@servidor

donde:

  • ssh Inicia la aplicación de shell segura.
  • -R 16789:localhost:22 vincula un puerto arbitrario(de nuestra elección) en el dispositivo de destino, al puerto 22 del servidor SSHd.
    • 16789 es un puerto arbitrario, es decir, podemos poner cualquiera, siempre que sea un puerto libre en nuestro equipo.
    • localhost o 127.0.0.1 es la dirección local de nuestro dispositivo.
    • 22 es el numero del puerto al que vamos a redirigir la conexión entrante. En este caso, es el puerto de SSHd.
  • usuario@servidor
    • usuario Es el nombre del usuario existente en el servidor al que nos vamos a conectar y utilizar de puente.
    • servidor Es el nombre de dominio o dirección IP del servidor al que nos estamos conectando.

Al ejecutar este comando, se abrirá una conexión con el servidor, que deberás mantener viva tanto como quieras acceder por el túnel inverso. Por supuesto, mientras estas en ese túnel, puedes usar libremente al servidor como si fuera una conexión normal.

Dispositivo de salida

Puede parecer raro comenzar por el dispositivo de salida, pero asumiremos que en el servidor ya está SSHd bien configurado.

En el dispositivo de salida deberás acceder al servidor con una conexión normal de SSH

ssh usuario@servidor

Donde:

  • ssh Es el comando de la consola segura
  • usuario@servidor
    • usuario Es el usuario del servidor
    • servidor Es el nombre de dominio o la dirección IP del servidor

Una vez que haya una sesión iniciada en el servidor, debemos ejecutar este comando y tendremos acceso a la consola del dispositivo de destino.

ssh usuario@localhost -p 16789

Donde:

  • ssh Es el comando de la consola segura.
  • usuario@localhost Es el usuario del dispositivo de destino.
    • usuario es el usuario del dispositivo de destino.
  • -p 16789 Es el puerto que abrimos en el dispositivo de destino.

Si queremos acceder a nuestro dispositivo de destino, hemos de dejar encendido el túnel, pero una falla de la red podría causar que la conexión se cierre. Al no estar cerca del equipo para restaurar la conexión, no podremos revivir el túnel hasta que volvamos presencialmente a restaurarla, así que lo recomendable es utilizar una herramienta que reviva el túnel cada que sea necesario o utilizar un cliente que pueda mantener conexiones persistentes a pesar de los quiebres de red.

Mientras tanto, se pueden utilizar sin problema los demás protocolos que funcionan sobre SSH, como SFTP o SCP por ejemplo.

Eso si, hay que recordar que para conectarse, hay que usar el puerto arbitrario que hemos definido anteriormente.

SFTP:

 sftp -oPort=16789 usuario@localhost

SCP

scp -p -P 16789 /tmp/ssh.txt usuario@localhost:

Hay muchas dudas que me quedan pendientes pero las iré estudiando conforme vaya necesitando mas cosas, pero tener este túnel es algo interesante para practicar.

Bibliografía/Webgrafía

Conexión a servidor mediante túnel inverso ssh Obtenido de https://openwebinars.net/blog/conexion-servidor-mediante-tunel-inverso-ssh/

Túnel inverso. Obtenido de :https://mundo-hackers.weebly.com/tuacutenel-inverso-ssh.html

Creación de túnel inverso. Obtenido de https://campusvirtual.ull.es/ocw/pluginfile.php/2172/mod_resource/content/0/perlexamples/node48.html

Bitácora de desarrollo: Bot Descargar Musica 31/5/22

El articulo a continuación es una bitácora del desarrollo de un bot o plataforma de bots que actualmente tengo funcionando. Se puede acceder usando el siguiente URL: Descargar_Musica

He comenzado esta especie de portafolio con un pequeño bot de Telegram para descargar música desde YouTube. Para esto, he recurrido a las siguientes tecnologías:

Los cambios que he realizado recientemente son muy escasos. Mi conocimiento sobre javascript es relativamente superficial, por lo que he decidido aprender clases, módulos y librerías en javascript. Debido a esto, ahora se han realizado los siguientes cambios:

Creación del directorio data/descargar_musica/descargas

Con el fin de crear un sistema de control para bots unificado, he creado este directorio para que en el futuro, pueda agregar mas directorios de datos para cada bot. En esta situación en especifico, este cambio se hace por la necesidad de asegurar que si hay datos corruptos o incompletos, o un sistema de borrado deficiente, al modificar o borrar los archivos, esto no afecte a otros bots o al propio código fuente.

Creación de las librerias db_logging_system y telegram_logging_system

Con el fin de crear un código mas simple y fácil de mantener, he creado estas librerías universales, que permiten registrar a los nuevos usuarios que utilicen el bot y otra que registre los eventos en un canal distinto.

PD: Sigo sin entender mucho esto, pero ahi logre hacerlo funcionar.

Creación de un vídeo tutorial

Este bot no tiene mayor complicación. Lo único que requiere es que envíen un enlace y comienza a trabajar, sin embargo, siguen mandandole enlaces de spotify e incluso itunes. Si bajar música de estas plataformas fuera legal (y hubiese alguna ganancia aparte de practicar programación) seguro que implementaría una función adecuada, pero por el momento, esto no es asi.

Telegram tiene muchas funciones interesantes y una de mis preferidas son los bots inline. Es posible conseguir la url de un vídeo de youtube, usando el bot @vid y una vez seleccionado el vídeo, mi bot comenzará a trabajar.

Uso de los directorios data/descargar_musica/descargas y data/descargar_musica/info

En la revisión anterior, los vídeos se descargaban en el mismo directorio de ejecución. Sean estos archivos temporales o archivos completos, todos se guardaban sin reparo allí, por lo que si ocurría un error o el sistema caía abruptamente, este directorio que contiene los ejecutables, se llenaba de basura que era difícil de limpiar. Para esto, se ha creado el directorio descargas, dentro de uno asignado al bot en cuestión, donde irán todas las descargas realizadas y las que hayan fallado también.

el directorio info, existe en cambio para guardar datos permanentes que no son parte del código, como el vídeo instructivo mencionado previamente

Otros cambios

  • Se agregaron mas registros para mas eventos (errores, eventos, arranque y comandos)
  • Se limpió el código aplicando las nuevas librerías creadas
  • Se optimizo el tiempo de respuesta del bot
  • Se agregaron nuevos mensajes de respuesta
  • Se agregaron nuevas instrucciones para situaciones no controladas
    • En caso de introducir un enlace no reconocido, se agregó la misma instrucción del comando sobre_mi.
    • En caso de haber un error al descargar, se agregó un mensaje de notificación.
    • En caso de haber un error al convertir el archivo.
    • En caso de haber un error al eliminar los archivos temporales.

Proximos cambios

El bot ya se encuentra en un estado bastante aceptable para lo que queria aprender, pero deseo aprender mas cosas, por lo que espero poder agregar lo siguiente:

  • Registro particular para cada bot (sistema “Panoptic”)
  • Control de depuración por comandos para el bot
  • Control web para el bot
  • Control general para todos los bots en el sistema
  • Mejor registro de información común
  • Mejor registro de información especifica
  • Control personalizado de acuerdo al usuario
  • Integración con otras plataformas de bot (discord, twitch, etc)

Revisando los logs de mi server: w00tw00t Scan

Últimamente he tenido problemas con mis servidores, en cuanto tuve que restaurar a los valores de fabrica mi VPS interlan.dev y ahora tuve que abandonar mi servicio de hosting para interlan.ec, así que mientras descubría como valerme por mi mismo sin depender de un hosting compartido, he visto con curiosidad algunos registros de mi log.

En adelante, seguiré revisando los logs, siempre tienen cosas curiosas puesto que prácticamente el estar en internet es una vulnerabilidad. Aunque no sea un ataque explicito hacia mi server, los escaneos de vulnerabilidades ya cuentan como ataques.

En este caso, me he encontrado con la siguiente línea:

[21/Jun/2016:06:35:55 -0400] “GET /w00tw00t.at.blackhats.romanian.anti-sec:) HTTP/1.1” 400 0 “-” “ZmEu”

En un principio me pareció algo extraño así que me dispuse a investigar. Realmente no hay mucha información en internet sobre esto, pero hay, así que di con el sitio Arnon on Technology donde se explica lo siguiente

Las entradas w00tw00t son creadas por los escáneres de vulnerabilidades “ZmEu” o “DFind” como parte de un intento de ataque “banner grabbing”. Este ataque es una técnica de enumeración y en este caso, el escáner estaba buscando información sobre mi server que pueda revelar posibles “exploits”. El proceso va algo como esto: Un bot, posiblemente una computadora infectada o un servidor proxy, envía una petición “HTTP GET” con una dirección falsificada con la esperanza de que el servidor objetivo, responda con algo de información sobre sus configuraciones. En mi caso, “Nginx” determino que la petición “HTTP” estaba malformada de alguna forma, así que la rechazo con un código de error “400 Bad Request”. Al parecer, la petición carecía del encabezado “Host”, con la esperanza de que mi servidor rellene esta información, o sirva alguna otra que revele mas datos.

Aron on Technology

Habiendo visto que mi servidor también respondió con un “400 Bad Request” pude respirar tranquilo, sabiendo que al menos esta vez, mis configuraciones me han mantenido a salvo.

Hace bastante tiempo que no oía de los escáneres de vulnerabilidad, así que he investigado un poco. En la Wikipedia he encontrado esto, aunque solo en inglés:

“ZmEu” es un escáner de vulnerabilidades que busca servidores web que estén abiertos a ataques a través de “phpMyAdmin”. También intenta adivinar contraseñas “ssh” a través de métodos de fuerza bruta y deja un “backdoor” persistente. Fue desarrollado en Romania y fue especialmente común en el año 2012

Wikipedia

No quisiera tener que estar cambiando las contraseñas a cada rato, así que el siguiente paso seria agregar un “fail2ban” o usar archivos de llaves para “ssh”.

Siempre me pareció raro que reciba tantos ataques siendo que mi servidor, aparte de nuevo, nunca ha sido publicitado en ningún lado, pero sabiendo que es un escáner (muchos escáneres), tiene sentido que hayan llegado. Aunque seria genial si me dejaran en paz, tendré que ir viendo como reforzar la seguridad.

Actualización del estado del bot de descargar música

No he tenido tiempo para hacer nuevas modificaciones al bot, pero por curiosidad revise las pocas estadísticas que he logrado recopilar. Son justamente estas estadísticas las que me disuaden de hacer mejoras, puesto que implican mucho esfuerzo adicional.

  • Cantidad de usuarios únicos: 580
  • Cantidad de nuevos usuarios por mes: 145
  • Cantidad de respuestas exitosas : 675
  • Cantidad de consultas de información: 45
  • Personas a las que invite personalmente: 3
  • Cantidad de URL correctas: 843
  • Cantidad de solicitudes malformadas: 348
  • Mensajes de spam: 2
  • Usuarios que escribieron mas de un mensaje: 320
  • Usuarios intensivos (mas de 10 mensajes): 30
  • Usuario que mas usa el bot: yo <3

Entre estos datos hay algunas cosas interesantes y desconcertantes. Por ejemplo, siendo que solo he invitado a tres personas, ¿De donde salieron esos 580 usuarios en apenas tres meses?

También es interesante notar que, a pesar de las instrucciones, hay cerca de 348 mensajes que no cumplen los requisitos para funcionar el bot. Muchos de usuarios, luego de un solo intento fallido, no vuelven a usar el bot nunca mas.

Además, se supone que un bot es una maquina que no es atendida a menos que requiera de cierto mantenimiento, dos de esos mensajes fueron intentos de estafa, donde me pedían depósitos a cierto numero de cuenta bancaria.

Después de ver estas cosas, he notado que hacen falta las siguientes cosas, que podrían ser mejoras inmediatas para el bot:

  • Mensajes variables: Actualmente los mensajes del bot están Hard Coded, por lo que para actualizar los mensajes, es necesario modificar el código del bot, para lo cual es necesario detenerlo y reiniciarlo, causando que usuarios poco pacientes pierdan el interés en el.
  • Sistemas de Login: Aunque he creado un sistema que hace que el bot me reporte mediante mensajes en un canal de Telegram, no se guarda nada en la base de datos, razón por la que no puedo obtener unas estadísticas confiables de los eventos que implican a mi bot.
  • Panel de control: Realmente no se si los otros bots lo tengan, todos los que he usado dependen únicamente de la interfaz de chat de Telegram, pero una interfaz web para el administrador suena genial, podría intentarlo.

El Voltaje de salida del modulo regulador cn2596-2 es igual al de entrada

cn2596-2
cn2596-2

He tenido este pequeñín durante algunos años dando vueltas por ahí, sin uso, debido a que al conectarlo, el voltaje de salida es igual al voltaje de entrada, por lo que siempre pensé que su uso no seria diferente de un cable, aun así, lo he conservado y he dedicado algo de tiempo a investigar su funcionamiento, hasta que finalmente encontré la respuesta en un video ruso que obviamente no logro entender, pero me hizo dar cuenta del problema.

Usando un multímetro midiendo los pines de salida, hay que girar el potenciómetro en sentido contrario a las manecillas del reloj hasta que el voltaje medido comience a cambiar.

Esto causara que el voltaje de salida medido por el aparato también cambie y se desajuste, por lo que por ahora, no confiaría en ese resultado, debido a que no es el dado por las pruebas.

Pongo las especificaciones técnicas y unas cuantas fotos de una tienda random donde lo encontré:

  1. Voltaje de entrada: 4-40 v (40 v máximo voltaje)
  2. Voltaje de salida: 1.25V – 37V (En el modo de descomprensión, la entrada debe ser superior a los dos voltios)
  3. Corriente de salida: 2 A salida continua.
  4. Potencia de salida: Máximo 15 w.
  5. Frecuencia: 150 KHZ.
  6. Pico de onda: 100 mv.
  7. Precisión del voltímetro: ± 5 ‰
  8. Potencia estática: 20 ma (desviación por voltaje de entrada, salida y display)
  9. Peso: 33g.10.
  10. Tamaño: 7cm*4cm.
  11. Con la función de display de voltaje, la precisión es de ± 0.05V, y el rango es de 0 – 40V.( Nota: para asegurar la máxima precisión, el voltaje de entrada debe ser superior a 4V)
  12. Botón para cambiar la medida del voltaje de entrada o salida y un led que indica el actual modo de medición, las configuraciones no se pierden al desconectar la alimentación
  13. Voltaje continuo ajustable de salida en un rango de 1.25V -37V, 4-40V de entrada. El voltaje de entrada debe ser superior a 1.5V)
  14. Oscilador de alta eficiencia ajustado a 150 kHz f
  15. Garantizado 3A de carga de corriente de salida.
  16. Protección por temperatura y limite de corriente.

Bibliografia/Webgrafia

Datasheet del modulo lm2596-d:

El video ruso que me sirvió:

Próximo material de estudio:

https://www.instructables.com/The-Introduction-of-LM2596-Step-Down-Power-Module-/

Notas adicionales

Yo compre este aparato en Banggood hace algunos años, pero no estoy seguro de si aun lo venden. Como mi pais tiene problemas de comercio exterior, banggood ya no manda casi nada aquí, y encima me bloquea los productos que sabe no me puede enviar.

Me interesa también la idea de hackearlo, pero por ahora me conformare con que pueda regular los voltajes de salida de forma correcta.

https://hackaday.io/project/19647-low-cost-programmable-power-supply/details

Desarrollo de Bots: Descargar_musica

El articulo a continuación es una bitácora del desarrollo de un bot o plataforma de bots que actualmente tengo funcionando. Para poder acceder a el se puede usar la siguiente URL: Descargar_Musica

Para comenzar una especie de portafolio, he creado un pequeño bot de Telegram para descargar musica desde YouTube. Para esto, he recurrido a las siguientes tecnologías:

Para comenzar, hacer un bot no es una labor especialmente compleja, la mayoría de los tutoriales hacen un buen trabajo explicando los fundamentos, pero el propósito de hacer un bot no se queda en solo esas cosas.

Con el tiempo me di cuenta de que el modelo inicial realmente era complicado de escalar, por lo tanto, presentaré mi primer propuesta, hecha a punta de tutoriales y extendiéndolo según voy descubriendo las necesidades.

Primeras aproximaciones

El getting started de Grammy es muy sencillo de usar y es mi primer acercamiento, pero necesitaba otros elementos para poder realizar descargas de youtube, asi que investigue mas herramientas:

  • Herramienta para descargar videos de youtube: Youtube-dl
  • Herramienta para convertir videos a mp3: FFmpeg

En este momento, me di cuenta de que tenia un problema entre manos; al menos dos de esas herramientas no son nativas para nodejs.

Por el momento el bot debe quedarse en un simple "hola mundo" del getting started.

Fuera de este inconveniente, el bot tiene que realizar las siguientes tareas:

  • Oír todos los mensajes entrantes y responder según los siguientes patrones:
    • Comando start: El usuario descubre el bot de alguna manera (enlace compartido o encontrado en la búsqueda global de Telegram)
    • Comando sobre mi: El usuario siente interés por el bot y busca mas información sobre el bot y el autor.
    • Detección de URL: El bot debe poder distinguir las URL que el usuario ingrese de los comentarios y de cualquier otro tipo de mensajes
    • Cualquier otro mensaje: Si no se cumple ninguna circunstancia anterior, se responde con algún tipo de consejo

Aparte de estas funciones básicas, el bot también debe poder realizar las siguientes funciones:

  • Almacenar el id y el nombre de los nuevos usuarios
  • Almacenar los mensajes recibidos para posterior estadísticas
  • Almacenar un registro de eventos.

Y por ultimo, debe haber una plataforma de Logging, que aun tengo que decidir como funciona.

Por el momento, dejo esta bitácora hasta aquí, esperando pronto hacer mas avances.

El juego de la vida. Una reescritura en vainilla JavaScript

Tabla de contenidos

Introducción

Hace mucho tiempo, antes de intentar tomarme en serio la programación, publique un post en mi blog sobre el juego de la vida. Habiendo mejorado mucho mis capacidades para leer código y hacer muchas cosas mas, decidí retomar mi blog para llenarlo con nuevos contenidos que ayuden a otras personas a las que les interesen las mismas cosas que a mi.

Mientras exploraba mis escasos aportes a la red, me topé con el post original que hice sobre el juego de la vida. Estaba completamente en ruinas en cuanto a estar lleno de links rotos, imágenes faltantes y contenidos desactualizados.

Desempolvando un poco, elimine los links rotos puesto que no llevaban a ningún lado. Las imágenes las tuve que eliminar porque los hostings que las contenían habían muerto o las habían removido y ni siquiera recuerdo de que iban (aprendí la lección, debo controlar que las imágenes que copio se alojen en mi propio servidor para evitar estas cosas, pero tampoco olvidare citar las fuentes originales).

Es bastante increíble que la primera vez que revise este algoritmo no haya validado si funcionaba o no, que tecnologías usaba o siquiera si el sitio continuaba vivo. Para ser un post del 2020, todos los links eran del año 2011 por lo que no es raro encontrar problemas como cosas desaparecidas.

Título: El sitio de Pulgarín esta muerto desde quien sabe cuando El sitio de Pulgarín esta muerto desde quien sabe cuando

Fuente: Elaboración propia

Justificación del trabajo

Realmente este trabajo es mas para pulir mis habilidades leyendo código ajeno, entendiendo como funciona y reescribiéndolo en una forma mejorada y funcional, pero también es un pequeño aporte en español para internet, puesto que es justo allí donde aprendí a programar.

Quien desee explorar mi código, puede hacerlo libremente. También publicaré en enlace de GitHub donde pueden descargar el proyecto completo y si lo desean, pueden citarme cuando realicen sus practicas.

Conforme he avanzado en el estudio de este código, también he aprovechado para aprender nuevas cosas. Por ejemplo, en este articulo también se explicará el uso de JSDoc, comentarios para JSDoc, aplicaciones web progresivas, deducción de herramientas de trabajo mediante la lectura de código incompleto y mas. Espero que este trabajo sea de ayuda para aquellos que estén comenzando en el mundo de la programación.

Contexto y estado del arte

El Juego de la vida es un autómata celular diseñado por el matemático británico John Horton Conway en 1970. Hizo su primera aparición pública en el número de octubre de 1970 de la revista Scientific American, en la columna de juegos matemáticos de Martin Gardner. Desde un punto de vista teórico, es interesante porque es equivalente a una máquina universal de Turing, es decir, todo lo que se puede computar algorítmicamente se puede computar en el juego de la vida.

Desde su publicación, ha atraído mucho interés debido a la gran variabilidad de la evolución de los patrones. Se considera que el Juego de la vida es un buen ejemplo de emergencia y autoorganización. Es interesante para científicos, matemáticos, economistas y otros observar cómo patrones complejos pueden provenir de la implementación de reglas muy sencillas. (Wikipedia,2021)

Las reglas del juego de la vida son sencillas:

  • Una célula viva con menos de dos vecinos vivos se muere (de soledad)
  • Una con dos o tres vecinos vivos sobrevive
  • Una con más de tres vecinos vivos se muere (por sobrepoblación)
  • Una célula muerta con exactamente tres vecinos vivos, nace (por reproducción)

Estas simplicidades de las reglas hacen que básicamente sea el juego mas fácil de programar que se me ocurre.

A pesar de que es algo que se conoce desde mas de medio siglo, me propuse buscar la implementación mas simple posible para poder aprender y entender como funciona este juego a nivel de programación. para esto, he localizado las siguientes implementaciones:

Pulgarin.co

La implementación mas sencilla que he encontrado. Pero debido a que es algo ya muy viejo, se ha perdido el código html y las dependencias jquery de las que depende.

pmav.eu

javascript game of life

Es tal vez una de las implementaciones mas extensas que he encontrado, pero muy compleja para entender como principiante. También es una implementación muy antigua, de alrededor del año 2008 o 2010. Al menos la pagina sigue viva.

javascript.plainenglish.io

the game of life using javascript

Tal vez la mejor documentada de todas las implementaciones que he visto. Una lastima no haberla visto antes de comenzar este proyecto, pero al menos aprendí a hacerlo por mi cuenta.

Desarrollo

Es raro que un código de 329 líneas de código requiera tanta atención, pero he de abarcar todas estas cosas en artículos distintos que enlazo a continuación.

Análisis de la situación

Análisis de situación

Como deducir la tecnología utilizada en el código original

Inicialización de un proyecto con npm init

uso del comando npm init y configuración de las opciones del proyecto

Uso de clases en JavaScript

razones para utilizar clases en javascript, como se crean, inicializan y razón para usarlas al reescribir este código

Implementación de un sistema de inicialización

forma en la que se inicializan las clases, variables dentro de las clases y funciones que llaman métodos en clases

Implementación de un sistema de dibujado

Estudio de la clase gráficos, como funciona en lienzo canvas y la variable de contexto (ctx)

funciones de dibujado y como se reciclan

Implantación de un sistema de funciones publicas

Funciones publicas que realizan tareas adicionales

Uso de comentarios para jsdoc

Comentarios en jsdoc, como pueden ayudar a la documentación de un código y como se ven en Visual Studio Code.

Compilando en JSDoc

Modificación del código de package.json para agregar comandos de jsdoc

opciones de configuración, y parámetros adicionales

Service workers y PWA

Web responsiva

Etiquetas que permiten a la pagina web adaptarse a los tamaños de pantalla

Readme.md y otros archivos de documentación

Conclusiones y trabajos futuros

Estoy bastante sorprendido de todo lo que se puede hacer con algo tan simple que se puede resumir en cuatro reglas. pero parece que queda mucho mas.

al juego le hacen falta muchas cosas, pero las que he visto hasta el momento son las siguientes:

  • Debería poder modificar el tamaño del lienzo

Al leer el código se pueden encontrar algunos comentarios que he dejado, que demuestran que al menos lo he intentado.

Me gustaría que el código permita crear un lienzo del ancho de la pantalla, pero eso tal vez haga el código muy extenso para mi propósito original.

He intentado utilizando algo de CSS, pero el resultado es que, si bien el lienzo se adapta al ancho de la pantalla, se pierde precisión del click, haciendo que solo un cuarto de la pantalla sea preciso y el resto no detecte el click en las partes que corresponde.

  • Debería permitir arrastrar y soltar para dibujar en el lienzo

Curiosamente es una función que se encuentra en el código original. El problema es que no logre implementarla sin que se rompa todo. Tengo que estudiar mas para saber como hacerlo.

  • Hay bugs extraños, muy extraños

Aunque he creado una función para iniciar y otra para parar la ejecución del temporizador para procesar las nuevas generaciones, en algunos casos y por alguna razón, es imposible detener el temporizador después de funcionar correctamente antes. Es un problema extraño que pensé que se solucionaría si controlaba las veces que se puede usar la función iniciar. Tal vez deba agregar un changelog y un control de versiones, quien sabe.

  • Funciones adicionales

Una vez terminado el proyecto me di cuenta de que seria genial que haya mas funciones, pero tal vez dejaría de ser didáctico si hago eso. Aun así destaco lo siguiente:

  1. Seria genial si se pudiera generar un arreglo aleatorio para iniciar
  2. Me gustaría agregar una lista de arreglos prediseñados para poder ver como funcionan los diferentes patrones ya descubiertos.
  3. Me gustaría que se puedan configurar los colores
  4. ¡Agregar publicidad! Bueno, es algo que siempre quise aprender a hacer y muchos de los códigos que he encontrado la agregan.
  5. ¿Comentarios y sugerencias? Tal vez es muy optimista agregar un elemento así en una aplicación super simple, mas aun sabiendo que no creo que reciba algún comentario en mi propio blog.

Bibliografía/webgrafía

Code katas, la practica hace al maestro(2011).Obtenido de www.codeandbeyond.org

El juego de la vida(2011), obtenido de www.pulgarin.co (enlace roto)

El juego de la vida(2021), obtenido de es.wikipedia.org

El juego de la vida (2008-2010), obtenido de pmav

the game of life using javascript (2020), javascript.plainenglish.io

El juego de la vida en JavaScript

Programando el Juego de la vida en javascript

Actualización: Este post esta muy desactualizado, de hecho los contenidos citados aquí están desactualizados desde antes de escribir este post, al punto de que he tenido que hacer un poco de minería de código para entender como funciona y realizar una reescritura completa en vainilla JavaScript. Todo el proyecto de reescritura se encuentra documentado en este post.

He localizado el lugar de donde extraje este texto, el cual también copia de algún otro lugar que seguro también esta caído. Tomare algunas ideas de allí para cuando haga nuevos post, pero la mayoría de mi contenido tratará de ser mas original.

El juego de la vida en HTML5 usando Canvas

El juego de la vida de Conway (también conocido simplemente como “Life”) es un ejemplo clásicos de Autómatas Celulares creado por John Horton Conway en los 70.

Consiste en una grilla de puntos (el universo) donde cada punto puede contener un individuo o célula (un punto de la grilla que está encendido o vivo (los puntos tienen estado binario: vivos o muertos).

El juego funciona sólo (se lo conoce como un juego de cero jugadores), y lo único que se puede hacer es preparar el estado inicial y luego echarlo a correr. La corrida involucra generaciones, o pasadas por la grilla completa para analizar el estado y calcular el de la siguiente pasada. El cálculo se hace analizando para cada punto su estado y el de los ocho que lo rodean (sus vecinos):

Para determinar si la célula analizada vive o muere, se aplican las siguientes reglas:

  • Una célula viva con menos de dos vecinos vivos se muere (de soledad)
  • Una con dos o tres vecinos vivos sobrevive
  • Una con más de tres vecinos vivos se muere (por sobrepoblación)
  • Una célula muerta con exactamente tres vecinos vivos, nace (por reproducción)

La grilla debe considerarse como un toroide, o sea que los puntos de la última fila continúan en la superior, y los de la última columna derecha continúan en la primera de la derecha y viceversa.

Parece sencillo, pero ha sido estudiado durante todos estos años no solamente como un interesante Code Kata, sino como un interesante ejercicio de simulación de ecosistemas.

Como no hay mejor manera de entenderlo que verlo en acción, les recomiendo ver esta versión implementada en HTML 5, utilizando el tag Canvas.

Cosas interesantes que ocurren con este juego es que espontáneamente aparecen configuraciones estables (que permanecen constantes durante muchas generaciones, variando entre un número determinado de estados. Es un interesante ejemplo de comportamiento emergente, y aunque parezca un oximorón, un ejemplo sencillo de sistemas complejos.

Por supuesto, una de las cosas muy interesantes de ver es código fuente. Son sólo 234 líneas de JavaScript y aunque parece un poco largo para un post, creo que vale la pena pegarlo completo, aunque recomiendo que si alguien lo quiere tocar baje siempre la última versión disponible en GitHub.

/*
 * Copyright 2011 Julian Pulgarin 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var Point = function(x, y) {
    this.x = x;
    this.y = y;
};

var graphics = function() {
    var canvas;
    var ctx;
    var canvasId;

    var cellSize = 10; // pixels
    var onColour = 'rgb(0, 200, 0)';
    var offColour = 'rgb(200, 0, 0)';
    var gridColour = 'rgb(50, 50, 50)';

    var initCanvas = function(canvasId) {
        this.canvas = $(canvasId).get(0);
        this.ctx = this.canvas.getContext('2d'); 
        this.canvasId = canvasId;
    }

    var drawCell = function(x, y, alive) {
        var g = graphics;
        g.ctx.fillStyle = (alive)? onColour : offColour;
        g.ctx.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 1, cellSize - 1);
    }

    var handleMouse = function(e) {
        var l = life;
        var g = graphics;
        var that = this;
        var cell = getCellPointUnderMouse(e);
        var state;
        processCell(cell);
        $(g.canvasId).mousemove(function(e) {
            cell = getCellPointUnderMouse(e);
            processCell(cell);
        });
        function getCellPointUnderMouse(e) {
            return new Point((e.pageX - that.offsetLeft) / g.cellSize | 0, ((e.pageY - that.offsetTop) / g.cellSize) | 0);
        }
        function processCell(cell) {
            var x = cell.x;
            var y = cell.y;
            if (x > l.xCells - 1 || y > l.yCells - 1) {
                return;
            }
            if (typeof state == 'undefined')
            {
                state = !l.prev[x][y];
            } 
            l.prev[x][y] = state;
            drawCell(x, y, state);
            // TODO: Consider setting next as well
        }
    }

    function paint() {
        var g = graphics;
        var l = life;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                g.drawCell(x, y, l.prev[x][y]);
            }
        }
    }

    return {
        canvas: canvas,
        ctx: ctx,
        canvasId: canvasId,
        cellSize: cellSize,
        onColour: onColour,
        offColour: offColour,
        gridColour: gridColour,
        initCanvas: initCanvas,
        drawCell: drawCell,
        handleMouse: handleMouse,
        paint: paint,
    }
}(); 

var life = function() { 

    var yCells; 
    var xCells;
    var prev = []; // previous generation
    var next = []; // next generation

    var _timeout;
    var _alive = false;

    var initUniverse = function(canvasId) {
        var l = life;
        var g = graphics;
        g.initCanvas(canvasId);
        l.xCells = ((g.canvas.width - 1) / g.cellSize) | 0;
        l.yCells = ((g.canvas.height - 1) / g.cellSize) | 0; 
        g.ctx.fillStyle = g.offColour;
        g.ctx.fillRect(0, 0, l.xCells * g.cellSize, l.yCells * g.cellSize);
        g.ctx.fillStyle = g.gridColour;

        for (var x = 0; x < l.xCells; x++) {
            l.prev[x] = [];
            l.next[x] = [];
            g.ctx.fillRect(x * g.cellSize, 0, 1, l.yCells * g.cellSize);
            for(var y = 0; y < l.yCells; y++)
            {
                l.prev[x][y] = false;
            }
        }
        g.ctx.fillRect(l.xCells * g.cellSize, 0, 1, l.yCells * g.cellSize);
        for(var y = 0; y < l.yCells; y++)
        {
            g.ctx.fillRect(0, y * g.cellSize, l.xCells * g.cellSize, 1);
        }
        g.ctx.fillRect(0, l.yCells * g.cellSize, l.xCells * g.cellSize, 1);
        $(canvasId).mousedown(g.handleMouse);
        $('body').mouseup(function(e)
        {
            $(g.canvasId).unbind('mousemove');
        });
    }

    var nextGen = function() {
        var l = life;
        var g = graphics;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.next[x][y] = l.prev[x][y];
            }
        }

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                count = _neighbourCount(x, y);

                // Game of Life rules
                if (prev[x][y]) {
                    if (count < 2 || count > 3) {
                        next[x][y] = false;
                    }
                } else if (count == 3) {
                    next[x][y] = true;
                } 
            }
        }

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.prev[x][y] = l.next[x][y];
            }
        }

        g.paint();
    }

    var toggleLife = function() {
        var l = life;

        if (!l._alive) {
            l._alive = true;
            l._timeout = setInterval("life.nextGen()", 100);
        } else {
            l._alive = false;
            clearInterval(l._timeout);
        }
    }

    var clear = function() {
        var l = life;
        var g = graphics;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.prev[x][y] = false;
            }
        }
        g.paint();
    }

    var _neighbourCount = function(x, y) {
        var l = life;
        var count = 0;
        var neighbours = [
            l.prev[x][(y - 1 + l.yCells) % l.yCells],
            l.prev[(x + 1 + l.xCells) % l.xCells][(y - 1 + l.yCells) % l.yCells],
            l.prev[(x + 1 + l.xCells) % l.xCells][y],
            l.prev[(x + 1 + l.xCells) % l.xCells][(y + 1 + l.yCells) % l.yCells],
            l.prev[x][(y + 1 + l.yCells) % l.yCells],
            l.prev[(x - 1 + l.xCells) % l.xCells][(y + 1 + l.yCells) % l.yCells],
            l.prev[(x - 1 + l.xCells) % l.xCells][y],
            l.prev[(x - 1 + l.xCells) % l.xCells][(y - 1 + l.yCells) % l.yCells],
        ];

        for (var i = 0; i < neighbours.length; i++) {
            if (neighbours[i]) {
                count++;
            }
        }
             
        return count;
    }

    return {
        yCells: yCells,
        xCells: xCells,
        prev: prev,
        next: next,
        initUniverse: initUniverse,
        nextGen: nextGen,
        toggleLife: toggleLife,
        clear: clear,
    }
}();

Primeras Pruebas con Solidoodle Press

La impresión 3D es una tecnología relativamente reciente, pero que ha alcanzado el mercado domestico gracias a iniciativas como el proyecto RepRap y Fab@Home sin embargo aun es muy experimental.

He conseguido una impresora básica, de marca Solidoodle y he comenzado a hacer algunos experimentos. Dada mi falta de experiencia los resultados son malos pero la meta es hacerla rendir.

Aquí algunos ejemplos de fallos.

El principal problema con esta impresora es la obstrucción del extrusor.

A pesar de las terribles reseñas que tiene esta impresora, en realidad no he tenido problemas tan serios o he sabido solventarlos. Debido a esto creo que publicare cada cierto tiempo como resolver los problemas que se presenten para que los visitantes puedan ver que hacer en caso de emergencia.

Los componentes de esta impresora no difieren mucho de los de otras, las piezas son intercambiables y a pesar de la caja que la contiene, es fácil llegar al extrusor en caso de problemas. Diria que su mayor defecto es el programa que viene por defecto que generalmente se puede arreglar cerrándolo y volviéndolo a abrir.

Es interesante que a pesar de los defectos que tiene, es bastante precisa con las piezas, pero de nuevo, el mayor problema que tiene es su software. Aquí unos ejemplos de piezas en mejor estado, ya solventado el problema del extrusor.

Aunque claro, todo requiere de prueba y error para funcionar.

Por ahora seguiré probando y en otros post mostrare progresos.

A %d blogueros les gusta esto: