commit ce8262cafbbe88fc9140c0d283ad0b900bdd2d5d Author: luisgulo Date: Tue Apr 28 13:24:44 2026 +0200 Subida inicial. Chequeada diff --git a/README.md b/README.md new file mode 100644 index 0000000..2082c34 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# Codificador visual de Morse + +Este programa está realizado en python. + +## Dependencias e instalación + +El programa unicamente tiene como dependencia `Pygame` y lo puedes instalar de la siguiente forma: + +1. Sistemas basados en Debian + +Si usas **Debian**, Ubuntu, Linux Mint o Raspberry Pi debes usar el gestor +de paquetes para evitar conflictos: + +```bash +sudo apt update +sudo apt -y install python3-pygame +``` + +2. Otros Sistemas Operativos + +Si usas Windows, macOS u otras distros tienes que instalar las dependencias +mediante el comando `pip` + +``` +pip install pygame +``` + +O usar el fichero requeriments.txt para resolver dependencias: + +``` +pip install -r requeriments.txt +``` + +## Ejecutar el programa + +``` +python morse.py +``` + +Captura de programa tras *codificar* en morse "SoloConLinux": + +![](morse.png) + + +Al finalizar la ejecución se muestra el texto y el código morse: + +```bash + +$ python morse.py + +======================================== +CODIFICADOR MORSE +TEXTO: SOLOCONLINUX +MORSE: ... --- ... --- ... --- ... ... --- .-.. --- -.-. --- -. .-.. .. -. ..- -..- +======================================== +``` + + + + + diff --git a/morse.png b/morse.png new file mode 100644 index 0000000..2a06968 Binary files /dev/null and b/morse.png differ diff --git a/morse.py b/morse.py new file mode 100755 index 0000000..108534c --- /dev/null +++ b/morse.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +import os +import warnings + +# Ignorar el aviso de AVX2 y otros avisos de ejecución +warnings.filterwarnings("ignore", category=RuntimeWarning) +# Silenciar el saludo de "Hello from the pygame community" +os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" + +import pygame +import sys + +# Inicialización +pygame.init() + +# Configuración de ventana +ANCHO, ALTO = 1000, 850 +ventana = pygame.display.set_mode((ANCHO, ALTO)) +pygame.display.set_caption("Codificador Morse - SoloConLinux") + +# Colores +COLOR_FONDO = (245, 245, 245) +COLOR_PUNTO = (70, 70, 160) +COLOR_RAYA = (180, 50, 50) +COLOR_LINEA = (210, 210, 210) +COLOR_RESALTE = (0, 200, 0) +COLOR_TEXTO = (20, 20, 20) +COLOR_BARRA = (40, 40, 40) +COLOR_LEYENDA = (220, 220, 220) +COLOR_HISTORIAL = (50, 50, 100) + +# Fuentes +fuente_nodos = pygame.font.SysFont("Arial", 20, bold=True) +fuente_leyenda = pygame.font.SysFont("Monospace", 16) +fuente_preview = pygame.font.SysFont("Arial", 40, bold=True) +fuente_mensaje = pygame.font.SysFont("Courier New", 36, bold=True) + +class NodoMorse: + def __init__(self, letra, x, y, es_raya=False): + self.letra = letra + self.x = x + self.y = y + self.es_raya = es_raya + self.punto = None + self.raya = None + self.padre = None + + def dibujar_lineas(self, superficie, nodo_actual): + es_camino_activo = self.es_parte_del_camino(nodo_actual) + color = COLOR_RESALTE if es_camino_activo else COLOR_LINEA + ancho = 4 if es_camino_activo else 2 + + if self.punto: + pygame.draw.line(superficie, color, (self.x, self.y), (self.punto.x, self.punto.y), ancho) + self.punto.dibujar_lineas(superficie, nodo_actual) + if self.raya: + pygame.draw.line(superficie, color, (self.x, self.y), (self.raya.x, self.raya.y), ancho) + self.raya.dibujar_lineas(superficie, nodo_actual) + + def dibujar_nodo(self, superficie, nodo_actual): + activo = (self == nodo_actual) + color = COLOR_RESALTE if activo else (COLOR_RAYA if self.es_raya else COLOR_PUNTO) + + if self.es_raya: + pygame.draw.rect(superficie, color, (self.x - 22, self.y - 11, 44, 22)) + else: + pygame.draw.circle(superficie, color, (self.x, self.y), 16) + + if self.letra: + color_letra = (255, 255, 255) if activo else COLOR_TEXTO + # Ajustar texto si no tiene fuente Arial instalada + # Generamos la superficie del texto + texto_surf = fuente_nodos.render(self.letra, True, color_letra) + + # Creamos un rectángulo con el tamaño del texto y lo centramos en (self.x, self.y) + texto_rect = texto_surf.get_rect(center=(self.x, self.y)) + + # Dibujamos la superficie en las coordenadas del rectángulo calculado + superficie.blit(texto_surf, texto_rect) + + def es_parte_del_camino(self, destino): + temp = destino + while temp is not None: + if temp == self: return True + temp = temp.padre + return False + +# --- CONSTRUCCIÓN DEL ÁRBOL --- +nodos_lista = [] +def crear_nodo(letra, x, y, es_raya=False): + nuevo = NodoMorse(letra, x, y, es_raya) + nodos_lista.append(nuevo) + return nuevo + +raiz = crear_nodo("", 500, 150) # Bajado un poco para dar espacio al texto arriba +# Derecha (Puntos) +e = crear_nodo("E", 700, 250); e.padre = raiz; raiz.punto = e +i = crear_nodo("I", 800, 250); i.padre = e; e.punto = i +s = crear_nodo("S", 900, 250); s.padre = i; i.punto = s +h = crear_nodo("H", 970, 250); h.padre = s; s.punto = h +v = crear_nodo("V", 900, 330, True); v.padre = s; s.raya = v +u = crear_nodo("U", 800, 330, True); u.padre = i; i.raya = u +f = crear_nodo("F", 800, 410); f.padre = u; u.punto = f +a = crear_nodo("A", 700, 450, True); a.padre = e; e.raya = a +r = crear_nodo("R", 800, 530); r.padre = a; a.punto = r +l = crear_nodo("L", 900, 530); l.padre = r; r.punto = l +w = crear_nodo("W", 700, 610, True); w.padre = a; a.raya = w +p = crear_nodo("P", 800, 610); p.padre = w; w.punto = p +j = crear_nodo("J", 700, 690, True); j.padre = w; w.raya = j +# Izquierda (Rayas) +t = crear_nodo("T", 300, 250, True); t.padre = raiz; raiz.raya = t +m = crear_nodo("M", 200, 250, True); m.padre = t; t.raya = m +o = crear_nodo("O", 100, 250, True); o.padre = m; m.raya = o +g = crear_nodo("G", 200, 330); g.padre = m; m.punto = g +q = crear_nodo("Q", 100, 410, True); q.padre = g; g.raya = q +z = crear_nodo("Z", 200, 410); z.padre = g; g.punto = z +n = crear_nodo("N", 300, 450); n.padre = t; t.punto = n +k = crear_nodo("K", 200, 530, True); k.padre = n; n.raya = k +y = crear_nodo("Y", 100, 530, True); y.padre = k; k.raya = y +c = crear_nodo("C", 200, 610); c.padre = k; k.punto = c +d = crear_nodo("D", 300, 610); d.padre = n; n.punto = d +x = crear_nodo("X", 200, 690, True); x.padre = d; d.raya = x +b = crear_nodo("B", 300, 690); b.padre = d; d.punto = b + +# --- VARIABLES DE ESTADO --- +nodo_actual = raiz +mensaje_completo = "" # El mensaje final que se imprimirá +codigo_morse_total = "" # El código final que se imprimirá +morse_actual = "" # Lo que se va pulsando para la letra actual +ejecutando = True +reloj = pygame.time.Clock() + +while ejecutando: + ventana.fill(COLOR_FONDO) + + # 1. Dibujar Cabecera (Mensaje generado) + pygame.draw.rect(ventana, (230, 230, 230), (0, 0, ANCHO, 80)) + pygame.draw.line(ventana, COLOR_BARRA, (0, 80), (ANCHO, 80), 2) + img_msg = fuente_mensaje.render(mensaje_completo + "_", True, COLOR_HISTORIAL) + ventana.blit(img_msg, (20, 20)) + + # 2. Dibujar Árbol + raiz.dibujar_lineas(ventana, nodo_actual) + for n in nodos_lista: + n.dibujar_nodo(ventana, nodo_actual) + + # 3. Dibujar Barra de Leyenda + pygame.draw.rect(ventana, COLOR_BARRA, (0, ALTO - 60, ANCHO, 60)) + texto_leyenda = ". (Punto) - (Raya) SPACE (Confirmar) BACKSPACE (Borrar) ESC (Salir)" + img_leyenda = fuente_leyenda.render(texto_leyenda, True, COLOR_LEYENDA) + ventana.blit(img_leyenda, (20, ALTO - 38)) + + # 4. Previsualización de letra actual + if nodo_actual.letra: + # Ajuste texto si no tiene fuente Arial + pygame.draw.circle(ventana, COLOR_RESALTE, (ANCHO - 60, ALTO - 120), 35, 3) + img_prev = fuente_preview.render(nodo_actual.letra, True, COLOR_TEXTO) + # Centrado dinámico: + rect_prev = img_prev.get_rect(center=(ANCHO - 60, ALTO - 120)) + ventana.blit(img_prev, rect_prev) + + for evento in pygame.event.get(): + if evento.type == pygame.QUIT: + ejecutando = False + if evento.type == pygame.KEYDOWN: + if evento.key == pygame.K_PERIOD: + if nodo_actual.punto: + nodo_actual = nodo_actual.punto + morse_actual += "." + elif evento.key in [pygame.K_MINUS, pygame.K_SLASH, pygame.K_KP_MINUS]: + if nodo_actual.raya: + nodo_actual = nodo_actual.raya + morse_actual += "-" + elif evento.key == pygame.K_SPACE: + if nodo_actual.letra: + mensaje_completo += nodo_actual.letra + codigo_morse_total += morse_actual + " " + else: + mensaje_completo += " " + codigo_morse_total += " " + # resetarar para siguiente letra + nodo_actual = raiz + morse_actual = "" + elif evento.key == pygame.K_BACKSPACE: + mensaje_completo = mensaje_completo[:-1] + morse_actual = "" + elif evento.key == pygame.K_ESCAPE: + ejecutando = False + + pygame.display.flip() + reloj.tick(30) + +# -- Al Salir imprimimos todo el texto -- +print("\n" + "="*40) +print("CODIFICADOR MORSE") +print(f"TEXTO: {mensaje_completo}") +print(f"MORSE: {codigo_morse_total.strip()}") +print("="*40 + "\n") + +pygame.quit() +sys.exit() diff --git a/morse2text.py b/morse2text.py new file mode 100755 index 0000000..c72ef66 --- /dev/null +++ b/morse2text.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import sys + +MORSE_MAP = {'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--', 'Z': '--..', '1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----'} +# Invertimos el diccionario: { '.-': 'A', ... } +REVERSE_MAP = {v: k for k, v in MORSE_MAP.items()} + +def decode(): + if len(sys.argv) < 2: + print("Uso: python3 morse2text.py \".... --- .-.. .-\"") + return + + # El input es la cadena morse + input_morse = sys.argv[1] + palabras_morse = input_morse.split(" ") # Separamos por doble espacio + frase_final = [] + + for palabra in palabras_morse: + letras = [REVERSE_MAP.get(cod, "?") for cod in palabra.split(" ")] + frase_final.append("".join(letras)) + + print(" ".join(frase_final)) + +if __name__ == "__main__": + decode() diff --git a/requeriments.txt b/requeriments.txt new file mode 100644 index 0000000..9a4d8c4 --- /dev/null +++ b/requeriments.txt @@ -0,0 +1 @@ +pygame>=2.6.0 diff --git a/text2morse.py b/text2morse.py new file mode 100755 index 0000000..95f4242 --- /dev/null +++ b/text2morse.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import sys + +MORSE_MAP = {'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--', 'Z': '--..', '1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----'} + +def encode(): + if len(sys.argv) < 2: + print("Uso: python3 text2morse.py \"TEXTO A CONVERTIR\"") + return + + texto = " ".join(sys.argv[1:]).upper() + resultado = [] + + for palabra in texto.split(" "): + codificado = [MORSE_MAP.get(letra, "") for letra in palabra] + resultado.append(" ".join(codificado)) + + # Unimos las palabras con 2 espacios + print(" ".join(resultado)) + +if __name__ == "__main__": + encode()