class Snake():
    """ Implementa un Snake que puede moverse en una 
        cuadrícula imaginaria dede m filas por n columnas.
        Las filas y columnas se numeran a partir de 1.
        El snake tendrá una largo, que incluye su cabeza.
        El largo tiene que ser mayor que 1.
        La cabeza se pinta de un color y los segmentos de
        su cuerpo en otro.
        El snake se puede mover en forma horizontal o
        vertical.
        Para mover el snake se utilizan las flechas
        direccionales y la tecla "x" (que también mueve
        el snake hacia abajo).
    """

    ## Atributos de clase que definen el tamaño por
    ## defecto de la cuadrícula.
    FILAS = 40
    COLUMNAS = 30

    ## Atributos de clase que define el tamaño (en pixeles)
    ## de cada elemento de la cuadrícula.
    DIM = 10

    ## Atributo de clase que defifne el tamaño por
    ## defecto del snake.
    LARGO_SNAKE = 4

    ## Atributos de clase que define a partir de que
    ## coordenadas en el eje horizontal(x) y vertical(y)
    ## se empieza a dibujar la cuadrícula.
    DY = 20
    DX = 20

    @staticmethod
    def deme_posicion(i,j):
        """ Retorna la posicion superior izquierda en eje x, eje y
            de un elemento i,j en la cuadrícula imaginaria.
            Entradas:
                i     : Fila en la cuadrícula imaginaria.
                j     : Columna en la cuadrícula imaginaria.
            Salidas:
                (x,y) : Posición de la esquina superior izquierda
                        en donde se encuentra la entrada (i,j)
            Supuesto:
                (i,j) es una posición válida en la cuadrícula
                      imaginaria.
        """
        x = Snake.DX + (j - 1) * (Snake.DIM + 1)
        y = Snake.DY + (i - 1) * (Snake.DIM + 1)
        return (x, y)
    

    def __init__(self, filas = FILAS, columnas = COLUMNAS,
                 largo = LARGO_SNAKE, cabeza = "blue", cuerpo="red"):
        """Crea la culebra y su cuadrícula.
            Entradas:
                filas    : # de filas en la cuadrícula imaginaria.
                columnas : # de columna en la cuadrícula imaginaria.
                largo    : Tamaño del snake incluyendo la cabeza.
            Salidas:
                Snake en una cuadrícula según las dimensiones indicadas.
            Supuesto:
                La ventana, según las dimensiones establecidas,
                cabe en la ventana.
                El snake cabe en la cuadrícula.

        """

        ## Funciones locales al constructor ##
        
        def segmento(color, i, j):
            """ Dibuja un segmento del snake en posicion i, j con el color
                indicado.
            Entradas:
                color : Color del segmento a dibujar.
                (i,j) : Ubicación en que se desea dibujar el
                        segmento en la cuadrícula imaginaria.
            Salidas:
                Dibujo del segemento con el color indicado en
                la posición (i,j) de la cuadrícula imaginaria.
            Supuesto:
                El color es uno válido en Tkinter.
                (i,j) es una posición válida en la
                cuadrícula imaginaria.
            """

            ## Determina la posición en los ejes
            ## reales de la posición (i,j) de la
            ## cuadrícula imaginaria.
            x, y = Snake.deme_posicion(i, j)

            ## Prepara el lápiz para dibujar
            ## un rectánculo relleno.
            self.lapiz.fillcolor(color)
            self.lapiz.pu()
            self.lapiz.setpos(x, y)
            self.lapiz.seth(0)
            self.lapiz.pd()
            self.lapiz.begin_fill()

            ## Dibuja el rectángulo con 4
            ## movimientos !!!
            for i in range(4):
                self.lapiz.fd(Snake.DIM+1)
                self.lapiz.left(90)

            ## Cierra el relleno.
            self.lapiz.end_fill()

        def mueva_snake(direccion):
            """ Mueve el snake en la dirección indicada.
            Entradas:
                direccion : Dirección en que se mueve el snake.
            Salidas:
                Actualización del snkae en pantalla.
                Si el snake pega contra un borde o contra ella
                misma no avanza.
            Supuesto:
                dirección es alguno de los valores 
                "Up", "Down", "Left" o "Right".
            """

            ## Obtiene la posición actual de la cabeza del snake.

            ci, cj = self.snake[-1][0], self.snake[-1][1]
            
            ## Calcula la nueva posición de la cabeza según
            ## la dirección indicada por el usuario.

            if direccion == "Up":
                i, j = (ci if ci == 1 else ci - 1, cj)
            elif direccion == "Down":
                i, j = (ci if ci == self.filas else ci + 1, cj)
            elif direccion == "Left":
                i, j = (ci, cj if cj == 1 else cj - 1)
            elif direccion == "Right":
                i, j = (ci, cj if cj == self.columnas else cj + 1)

         
            if not((i,j) in self.snake): ## se asegura que el snake
                                         ## no choque contra sí mismo !!

                self.scr.tracer(False) 

                ## Borra la cola. La cola está en la
                ## posición 0 de la lista self.snake.

                segmento("white", self.snake[0][0], self.snake[0][1])
                del self.snake[0]

                ## Pinta la antigua cabeza de color azul para que sea
                ## parte del cuerpo.  La cabeza es el último elemento
                ## de la lista self.snake.

                segmento(cuerpo, self.snake[-1][0], self.snake[-1][1])

                ## Agrega la nueva cabeza.  La cabeza nueva cabeza
                ## se agrega al final de la lista. 

                self.snake.append((i, j))
                segmento(cabeza, i, j)

                self.scr.tracer(True)
                
        def dibuja_horizontales():
            """ Dibuja las filas+1 lineas horizontales.
            Entradas:
                Ninguna.
            Salidas:
                Dibujo de las líneas horizontales de la
                cuadrícula imaginaria en que se moverá el
                snake.
            """
            
            dy = Snake.DY ## Posición en eje y de la primera 
                          ## línea horizontal.

            ## Calcula la posición en eje x en que finalizan
            ## todas la líneas horizontales.
            
            posFin_x = Snake.DX + self.columnas * (Snake.DIM + 1)

            for i in range(self.filas+1):
                self.lapiz.up()
                self.lapiz.setpos(Snake.DX, dy)
                self.lapiz.down()
                self.lapiz.setpos(posFin_x, dy)
                dy += Snake.DIM + 1

        def dibuja_verticales():
            """ Dibuja las columnas+1 lineas verticales 
            Entradas:
                Ninguna.
            Salidas:
                Dibujo de las líneas verticales de la
                cuadrícula imaginaria en que se moverá el
                snake.
            """

            dx = Snake.DX ## Posición en eje x de la primera
                          ## línea vertical.

            ## Calcula la posición en eje y en que finalizan
            ## todas la líneas verticales.
            
            posFin_y = Snake.DY + self.filas * (Snake.DIM + 1)
            for j in range(self.columnas+1):
                self.lapiz.up()
                self.lapiz.setpos(dx,Snake.DY)
                self.lapiz.down()
                self.lapiz.setpos(dx, posFin_y)
                dx += Snake.DIM + 1

        def dibuja_escenario():
            """ Dibuja la cuadrícula y el snake: el cuerpo en azul y la
                cabeza en rojo.
            Entradas:
                Ninguna.
            Salidas:
                Dibujo de la cuadrícula imaginaria en que se moverá el
                snake.
            """

            self.scr.tracer(False)

            ## Dibuja la cuadricula
            dibuja_horizontales()
            dibuja_verticales()

            ## Dibuja el cuerpo del snake
            for x in self.snake[:-1]:
                segmento(cuerpo,x[0],x[1])

            ## Dibuja la cabeza
            segmento(cabeza, self.snake[-1][0], self.snake[-1][1])    

            self.scr.tracer(True)

        ############################################################    
        ## Inicio de las instrucciones del constructor __init__.  ##
        ############################################################

        ## Verifica restricciones, sino se cumplen dispara un
        ## SnakeError.
        if not (isinstance(filas,int) and isinstance(columnas,int) and \
           isinstance(largo,int)):
            raise SnakeError("Type Error")
        else:
            
            ## Guarda las dimensiones de la cuadrícula
            ## imaginaria y del snake en atributos de instancia.
            self.filas = filas
            self.columnas = columnas
            self.largo = largo

            ## Crea la ventana y estable un título para la misma.
            self.root = TK.Tk()
            self.root.title("Snake v1.0 / 2014")

            ## Obtiene la posición (x,y) en la ventana de la 
            ## última fila y columna de la cuadrícula imaginaria,
            ## lo anterior con el objetivo de establecer el
            ## tamaño de la ventana.
            x, y = Snake.deme_posicion(filas, columnas)

            ## Calcula el ancho y el alto de la ventana
            anchoVentana = x + Snake.DX + Snake.DIM + 1
            altoVentana  = y + Snake.DY + Snake.DIM + 1

            ## Crea un área de dibujo (canvas) con un ancho y
            ## alto que está en función de la cuadrícula
            ## en que se moverá el snake.
            
            self.canvas = TK.Canvas(self.root, width=anchoVentana,
                                    height=altoVentana)
            self.canvas.pack()

            ## Crea un área de dibujo para tortugas.
            self.scr = TurtleScreen(self.canvas)

            ## Establece un sistema de coordenadas en donde
            ## el punto (0,0) se ubica en la esquina superior
            ## izquierda.
            ## El eje x va de 0 a positivos de izquierda a derecha.
            ## El eje y va de 0 a positivos de arriba hacia abajo.
            self.scr.setworldcoordinates(0,altoVentana,anchoVentana,0)
            self.scr.reset()

            ## Crea la tortuga para dibujar
            self.lapiz = RawTurtle(self.scr)
            self.lapiz.ht()

            ## Crea el snake.
            ## El snake corresponde a una lista de pares
            ## ordenados (x, y) en donde cada uno de estos
            ## elementos corresponde a un segmento del snake.
            ## La cabeza del snake se ubica en la última
            ## posición de la lista.
            ## El snake creado queda en posición horizontal
            ## y su cabeza mira hacia la derecha.  La cola
            ## se ubica en la posición 1,1. Observe que se
            ## utilizaron listas por comprensión para
            ## construir el snake.  En este punto el snake
            ## no es visible.
            
            self.snake = [(1,eje_y) for eje_y in range(1, self.largo + 1)]

            ## Dibuja la cuadrícula y el snake.
            dibuja_escenario()

            ## Establece el binding entre las teclas para el movimiento
            ## del snake y las funciones que atenderán dicho movimiento.
            ## En todos los casos se utiliza la misma función solo que
            ## el parámetro con que se invoca es diferente.
            
            self.scr.onkeypress(lambda : mueva_snake("Up"), "Up")       # Arriba
            self.scr.onkeypress(lambda : mueva_snake("Right"), "Right") # Derecha
            self.scr.onkeypress(lambda : mueva_snake("Down"), "Down")   # Abajo
            self.scr.onkeypress(lambda : mueva_snake("Left"), "Left")   # Izquierda
            self.scr.onkeypress(lambda : mueva_snake("Down"), "x")      # Otra vez abajo

            ## Se queda escuchando los eventos.
            ## El programa termina cuando el usuario cierre la ventana.
            self.scr.listen()
Example #2
0
class TurtleWidget(CanvasWidget):
    def newTKWidget(self, parent, hOptions):
        return super().newTKWidget(parent, hOptions)

    def initialize(self, hOptions):
        self.screen = TurtleScreen(self.tkWidget)
        self.tkTurtle = RawTurtle(self.screen)
        self.screen.mode('world')

        self.tkTurtle.setheading(90)

        if 'speed' in hOptions:
            self.curSpeed = int(hOptions['speed'])
        else:
            self.curSpeed = 1
        self.lSaveStack = []  # stack to save/restore state on

    def setBounds(self, xmin, ymin, xmax, ymax, padding=15):
        width = xmax - xmin
        height = ymax - ymin
        if (width > height):
            halfdiff = (width - height) / 2
            ymin -= halfdiff
            ymax += halfdiff
        else:
            halfdiff = (height - width) / 2
            xmin -= halfdiff
            xmax += halfdiff
        self.screen.setworldcoordinates(
            xmin - padding,
            ymin - padding,
            xmax + padding,
            ymax + padding,
        )

    def reset(self):
        self.screen.reset()

    def move(self, n):
        tkTurtle = self.tkTurtle
        assert isinstance(tkTurtle, RawTurtle)
        tkTurtle.speed(self.curSpeed)
        tkTurtle.forward(n)

    def turn(self, d):
        tkTurtle = self.tkTurtle
        assert isinstance(tkTurtle, RawTurtle)
        tkTurtle.speed(self.curSpeed)
        tkTurtle.right(d)

    # --- Turtle state includes these fields:
    #     xpos, ypos, heading, isvisible, isdown

    def saveState(self):

        tkTurtle = self.tkTurtle
        assert isinstance(tkTurtle, RawTurtle)
        self.lSaveStack.append([
            tkTurtle.xcor(),
            tkTurtle.ycor(),
            tkTurtle.heading(),
            tkTurtle.isvisible(),
            tkTurtle.isdown(),
        ])

    def restoreState(self):

        tkTurtle = self.tkTurtle
        assert isinstance(tkTurtle, RawTurtle)
        if len(self.lSaveStack) == 0:
            raise Exception("restoreState(): No saved state to restore")
        lState = self.lSaveStack.pop()
        saved_x = lState[0]
        saved_y = lState[1]

        cur_x = tkTurtle.xcor()
        cur_y = tkTurtle.ycor()

        # --- determine whether we need to hide the turtle
        if tkTurtle.isvisible():
            dist = hypot(saved_x - cur_x, saved_y - cur_y)
            mustHide = (dist > 1)
        else:
            mustHide = False

        if mustHide:
            tkTurtle.hideturtle()

        tkTurtle.penup()
        tkTurtle.setposition(saved_x, saved_y)
        tkTurtle.setheading(lState[2])

        if lState[3] and mustHide:
            tkTurtle.showturtle()
        if lState[4]:
            tkTurtle.pendown()

    def moveTo(self, x, y):
        tkTurtle = self.tkTurtle
        tkTurtle.penup()
        tkTurtle.hideturtle()
        tkTurtle.setposition(x, y)
        tkTurtle.setheading(90)
        tkTurtle.pendown()
        tkTurtle.showturtle()

    def drawAt(self, x, y, func):

        # --- Must not draw or show movement
        tkTurtle = self.tkTurtle

        self.saveState()
        self.moveTo(x, y)
        func(self)
        self.restoreState()