Saltar al contenido
Teclados Chulos

Etch A Sketch con Python Turtle: dibuja con el teclado (borrar, guardar y modo difícil)

¿Te apetece un Etch A Sketch en versión “maker”, pero en la pantalla? Hoy montamos uno con Python Turtle y lo controlamos con el teclado usando eventos (y binds de Tkinter para que vaya suave). Flechas para dibujar, tecla para borrar, tecla para guardar… y un modo difícil para que no sea solo “paseo por el parque”. Sí: Python turtle keyboard input convertido en juguete.

Respuesta rápida (controles del Etch A Sketch)

  • = mover y dibujar (también vale para diagonal si mantienes dos teclas)
  • Shift (mantener) = “turbo” (más velocidad)
  • Espacio = pluma arriba/abajo (dibujar sin pintar)
  • C = limpiar pantalla (borrar)
  • U = deshacer último trazo
  • S = guardar dibujo (EPS)
  • + / - = grosor del trazo
  • 15 = color rápido · R = color aleatorio
  • H = activar/desactivar modo difícil · L = subir nivel
  • Esc = salir

Más:
Atajos de teclado ·
Ctrl + S (guardar) ·
Ctrl + Z (deshacer) ·
Teclas WASD ·
Teclas de edición

Índice

Qué vas a construir (y por qué mola)

Un Etch A Sketch tiene dos “mandos” (horizontal/vertical). Lo imitamos con el teclado: las flechas cambian la posición X/Y y la tortuga va dejando rastro. Lo divertido viene cuando le metes:

  • Control fino con “KeyPress/KeyRelease” (no depende del autorepeat del teclado).
  • Borrar / deshacer sin reiniciar el programa.
  • Guardar el dibujo a archivo.
  • Modo difícil: jitter, límites y niveles para convertirlo en mini-juego.

Requisitos

  • Python 3 (Turtle viene en la librería estándar).
  • Windows / macOS / Linux (en Mac, a veces necesitas hacer click en la ventana para darle foco al teclado).

Eventos vs binds: por qué usamos Tkinter bind

Turtle tiene onkey() y onkeypress(), que van genial para atajos “sueltos” (pulsar una vez). Pero si quieres movimiento suave, lo ideal es saber cuándo una tecla se mantiene y cuándo se suelta. Para eso, Turtle está montado sobre Tkinter, y podemos hacer:

  • screen.getcanvas().bind("<KeyPress>", ...)
  • screen.getcanvas().bind("<KeyRelease>", ...)

Así creamos un “loop” con ontimer() que mueve la tortuga a 60 FPS aprox. Resultado: se siente como un juguete de verdad.

Código completo: Etch A Sketch con Python Turtle + teclado (binds)

import turtle
import random
import os
from datetime import datetime

WIDTH, HEIGHT = 900, 600
MARGIN = 18  # margen para no pegarse al borde


class EtchASketch:
    def __init__(self):
        # --- pantalla ---
        self.screen = turtle.Screen()
        self.screen.setup(WIDTH, HEIGHT)
        self.screen.title("Etch A Sketch (Python Turtle) — Teclados Chulos")
        self.screen.bgcolor("white")
        self.screen.tracer(0)  # render manual (suave)

        # --- tortuga que dibuja ---
        self.pen = turtle.Turtle(visible=False)
        self.pen.speed(0)
        self.pen.pensize(3)
        self.pen.color("black")
        self.pen.penup()
        self.pen.goto(0, 0)
        self.pen.pendown()

        # --- HUD (mensajes) ---
        self.hud = turtle.Turtle(visible=False)
        self.hud.penup()
        self.hud.color("#111")
        self.hud.goto(-WIDTH // 2 + 12, HEIGHT // 2 - 36)

        # --- estado ---
        self.base_step = 6
        self.step = self.base_step
        self.fast_mult = 3
        self.level = 1
        self.hard_mode = False
        self.jitter = 0  # “tembleque” en modo difícil

        self.is_pendown = True
        self.keys = {}  # keysym -> bool

        # --- binds (Tkinter) ---
        canvas = self.screen.getcanvas()
        canvas.focus_set()
        canvas.bind("<KeyPress>", self._on_key_press)
        canvas.bind("<KeyRelease>", self._on_key_release)

        # --- atajos “de una pulsación” (Turtle) ---
        self.screen.onkey(self.toggle_pen, "space")
        self.screen.onkey(self.clear, "c")
        self.screen.onkey(self.reset, "x")
        self.screen.onkey(self.undo, "u")
        self.screen.onkey(self.save, "s")
        self.screen.onkey(self.toggle_hard_mode, "h")
        self.screen.onkey(self.next_level, "l")
        self.screen.onkey(self.quit, "Escape")
        self.screen.listen()

        self._msg("Listo: flechas para dibujar · C borrar · S guardar · H modo difícil")
        self._tick()
        turtle.done()

    # ---------------------------
    # Input
    # ---------------------------
    def _on_key_press(self, event):
        k = event.keysym
        self.keys[k] = True

        # grosor (+ / -) en varios teclados
        if k in ("plus", "equal", "KP_Add"):
            self.bigger()
        elif k in ("minus", "underscore", "KP_Subtract"):
            self.smaller()

        # colores rápidos
        if k in ("1", "2", "3", "4", "5"):
            self.set_color(int(k))
        elif k in ("r", "R"):
            self.random_color()

    def _on_key_release(self, event):
        self.keys[event.keysym] = False

    # ---------------------------
    # Loop de movimiento (60 FPS aprox.)
    # ---------------------------
    def _tick(self):
        mult = self.fast_mult if (self.keys.get("Shift_L") or self.keys.get("Shift_R")) else 1

        dx = 0
        dy = 0

        if self.keys.get("Up"):
            dy += self.step * mult
        if self.keys.get("Down"):
            dy -= self.step * mult
        if self.keys.get("Right"):
            dx += self.step * mult
        if self.keys.get("Left"):
            dx -= self.step * mult

        # modo difícil: jitter y “fricción rara”
        if self.hard_mode and (dx or dy):
            dx += random.randint(-self.jitter, self.jitter)
            dy += random.randint(-self.jitter, self.jitter)

        if dx or dy:
            x, y = self.pen.position()
            nx = x + dx
            ny = y + dy

            # límites (clamp)
            half_w = WIDTH / 2 - MARGIN
            half_h = HEIGHT / 2 - MARGIN
            nx = max(-half_w, min(half_w, nx))
            ny = max(-half_h, min(half_h, ny))

            self.pen.goto(nx, ny)

        self.screen.update()
        self.screen.ontimer(self._tick, 16)  # ~60 fps

    # ---------------------------
    # Acciones
    # ---------------------------
    def toggle_pen(self):
        self.is_pendown = not self.is_pendown
        if self.is_pendown:
            self.pen.pendown()
            self._msg("Pluma: ABAJO (dibujando)")
        else:
            self.pen.penup()
            self._msg("Pluma: ARRIBA (mover sin dibujar)")

    def clear(self):
        # borra sin mover la tortuga
        self.pen.clear()
        self._msg("Borrado: pantalla limpia (C)")

    def reset(self):
        # reset tipo “sacudida” (borra y vuelve al centro)
        self.pen.clear()
        self.pen.penup()
        self.pen.goto(0, 0)
        self.pen.pendown() if self.is_pendown else self.pen.penup()
        self._msg("Reset: centro + limpio (X)")

    def undo(self):
        # deshace “algo” del historial (depende de la complejidad del trazo)
        try:
            self.pen.undo()
            self._msg("Undo: deshacer (U)")
        except turtle.TurtleGraphicsError:
            self._msg("Undo: nada que deshacer")

    def bigger(self):
        size = min(20, self.pen.pensize() + 1)
        self.pen.pensize(size)
        self._msg(f"Grosor: {size}")

    def smaller(self):
        size = max(1, self.pen.pensize() - 1)
        self.pen.pensize(size)
        self._msg(f"Grosor: {size}")

    def set_color(self, n):
        palette = {
            1: "black",
            2: "red",
            3: "blue",
            4: "green",
            5: "purple",
        }
        self.pen.color(palette.get(n, "black"))
        self._msg(f"Color: {palette.get(n, 'black')}")

    def random_color(self):
        self.pen.color(random.random(), random.random(), random.random())
        self._msg("Color: aleatorio (R)")

    def save(self):
        # Guarda como .eps (vector). Luego puedes convertir a PNG si quieres.
        name = self.screen.textinput("Guardar dibujo", "Nombre de archivo (sin extensión):")
        if not name:
            self._msg("Guardar: cancelado")
            return

        safe = "".join(ch for ch in name.strip().replace(" ", "_") if ch.isalnum() or ch in ("_", "-"))
        if not safe:
            safe = datetime.now().strftime("etch_%Y%m%d_%H%M%S")

        filename = f"{safe}.eps"
        canvas = self.screen.getcanvas()
        canvas.postscript(file=filename, colormode="color")

        self._msg(f"Guardado: {filename} (EPS)")

    def toggle_hard_mode(self):
        self.hard_mode = not self.hard_mode
        if self.hard_mode:
            self.jitter = 1 + self.level  # temblor depende del nivel
            self._msg(f"Modo difícil: ON (jitter {self.jitter})")
        else:
            self.jitter = 0
            self._msg("Modo difícil: OFF")

    def next_level(self):
        self.level = 1 if self.level >= 5 else self.level + 1
        self.step = self.base_step + (self.level - 1) * 2
        if self.hard_mode:
            self.jitter = 1 + self.level
        self._msg(f"Nivel: {self.level} (velocidad base {self.step})")

    def quit(self):
        self._msg("Saliendo…")
        try:
            self.screen.bye()
        except turtle.Terminator:
            pass

    # ---------------------------
    # HUD
    # ---------------------------
    def _msg(self, text, ms=1400):
        self.hud.clear()
        self.hud.write(text, font=("Arial", 12, "normal"))
        self.screen.ontimer(self.hud.clear, ms)


if __name__ == "__main__":
    EtchASketch()

Cómo usarlo (paso a paso)

  1. Copia el código en un archivo: etch_a_sketch.py
  2. Ejecuta: python etch_a_sketch.py
  3. Haz click dentro de la ventana si no te coge el teclado (foco).
  4. Dibuja con flechas, borra con C, guarda con S, y prueba el H 😈

Borrar, deshacer y guardar: “lo básico” de cualquier maker-tool

  • Borrar (C) limpia la pantalla pero no te mueve del sitio.
  • Reset (X) limpia y vuelve al centro (como sacudir el Etch A Sketch).
  • Deshacer (U) usa undo(). Ojo: Turtle deshace acciones del historial; en trazos largos puede sentirse “por trozos”.
  • Guardar (S) exporta a .EPS (vector). Si quieres PNG/JPG, lo típico es convertirlo luego con una herramienta externa (o un script con Pillow, si te pica el gusanillo).

Sube la dificultad (modo “post maker”)

El modo difícil (H) mete “tembleque” y el nivel (L) sube la velocidad. Pero si quieres que esto sea un mini-juego de verdad, aquí van ideas evergreen:

  1. Ink limit: tienes “tinta” (por ejemplo 30 segundos). Si se acaba, solo puedes mover con pluma arriba.
  2. Zona prohibida: dibuja un rectángulo “lava” y si lo cruzas, pierde (reset).
  3. Objetivo: genera una forma fantasma (círculo/cuadrado) y reta a calcarla.
  4. Tiempo: “dibuja una casa en 20 segundos” y guarda el resultado.
  5. Modo espejo: cada movimiento se duplica simétrico (arte generativo instantáneo).

Trucos PRO (cuando te pica optimizar)

  • WASD: si prefieres estilo gamer, puedes mapear WASD a lo mismo que las flechas (y dejar flechas para ajustar grosor/color).
  • Snap: que el trazo vaya en “grid” (como píxeles gordos) para arte retro.
  • Paletas: guarda 8 colores y cambia con 18.

Preguntas frecuentes

¿Por qué Turtle no detecta mis teclas?

Casi siempre es foco: haz click dentro de la ventana. En este script forzamos focus_set(), pero algunos sistemas igual requieren ese click inicial.

¿Qué diferencia hay entre onkey y los binds?

onkey/onkeypress van genial para “pulsar una vez”. Con bind(KeyPress/KeyRelease) controlas mantener/soltar, y eso hace que el movimiento sea suave (tipo joystick).

¿Se puede guardar en PNG en vez de EPS?

Sí, pero EPS es lo más directo con Tkinter. Para PNG suele hacerse conversión (por ejemplo con herramientas externas o librerías). Si quieres, puedes quedarte con EPS porque es vector (calidad top).

¿Cómo cambio el tamaño del lienzo?

Arriba tienes WIDTH y HEIGHT. Ajusta esos valores y listo.

¿Cómo hago que las flechas dibujen más “lento”?

Baja base_step (por ejemplo 4) o reduce el multiplicador de Shift (fast_mult).

¿Puedo usar esto para enseñar eventos en clase?

Sí: es perfecto para explicar “estado” (teclas pulsadas), bucles con ontimer(), y cómo un programa reacciona a eventos del usuario.

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuarlo!

Promedio de puntuación 5 / 5. Recuento de votos: 1

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.

También te puede interesar