PItando http://pitando.net Para los que queremos acercar la tecnología a niños y no tan niños. Thu, 24 Nov 2016 06:00:00 +0000 es-ES hourly 1 https://wordpress.org/?v=4.9.4 http://pitando.net/wp-content/uploads/2015/08/pitando1-55d0b62fv1_site_icon-32x32.png PItando http://pitando.net 32 32 97545583 31 – Minecraft Education Edition http://pitando.net/2016/11/24/31-minecraft-education-edition/ Thu, 24 Nov 2016 06:00:00 +0000 http://pitando.net/?p=1525 Sigue leyendo 31 – Minecraft Education Edition ]]>  

En el episodio de hoy me hago eco del lanzamiento de Minecraft Education Edition, por parte de Microsoft, el 1 de noviembre de 2016. No sólo se trata de una versión del popular videojuego, sino de toda una colección de recursos y espacios colaborativos al alcance de museos, colegios e institutos, bibliotecas y asociaciones de educación en casa.

Si eres profesor o administrador de sistemas de alguna de esas instituciones, o educas desde casa, en este episodio te cuento los requisitos que aplican y cómo conseguir esta edición de Minecraft para aplicarla en tus clases.

Enlaces de interés:

Puedes dejarme comentarios en http://pitando.net o en Twitter (https://twitter.com/pitandonet)

]]>
1525
30 – Martina Matarí, de The Hacker’s Garage http://pitando.net/2016/11/10/30-martina-matari-de-the-hackers-garage/ http://pitando.net/2016/11/10/30-martina-matari-de-the-hackers-garage/#comments Thu, 10 Nov 2016 05:00:53 +0000 http://pitando.net/?p=1517 Sigue leyendo 30 – Martina Matarí, de The Hacker’s Garage ]]>  

En el episodio de hoy tengo conmigo a Martina Matarí, Analista de Seguridad y Hacker Ética, que nos trae su proyecto “The Hacker’s Garage”. Se trata de una colección de cajas por suscripción mensual que nos traen un montón de componentes electrónicos, placas Arduino y extensiones para Arduino, para hacer proyectos de seguridad.

Por el momento llevan 2 cajas, una de las cuales podéis ver en el vídeo que está sobre estas líneas (si en tu podcatcher no lo ves, visita la entrada del episodio en pitando.net), y con las que podremos ir poblando de componentes nuestro pequeño laboratorio de electrónica. En nuestra charla podréis saber muchas más cosas sobre el proyecto, la práctica profesional del hacking ético y muchas otras cosas.

Enlaces de interés (en inglés la mayoría):

Puedes dejarme comentarios en http://pitando.net o en Twitter (https://twitter.com/pitandonet) y también, si lo deseas, en este wave de Anchor: https://anchor.fm/w/A4C091

]]>
http://pitando.net/2016/11/10/30-martina-matari-de-the-hackers-garage/feed/ 1 1517
29 – Twitter, terremotos, computación física y pensamiento computacional http://pitando.net/2016/10/27/29-twitter-terremotos-computacion-fisica-y-pensamiento-computacional/ Thu, 27 Oct 2016 05:00:13 +0000 http://pitando.net/?p=1510 Sigue leyendo 29 – Twitter, terremotos, computación física y pensamiento computacional ]]>  

En este episodio, grabado en Málaga el 15 de octubre de 2016 en directo, cuento con Luís del Valle (https://twitter.com/ldelvalleh y http://programarfacil.com) de “La tecnología para todos” para hablaros de varios temas: Computación (o programación) física y pensamiento computacional. Usamos como hilo introductorio el proyecto Alarma Sismos (https://twitter.com/alarmasismos), un sistema de detección temprana de terremotos ideado por Sebastián Alegría (https://twitter.com/sebasak).

Este sistema está construido usando arduino, sensores de ondas sísmicas de tipo P (https://es.wikipedia.org/wiki/Onda_s%C3%ADsmica#Ondas_P) que se alimentan a un servidor centralizado que, usando datos de varios puntos geográficos, permite hallar el epicentro, enviar tweets de alerta, y muchas más cosas que comentamos en el episodio.

Enlaces de interés (además de los ya publicados):

Vídeo de programación en riguroso “falso directo” con Scratch: https://www.youtube.com/watch?v=bKOo0C7dkSA

Post-data: Sebastián Alegría ideó Alarma Sismos con 14 años.

Puedes dejarme comentarios en http://pitando.net o en Twitter (https://twitter.com/pitandonet) y también, si lo deseas, en este wave de Anchor: https://anchor.fm/w/A48578

]]>
1510
28 – Astro Pi europeo http://pitando.net/2016/10/13/28-astro-pi-europeo/ Thu, 13 Oct 2016 05:00:01 +0000 http://pitando.net/?p=1492 Sigue leyendo 28 – Astro Pi europeo ]]> astro_pi_1-01

Astro Pi vuelve, y con mucha fuerza. Este año, la fundación Raspberry Pi junto con la Agencia Espacial Europea han planteado un concurso de programación para niños, pero no ya sólo del reino unido, sino abierto para todos los colegios y niños menores de 16 años, de toda Europa.

Los colegios podrán inscribirse hasta el 1 de noviembre presentando un proyecto ante la ESA, en inglés, saliendo las listas de proyectos seleccionados el día 15 de noviembre. Los equipos recibirán un kit Astro Pi y una serie de desafíos del astronauta Thomas Pesquet, que será quien lleve a cabo las misiones de los estudiantes una vez los afortunados ganadores vean su código enviado a la Estación Espacial Internacional. Recordad que en la Estación Espacial Internacional ya están instaladas las dos Astro Pi desde el año pasado, y que ya sirvieron a las misiones presentadas durante la pasada edición y que fueron ejecutadas por Tim Peake.

Enlaces interesantes del episodio:

Podéis dejarme cualquier comentario en esta misma entrada de http://pitando.net, o enviándome cualquier comentario a través del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico.

Además y desde ahora estoy en Anchor (https://anchor.fm/u/d29308/GabrielViso), una red social de notas de voz donde me podéis dejar mensajes relacionadas con esta entrada en este wavehttps://anchor.fm/w/A43DCE

Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast en:

]]>
1492
27 – Propósitos de nuevo curso… y JPOD http://pitando.net/2016/09/23/27-propositos-de-nuevo-curso-y-jpod/ Fri, 23 Sep 2016 18:49:07 +0000 http://pitando.net/?p=1486 Sigue leyendo 27 – Propósitos de nuevo curso… y JPOD ]]>  

Vuelve el curso y vuelve la actividad al podcast, y al blog. En este pequeño audio os cuento cuál va a ser el enfoque de PItando para esta nueva temporada, y dos primicias primiciosas:

  • Estaré con Luís del Valle, de La Tecnología Para Todos (suscríbete en http://programarfacil.com/feed/podcast/) y programarfacil.com en las XI Jornadas de Podcasting haciendo un directo que, si estáis atentos al podcast podréis intuir de qué va a tratar, pero que no os voy a desvelar :p
  • Además me ha tocado presentar los premios de la Asociación Pocast, con lo que tendré que pensar en algo gracioso

Espero vuestros comentarios acerca de los cambios que tengo previstos para este proyecto, y también vuestras ideas y sugerencias, en http://pitando.net o en twitter (https://twitter.com/pitandonet) y facebook (https://facebook.com/pitandonet)

]]>
1486
Episodio 26 – CoderDojo: tu club juvenil de programación http://pitando.net/2016/06/30/episodio-26-coderdojo-tu-club-juvenil-de-programacion/ http://pitando.net/2016/06/30/episodio-26-coderdojo-tu-club-juvenil-de-programacion/#comments Thu, 30 Jun 2016 05:00:55 +0000 http://pitando.net/?p=1470 Sigue leyendo Episodio 26 – CoderDojo: tu club juvenil de programación ]]>  

En este episodio, en el que PItando cierra por vacaciones, os presento CoderDojo, por si no lo conocíais. Es un club de programación donde niños entre 7 y 17 años aprenden y comparten conocimientos tecnológicos usando Scratch, Python, App Inventor, Processing y muchas cosas más, aprendiendo a programar, a trabajar en equipo y a presentar sus logros a sus padres y compañeros.

Desde aquí muchas gracias a Lola por descubrirme el movimiento escribiéndome al blog, y por supuesto a Miguel Abellán y a Marcos López por dedicar un tiempo a charlar conmigo acerca de esta iniciativa. También quiero agradecer a Bernard y a Loli, del CoderDojo de Valencia, a Carmen del CoderDojo de Murcia, y nuevamente a Marcos, del CoderDojo Medialab Prado de Madrid por enviarme sus grabaciones para este episodio.

Enlaces:

Más información acerca de CoderDojo:

Podcast recomendado: “Jarras y Podcasts” — https://itunes.apple.com/es/podcast/jarras-y-podcast/id1023236212?mt=2

Espero vuestros comentarios en http://pitando.net

]]>
http://pitando.net/2016/06/30/episodio-26-coderdojo-tu-club-juvenil-de-programacion/feed/ 1 1470
¡Cuatris terminado!: refactorizando el clon de Tetris http://pitando.net/2016/06/23/cuatris-terminado-refactorizando-el-clon-de-tetris/ Thu, 23 Jun 2016 09:00:23 +0000 http://pitando.net/?p=1467 Sigue leyendo ¡Cuatris terminado!: refactorizando el clon de Tetris ]]> Refactorizar es un término obviamente importado del inglés (refactor) que se suele usar para referirse a la actividad de mejorar la calidad del código de un programa, reorganizándolo para que quede más organizado, con menos “números mágicos”, uniformizando la calidad y haciéndolo más fácil de mantener. Se trata de alterar la estructura del código sin, necesariamente, cambiar su comportamiento.

Para terminar nuestro clon de Tetris os voy a proponer una siguiente versión del programa en el que:

  1. Mejoramos la gestión de textos en la pantalla
  2. Generalizamos comportamiento repetido en el tratamiento de eventos, creando nuevos métodos
  3. Queda totalmente orientado a objetos, pudiendo ser ejecutado con las siguientes líneas:
jugar = True
while jugar == True:
    juego = Cuatris(30) # 30: lado del cuadrado
    jugar = juego.ejecutar_juego()

No voy a entrar en detalles acerca de las transformaciones del código, sino que lo que haré será, directamente, dejaros el código en el recuadro inferior (que tendréis que desplegar) y desearos un buen verano:

import pygame
import random

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, pantalla, lado_cuadrado):
        self.matriz = matriz
        # self.color = color
        # self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, Cuatris.colores[0], rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, Cuatris.colores[self.matriz[i][j]])
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, Cuatris.colores[0])

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    if resto.matriz[self.y + i][self.x + j] != 0:
                        return True
        return False

class Recipiente(Pieza):
    
    def __quitar_fila(self, linea):
        'retira la fila indicada, poniendo una nueva fila en la parte de arriba de la pantalla'
        for fila in range(linea, 0, -1):
            self.matriz[fila] = self.matriz[fila - 1]
        self.matriz[0] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
        
    def comprobar_lineas(self, otra_pieza):
        'comprueba las líneas formadas por la nueva pieza'
        lineas = 0
        for fila in range(otra_pieza.y, otra_pieza.y + otra_pieza.filas):
            producto = 1;
            for valor in self.matriz[fila]:
                producto = producto * valor
                if producto == 0:
                    break
            if producto > 0:
                lineas = lineas +1
                self.__quitar_fila(fila)
        return lineas
        
    def incorporar(self, otra_pieza):
        'incorpora la matriz de otra pieza a la de si mismo, devolviendo el nº de líneas que se han hecho'
        for i in range(0,len(otra_pieza.matriz)):
            for j in range(0,len(otra_pieza.matriz[i])):
                if otra_pieza.matriz[i][j] != 0:
                    self.matriz[otra_pieza.y+i][otra_pieza.x+j] = otra_pieza.matriz[i][j]
        return self.comprobar_lineas(otra_pieza)      

class Cuatris:
    'Objeto Cuatris: implementa la lógica del videojuego'

    # Constantes de los colores
    BLANCO = (255,255,255)
    GRIS = (127,127,127)
    NEGRO = (0, 0, 0)
    MAGENTA = (200,0,200)
    VERDE = (0, 200, 0)
    ROJO = (200, 0, 0)
    AZUL = (0, 0, 200)
    AMARILLO = (200, 200, 0)
    MARRON = (0, 200, 200)
    BLANCO = (200, 200, 200)
    colores = [NEGRO, GRIS, MARRON, AZUL, BLANCO, VERDE, ROJO, MAGENTA, AMARILLO]

    

    matriz_L = [(2, 0),
                (2, 0),
                (2, 2)]

    matriz_J = [(0, 3),
                (0, 3),
                (3, 3)]

    matriz_I = [(4, 0),
                (4, 0),
                (4, 0),
                (4, 0)]

    matriz_S = [(0, 5, 5),
                (5, 5, 0)]

    matriz_Z = [(6, 6, 0),
                (0, 6, 6)]

    matriz_T = [(7, 7, 7),
                (0, 7, 0)]

    matriz_O = [(8, 8),
                (8, 8)]

    matrices = [0, 0, matriz_L, matriz_J, matriz_I, matriz_S, matriz_Z, matriz_T, matriz_O]

    matriz_siguiente = [[1, 1, 1, 1, 1, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 0, 0, 0, 0, 1],
                        [1, 1, 1, 1, 1, 1]]

    
    
    # Nº de cuadros por segundo
    FPS = 60

    # Lado del cuadrado por defecto
    SLOT = 40

    # Evento de Gravedad
    GRAVEDAD = pygame.USEREVENT + 1

    # Coordenadas iniciales de las piezas
    x_ini = 4
    y_ini = 0
    
    def __init__(self, lado_cuadrado):
        try:
            self.slot = int(lado_cuadrado)
        except ValueError:
            # Valor por defecto
            self.slot = SLOT
        self.lineas = 0
        self.lineas_siguiente_nivel = 20
        self.nivel = 0
        self.ancho = 21*self.slot
        self.alto = 22*self.slot
        # inicializamos pygame
        pygame.init()

        # definición de la pantalla
        self.pantalla = pygame.display.set_mode((self.ancho, self.alto))
        self.pantalla.fill(Cuatris.NEGRO)
        matriz_Recipiente = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
        self.siguiente = Recipiente (Cuatris.matriz_siguiente, self.pantalla, self.slot)
        self.siguiente.pinta(13, 0)

        self.recipiente = Recipiente (matriz_Recipiente, self.pantalla, self.slot)
        self.recipiente.pinta(0, 0)
        
        self.pieza = self.siguiente_pieza()
        self.pieza.pinta(4,0)
        self.pieza_siguiente = self.siguiente_pieza()
        self.pieza_siguiente.pinta(15,1)

        pygame.display.update()

        # reloj de control de refresco
        self.clock = pygame.time.Clock()

        # Evento de gravedad
        
        self.t_CAIDA = 70
        self.t_GRAVEDAD = 2000 #milisegundos
        pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_GRAVEDAD)
        
        
    def siguiente_pieza(self):
        n_pieza = random.randint(2, 8)
        pieza = Pieza (self.matrices[n_pieza], self.pantalla, self.slot)
        return pieza

    def _imprime(self, font, cadena, left, top, color, fondo):
        text = font.render(cadena, True, color, fondo)
        textrect = text.get_rect()
        textrect.left= left
        textrect.top= top
        self.pantalla.blit(text, textrect)

    def marcador(self):
        basicfont = pygame.font.SysFont(None, self.slot)
        self._imprime(basicfont,'Líneas: '+str(self.lineas), 13*self.slot, 8*self.slot, Cuatris.BLANCO, Cuatris.NEGRO)
        self._imprime(basicfont,'Nivel: '+str(self.nivel), 13*self.slot, 9*self.slot, Cuatris.GRIS, Cuatris.NEGRO) 
        self._imprime(basicfont,'Siguiente: ' + str(self.lineas_siguiente_nivel), 13*self.slot, 10*self.slot, Cuatris.GRIS, Cuatris.NEGRO)
        

    def juego_terminado(self):
        basicfont = pygame.font.SysFont(None, self.slot) # a refactorizar
        left = self.pantalla.get_rect().centerx - len('JUEGO T')*self.slot
        top = self.pantalla.get_rect().centery
        self._imprime(basicfont,'JUEGO TERMINADO', left, top, Cuatris.ROJO, Cuatris.GRIS)
        self._imprime(basicfont,'INTRO PARA REPETIR', left, top+self.slot, Cuatris.ROJO, Cuatris.GRIS)

    def giro(self, antihorario, pieza, recipiente):
        pieza.auto_borra()
        if antihorario:
            pieza.rotarM90()
        else:
            pieza.rotar90()
        if pieza.colisiona_con(recipiente):
            pieza.mover(-1,0)
        pieza.auto_pinta()

    def mover_derecha(self, unidades, pieza, recipiente):
        pieza.auto_borra()
        pieza.mover(unidades,0)
        if pieza.colisiona_con(recipiente):
            pieza.mover(-unidades, 0)
        pieza.auto_pinta()

    def ejecutar_juego(self):
        
        continuar = True

        self.marcador()

        while continuar: 
            # pausa hasta el siguiente "tick" de reloj
            self.clock.tick(Cuatris.FPS)

            # detección de evento QUIT (aspa)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    continuar = False
                else:
                    # Procesar la gravedad
                    if event.type == Cuatris.GRAVEDAD:
                        self.pieza.auto_borra()
                        self.pieza.mover(0, 1)
                        if self.pieza.colisiona_con(self.recipiente):
                            pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_GRAVEDAD)
                            self.pieza.mover(0,-1)
                            # L.auto_pinta()
                            # Incorporar pieza a "P"
                            self.recipiente.auto_borra()
                            self.lineas = self.lineas + self.recipiente.incorporar(self.pieza)
                            # niveles
                            if self.lineas >= self.lineas_siguiente_nivel:
                                self.nivel = self.nivel +1
                                self.lineas_siguiente_nivel = self.lineas_siguiente_nivel + 20
                                if self.t_GRAVEDAD > 100:
                                    self.t_GRAVEDAD = self.t_GRAVEDAD - 1000
                            self.marcador()
                            self.recipiente.auto_pinta()
                            self.pieza_siguiente.auto_borra()
                            self.pieza = self.pieza_siguiente
                            self.pieza.pinta(4,0)
                            self.pieza_siguiente = self.siguiente_pieza()
                            self.pieza_siguiente.pinta(15,1)
                            if self.pieza.colisiona_con(self.recipiente):
                                continuar = False
                                self.juego_terminado()
                        else:
                            self.pieza.auto_pinta()
                
                    if event.type == pygame.KEYDOWN:

                        # El giro de las piezas puede provocar colisiones por el lado derecho
                        if event.key == pygame.K_z:
                            self.giro(True, self.pieza, self.recipiente)

                        if event.key == pygame.K_x:
                            self.giro(False, self.pieza, self.recipiente)

                        if event.key == pygame.K_LEFT:
                            self.mover_derecha(-1,self.pieza, self.recipiente)

                        if event.key == pygame.K_RIGHT:
                            self.mover_derecha(1,self.pieza, self.recipiente)

                        # acelerar gravedad
                        if event.key == pygame.K_DOWN:
                            pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_CAIDA)
                        else:
                            pygame.time.set_timer(Cuatris.GRAVEDAD, self.t_GRAVEDAD)

                    pygame.display.update()
        while True:
            # pausa hasta el siguiente "tick" de reloj
            self.clock.tick(Cuatris.FPS)
            for event in pygame.event.get():
                if event.type==pygame.KEYDOWN and event.key == pygame.K_RETURN:
                    return True
                else:
                    return False
            
jugar = True
while jugar == True:
    juego = Cuatris(30)
    jugar= juego.ejecutar_juego()

print ("Fin del juego")
pygame.quit()

Recuerda que, como siempre, el código que os proporciono es una propuesta y no la única solución posible; de hecho, el código sigue siendo patentemente mejorable: faltan comentarios, ha quedado ligeramente desorganizado y hay métodos de los objetos que son excesivamente largos. Así pues, siéntete libre de dedicar tus ratos libres vacacionales en mejorarlo y, si tienes dudas o cualquier consulta que quieras hacer, no dudes en ponerte en contacto mediante los comentarios o el formulario de contacto.

]]>
1467
Cuatris v0.9: nuestro clon de Tetris está casi listo http://pitando.net/2016/06/16/cuatris-v0-9-nuestro-clon-de-tetris-esta-casi-listo/ http://pitando.net/2016/06/16/cuatris-v0-9-nuestro-clon-de-tetris-esta-casi-listo/#comments Thu, 16 Jun 2016 09:00:23 +0000 http://pitando.net/?p=1458 Sigue leyendo Cuatris v0.9: nuestro clon de Tetris está casi listo ]]> Poco nos queda ya para tener un clon de Tetris con el que poder jugar con nuestros amigos y familiares. Lo que vamos a hacer en este artículo, quizá cambiando un poco la estructura de la serie, es mostrar un recuadro con la siguiente pieza que saldrá, la puntuación del jugador (en nº de líneas), el nivel en el que está y cuántas líneas le quedan para pasar al siguiente nivel.

El Cuatris: casi, casi terminado
El Cuatris: casi, casi terminado

Recapitulando, hasta ahora hemos hecho los siguientes avances:

  1. Dibujar una pieza (básica) y programar la rotación: lo vimos en este artículo
  2. Programar la gravedad y el resto de movimientos: lo vimos en este otro artículo
  3. Colisiones de la pieza con otras piezas y el borde de la pantalla: fue el en esta otra entrada
  4. Haciendo líneas y retocando la mecánica anterior: el último artículo de la serie
  5. Puntuaciones y niveles: el artículo de hoy
  6. Borde, marcador, tabla de puntuaciones.
  7. Refinando los gráficos de las piezas

Para el siguiente capítulo lo que haremos será aplicar una actividad llamada refactorización de forma que tengamos el videojuego 100 % orientado a objetos y sea muy fácil implementar una opción para repetir la partida si el jugador pierde, dándolo así por terminado y dejándoos a vosotros el resto de mejoras que se os ocurran.

Os recomiendo qué debéis leer si llegáis a PItando en este momento, para poder aprovechar este artículo:

  1. La serie de Python, en general.
  2. La serie de Pygame, en particular.

Recuadro de siguiente pieza

El enfoque para poner este recuadro es bien sencillo: dibujaremos otra Pieza más (o un Recipiente adicional, si queremos), con una matriz de 6 por 6 posiciones y bordes grises. Dentro de esa matriz dibujaremos la pieza siguiente.

matriz_siguiente = [[1, 1, 1, 1, 1, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1]]

siguiente = Recipiente (matriz_siguiente, pantalla, SLOT)
siguiente.pinta(13, 0)

Para poder alojar la matriz, deberemos proporcionar más espacio en la pantalla.

# pantalla 21 x 22 y posición inicial de la pieza
ANCHO = 21*SLOT
ALTO = 22*SLOT
x_ini = 4
y_ini = 0

Necesitaremos por lo tanto, siempre actualizadas, dos objetos de tipo Pieza: la pieza en curso que sufre el movimiento por gravedad y que reacciona a los eventos del juego, y la pieza siguiente que estará estática en nuestro recuadro de “siguiente pieza”. Cuando incorporemos la pieza en movimiento al recipiente, “sacaremos la siguiente pieza del recuadro” y generaremos una nueva, con la función que hemos diseñado.

def siguiente_pieza():
    n_pieza = random.randint(2, 8)
    pieza = Pieza (matrices[n_pieza], pantalla, SLOT)
    return pieza

Esta función tiene unos ligeros cambios puesto que concentraremos todas las sentencias de representación de piezas en el bucle principal de eventos, como veréis al final del artículo en el código completo.

Escribir texto en Pygame

Para escribir textos en Pygame tenemos que tener en cuenta las siguientes cuestiones:

  • Hay que escoger una de las fuentes que tenga el sistema, y un tamaño
  • Debemos acceder directamente a una de las superficies que definimos en el juego y combinar el texto con el fondo de dicha superficie, usando para ello los rectángulos contenedores.

Así, por ejemplo, para escribir el texto “Hola, mundo” en la ventana de Pygame escribiremos lo que sigue (puedes probarlo en IDLE):

import pygame
pygame.init()
basicfont = pygame.font.SysFont(None, 48) 
pantalla = pygame.display.set_mode((480, 320))
pantalla.fill((0,0,0))
text = basicfont.render('JUEGO TERMINADO', True, (0,200,200), (127,127,127))
textrect = text.get_rect()
textrect.centerx= pantalla.get_rect().centerx
textrect.centery= pantalla.get_rect().centery
pantalla.blit(text, textrect)
pygame.display.update()

Este código da como resultado la siguiente ventana:

Representación de texto en Python con Pygame
Representación de texto en Python con Pygame

Lo que hemos hecho en él ha sido:

  • Incializar Pygame,
  • definir la fuente que usaremos (la de por defecto, sin estilos y a 48 puntos de tamaño),
  • preparar la pantalla,
  • crear un gráfico de texto, con color azul y fondo gris
  • y combinar el rectángulo contenedor del texto con la pantalla.

Hay que tener en cuenta que el módulo de texto no interpreta los caracteres de retorno de carro / nueva línea: por lo tanto, si queremos escribir varias líneas de texto, deberemos crear varios objetos con basicfont.render .

Para crear el marcador, que alojaremos bajo el recuadro de la siguiente pieza que hemos discutido en el apartado anterior, dispondremos la siguiente función:

def marcador():
    basicfont = pygame.font.SysFont(None, 48) # a refactorizar
    text = basicfont.render('Líneas: '+str(lineas), True, BLANCO, NEGRO)
    textrect = text.get_rect()
    textrect.left= 13*SLOT
    textrect.top= 8*SLOT
    pantalla.blit(text, textrect)
    text2 = basicfont.render('Nivel: '+str(nivel), True, GRIS, NEGRO)
    textrect2 = text2.get_rect()
    textrect2.left= 13*SLOT
    textrect2.top= 9*SLOT
    pantalla.blit(text2, textrect2)
    text3 = basicfont.render('Siguiente: ' + str(lineas_siguiente_nivel), True, GRIS, NEGRO)
    textrect3 = text3.get_rect()
    textrect3.left= 13*SLOT
    textrect3.top= 10*SLOT 
    pantalla.blit(text3, textrect3)

Además, y para cuando las piezas lleguen al borde superior del recipiente, programaremos otra función que detenga el juego, enseñando un cartel con el mensaje “JUEGO TERMINADO“:

def juego_terminado():
    basicfont = pygame.font.SysFont(None, 48) # a refactorizar
    text = basicfont.render('JUEGO TERMINADO', True, ROJO, GRIS)
    textrect = text.get_rect()
    textrect.centerx= pantalla.get_rect().centerx
    textrect.centery= pantalla.get_rect().centery
    pantalla.blit(text, textrect)

Puntuaciones y niveles

Para ello programaremos contadores de líneas y niveles. Podemos hacer, por ejemplo, que cada 20 líneas la velocidad de la gravedad aumente (reduciendo t_GRAVEDAD) e indicaremos este hecho incrementando el nivel. Os recomiendo que no incrementéis la velocidad más allá de un punto en el que el juego sería injugable; por ejemplo, podéis dejar de reducir t_GRAVEDAD cuando éste sea menor a 100 ó 200 milisegundos.

Mejorando el bucle principal del juego

Ya sólo nos queda atar cabos gracias a estas nuevas funciones y objetos en el juego, realizando cambios en el bucle principal del programa. Puedes consultar la solución completa y los cambios en el bucle (si no te salen) expandiendo este bloque de código:

import pygame
import random

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, pantalla, lado_cuadrado):
        self.matriz = matriz
        # self.color = color
        # self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, colores[0], rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, colores[self.matriz[i][j]])
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, colores[0])

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    if resto.matriz[self.y + i][self.x + j] != 0:
                        return True
        return False

class Recipiente(Pieza):
    
    def __quitar_fila(self, linea):
        'retira la fila indicada, poniendo una nueva fila en la parte de arriba de la pantalla'
        for fila in range(linea, 0, -1):
            self.matriz[fila] = self.matriz[fila - 1]
        self.matriz[0] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
        
    def comprobar_lineas(self, otra_pieza):
        'comprueba las líneas formadas por la nueva pieza'
        lineas = 0
        for fila in range(otra_pieza.y, otra_pieza.y + otra_pieza.filas):
            producto = 1;
            for valor in self.matriz[fila]:
                producto = producto * valor
                if producto == 0:
                    break
            if producto > 0:
                lineas = lineas +1
                self.__quitar_fila(fila)
        return lineas
        
    def incorporar(self, otra_pieza):
        'incorpora la matriz de otra pieza a la de si mismo, devolviendo el nº de líneas que se han hecho'
        for i in range(0,len(otra_pieza.matriz)):
            for j in range(0,len(otra_pieza.matriz[i])):
                if otra_pieza.matriz[i][j] != 0:
                    self.matriz[otra_pieza.y+i][otra_pieza.x+j] = otra_pieza.matriz[i][j]
        return self.comprobar_lineas(otra_pieza)      

def siguiente_pieza():
    n_pieza = random.randint(2, 8)
    pieza = Pieza (matrices[n_pieza], pantalla, SLOT)
    return pieza

def marcador():
    basicfont = pygame.font.SysFont(None, 48) # a refactorizar
    text = basicfont.render('Líneas: '+str(lineas), True, BLANCO, NEGRO)
    textrect = text.get_rect()
    textrect.left= 13*SLOT
    textrect.top= 8*SLOT
    pantalla.blit(text, textrect)
    text2 = basicfont.render('Nivel: '+str(nivel), True, GRIS, NEGRO)
    textrect2 = text2.get_rect()
    textrect2.left= 13*SLOT
    textrect2.top= 9*SLOT
    pantalla.blit(text2, textrect2)
    text3 = basicfont.render('Siguiente: ' + str(lineas_siguiente_nivel), True, GRIS, NEGRO)
    textrect3 = text3.get_rect()
    textrect3.left= 13*SLOT
    textrect3.top= 10*SLOT 
    pantalla.blit(text3, textrect3)

def juego_terminado():
    basicfont = pygame.font.SysFont(None, 48) # a refactorizar
    text = basicfont.render('JUEGO TERMINADO', True, ROJO, GRIS)
    textrect = text.get_rect()
    textrect.centerx= pantalla.get_rect().centerx
    textrect.centery= pantalla.get_rect().centery
    pantalla.blit(text, textrect)
    

BLANCO = (255,255,255)
GRIS = (127,127,127)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
VERDE = (0, 200, 0)
ROJO = (200, 0, 0)
AZUL = (0, 0, 200)
AMARILLO = (200, 200, 0)
MARRON = (0, 200, 200)
BLANCO = (200, 200, 200)
FPS = 60
SLOT = 40

colores = [NEGRO, GRIS, MARRON, AZUL, BLANCO, VERDE, ROJO, MAGENTA, AMARILLO]

# pantalla 21 x 22 y posición inicial de la pieza
ANCHO = 21*SLOT
ALTO = 22*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_Recipiente = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

matriz_L = [(2, 0),
            (2, 0),
            (2, 2)]

matriz_J = [(0, 3),
            (0, 3),
            (3, 3)]

matriz_I = [(4, 0),
            (4, 0),
            (4, 0),
            (4, 0)]

matriz_S = [(0, 5, 5),
            (5, 5, 0)]

matriz_Z = [(6, 6, 0),
            (0, 6, 6)]

matriz_T = [(7, 7, 7),
            (0, 7, 0)]

matriz_O = [(8, 8),
            (8, 8)]

matrices = [0, matriz_Recipiente, matriz_L, matriz_J, matriz_I, matriz_S, matriz_Z, matriz_T, matriz_O]

matriz_siguiente = [[1, 1, 1, 1, 1, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1]]

siguiente = Recipiente (matriz_siguiente, pantalla, SLOT)
siguiente.pinta(13, 0)

recipiente = Recipiente (matriz_Recipiente, pantalla, SLOT)
recipiente.pinta(0, 0)

pieza = siguiente_pieza()
pieza.pinta(4,0)
pieza_siguiente = siguiente_pieza()
pieza_siguiente.pinta(15,1)

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
t_CAIDA = 70
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True

lineas = 0
lineas_siguiente_nivel = 20
nivel = 0

marcador()

while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                pieza.auto_borra()
                pieza.mover(0, 1)
                if pieza.colisiona_con(recipiente):
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                    pieza.mover(0,-1)
                    # L.auto_pinta()
                    # Incorporar pieza a "P"
                    recipiente.auto_borra()
                    lineas = lineas + recipiente.incorporar(pieza)
                    # niveles
                    if lineas >= lineas_siguiente_nivel:
                        nivel = nivel +1
                        lineas_siguiente_nivel = lineas_siguiente_nivel + 20
                        print("NIVEL:",nivel)
                        if t_GRAVEDAD > 100:
                            t_GRAVEDAD = t_GRAVEDAD - 1000
                    marcador()
                    recipiente.auto_pinta()
                    pieza_siguiente.auto_borra()
                    pieza = pieza_siguiente
                    pieza.pinta(4,0)
                    pieza_siguiente = siguiente_pieza()
                    pieza_siguiente.pinta(15,1)
                    if pieza.colisiona_con(recipiente):
                        continuar = False
                        juego_terminado()
                else:
                    pieza.auto_pinta()
        
            if event.type == pygame.KEYDOWN:

                # El giro de las piezas puede provocar colisiones por el lado derecho
                if event.key == pygame.K_z:
                    pieza.auto_borra()
                    pieza.rotarM90()
                    if pieza.colisiona_con(recipiente):
                        pieza.mover(-1,0)
                    pieza.auto_pinta()

                if event.key == pygame.K_x:
                    pieza.auto_borra()
                    pieza.rotar90()
                    if pieza.colisiona_con(recipiente):
                        pieza.mover(-1,0)
                    pieza.auto_pinta()

                if event.key == pygame.K_LEFT:
                    pieza.auto_borra()
                    pieza.mover(-1,0)
                    if pieza.colisiona_con(recipiente):
                        # deshacer
                        pieza.mover(1, 0)
                    pieza.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    pieza.auto_borra()
                    pieza.mover(1,0)
                    if pieza.colisiona_con(recipiente):
                        # deshacer
                        pieza.mover(-1, 0)
                    pieza.auto_pinta()

                # acelerar gravedad
                if event.key == pygame.K_DOWN:
                    pygame.time.set_timer(GRAVEDAD, t_CAIDA)
                else:
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

            pygame.display.update()

Siguientes (y últimos) pasos

El juego no es continuo, es decir, no ofrece la opción de empezar una nueva partida cuando perdemos. También hay código repetido y, además, el programa no está totalmente orientado a objetos.

Lo que haremos en la siguiente entrega será hablar del refinado del código, o la refactorización de programas, y, como ejemplo, convertiremos nuestro juego en un programa totalmente orientado a objetos y que, además:

  • No tenga código Python replicado
  • Tenga un punto único de reinicialización
  • Sea fácilmente configurable para, por ejemplo, variar el tamaño global de la ventana.
]]>
http://pitando.net/2016/06/16/cuatris-v0-9-nuestro-clon-de-tetris-esta-casi-listo/feed/ 1 1458
Episodio 25 – Año 1 y Scratch Jr. http://pitando.net/2016/06/16/episodio-25-ano-1-y-scratch-jr/ Thu, 16 Jun 2016 05:00:51 +0000 http://pitando.net/?p=1464  

En este episodio, publicado un día después del primer cumpleaños de PItando (nació el 15 de junio de 2015), hago una retrospectiva de este primer año de proyecto, cargado de agradecimientos que repartir, y os cuento qué es Scratch Jr https://www.scratchjr.org/

Espero que hayáis disfrutado tanto como yo durante este año.

¡Gracias!

]]>
1464
Líneas en el clon de Tetris http://pitando.net/2016/06/09/lineas-en-el-clon-de-tetris/ Thu, 09 Jun 2016 09:00:38 +0000 http://pitando.net/?p=1450 Sigue leyendo Líneas en el clon de Tetris ]]> Estamos muy cerca ya de tener un clon de Tetris funcional, es decir, “algo” con lo que poder hacer líneas y que por lo tanto reproduzca la mecánica del juego. Recapitulando, hasta ahora hemos hecho los siguientes avances:

  1. Dibujar una pieza (básica) y programar la rotación: lo vimos en este artículo
  2. Programar la gravedad y el resto de movimientos: lo vimos en este otro artículo
  3. Colisiones de la pieza con otras piezas y el borde de la pantalla: fue el último artículo de la serie
  4. Haciendo líneas y retocando la mecánica anterior: el artículo de hoy
  5. Puntuaciones y niveles
  6. Borde, marcador, tabla de puntuaciones.
  7. Refinando los gráficos de las piezas

El artículo de hoy contempla muchas modificaciones al programa que tenemos construido, a saber:

  • Detectar que se ha producido una línea
  • Retirar dicha línea del área de juego
  • Generar piezas aleatoriamente, de diferentes colores.
  • Retoques gráficos al área de juego

Con esto podremos estar jugando indefinidamente y tendremos el juego listo para crear puntuaciones, niveles, y representar en la pantalla tanto la puntuación como la siguiente pieza, por ejemplo.

Os recomiendo qué debéis leer si llegáis a PItando en este momento, para poder aprovechar este artículo:

  1. La serie de Python, en general.
  2. La serie de Pygame, en particular.

El programa de partida para este artículo es el que viene de la semana anterior, que podéis ver en este cuadro (tendréis que desplegarlo):

import pygame

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, color, fondo, pantalla, lado_cuadrado):
        self.matriz = matriz
        self.color = color
        self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, self.fondo, rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.color)
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.fondo)

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    if resto.matriz[self.y + i][self.x + j] == 1:
                        return True
        return False

class Recipiente(Pieza):
    def incorporar(self, otra_pieza):
        'incorpora la matriz de otra pieza a la de si mismo'
        for i in range(0,len(otra_pieza.matriz)):
            for j in range(0,len(otra_pieza.matriz[i])):
                if otra_pieza.matriz[i][j] == 1:
                    self.matriz[otra_pieza.y+i][otra_pieza.x+j] = otra_pieza.matriz[i][j]
                    

def siguiente_pieza():
    L = Pieza (matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
    L.pinta(4, 0)
    return L


BLANCO = (255,255,255)
GRIS = (127,127,127)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla 12 x 22 y posición inicial de la pieza
ANCHO = 12*SLOT
ALTO = 22*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

matriz_Recipiente = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

recipiente = Recipiente (matriz_Recipiente, GRIS, NEGRO, pantalla, SLOT)
recipiente.pinta(0, 0)
L = siguiente_pieza()

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True


while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                L.auto_borra()
                L.mover(0, 1)
                if L.colisiona_con(recipiente):
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                    L.mover(0,-1)
                    L.auto_pinta()
                    # Incorporar pieza a "P"
                    recipiente.incorporar(L)
                    L = siguiente_pieza()
                else:
                    L.auto_pinta()
     
            
        
            if event.type == pygame.KEYDOWN:

                # El giro de las piezas puede provocar colisiones por el lado derecho
                if event.key == pygame.K_z:
                    L.auto_borra()
                    L.rotarM90()
                    if L.colisiona_con(recipiente):
                        L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_x:
                    L.auto_borra()
                    L.rotar90()
                    if L.colisiona_con(recipiente):
                        L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_LEFT:
                    L.auto_borra()
                    L.mover(-1,0)
                    if L.colisiona_con(recipiente):
                        # deshacer
                        L.mover(1, 0)
                    L.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    L.auto_borra()
                    L.mover(1,0)
                    if L.colisiona_con(recipiente):
                        # deshacer
                        L.mover(-1, 0)
                    L.auto_pinta()

                # acelerar gravedad
                if event.key == pygame.K_DOWN:
                    t_GRAVEDAD = 70
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                else:
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
           
            pygame.display.update()

Detección de líneas

Para detectar que se ha producido una línea voy a aprovecharme de que, independientemente de lo que vemos por pantalla, tenemos un modelo por detrás que consta de matrices de ceros (“0”) y valores diferentes de cero (“1” por el momento). Además, tenemos todas esas matrices consolidadas en el objeto Recipiente, porque cuando detectamos una colisión por el lateral inferior, incorporamos la pieza que ha caído al propio recipiente.

Teniendo en cuenta eso, darnos cuenta de que hemos hecho una línea es tan sencillo como multiplicar todos los elementos de cada fila del objeto Recipiente y comprobar si el producto es cero (“0”). Si es cero, no hay línea. Si es distinto de cero, hay línea.

Tan sencillo como eso. Luego, lo que tenemos que hacer una vez detectada la línea es retirarla del recipiente. Supongamos que hago línea en la fila 17, empezando a contar por arriba:

  • Copio la línea 16 en la 17,
  • Copio la línea 15 en la 16,
  • … copio la fila 0 en la 1 y, finalmente, pongo una nueva fila (sólo bordes de pantalla) en la fila 0.

Estas dos funciones, en el objeto Recipiente, son las que desarrollan este comportamiento:

    def quitar_fila(self, linea):
        'retira la fila indicada, poniendo una nueva fila en la parte de arriba de la pantalla'
        for fila in range(linea, 0, -1):
            self.matriz[fila] = self.matriz[fila - 1]
        self.matriz[0] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
        
    def comprobar_lineas(self, otra_pieza):
        'comprueba las líneas formadas por la nueva pieza'
        for fila in range(otra_pieza.y, otra_pieza.y + otra_pieza.filas):
            producto = 1;
            for valor in self.matriz[fila]:
                producto = producto * valor
                if producto == 0:
                    break
            if producto > 0:
                self.quitar_fila(fila)

Fijaos que sólo compruebo las potenciales filas en el rango que ocupa la pieza recién incorporada al Recipiente  (en este código, representado por el objeto self ), no en el Recipiente  entero.

Para repintar la pantalla, debemos repintar el Recipiente además de las piezas, cuando hayamos ejecutado todo esto. Al escribir las siguientes líneas en el bucle principal del juego, veremos que al repintar el Recipiente las piezas anteriormente depositadas se vuelven grises.

                if pieza.colisiona_con(recipiente):
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                    pieza.mover(0,-1)
                    # L.auto_pinta()
                    # Incorporar pieza a "P"
                    recipiente.auto_borra()
                    recipiente.incorporar(pieza)
                    recipiente.auto_pinta()
                    pieza = siguiente_pieza()
                else:
                    pieza.auto_pinta()

(Nota: he cambiado el nombre del objeto “L” por “pieza”, pero seguro que lo has notado)

Eso es por la forma en la que tenemos programada la gestión de colores (cada pieza tiene su color característico y el delRecipiente  es el gris), pero resolverlo es muy fácil.

Mejorando la gestión de colores

Para lograr un comportamiento normal en los colores de las piezas cuando éstas se depositan, lo que vamos a hacer hacer que las piezas, en vez de tener valores (“0” y “1”) en sus matrices, tengan valores mayores (“1”, “2”,…), representando cada uno de los valores un color diferente. Deshecharemos los parámetro color  de los constructores, porque ya no nos harán falta, y crearemos una lista de colores que, en cada posición (índices 1, 2, 3,…) almacene los valores RGB del color de cada pieza.

Así:

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, pantalla, lado_cuadrado):
        self.matriz = matriz
        # self.color = color
        # self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, colores[0], rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, colores[self.matriz[i][j]])
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, colores[0])

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    if resto.matriz[self.y + i][self.x + j] != 0:
                        return True
        return False

La definición de los colores y su matriz colores  es la siguiente:

BLANCO = (255,255,255)
GRIS = (127,127,127)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
VERDE = (0, 200, 0)
ROJO = (200, 0, 0)
AZUL = (0, 0, 200)
AMARILLO = (200, 200, 0)
MARRON = (0, 200, 200)
BLANCO = (200, 200, 200)

colores = [NEGRO, GRIS, MARRON, AZUL, BLANCO, VERDE, ROJO, MAGENTA, AMARILLO]

Ahora ya sólo queda generar las piezas, escoger la siguiente pieza de forma aleatoria, y proporcionaros el código completo.

Siguiente pieza

De acuerdo con las mejoras a los colores y la función siguiente_pieza , debemos hacer lo siguiente con ayuda de un módulo llamado random que proporciona una función para extraer un número entero de una sencuencia pseudo-aleatoria.

Lo primero, definir las piezas:

matriz_L = [(2, 0),
            (2, 0),
            (2, 2)]

matriz_J = [(0, 3),
            (0, 3),
            (3, 3)]

matriz_I = [(4, 0),
            (4, 0),
            (4, 0),
            (4, 0)]

matriz_S = [(0, 5, 5),
            (5, 5, 0)]

matriz_Z = [(6, 6, 0),
            (0, 6, 6)]

matriz_T = [(7, 7, 7),
            (0, 7, 0)]

matriz_O = [(8, 8),
            (8, 8)]

matrices = [0, matriz_Recipiente, matriz_L, matriz_J, matriz_I, matriz_S, matriz_Z, matriz_T, matriz_O]

La matriz de piezas la usaremos para escoger la pieza usando un número aleatorio entre 2 y 8. Realmente, las dos primeras posiciones están ahí “de relleno” para que los bucles de escoger color y pieza sean coherente (la pieza nº 8, que es el cuadrado, tiene el color nº 8 que es el amarillo.

Esta es la función de generar la siguiente pieza:

def siguiente_pieza():
    n_pieza = random.randint(2, 8)
    pieza = Pieza (matrices[n_pieza], pantalla, SLOT)
    pieza.pinta(4, 0)
    return pieza

Y esta es la sentencia para generar el recipiente: recipiente = Recipiente (matriz_Recipiente, pantalla, SLOT)

Conclusión

Estas son todas las mejoras y ampliaciones de calado que corresponden a este artículo. Podéis enviarme vuestros comentarios, dudas y sugerencias a través del formulario de contacto y la dirección de correo que allí se encuentra, o de las redes sociales: @pitandonet en Twitter y PItandonet en facebook.

Os dejo aquí el código del programa completo (que tendréis que desplegar para verlo) y un Vine con el resultado que podréis ver, examinar y (por supuesto) mejorar.

import pygame
import random

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, pantalla, lado_cuadrado):
        self.matriz = matriz
        # self.color = color
        # self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, colores[0], rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, colores[self.matriz[i][j]])
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, colores[0])

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] != 0:
                    if resto.matriz[self.y + i][self.x + j] != 0:
                        return True
        return False

class Recipiente(Pieza):

    def __quitar_fila(self, linea):
        'retira la fila indicada, poniendo una nueva fila en la parte de arriba de la pantalla'
        for fila in range(linea, 0, -1):
            self.matriz[fila] = self.matriz[fila - 1]
        self.matriz[0] = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
        
    def comprobar_lineas(self, otra_pieza):
        'comprueba las líneas formadas por la nueva pieza'
        for fila in range(otra_pieza.y, otra_pieza.y + otra_pieza.filas):
            producto = 1;
            for valor in self.matriz[fila]:
                producto = producto * valor
                if producto == 0:
                    break
            if producto > 0:
                self.__quitar_fila(fila)
        
    def incorporar(self, otra_pieza):
        'incorpora la matriz de otra pieza a la de si mismo'
        for i in range(0,len(otra_pieza.matriz)):
            for j in range(0,len(otra_pieza.matriz[i])):
                if otra_pieza.matriz[i][j] != 0:
                    self.matriz[otra_pieza.y+i][otra_pieza.x+j] = otra_pieza.matriz[i][j]
        self.comprobar_lineas(otra_pieza)
                    

def siguiente_pieza():
    n_pieza = random.randint(2, 8)
    pieza = Pieza (matrices[n_pieza], pantalla, SLOT)
    pieza.pinta(4, 0)
    return pieza


BLANCO = (255,255,255)
GRIS = (127,127,127)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
VERDE = (0, 200, 0)
ROJO = (200, 0, 0)
AZUL = (0, 0, 200)
AMARILLO = (200, 200, 0)
MARRON = (0, 200, 200)
BLANCO = (200, 200, 200)
FPS = 60
SLOT = 40

colores = [NEGRO, GRIS, MARRON, AZUL, BLANCO, VERDE, ROJO, MAGENTA, AMARILLO]

# pantalla 12 x 22 y posición inicial de la pieza
ANCHO = 12*SLOT
ALTO = 22*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_Recipiente = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

matriz_L = [(2, 0),
            (2, 0),
            (2, 2)]

matriz_J = [(0, 3),
            (0, 3),
            (3, 3)]

matriz_I = [(4, 0),
            (4, 0),
            (4, 0),
            (4, 0)]

matriz_S = [(0, 5, 5),
            (5, 5, 0)]

matriz_Z = [(6, 6, 0),
            (0, 6, 6)]

matriz_T = [(7, 7, 7),
            (0, 7, 0)]

matriz_O = [(8, 8),
            (8, 8)]

matrices = [0, matriz_Recipiente, matriz_L, matriz_J, matriz_I, matriz_S, matriz_Z, matriz_T, matriz_O]

recipiente = Recipiente (matriz_Recipiente, pantalla, SLOT)
recipiente.pinta(0, 0)
pieza = siguiente_pieza()

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True


while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                pieza.auto_borra()
                pieza.mover(0, 1)
                if pieza.colisiona_con(recipiente):
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                    pieza.mover(0,-1)
                    # L.auto_pinta()
                    # Incorporar pieza a "P"
                    recipiente.auto_borra()
                    recipiente.incorporar(pieza)
                    recipiente.auto_pinta()
                    pieza = siguiente_pieza()
                else:
                    pieza.auto_pinta()
     
            
        
            if event.type == pygame.KEYDOWN:

                # El giro de las piezas puede provocar colisiones por el lado derecho
                if event.key == pygame.K_z:
                    pieza.auto_borra()
                    pieza.rotarM90()
                    if pieza.colisiona_con(recipiente):
                        pieza.mover(-1,0)
                    pieza.auto_pinta()

                if event.key == pygame.K_x:
                    pieza.auto_borra()
                    pieza.rotar90()
                    if pieza.colisiona_con(recipiente):
                        pieza.mover(-1,0)
                    pieza.auto_pinta()

                if event.key == pygame.K_LEFT:
                    pieza.auto_borra()
                    pieza.mover(-1,0)
                    if pieza.colisiona_con(recipiente):
                        # deshacer
                        pieza.mover(1, 0)
                    pieza.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    pieza.auto_borra()
                    pieza.mover(1,0)
                    if pieza.colisiona_con(recipiente):
                        # deshacer
                        pieza.mover(-1, 0)
                    pieza.auto_pinta()

                # acelerar gravedad
                if event.key == pygame.K_DOWN:
                    t_GRAVEDAD = 70
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                else:
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

            
            pygame.display.update()
]]>
1450
Paredes y colisiones en nuestro clon de Tetris http://pitando.net/2016/06/02/paredes-y-colisiones-en-nuestro-clon-de-tetris/ Thu, 02 Jun 2016 09:00:14 +0000 http://pitando.net/?p=1436 Sigue leyendo Paredes y colisiones en nuestro clon de Tetris ]]> Vamos a dar más cuerpo a nuestro clon de Tetris, añadiendo los siguientes elementos:

  • Paredes del juego, bien visibles en la pantalla, para saber en todo momento dónde están los límites
  • Colisiones de las piezas con esas paredes. Además, si la colisión es desde arriba, la pieza queda fija en su posición y da paso a la siguiente.
  • Cuando pulsemos la flecha hacia abajo y mientras no pulsemos otra tecla, la pieza baja más rápido.

Con esto, habríamos cubierto estos puntos del plan que, realmente, cubren la parte más difícil de toda la programación:

  1. Dibujar una pieza (básica) y programar la rotación: lo vimos hace tres semanas
  2. Programar la gravedad y el resto de movimientos: lo vimos hace dos semanas
  3. Colisiones de la pieza con otras piezas y el borde de la pantalla. El artículo de hoy
  4. Haciendo líneas y retocando la mecánica anterior
  5. Puntuaciones y niveles
  6. Borde, marcador, tabla de puntuaciones. Con este artículo cubriremos también el borde, entendiendo borde como un marco visible
  7. Refinando los gráficos de las piezas

Una vez lo tengamos hecho y para poder jugar, sólo tendremos que programar la mecánica de las líneas. El resto de artículos añaden ese “acabado” que hace posible competir entre varios jugadores, y algo de estética o retoques de código. Dentro de poco, en cualquier caso, habremos superado lo realmente importante del juego y podremos jugar partidas “infinitas”.

Os recomiendo qué debéis leer si llegáis a PItando en este momento, para poder aprovechar este artículo:

  1. La serie de Python, en general.
  2. La serie de Pygame, en particular.

¡Vamos a por ello!

Programa de partida

Partiremos del resultado del último artículo, que puedes copiar desde este cuadro (que tendrás que expandir):

import pygame

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, color, fondo, pantalla, lado_cuadrado):
        self.matriz = matriz
        self.color = color
        self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, self.fondo, rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.color)
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        rectangulo_pieza = (x_ini*self.lado_cuadrado,
                          y_ini*self.lado_cuadrado,
                          (x_ini+self.columnas)*self.lado_cuadrado,
                          (y_ini+self.filas)*self.lado_cuadrado)
        self.__borra_rectangulo(rectangulo_pieza)

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)


BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla 10 x 20 y posición inicial de la pieza
ANCHO = 10*SLOT
ALTO = 20*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

L = Pieza (matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
L.pinta(x_ini, y_ini)

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True
while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                L.auto_borra()
                L.mover(0, 1)
                L.auto_pinta()
     
            # Movimiento
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_z:
                    L.auto_borra()
                    L.rotarM90()
                    L.auto_pinta()

                if event.key == pygame.K_x:
                    L.auto_borra()
                    L.rotar90()
                    L.auto_pinta()

                if event.key == pygame.K_LEFT:
                    L.auto_borra()
                    L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    L.auto_borra()
                    L.mover(1,0)
                    L.auto_pinta()

            pygame.display.update()

Detección de colisiones por la propia pieza

Estamos orientando nuestra programación a objetos: si bien el bucle principal todavía es un guión en Python (cosa que mejoraremos antes de terminar de escribir el juego), nuestra pieza es un objeto que tiene datos y comportamiento. Hasta ahora el comportamiento era girar, moverse y representarse en la pantalla.

Normalmente, un objeto sufre un efecto si colisiona con otro y, de alguna forma, modifica su comportamiento. En este caso, para incorporar al juego los límites de la pantalla nos basta con poder preguntarle a la pieza si está colisionando con otra pieza, o con los bordes de la pantalla.

Vamos a crear una función en nuestra pieza que devuelva True  si está en colisión con “el resto”, y False  en caso contrario. “El resto” será el conjunto formado por los bordes de la pantalla y el resto de piezas que se hayan ido depositando. Como veremos más adelante, lo modelaremos como un todo que se irá ampliando cada vez que vayan cayendo piezas. “El resto”, por lo tanto, tendrá una matriz de dimensiones iguales a las de la pantalla y “1” en los bordes más en las piezas que se han ido depositando.

En este caso, en lugar de detectar colisiones gráficas detectaremos colisiones usando la matriz que representa a la pieza, es decir, el campo .matriz  del objeto Pieza .

Sobre el código resultante del programa anterior, que está más arriba en este artículo, añade a la clase Pieza  la siguiente función:

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    if resto.matriz[self.y + i][self.x + j] == 1:
                        return True
        return False

El código de arriba devuelve un resultado positivo si ambas matrices tienen un 1 en alguna coordenada de la pantalla en la que coincidan. Leyéndolo en voz alta, lo que hace este método de la pieza es lo siguiente:

  • Para cada fila de la matriz de la pieza,
  • Recorro todas sus celdas (columnas),
  • Si la matriz de la pieza, en la coordenada formada por la fila y la columna actual, tiene un 1,
  • Compruebo si en esa coordenada de la pantalla, relativa a la posición actual de mi pieza, hay un “1” en la matriz de “el resto”.

En resumen, la forma de modelar “el resto” y esta función tiene truco, y está adaptada a la mecánica del Tetris: funciona y se entiende correctamente si pensamos que la actual (self) es la que se mueve, y que cualquier otra pieza está ya colocada y estática en una determinada posición de la pantalla.

A partir de ahora, llamaremos a “el resto” Recipiente, porque recuerda a un recipiente (o un vaso) que se va llenando de piezas.

Modelar los bordes y las piezas depositadas: objeto “Recipiente”

No sé si el nombre es lo más correcto, pero alguno hay que ponerle y “el resto” no es muy descriptivo. “Recipiente” puede valer, por el momento.

El Recipiente comparte bastante lógica con la Pieza; al menos toda la lógica que le permite pintarse en la pantalla. Aunque no sea lo más eficiente a ojos del programador experimentado y por ello más adelante reorganizaremos la jerarquía de objetos del juego para ahorrar recursos, de momento haremos lo siguiente:

  • Crearemos una clase que heredará de pieza, para reutilizar la infraestructura gráfica de la misma:
    • Su matriz
    • Las funciones de dibujarse cuadrado a cuadrado.
  • Le añadiremos un método que permitirá añadir a su matriz los “1” de la pieza que se acabe por depositar a medida que vaya cayendo.

La definición de la clase Recipiente  es la siguiente:

class Recipiente(Pieza):
    def incorporar(self, otra_pieza):
        'incorpora la matriz de otra pieza a la de si mismo'
        for i in range(0,len(otra_pieza.matriz)):
            for j in range(0,len(otra_pieza.matriz[i])):
                if otra_pieza.matriz[i][j] == 1:
                    self.matriz[otra_pieza.y+i][otra_pieza.x+j] = otra_pieza.matriz[i][j]

Lo que hace es añadir los “1” de la pieza que se pasa como parámetro en las coordenadas del recipiente donde se encuentre en ese momento. Esta funcionalidad será la que usaremos cuando una pieza colisione con algo “desde arriba”, es decir, cuando caiga encima del recipiente o de su contenido.

Sin embargo, hay que tener en cuenta una cosa: para poder cambiar el contenido de los elementos de una matriz, ésta no puede estar formada por tuplas (por ejemplo, (0, 1, 0) ), sino que tiene que estar formada por listas (por ejemplo, [0, 1, 0]). Así pues, en la siguiente sección, en la que veremos las modificaciones que hay que hacer al programa principal, tendremos que ser cuidadosos al definir la matriz del Recipiente.

Bucle principal

El bucle principal tiene que ser ampliado para implementar las siguientes cosas:

  1. La definición del objeto recipiente , es decir, la definición de una instancia de la clase Recipiente .
    • Esto va a implicar agrandar la pantalla: si queremos representar el borde de la pantalla como cuadrados visibles sin restar área de juego.
    • También habrá que definir un color nuevo para representar esos cuadrados.
  2. Un mecanismo que permita acelerar la gravedad si la tecla que se pulsa es la flecha hacia abajo
  3. Los rebotes: si una pieza está colisionando con el recipiente, hay solape entre dos cuadrados: es necesario deshacer el último paso del movimiento antes de redibujarla.
    • Esto también hay que controlarlo al hacer giros de las piezas: concretamente y por cómo está programado el giro, puede haber una colisión a la derecha de la pieza que gira.
  4. El depósito de la pieza cuando la colisión es por gravedad, o desde arriba:
    • La pieza que cae debe pasar a formar parte del recipiente
    • Debemos tener una forma de “pedir otra pieza” para repetir el ejercicio una y otra vez. Por este artículo pediremos al juego otra pieza igual.

El código del bucle principal es el siguiente, sobre el que es muy fácil localizar los puntos arriba mencionados (además, os resalto las líneas de código que merecen vuestra atención):

def siguiente_pieza():
    L = Pieza (matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
    L.pinta(4, 0)
    return L


BLANCO = (255,255,255)
GRIS = (127,127,127)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla 12 x 22 y posición inicial de la pieza
ANCHO = 12*SLOT
ALTO = 22*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

matriz_Recipiente = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

recipiente = Recipiente (matriz_Recipiente, GRIS, NEGRO, pantalla, SLOT)
recipiente.pinta(0, 0)
L = siguiente_pieza()

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True


while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                L.auto_borra()
                L.mover(0, 1)
                if L.colisiona_con(recipiente):
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                    L.mover(0,-1)
                    L.auto_pinta()
                    # Incorporar pieza a "P"
                    recipiente.incorporar(L)
                    L = siguiente_pieza()
                else:
                    L.auto_pinta()
     
            
        
            if event.type == pygame.KEYDOWN:

                # El giro de las piezas puede provocar colisiones por el lado derecho
                if event.key == pygame.K_z:
                    L.auto_borra()
                    L.rotarM90()
                    if L.colisiona_con(recipiente):
                        L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_x:
                    L.auto_borra()
                    L.rotar90()
                    if L.colisiona_con(recipiente):
                        L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_LEFT:
                    L.auto_borra()
                    L.mover(-1,0)
                    if L.colisiona_con(recipiente):
                        L.mover(1, 0)
                    L.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    L.auto_borra()
                    L.mover(1,0)
                    if L.colisiona_con(recipiente):
                        L.mover(-1, 0)
                    L.auto_pinta()

                # acelerar gravedad
                if event.key == pygame.K_DOWN:
                    t_GRAVEDAD = 70
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                else:
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

            
            pygame.display.update()

Fijaos que no controlamos el hecho de que las piezas “rebosen de la pantalla por arriba”, que sería la condición final del juego. Eso lo dejaremos para otro artículo.

Por último, sólo faltan ya unas pequeñas correcciones a la clase que representa a las piezas para que, cuando se re-pinte (más bien, cuando se borre), no borre por accidente partes del recipiente que puedan quedar en los entresijos de nuestras piezas.

Ajuste fino a la parte gráfica del objeto que modela la pieza

La clase pieza original borra “de un plumazo” todo el rectángulo de la pantalla que está ocupado por la pieza que se va moviendo. Esto puede dar problemas cuando estamos encajando la pieza, mientras cae, en “la montaña” que representan las piezas depositadas en el recipiente (pruébalo si quieres).

Para arreglar ese problema, lo que tienes que hacer es reprogramar la función de borrado para que sólo borre aquellos cuadrados visibles, es decir, los que se corresponden con posiciones a “1” de la matriz de la pieza.

Una solución puede ser ésta:

    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.fondo)

En ella, pintamos del color del fondo a todos los cuadrados que se corresponden con posiciones de la matriz a “1”.

Resultado final

Poniendo todas estas modificaciones juntas y probándolas, obtendremos un comportamiento como el representado por este Vine:

En la siguiente entrega viene programaremos la mecánica de las líneas y la sucesión aleatoria de las distintas piezas del juego. Si hay algo que refinar de todo lo hecho hasta ahora, lo iremos haciendo. Hasta entonces no dudes en ponerte en contacto conmigo si tuvieras cualquier duda, tanto usando los comentarios de este artículo como a través del formulario de contacto.

El código completo del programa de hoy se encuentra en el siguiente cuadro, que tendrás que expandir para poderlo copiar por si te hace falta.

import pygame

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, color, fondo, pantalla, lado_cuadrado):
        self.matriz = matriz
        self.color = color
        self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, self.fondo, rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.color)
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.fondo)

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

    def colisiona_con(self, resto):
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    if resto.matriz[self.y + i][self.x + j] == 1:
                        return True
        return False

class Recipiente(Pieza):
    def incorporar(self, otra_pieza):
        'incorpora la matriz de otra pieza a la de si mismo'
        for i in range(0,len(otra_pieza.matriz)):
            for j in range(0,len(otra_pieza.matriz[i])):
                if otra_pieza.matriz[i][j] == 1:
                    self.matriz[otra_pieza.y+i][otra_pieza.x+j] = otra_pieza.matriz[i][j]
                    

def siguiente_pieza():
    L = Pieza (matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
    L.pinta(4, 0)
    return L


BLANCO = (255,255,255)
GRIS = (127,127,127)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla 12 x 22 y posición inicial de la pieza
ANCHO = 12*SLOT
ALTO = 22*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

matriz_Recipiente = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

recipiente = Recipiente (matriz_Recipiente, GRIS, NEGRO, pantalla, SLOT)
recipiente.pinta(0, 0)
L = siguiente_pieza()

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True


while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                L.auto_borra()
                L.mover(0, 1)
                if L.colisiona_con(recipiente):
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                    L.mover(0,-1)
                    L.auto_pinta()
                    # Incorporar pieza a "P"
                    recipiente.incorporar(L)
                    L = siguiente_pieza()
                else:
                    L.auto_pinta()
     
            
        
            if event.type == pygame.KEYDOWN:

                # El giro de las piezas puede provocar colisiones por el lado derecho
                if event.key == pygame.K_z:
                    L.auto_borra()
                    L.rotarM90()
                    if L.colisiona_con(recipiente):
                        L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_x:
                    L.auto_borra()
                    L.rotar90()
                    if L.colisiona_con(recipiente):
                        L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_LEFT:
                    L.auto_borra()
                    L.mover(-1,0)
                    if L.colisiona_con(recipiente):
                        # deshacer
                        L.mover(1, 0)
                    L.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    L.auto_borra()
                    L.mover(1,0)
                    if L.colisiona_con(recipiente):
                        # deshacer
                        L.mover(-1, 0)
                    L.auto_pinta()

                # acelerar gravedad
                if event.key == pygame.K_DOWN:
                    t_GRAVEDAD = 70
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)
                else:
                    t_GRAVEDAD = 2000
                    pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

            
            pygame.display.update()
]]>
1436
Episodio 24 – Dos formas de proteger la navegación web del menor http://pitando.net/2016/06/02/dos-formas-de-proteger-la-navegacion-web-del-menor/ http://pitando.net/2016/06/02/dos-formas-de-proteger-la-navegacion-web-del-menor/#comments Thu, 02 Jun 2016 05:00:02 +0000 http://pitando.net/?p=1443 Sigue leyendo Episodio 24 – Dos formas de proteger la navegación web del menor ]]>  

La existencia y el precio de la Raspberry Pi hacen muy fácil y accesible otorgar la responsabilidad y el incentivo a un menor de tener su propio ordenador personal: estamos ante una máquina de propósito general muy capaz, gracias a sus prestaciones nada despreciables.

Pero claro, llega un momento en el que los deberes de nuestros chavales requieren acceder a internet a buscar información, a investigar, etc. ¿Cómo proteger a nuestros menores en la red?

En este episodio os voy a hablar de dos formas de proteger la navegación de vuestros menores, tanto en la escuela como en el hogar, y ya usen Raspberry Pi o cualquier otro dispositivo, porque se trata de filtros de tráfico que colocaremos en la propia red, y no configuraciones o programas que tendremos que instalarle en sus ordenadores, ya sean Raspberry Pi o cualquier otro.

El primero es KidSafe y su configuración, uso y mantenimiento es totalmente manual. Aquí tenéis un tutorial para instalarlo en (otra) Raspberry Pi: http://www.penguintutor.com/linux/raspberrypi-kidsafe

El segundo es OpenDNS: centralizado y disponible para empezar ya. http://www.opendns.com/home-internet-security

Espero vuestros comentarios en http://pitando.net

]]>
http://pitando.net/2016/06/02/dos-formas-de-proteger-la-navegacion-web-del-menor/feed/ 4 1443
RasPiO Pro Hat http://pitando.net/2016/05/26/raspio-pro-hat/ Thu, 26 May 2016 09:00:35 +0000 http://pitando.net/?p=1427 Sigue leyendo RasPiO Pro Hat ]]> Hace unas semanas publicaba un episodio del podcast acerca del RasPiO Pro Hat, una expansión para la Raspberry Pi que protege, rotula y ordena todos los pines GPIO para que usar nuestra Raspberry Pi para prototipar sea más fácil y seguro que nunca. En aquel momento, este proyecto estaba todavía en fase de financiación en Kickstarter y puse mi granito de arena aportando una contribución que me premiaría con uno de los primeros ejemplares de este aparato.

Mi RasPiO Pro Hat :)
Mi RasPiO Pro Hat 🙂

Hace unos pocos días me llegó, por fin, mi Pro Hat. En este pequeño artículo y para todos los que no habéis escuchado el episodio en cuestión os contaré de qué se trata y bajo qué circunstancias nos puede interesar adquirir este complemento que actualmente se encuentra en fase de pedido anticipado en la web de Alex Eames, su creador, por 15 £ envío  internacional incluido.

RasPiO Pro Hat: recordando qué es

El RasPiO Pro Hat es una expansión HAT (Hardware Attached on Top) que se conecta a los pines GPIO de cualquier modelo de Raspberry Pi de 40 pines. Básicamente y como decíamos antes, reordena, rotula y protege los pines GPIO usando una rama formada por un diodo especial (zener) y un resistor de 330 Ω. Además, y como podéis apreciar en la foto con la que abría este artículo, incorpora:

  • Una mini-placa de prototipado de 17 filas, como la que estamos acostumbrados a usar. Es decir: los conectores de sus filas están conectados entre sí dentro de cada grupo (o “mitad”)
  • Conectores negros rodeando a la placa de prototipado por 3 de sus cuatro laterales:
    • 16 pines de alimentación y tierra:
      • 8 de 3,3 V, rotulados como 3V3
      • 8 de 0 V, rotulados como GND (de GrouND, tierra)
      • 2 de 5V
    • Los pines GPIO de la Raspberry protegidos y ordenados, rotulados siguiendo la numeración BCM (del 2 al 27)
  • Los pines GPIO de la Raspberry están disponibles sin proteger, y a ellos podemos soldar terminales para usarlos adicionalmente

Con este HAT podremos comenzar a programar elementos físicos electrónicos sin usar resistores, esto es: mediante la simple conexión de cables desde los conectores GPIO protegidos y los de alimentación y tierra. De este modo, con un juego de componentes y algunos cables podremos prototipar muchas cosas sin miedo a dañar el microprocesador de la Raspberry Pi si fallamos al diseñar las protecciones de los pines (resistencias de limitación, divisores de tensión). Además, es un complemento ideal a la librería GPIO Zero de Ben Nutall que permitía programar las cosas por su nombre: un led como LED…

Os recuerdo el código necesario para hacer encender un LED conectado al GPIO número 12:

from gpiozero import LED
led = LED(12)
led.on()

Os podéis imaginar qué conveniente es todo esto cuando queremos enseñar a un chaval (o varios, o un tropel de ellos) los fundamentos combinados de la programación y la electrónica sin tener que darle una aburridísima clase de resistores, resistencias, corrientes, voltajes e intensidades para no quemar su Raspberry Pi y una clase de medida con un polímetro. Cambiando la instrucción de encendido por una de parpadeo y el puerto 12 por el 5…

¡Y sólo con dos cables y un LED!

Conclusiones

El RasPiO Pro Hat es una pequeña gran inversión para cualquiera, de cualquier edad, que esté empezando a combinar programación y electrónica. Usemos o no GPIO Zero, proteger los pines GPIO es algo esencial para no dañar la Raspberry Pi irremediablmente.

Además de lo conveniente que es la protección de por sí, lo más obvio que este aparato permite es realizar muy rápidamente un montaje de prácticamente cualquier tipo en muy poco tiempo. De esta forma, podemos centrarnos en la conexión básica de elementos y en la programación, obteniendo resultados prácticos en apenas minutos:

  • Te va a permitir captar la atención de los niños a la hora de introducirlos en la materia, evitando ahuyentar a los más distraídos al hablarles de conceptos como los divisores de tensión y las leyes de Ohm y de Khirchoff y las consecuencias que podría sufrir su Raspberry Pi de hacerlo mal.
  • Dejar tu Raspberry Pi a un niño para que experimente con ella con total libertad y unas mínimas precauciones y guía, dando rienda suelta a su creatividad sin consecuencias económicas 🙂

Obviamente esto no es una solución de futuro para todos; lo lógico es plantear un aprendizaje gradual para aquellos que lo deseen pasando, a su debido momento, por el capítulo de la protección de pines, pero en cualquier caso es una gran herramienta para iniciar a cualquiera en estos temas.

Si queréis más información, podéis consultar la web del producto y el manual de primeros pasos con GPIO Zero (ambos en inglés, este último en formato PDF).

]]>
1427
Aplicando gravedad a nuestro clon de Tetris http://pitando.net/2016/05/19/aplicando-gravedad-a-nuestro-clon-de-tetris/ http://pitando.net/2016/05/19/aplicando-gravedad-a-nuestro-clon-de-tetris/#comments Thu, 19 May 2016 09:00:27 +0000 http://pitando.net/?p=1414 Sigue leyendo Aplicando gravedad a nuestro clon de Tetris ]]> En esta semana vamos a retomar Cuatris, nuestro clon de Tetris, donde lo dejamos la pasada semana. Recordando el plan, la secuencia que íbamos a seguir es la siguiente:

  1. Dibujar una pieza (básica) y programar la rotación: lo vimos la semana pasada
  2. Programar la gravedad y el resto de movimientos: el artículo de hoy
  3. Colisiones de la pieza con otras piezas y el borde de la pantalla.
  4. Haciendo líneas y retocando la mecánica anterior
  5. Puntuaciones y niveles
  6. Borde, marcador, tabla de puntuaciones
  7. Refinando los gráficos de las piezas

Lo que haremos para eso va a ser usar los eventos de usuario que proporciona Pygame, de tal modo que nos adecuaremos a las reglas internas de funcionamiento del bucle de procesado de eventos que ya tenemos, programando la ocurrencia de un evento cada cierto tiempo (dos segundos por ahora).

Además, programaremos dos movimientos más, izquierda y derecha, a lo largo de una pantalla que ya redimensionaremos a su tamaño definitivo (10 cuadros de ancho por 20 de alto).

¡Manos a la obra!

(Foto: Museos Científicos Coruñeses)

Eventos de usuario en Pygame

Los eventos en Pygame no son más que códigos numéricos que la librería reconoce, y existen 8 disponibles para el uso libre por parte de los programadores. En concreto, disponemos de todos los valores que se encuentran entre pygame.USEREVENT y pygame.NUMEVENTS:

Eventos de usuario: entre 24 y 32
Eventos de usuario: entre 24 y 32

Los eventos de usuario, entre otras muchas cosas (que iremos viendo a medida que las necesitemos) se pueden temporizar fácilmente, es decir, podemos pedirle a Pygame que produzca un evento cada cierto tiempo. Prueba el siguiente programa:

import pygame
import datetime

pygame.init()

FPS = 60

MI_EVENTO = pygame.USEREVENT + 1
t_MI_EVENTO = 2000
pygame.time.set_timer(MI_EVENTO, t_MI_EVENTO)

clock = pygame.time.Clock()
continuar = True

while continuar:
    clock.tick(FPS)

    for event in pygame.event.get():
        if event.type == MI_EVENTO:
            t = datetime.datetime.now()
            print ("Mi evento en t = ", t.second)

pygame.quit()

Al ejecutarlo, verás que cada 2 segundos el mensaje “Mi evento en t = ” aparece, mostrando los segundos del instante actual. Sal del programa usando la combinación de teclas Control + C. El trozo de código importante está resaltado, entre las líneas 8 y 10. Experimenta con ellas.

Salida del programa
Salida del programa

Lo que haremos en nuestro juego será ampliarlo para que, cada 2 segundos, Pygame nos notifique un evento que llamaremos GRAVEDAD y ante el que moveremos la pieza una posición hacia abajo. Eso exige unos preparativos previos.

Ampliando la pieza

Necesitamos que la pieza amplíe su comportamiento para:

  • Almacenar las posiciones actuales.
  • Sea capaz de borrarse de las posiciones que esté ocupando en la pantalla en un momento dado
  • Podamos invocar un método para que se mueva un determinado número de cuadros en un momento dado.

Veamos una propuesta de clase que lo hace, atendiendo a las líneas resaltadas en el código:

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, color, fondo, pantalla, lado_cuadrado):
        self.matriz = matriz
        self.color = color
        self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, self.fondo, rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.color)
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        rectangulo_pieza = (x_ini*self.lado_cuadrado,
                          y_ini*self.lado_cuadrado,
                          (x_ini+self.columnas)*self.lado_cuadrado,
                          (y_ini+self.filas)*self.lado_cuadrado)
        self.__borra_rectangulo(rectangulo_pieza)

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)

Lo único que hacemos aquí es inicializar las coordenadas a valores significativos la primera vez que la pintamos en la pantalla. Por otro lado, le hemos dotado de funciones que borran y pintan la pieza utilizando esas coordenadas internas, y otra que la mueve un determinado número de casillas en las direcciones horizontal y vertical.

Programando el evento GRAVEDAD y los movimientos horizontales de la pieza

Este apartado se resuelve simplemente tomando la técnica que vimos en el primer listado del artículo, e incorporándolo al bucle. Posteriormente tendremos que modificar el bucle para que todas las funciones de rotación y movimiento tengan en cuenta las coordenadas que nuestra clase almacena en todo momento, es decir: usar las funciones nuevas.

El código queda así:

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla 10 x 20 y posición inicial de la pieza
ANCHO = 10*SLOT
ALTO = 20*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

L = Pieza (matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
L.pinta(x_ini, y_ini)

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True
while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                L.auto_borra()
                L.mover(0, 1)
                L.auto_pinta()
     
            # Movimiento
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_z:
                    L.auto_borra()
                    L.rotarM90()
                    L.auto_pinta()

                if event.key == pygame.K_x:
                    L.auto_borra()
                    L.rotar90()
                    L.auto_pinta()

                if event.key == pygame.K_LEFT:
                    L.auto_borra()
                    L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    L.auto_borra()
                    L.mover(1,0)
                    L.auto_pinta()

            pygame.display.update()

Si lo pruebas, verás que puedes desplazar la pieza en horizontal usando las flechas del cursor, además de girarla en ambos sentidos con las teclas Z y X. Fíjate en que, además, he aprovechado para redimensionar la pantalla y recolocar la pieza en su posición inicial.

En la siguiente entrega viene programaremos los límites de la pantalla en lo que a movimiento se refiere, y empezaremos a apilar piezas en el fondo de la pantalla de tal forma que encajen bien. Hasta entonces no dudes en ponerte en contacto conmigo si tuvieras cualquier duda, tanto usando los comentarios de este artículo como a través del formulario de contacto.

Por si tienes algún problema, te dejo el código fuente completo en este cuadro (que tendrás que desplegar).

import pygame

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, color, fondo, pantalla, lado_cuadrado):
        self.matriz = matriz
        self.color = color
        self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        # posición de la pieza
        self.x = 0
        self.y = 0
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, self.fondo, rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        # actualizamos la posición de la pieza
        self.x = x_ini
        self.y = y_ini
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.color)
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        rectangulo_pieza = (x_ini*self.lado_cuadrado,
                          y_ini*self.lado_cuadrado,
                          (x_ini+self.columnas)*self.lado_cuadrado,
                          (y_ini+self.filas)*self.lado_cuadrado)
        self.__borra_rectangulo(rectangulo_pieza)

    # Movimiento de la pieza
    def mover(self, desplazamiento_x, desplazamiento_y):
        'mueve la pieza el desplazamiento indicado (sin pintarla), en coordenadas de malla'
        self.x = self.x + desplazamiento_x
        self.y = self.y + desplazamiento_y

    # Funciones que hacen uso del estado de la pieza
    def auto_pinta(self):
        'pintar la pieza en las propiedades autocontenidas'
        self.pinta(self.x, self.y)

    def auto_borra(self):
        'borrar la piezaden las propiedades autocontenidas'
        self.borra(self.x, self.y)


BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla 10 x 20 y posición inicial de la pieza
ANCHO = 10*SLOT
ALTO = 20*SLOT
x_ini = 4
y_ini = 0

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

L = Pieza (matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
L.pinta(x_ini, y_ini)

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

# Evento de gravedad
GRAVEDAD = pygame.USEREVENT + 1
t_GRAVEDAD = 2000 #milisegundos
pygame.time.set_timer(GRAVEDAD, t_GRAVEDAD)

continuar = True
while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
            # Procesar la gravedad
            if event.type == GRAVEDAD:
                L.auto_borra()
                L.mover(0, 1)
                L.auto_pinta()
     
            # Movimiento
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_z:
                    L.auto_borra()
                    L.rotarM90()
                    L.auto_pinta()

                if event.key == pygame.K_x:
                    L.auto_borra()
                    L.rotar90()
                    L.auto_pinta()

                if event.key == pygame.K_LEFT:
                    L.auto_borra()
                    L.mover(-1,0)
                    L.auto_pinta()

                if event.key == pygame.K_RIGHT:
                    L.auto_borra()
                    L.mover(1,0)
                    L.auto_pinta()

            pygame.display.update()

 

]]>
http://pitando.net/2016/05/19/aplicando-gravedad-a-nuestro-clon-de-tetris/feed/ 2 1414
Episodio 23 – Videojuegos y aprendizaje http://pitando.net/2016/05/19/episodio-23-videojuegos-y-aprendizaje/ Thu, 19 May 2016 05:00:59 +0000 http://pitando.net/?p=1422 Sigue leyendo Episodio 23 – Videojuegos y aprendizaje ]]> En el episodio de hoy os hablo de videojuegos desde el punto de vista del aprendizaje: qué aportan tanto al que juega como al que usa la programación de videojuegos como vehículo para aprender a programar.

Enlaces del episodio:

Espero vuestros comentarios en http://pitando.net

]]>
1422
“Cuatris”, o nuestro clon de Tetris en Python: ¡comenzamos! http://pitando.net/2016/05/12/cuatris-o-nuestro-clon-de-tetris-en-python-comenzamos/ http://pitando.net/2016/05/12/cuatris-o-nuestro-clon-de-tetris-en-python-comenzamos/#comments Thu, 12 May 2016 09:00:41 +0000 http://pitando.net/?p=1396 Sigue leyendo “Cuatris”, o nuestro clon de Tetris en Python: ¡comenzamos! ]]> Tetris es un juego que marcó una época. Llegó a occidente en 1986 y fue desarrollado en 1984 por Alekséi Pázhitnov (Muscú, 1956), con la ayuda de Dmitri Pavlovski y Vadim Gerasimov mientras trabajaba en la Academia de Ciencias de la URSS.

La mecánica es sencilla: tenemos una serie de piezas llamadas Tretriminos J, L, Z, S, I, O y T, que caen desde la parte de arriba de la pantalla con ayuda de la gravedad.

Los Tetriminos: I, J, L, O, S, T y Z
Los Tetriminos: I, J, L, O, S, T y Z
  • La pantalla visible del Tetris mide 10 cuadros de ancho por 20 de alto.
  • El jugador, con ayuda de los controles, puede desplazarlas en horizontal (izquierda y derecha) y girarlas de 90 en 90º tanto en sentido horario como en sentido antihorario.
  • Cuando la haya colocado correctamente, podrá precipitarla hacia abajo para encajarlas en las piezas ya depositadas, para formar líneas.
  • Cuando se forma una línea horizontal, todos los segmentos de las piezas que forman una línea desaparecen, y todos los elementos que hubiera por encima, caen.
  • Cada cierto número de líneas, que forman un nivel, la velocidad aumenta.

Lo que vamos a hacer es programar poco a poco un juego como éste, con una estructura que a día de hoy seguirá más o menos este guión:

  1. Dibujar una pieza (básica) y programar la rotación: el artículo de hoy
  2. Programar la gravedad y el resto de movimientos
  3. Colisiones de la pieza con otras piezas y el borde de la pantalla.
  4. Haciendo líneas y retocando la mecánica anterior
  5. Puntuaciones y niveles
  6. Borde, marcador, tabla de puntuaciones
  7. Refinando los gráficos de las piezas

Para poder aprovechar estos artículos conviene que hayas hecho los anteriores artículos de la serie de Pygame con Python, y la propia serie de Python.

¡Vamos a ello!

Dibujar una pieza

Para este apartado voy a generalizar el ejercicio que hicimos con los cuadrados en los dos artículos anteriores, de tal forma que partiremos de una matriz donde expresaremos con un “1” aquellas posiciones donde hay que pintar un cuadrado, y con un “0” aquellas posiciones donde no hay que pintarlo.

Nuestra pieza, entonces, va a tener los siguientes datos:

  • Una matriz de posiciones que la va a describir
  • Un color para sus cuadros
  • Un color de fondo
  • Una referencia a la pantalla, para poder pintarse
  • El lado del cuadrado que formará parte de la pieza, en píxels

A lo largo de todo el juego voy a manejar dos tipos de coordenadas diferentes: coordenadas en píxels y coordenadas de malla. Las coordenadas de malla se refieren a los cuadrados que forman las piezas en sí, es decir, a cada una de las posiciones de la pantalla de 10×20.

Empezaremos programando una clase con estos datos, y que sea capaz de calcular sus dimensiones “en cuadros” a partir de una matriz, dejando el resto de los métodos vacíos, es decir, con la sentencia pass  de Python.

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, color, fondo, pantalla, lado_cuadrado):
        self.matriz = matriz
        self.color = color
        self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        pass
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        pass

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        pass

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pass

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        pass
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        pass

Los métodos pinta  y borra funcionarán de la siguiente forma:

  • pinta recorrerá la matriz de definición de la pieza y cuando encuentren un cuadrado lo pintarán en la pantalla usando las función privada (no visible desde fuera de la clase) __pinta_cuadrado
  • borra, por su parte, borrará el área de la pieza completa puesto que ya no es necesario implementar un comportamiento tan selectivo. Usará  __borra_cuadrado para encapsular ahí todo el código que acceda a la pantalla de Pygame.

Este es el código de estas funciones:

def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, self.fondo, rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.color)
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        rectangulo_pieza = (x_ini*self.lado_cuadrado,
                          y_ini*self.lado_cuadrado,
                          (x_ini+self.columnas)*self.lado_cuadrado,
                          (y_ini+self.filas)*self.lado_cuadrado)
        self.__borra_rectangulo(rectangulo_pieza)

Fíjate en él; no es muy complicado habiendo repasado el artículo donde empezamos a pintar y mover objetos por la pantalla. Lo único destacable, quizá, es que el cuadrado lo pintamos doblemente, para tener un cuadrado más pequeño y más brillante dentro de otro mayor.

Vamos a probarlo con la pieza L. La pieza la definiremos con una matriz, que no es otra cosa que una lista de tuplas, en donde colocaremos un “1” donde queramos pintar un cuadrado, y un “0” donde no. Así:

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

De tal forma que, para crear nuestra clase y dibujarla en una pantalla de trabajo (de momento, de 8 por 8 cuadros), os propongo el siguiente programa de pruebas:

NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla provisional
ANCHO = 8*SLOT
ALTO = 8*SLOT

x_ini = 2
y_ini = 2

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

L = Pieza(matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
L.pinta(x_ini, y_ini)

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

continuar = True
while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False

He resaltado las líneas interesantes; el resto es el clásico bucle de control en Pygame, que poco debería sorprendernos a estas alturas. Copia la clase anterior y este programa en un nuevo fichero en IDLE, y asegúrate de comenzarlo con la sentencia import pygame . Grábalo con un nombre y pulsa F5. Deberías ver una ventana como ésta:

Nuestra pieza L
Nuestra pieza L

Vamos ahora a girarla

Rotación de la pieza

Para esto simplemente tenemos que:

  • Programar la rotación de la matriz de la pieza, rellenando los métodos que ya tenemos preparados.
  • Asociar dichos métodos a la pulsación de dos teclas.
  • La rotación implicará:
    • Detectar la tecla
    • Borrar la pieza
    • Rotar la matriz de la pieza
    • Volver a pintar la pìeza

La rotación es algo que Python pone bastante fácil con una función predefinida que es algo complicada de entender: zip. Esta función recibe como parámetro elementos sobre los que se puede iterar (como son las tuplas) y te devuelve tuplas construirdas a base de asociar el primer elemento de la primera con el primer elemento de la segunda, el primero de la tercera,… y así sucesivamente. En matemáticas, esto sirve en gran medida para hacer operaciones como trasponer matrices.

Con un ejemplo se ve más claro. Introduce estas líneas en el intérprete de IDLE:

matriz_L = [(1, 0), (1, 0), (1, 1)]
matriz_L2 = list(zip(*matriz_L))
matriz_L2

Como he dicho, zip  recibe elementos sobre los que iterar, directamente: no listas. Por eso, no podemos pasarle la matriz de la pieza L directamente ya que es una lista: debemos pasarle el contenido de la lista:*matriz_L . De igual manera, zip devuelve tuplas “sueltas”, por lo que debemos usar la función list  para acabar teniendo una matriz.

Al ejecutar el código de arriba tendremos lo siguiente:

De una L de pie a una J tumbada
De una L de pie a una J tumbada

Hemos pasado de una L de pie a una J “tumbada”, es decir, esa operación es como si hubiéramos colocado un espejo inclinado 45º a nuestra L. Sólo nos queda reflejar la lista de tuplas, es decir, invertir su orden. Esto se puede hacer elegantemente con las construcciones abreviadas que nos permiten recorrer listas. Escribe ahora matriz_L2 así: matriz_L2 = list(zip(*matriz_L[::-1])). Lo que estaremos haciendo será invertir el orden de la lista de tuplas antes de aplicarle zip. Esto nos dejará una L, ahora sí, rotada a favor de las agujas del reloj:

rotado-90
Una L rotada 90º a favor de las agujas del reloj

Para conseguir la rotación contraria, deberíamos aplicar la reflexión una vez ejecutada la función list: matriz_L2 = list(zip(*matriz_L))[::-1].

Rotación contraria
Rotación contraria

Antes de lanzaros a codificar las funciones de rotación, ten en cuenta que ahora la matriz es diferente y sus dimensiones han cambiado. Por lo tanto, habrá que recalcularlas si pretendemos que las funciones de pintado y borrado de las piezas funcionen bien. Dicho lo cual, el código para nuestras funciones es el siguiente:

    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

Ya sólo queda programar la rotación en el propio programa de pruebas, para lo cual ya os dejo el código del ejercicio completo, convenientemente resaltado:

import pygame

class Pieza:
    'Modela una pieza de Cuatris :)'
    def __init__(self, matriz, color, fondo, pantalla, lado_cuadrado):
        self.matriz = matriz
        self.color = color
        self.fondo = fondo
        self.pantalla = pantalla
        self.lado_cuadrado = lado_cuadrado
        self.calcula_dimensiones()

    def calcula_dimensiones(self):
        self.filas = len(self.matriz)
        self.columnas = len(self.matriz[0])
        
    def rotar90(self):
        'Rota la pieza en el sentido horario'
        self.matriz = list(zip(*self.matriz[::-1]))
        self.calcula_dimensiones()
        
    def rotarM90(self):
        'Rota la pieza en el sentido antihorario'
        self.matriz = list(zip(*self.matriz))[::-1]
        self.calcula_dimensiones()

    def __pinta_cuadrado(self, x, y, color):
        'dibuja un cuadrado en las coordenadas de malla especificadas'
        cuadrado = (x*self.lado_cuadrado,
                    y*self.lado_cuadrado,
                    self.lado_cuadrado,
                    self.lado_cuadrado)
        cuadrado_interno = (x*self.lado_cuadrado+8,
                            y*self.lado_cuadrado+8,
                            self.lado_cuadrado-16,
                            self.lado_cuadrado-16)
        pygame.draw.rect(self.pantalla,
                         (int(color[0]/2),
                          int(color[1]/2),
                          int(color[2]/2)),
                         cuadrado,
                         0)
        pygame.draw.rect(self.pantalla, color, cuadrado_interno, 0)

    def __borra_rectangulo(self, rectangulo):
        'borra el área especificada'
        pygame.draw.rect(self.pantalla, self.fondo, rectangulo, 0)

    def pinta(self, x_ini, y_ini):
        'pinta la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        for i in range(0,len(self.matriz)):
            for j in range(0,len(self.matriz[i])):
                if self.matriz[i][j] == 1:
                    self.__pinta_cuadrado(x_ini+j, y_ini+i, self.color)
                    
    def borra(self, x_ini, y_ini):
        'borra la pieza especificada a partir de las coordenadas de malla x_ini, y_ini'
        rectangulo_pieza = (x_ini*self.lado_cuadrado,
                          y_ini*self.lado_cuadrado,
                          (x_ini+self.columnas)*self.lado_cuadrado,
                          (y_ini+self.filas)*self.lado_cuadrado)
        self.__borra_rectangulo(rectangulo_pieza)


BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (200,0,200)
FPS = 60
SLOT = 40

# pantalla provisional
ANCHO = 8*SLOT
ALTO = 8*SLOT

x_ini = 2
y_ini = 2

# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((ANCHO, ALTO))
pantalla.fill(NEGRO)

matriz_L = [(1, 0),
            (1, 0),
            (1, 1)]

L = Pieza (matriz_L, MAGENTA, NEGRO, pantalla, SLOT)
L.pinta(x_ini, y_ini)

pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

continuar = True
while continuar: 
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            continuar = False
        else:
 
            # Movimiento
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_z:
                    L.borra(x_ini, y_ini)
                    L.rotarM90()
                    L.pinta(x_ini, y_ini)

                if event.key == pygame.K_x:
                    L.borra(x_ini, y_ini)
                    L.rotar90()
                    L.pinta(x_ini, y_ini)

            pygame.display.update()

Si ahora lo ejecutas, al pulsar las teclas X y Z podrás girar la pieza como en este Vine que acabo de cargar en mi cuenta (y que si quieres puedes seguir):


Espero que os haya resultado interesante, y que al mismo tiempo la serie sobre la que iremos progresando hasta llegar a tener el juego completo os resulte motivadora. Estaremos yendo paso a paso e intercalados a los artículos que os proponía como el índice (o el camino) hacia el resultado final iremos viendo todo aquello que nos haga falta, como podrá ser la representación de texto en Pygame, entre otras cosas.

Como siempre, no dudéis en dejarme vuestros comentarios en este artículo o a través del formulario de contacto.

]]>
http://pitando.net/2016/05/12/cuatris-o-nuestro-clon-de-tetris-en-python-comenzamos/feed/ 3 1396
Comparar objetos en Python y otras sutilezas http://pitando.net/2016/05/05/comparar-objetos-en-python-y-otras-sutilezas/ Thu, 05 May 2016 09:00:46 +0000 http://pitando.net/?p=1369 Sigue leyendo Comparar objetos en Python y otras sutilezas ]]> En esta entrada vamos a continuar aprendiendo técnicas y conceptos relacionados con la programación orientada a objetos, que en la anterior entrega quedaron postergados de forma deliberada porque de por sí daban para bastante discusión.

En concreto, veremos tres cosas muy útiles cuando se trata de hacer programas potentes:

  • Comparación entre objetos de una misma clase: cómo comparar objetos correctamente.
  • Acceso a los métodos y variables de la superclase, o clase de la cual heredamos.
  • Ocultación de la información, o encapsulamiento.

¡Vamos allá!

Comparación entre objetos

La comparación entre objetos tiene truco. Siempre tiene truco. Define la clase siguiente en IDLE:

class Test:
    def __init__(self, cantidad, color):
        self.cantidad = cantidad
        self.color = color
    def set_color(self, color):
        self.color = color
    def set_id (self, cantidad):
        self.cantidad = cantidad

Esta clase es un simple contenedor de dos variables: cantidad y color. Si creamos dos objetos con valores distintos y los comparamos en IDLE, veremos que el resultado que arroja (False ) es el esperado.

t1 = Test(0, "rojo")
t2 = Test(1, "verde")
t1 == t2

 

Comparamos dos objetos con atributos cuyo valor es distinto; de momento todo bien
Pinta bien

Sin embargo, si creamos una tercera instancia con valores idénticos a la segunda, t3 = Test(1, "verde"), veremos que el resultado sigue siendo False , y esta vez no es el esperado.

Esto no nos lo esperábamos.
Esto no nos lo esperábamos.

¿Qué ocurre aquí? Que Python, si no le decimos otra cosa, compara las instancias de los objetos. Es decir, lo que nos dice Python es si las dos variables, t2 y t2, se refieren al mismo objeto, no comparan el contenido de los objetos. A un nivel máximo de detalle, lo que está comparando Python es a qué zona de memoria están apuntando esas variables:

¿Qué estamos comparando?
Las dos variables se refieren a objetos diferentes, y eso es lo que compara Python

¿Qué podemos hacer para comparar los contenidos? Tenemos dos opciones:

  1. Sobrecargar el operador ==
  2. Definir una función más, que realice las comparaciones necesarias

Ambas tienen sentido, cada una bajo ciertas circunstancias.

Sobrecargar el operador == … y los demás

Es tan sencillo como definir una función llamada __eq__ que recibirá como parámetros el objeto mismo, es decir, self , y el otro, que llamaremos other . Lo que debemos hacer dentro de esa función es… obvio: comparar los atributos del objeto, uno por uno. Si son iguales devolveremos True , y si no lo son, False .

Redefine la clase Test para que incluya la función resaltada:

class Test:
    def __init__(self, cantidad, color):
        self.cantidad = cantidad
        self.color = color
    def set_color(self, color):
        self.color = color
    def set_id (self, cantidad):
        self.cantidad = cantidad
    def __eq__(self, other):
        if self.cantidad == other.cantidad and self.color == other.color:
            return True
        else:
            return False

Tendrás que recrear todos los objetos (t1, t2 y t3), pero verás que ahora sí que sí:

Comparación por sobrecarga
Comparación por sobrecarga

No basta con sobreescribir solamente el método de igualdad si quieres tener todos los operadores referidos al contenido de tu objeto, tendrás que trabajar algo más. Igual que __eq__ tienes otros métodos al respecto como __le__ (menor o igual), __lt__ (menor estricto), __ne__ (distinto), __gt__ (mayor estricto), __ge__ (mayor o igual).

¿Qué ocurre con esta forma de resolver nuestro problema? Pues que si necesitásemos la funcionalidad por defecto del operador == , por la razón que fuera, la habríamos perdido.

Definir otra función de comparación

Si no queremos perder el comportamiento por defecto, o nativo, de los operadores, lo que podemos hacer es llamar a las funciones de otro modo, sin más. Redefine tu clase para que, en lugar de __eq__ se llame de otro modo, como puede ser es_igual(self, other). De ese modo, el operador original de Python conservará su función, que es la que te dice si las variables referencian a la misma instancia, o no.

class Test:
    def __init__(self, cantidad, color):
        self.cantidad = cantidad
        self.color = color
    def set_color(self, color):
        self.color = color
    def set_id (self, cantidad):
        self.cantidad = cantidad
    def es_igual(self, other):
        if self.cantidad == other.cantidad and self.color == other.color:
            return True
        else:
            return False

Ahora ya no podrás usar el operador, si no que tendrás que invocar la función directamente:

Función equivalente
Función equivalente

Acceso a los métodos y variables definidos en la clase de la que heredamos, o superclase

En ocasiones nos puede ser necesario acceder a datos o comportamientos que están definidos en la clase de la cual heredamos a la hora de extenderlos. No me refiero a, desde fuera, invocar los métodos heredados (en el artículo anterior, estaría hablando de usar el método frenar  en los objetos de clase BiciCarreras , estando definido en la clase Bicicleta ), sino a invocarlos desde la definición de la clase que extiende a la superclase.

Para hacerlo debemos hacer dos cosas:

  1. Todas las clases deben pertenecer a lo que en Python se llama “clases del nuevo estilo”. Esto se consigue haciendo que la clase de la que vamos a heredar herede, a su vez, de object .
  2. Una vez hecho esto, dentro de nuestras clases, podremos usar la función super(Clase, self) .

La función super(Clase, self)  localiza en la jerarquía de objetos de self  la definición de la clase de la cual nuestra Clase  hereda, y nos permite ejecutar sus métodos desde nuestro código para ampliarlos, por ejemplo.

Vamos a conseguir que una clase Prueba, heredando de Test, redefina el método es_igual escribiendo un mensaje por la pantalla además de invocar al método es_igual tal y como se define en la clase Test.

Lo primero, volvemos a declarar la clase Test para que extienda (o herede de) object:

class Test(object):
    def __init__(self, cantidad, color):
        self.cantidad = cantidad
        self.color = color
    def set_color(self, color):
        self.color = color
    def set_id (self, cantidad):
        self.cantidad = cantidad
    def es_igual(self, other):
        if self.cantidad == other.cantidad and self.color == other.color:
            return True
        else:
            return False

A continuación, definimos la clase Prueba  tomando como superclase Test , y usando la función super()  para acceder al método tal y como se definió en Test:

class Prueba(Test):
    def set_color(self, color):
        self.color = color
    def set_id (self, cantidad):
        self.cantidad = cantidad
    def es_igual(self, other):
        print("Llamando a la superclase")
        return super(Prueba, self).es_igual(other)

Si ahora creas dos instancias de Prueba y ejecutas su método de comparación, verás que el efecto es el pretendido:

Ampliación de métodos por sobrecarga
Ampliación de métodos por sobrecarga

Para acceder a las variables basta con hacer self.variable , puesto que igual que no tiene sentido sobreescribirlas, no tiene sentido acceder a las de la superclase: se heredan tal cual.

Ocultación de métodos y variables en nuestro código

Es algo que hemos visto ya en nuestros experimentos de videojuegos. Reproduzco aquí el fragmento, tal cual, que usaba para introducir una clase que representaba un cuadrado:

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

Como veis, he añadido la creación de un objeto de tipo pygame.Rect  en el constructor, pasándole la posición inicial y la dimensión de su lado. Después he implementado su comportamiento haciendo uso del objeto rect.

El método __pinta  está oculto para el programador, de forma que si intentáseis invocarla directamente, el intérprete os devolvería un error. Sólo se puede usar desde dentro del propio objeto, como en los métodos pinta y borra: es una función privada gracias al prefijo “__”, esto es, dos caracteres de subrayado (aunque realmente no está muy bien protegida, pero eso es otro tema).

Puedes probarlo en IDLE (no te hará falta ni siquiera definir una pantalla, aunque sí importar Pygame) como verás en la imagen siguiente:

Los miembros de una clase, si empiezan por __, están ocultos (o son privados)
Los miembros de una clase, si empiezan por __, están ocultos (o son privados)

Los temas tratados en este artículo pueden resultar, a golpe y porrazo, algo áridos. Sin embargo, son útiles y a medida que avancemos en nuestro camino y ganemos experiencia los iremos valorando más y más. Mientras tanto, no dudéis en dejarme vuestros comentarios en este artículo o a través del formulario de contacto.

]]>
1369
Episodio 22 – IoT HAT para Raspberry Pi en KickStarter http://pitando.net/2016/05/05/episodio-22-iot-hat-para-raspberry-pi-en-kickstarter/ Thu, 05 May 2016 05:00:05 +0000 http://pitando.net/?p=1386 Sigue leyendo Episodio 22 – IoT HAT para Raspberry Pi en KickStarter ]]>  

En este episodio os hablo de un proyecto llamado IoT Hat para Raspberry Pi, originalmente ideado para la Raspberry Pi Zero pero compatible con cualquier modelo de 40 pines. Este HAT proporciona a nuestro microordenador favorito Bluetooth y WiFi de muy baja potencia y a bajo coste.

Al proyecto le quedan 6 días en KickStarter y podéis respaldarlo con tan sólo 9$ más 5$ de gastos de envío, obteniendo a cambio un HAT que se enviaría durante el mes de agosto.

Enlaces de estos y otros comentarios del episodio:

Espero tus comentarios en http://pitando.net

]]>
1386
Eventos en Pygame (1) – Mover un objeto con tu teclado http://pitando.net/2016/04/28/eventos-en-pygame-1-mover-un-objeto-con-tu-teclado/ Thu, 28 Apr 2016 09:00:26 +0000 http://pitando.net/?p=1361 Sigue leyendo Eventos en Pygame (1) – Mover un objeto con tu teclado ]]> Si has seguido los dos artículos que han aparecido acerca de Pygame, habrás notado que el programa se articula en torno a un bucle que controla unas variables llamadas genéricamente “eventos”, esto es, cosas que han pasado. Como muchas librerías que proporcionan mecanismos para desarrollar interactividad con el usuario, Pygame proporciona un bucle de eventos, o eventos en un bucle. Esto significa que cada cierto tiempo nos da la oportunidad de saber qué ocurre en los dispositivos de entrada (teclado, ratón, joystick) y nos proporciona esa información en orden, en una estructura de tipo lista ordenada. En general, los eventos se deberían procesar en modo cola: las colas son estructuras de datos en cuya cabecera encontraremos el primer elemento que se ha introducido, y podremos así consultar su contenido en el orden correcto. En Pygame los obtendremos en forma de lista, pudiendo procesarlos en orden, pero también acceder aleatoriamente a los elementos de la lista.

Vamos a ver los tipos de eventos que ofrece Pygame para el teclado y cómo se comportan, reutilizando para ello el programa del artículo anterior.

El bucle de procesado de eventos de Pygame

El bucle de eventos es la pieza central del programa, y en ella podemos comprobar las cosas que han ido ocurriendo y traducirlas en efectos sobre la pantalla. Lo hemos hecho con un fragmento de código que suele tener una estructura como la siguiente

while [condición]:
    for event in pygame.event.get():
        if event.type == [evento]:
            [acción]

Es decir, mientras se cumpla una determinada condición, extraeremos los eventos de la cola y los examinaremos uno a uno, programando acciones en nuestro programa con respecto al tipo (y contenido) del evento. Para eso, obtendremos el contenido de la cola en forma de lista ordenada mediante pygame.event.get()

Visto así, Pygame es una librería orientada a eventos, puesto que la forma de controlar el ritmo del juego que nos propone es precisamente procesar los eventos que se registran entre una pasada y otra del bucle. Pero, la cuestión: ¿cómo de frecuente debe ser el bucle para que el programa funcione de forma fluida, pero sin hacer que la frecuencia del mismo degrade el rendimiento?

En general, desde el punto de vista del programador, no interesa consultar los eventos a más frecuencia que la de refresco en pantalla puesto que la mayoría de periféricos de entrada y de reacciones del usuario son más lentas que la pantalla. En los vídeos de alta resolución, esta frecuencia es de 60 Hz, es decir, 60 cuadros por segundo.

Para realizar este control, Pygame nos ofrece un reloj que permite estructurar el programa en intervalos temporales, de tal forma que podamos esperar a que se cumpla una determinada frecuencia. Lo hemos usado ya, son las líneas resaltadas de este código esquemático:

FPS = [valor] # frecuencia: nº de veces por segundo.
clock = pygame.time.Clock()

while [condición]
    clock.tick(FPS) # pausa hasta el siguiente "tick" de reloj

    for event in pygame.event.get():
        if event.type == [evento]:
            [acción]

clock.tick(FPS)  suspende la ejecución del programa hasta que el tiempo no llega a la siguiente fracción de segundo de la duración que hemos definido (1/FPS segundos).

Procesando teclas: eventos de teclado

Vamos a ver muy brevemente cómo mover el objeto que ya teníamos construido en el artículo anterior. Para ello os presento primero el programa modificado en donde podréis ver dos novedades (resaltadas en el código):

  • He movido la acción de mover el cuadrado por la pantalla a la propia clase Cuadrado , creando un nuevo método en ella, mover_p  a la que le podemos proporcionar la pantalla como parámetro, de forma que se pueda encapsular el borrado y el redibujado de las zonas afectadas por el movimiento.
  • Por otro lado, hay código nuevo en el bucle de eventos, que analizaré a continuación. Puedes ejecutar primero el programa y ver que, ahora, el objeto se puede mover en horizontal con las flechas del cursor. Sin embargo, se mueve “por pasos”, es decir: sólo detecta una pulsación y lo mueve de 20 en 20 píxels.
import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

    def mover_p(self, pantalla, avance_x, avance_y):
        'avance del cuadrado en un cuadro, con refresco de pantalla'
        cuadrado.borra(pantalla)
        rect_inicial = cuadrado.rect.copy()
        cuadrado.mover(avance_x, avance_y)
        rect_final = cuadrado.rect.copy()
        cuadrado.pinta(pantalla)
        pygame.display.update(rect_final.union(rect_inicial))


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
    
        # Movimiento
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                cuadrado.mover_p(pantalla, -20, 0)

            if event.key == pygame.K_RIGHT:
                cuadrado.mover_p(pantalla, 20, 0)

    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()

Vamos a examinar el bucle que procesa los eventos:

  • Ya sabemos que para obtenerlos podemos invocar a pygame.event.get(), el cual los devuelve en una lista sobra la que podemos iterar con cualquier construcción de tipo bucle.
  • Dentro del bucle, comprobaremos para cada evento su tipo mediante el valor event.type . En nuestro programa controlamos los eventos de tipo pygame.QUIT ,  y pygame.KEYDOWN  (“tecla abajo”).
    • Una vez detectado el tipo de evento que nos interesa, y atendiendo a esto, podemos examinar la información que trae dicho evento como contenedor de datos.
    • En este caso, nos interesa el atributo event.key, que nos dice la tecla pulsada.
    • Para este artículo reaccionaremos ante pygame.K_LEFT  y pygame.K_RIGHT , que se corresponden con las flechas izquierda y derecha del cursor.

El funcionamiento de este programa es “paso a paso”, es decir, Pygame sólo va a proporcionar una pulsación de teclas sin importar el tiempo que mantengamos la tecla pulsada. Así pues, no responde a un movimiento fluido.

Para conseguir un movimiento fluido basta con configurar el módulo de teclado con la sentencia que os he resaltado en la siguiente versión del programa (también he modificado las líneas que ejecutan el movimiento para suavizarlo un poco):

import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

    def mover_p(self, pantalla, avance_x, avance_y):
        'avance del cuadrado en un cuadro, con refresco de pantalla'
        cuadrado.borra(pantalla)
        rect_inicial = cuadrado.rect.copy()
        cuadrado.mover(avance_x, avance_y)
        rect_final = cuadrado.rect.copy()
        cuadrado.pinta(pantalla)
        pygame.display.update(rect_final.union(rect_inicial))


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()
pygame.key.set_repeat(True)

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
    
        # Movimiento
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                cuadrado.mover_p(pantalla, -2, 0)

            if event.key == pygame.K_RIGHT:
                cuadrado.mover_p(pantalla, 2, 0)

    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()
    

En el sentido de ampliar esta sencilla técnica con todas sus posibilidades, y ya para terminar este artículo, os dejo en el apartado de referencias una serie de enlaces que tener a mano para dominar los eventos de teclado (están en inglés). Con lo que sabéis ya podéis hacer un pequeño experimento de juego en el que un cuadrado tenga que llegar hasta el otro esquivando objetos que se muevan por la pantalla. ¿Os atrevéis?

No dudéis en dejarme vuestros comentarios en este artículo o a través del formulario de contacto.

Referencias

  • Referencia de los eventos en Pygame: qué tipos de eventos hay tanto de teclado como de otros dispositivos, cuál es su estructura, y cómo gestionarlos o leer su contenido: http://www.pygame.org/docs/ref/event.html
  • Mapa de teclado – teclas que reconoce Pygame: http://www.pygame.org/docs/ref/key.html. En este enlace, además, encontraréis la referencia completa del teclado para poderlo configurar.
]]>
1361
Movimiento y colisiones en Pygame http://pitando.net/2016/04/21/movimiento-y-colisiones-en-pygame/ http://pitando.net/2016/04/21/movimiento-y-colisiones-en-pygame/#comments Thu, 21 Apr 2016 09:00:24 +0000 http://pitando.net/?p=1328 Sigue leyendo Movimiento y colisiones en Pygame ]]> Gran parte de los videojuegos se basan en un funcionamiento básico en el que un objeto interactúa con otro en la pantalla. En esos videojuegos, la gracia está en evitar a otros objetos, o por el contrario en buscar esos otros objetos. Piensa en Pac-Man, o el comecocos de toda la vida: el protagonista debe comer todas las fichas de un laberinto, mientras los fantasmas le persiguen. Las paredes no se pueden atravesar, y si los fantasmas tocan a nuestro héroe, éste muere. Pero sin embargo, si el comecocos se come una ficha especial, la mecánica es la inversa: se puede comer a los fantasmas enviándolos a una especie de cárcel.

De esta forma, tenemos:

  • Varios objetos moviéndose: un comecocos y cuatro fantasmas.
  • Un objeto especial (el comecocos) debe ir colisionando con cuantos objetos inmóviles pueda (fichas), pero no puede atravesar los límites que definen el laberinto (las paredes).
  • Si los fantasmas chocan con el comecocos, en función del estado de este último, ocurren eventos especiales (muerte del comecocos o captura del fantasma).

Todo en este tipo de juegos se basa en dos mecanismos: el movimiento y la detección de colisiones. Vamos a examinar el funcionamiento de estos dos mecanismos con Pygame.

Necesitarás haber leído…

Conviene también que repases la serie de creación de un videojuego con Scratch, para que puedas comparar conceptos y técnicas.

Planteamiento

Lo que vamos a hacer en este artículo es:

  1. Inicializar correctamente Pygame y definir una ventana para trabajar
  2. Dibujar dos objetos cuadrados, de diferente color. Usaremos los recursos de dibujo de Pygame, es decir: no vamos a cargar imágenes por esta vez
  3. Moveremos un cuadrado contra otro
  4. Detectaremos su colisión
  5. Cerraremos Pygame de forma limpia, tanto un tiempo después de que el primer objeto colisione con el segundo, como si el usuario hace click en el aspa de la ventana

Lo haremos con orientación a objetos, es decir: cada uno de los objetos cuadrados será una instancia de una clase en la cual definiremos su comportamiento.

Condiciones iniciales

Vamos a definir una ventana de 640×480 píxels y fondo negro. Sobre ella dibujaremos dos cuadrados, uno blanco y uno magenta. El blanco lo moveremos hacia el magenta procurando que vayan a colisionar, a una velocidad de 60 píxels por segundo, es decir, 1 píxel en cada cuadro, si el refresco de la ventana es de 60 cuadros por segundo.

La parte inicial de nuestro programa, donde definimos todos esos valores, es la siguiente:

import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

# inicializamos pygame
pygame.init()

#definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

Todavía no podemos colocar los cuadrados en la pantalla, porque no tenemos la clase Cuadrado  definida.

Clase Cuadrado

Vamos a crear la plantilla o el modelo de un cuadrado para poder crear cuantas instancias queramos. Definiremos un cuadrado móvil de la siguiente forma:

  • Tendrá un color de relleno
  • Sabrá cual es el color de fondo de la pantalla
  • Una posición inicial, formada por un par de coordenadas horizontales (x) y verticales (y).
  • Sabrá si está colisionando con otro Cuadrado que le señalaremos, es decir, podremos preguntarle al cuadrado “¿estás colisionando con este otro cuadrado?
  • Podrá dibujarse a sí mismo en la pantalla, y borrarse de ella

Recuerda: definir una clase implica definir sus datos y su comportamiento. Empecemos por los datos:

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado

En este momento tenemos que resolver tres comportamientos: el comportamiento gráfico para pintarse a sí mismo y para borrarse, el comportamiento móvil y el que nos permite saber si está en estado de colisión con otro cuadrado. Para eso vamos a introducir el objeto pygame.Rect  y el módulo pygame.draw .

Recursos de Pygame para el comportamiento del cuadrado

El objeto pygame.Rect  es un rectángulo con ciertos métodos asociados, como es precisamente el de moverse y el de las operaciones gráficas más comunes entre formas geométricas: unión, intersección,…

La parte que nos interesa a nosotros es la del cálculo de la colisión, y la del movimiento. Para eso usaremos lo siguiente: asumamos que rect es un objeto de la clase pygame.Rect, es decir, que hemos hecho rect = pygame.Rect(...) con los argumentos que fueran necesarios. Lo mismo si veis un  rect2.

  • rect.colliderect(rect2)  devuelve True  si los dos rectángulos rect  y rect2  están en un estado de colisión, es decir, si sus áreas coinciden (se solapan) en, al menos, un píxel.
  • rect.move(avance_x, avance_y)  devuelve una copia del rectángulo desplazada lo especificado en los avances que le pasamos como argumento. El movimiento tiene lugar en un cuadro, es decir, en un sólo refresco de la pantalla. Si escribimos rect.move_ip(avance_x, avance_y)  es el propio rectángulo  rect el que se mueve, es decir: no se crea copia alguna sino que el mismo rectángulo sobre el que se invoca el método move_ip se desplaza. _ip es un sufijo que significa in place (en su lugar) y que indica esto mismo.

El módulo pygame.draw  permite dibujar muchas formas y líneas, entre las cuales se encuentra el rectángulo. La función pygame.draw.rect(pantalla, color, rect, grosor)  dibuja un rectángulo como sigue:

  • Hay que proporcionarle una superficie sobre la cual dibujar, esto es, un objeto de clase Surface, como es la pantalla de Pygame que hemos configurado.
  • Se le proporciona un color en forma de tupla (tuple de Python) para el objeto a dibujar. En dicha tupla especificamos los valores de rojo, verde y azul que codifican el color RGB del cuadrado. Los colores que hemos definido ya los hemos definido en formato de tupla, son objetos tuple de Python: NEGRO = (0, 0, 0)
  • El tercer parámetro, rect , es el objeto de clase pygame.Rect que será dibujado
  • Por último, el cuarto parámetro es el grosor, en píxels, de las líneas que forman el cuadrado. Si se le proporciona el valor 0, el cuadrado saldrá relleno del color que le hayamos indicado.

Vamos, pues, a escribir la clase Cuadrado

Código Python de la clase Cuadrado

Aquí va mi propuesta:

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

Como veis, he añadido la creación de un objeto de tipo pygame.Rect  en el constructor, pasándole la posición inicial y la dimensión de su lado. Después he implementado su comportamiento haciendo uso del objeto rect.

El método __pinta  está oculto para el programador, de forma que si intentáseis invocarla directamente, el intérprete os devolvería un error. Sólo se puede usar desde dentro del propio objeto, como en los métodos pinta y borra: es una función privada gracias al prefijo “__”, esto es, dos caracteres de subrayado (aunque realmente no está muy bien protegida, pero eso es otro tema).

Puedes probarlo en IDLE (no te hará falta ni siquiera definir una pantalla, aunque sí importar Pygame) como verás en la imagen siguiente:

Los miembros de una clase, si empiezan por __, están ocultos (o son privados)
Los miembros de una clase, si empiezan por __, están ocultos (o son privados)

Incluye el código e tu clase Cuadrado en tu programa, tras el import pygame  pero antes de la línea # inicializamos pygame

Bucle del programa

Lo que vamos a hacer ahora es el propio programa en sí:

  • Crearemos los dos objetos, alineados (para que haya colisión)
  • Los pintaremos en la pantalla
  • Moveremos uno de ellos hacia el otro hasta que colisionen. Para esto hay que tener en cuenta que no basta con mover el objeto de tipo pygame.Rect, porque no tiene una pantalla asociada: fíjate que cuando invocas a su constructor no le puedes proporcionar como parámetro una referencia a un objeto de tipo Surface (como es la ventana que hace de pantalla). Por lo tanto, tú tendrás que realizar esa relación entre objeto y pantalla. Así pues, mover un objeto implica, además, reflejar dicho movimiento en la pantalla:
    • Borrarlo de la pantalla en su posición actual
    • Moverlo a una nueva posición
    • Pintarlo en la pantalla, en su nueva posición
  • Actualizaremos la pantalla
  • Si hay colisión, escribiremos un mensaje por la consola y saldremos del programa
  • Si el usuario hace click en el botón en forma de aspa de la ventana de Pygame, saldremos del programa

Usaremos muchos de los recursos que hemos aprendido en el primer ejemplo de prueba de Pygame, que salió en el blog hace unas semanas, como es la detección de eventos en la ventana del programa, el uso del reloj de Pygame para controlar el refresco de la pantalla y las funcionalidades de refresco (o acualización de la misma).

Aquí tenéis mi propuesta, que iría al final del fichero:

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # borrar el cuadrado de la pantalla
    cuadrado.borra(pantalla)
    cuadrado.mover(1, 0)
    cuadrado.pinta(pantalla)
    pygame.display.update()
    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()

Como veis, el código del programa, que en sucesivos artículos iremos llamando “juego”, queda sencillísimo cuando trabajamos con Python orientado a objetos: fijaos por ejemplo en la condición del bucle.

El código completo está a continuación.

import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)
    


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # borrar el cuadrado de la pantalla
    cuadrado.borra(pantalla)
    cuadrado.mover(1, 0)
    cuadrado.pinta(pantalla)
    pygame.display.update()
    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()

Pulsa F5 desde el editor del IDLE, y verás que aparecen dos cuadrados y uno se mueve hasta colisionar con el otro. Cuando esto ocurre, el programa imprime un mensaje en la ventana del intérprete y, a los 3 segundos, termina:

También puedes probar a parar la ejecución con el botón en forma de aspa.

Optimización: actualización selectiva de la pantalla

En Python es muy frecuente encontrar juegos escritos con Pygame en los que la pantalla parpadea y el refresco es lento. Esto suele pasar porque los programadores no seleccionan cuidadosamente las zonas de la pantalla que deben ser actualizadas para optimizar el comportamiento de su juego: no es lo mismo actualizar dos zonas de algo más de 35 píxels de lado que toda una pantalla de 640 por 480 píxels.

La clave está en el uso de la función pygame.display.update(), la cual admite una lista de zonas rectangulares pygame.Rect para actualizar. El siguiente código que te propongo a continuación sólo actualiza la unión de los rectángulos correspondientes a la posición inicial del cuadrado blanco y la final, es decir, el trocito de pantalla que se ha visto afectado por el movimiento. Las líneas relevantes están resaltadas.

import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)
    


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

    # borrar el cuadrado de la pantalla
    cuadrado.borra(pantalla)
    rect_inicial = cuadrado.rect.copy()
    cuadrado.mover(1, 0)
    rect_final = cuadrado.rect.copy()
    cuadrado.pinta(pantalla)
    pygame.display.update(rect_final.union(rect_inicial))
    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()

Lo que he hecho ahí ha sido sacar copias del rectángulo para calcular la unión. Si no entiendes por qué hay que sacar copias, prueba a quitar el .copy()  de las líneas 70 y 72 🙂 En breve recapitularé novedades de programación orientada a objetos para complementar la entrada de la semana pasada.

Conclusiones: algo que te llevas

Mover un cuadrado por un escenario y calcular la colisión con otro puede parecer algo trivial, pero es extremadamente útil.

Una técnica muy ocurrente y efectiva para calcular colisiones entre personajes representados mediante imágenes es construir un objeto consistente en un rectángulo ligeramente más pequeño que la imagen y usar dicho rectángulo para representar al héroe del juego en lo que a impactos y colisiones se refiere.

De esa forma, el juego representa a un comecocos moviéndose por un escenario, pero a efectos prácticos mueve también un rectángulo más pequeño que el comecocos y en su misma posición. De la misma manera, los fantasmas se mueven por partida doble: la imagen del fantasma y su rectángulo de impactos asociado.

Un comecocos con un rectángulo asociado para cálculo de colisiones
Un comecocos con un rectángulo asociado para cálculo de colisiones

Si reducimos el detectar la colisión entre el comecocos y un fantasma a detectar la colisión entre sus rectángulos asociados, programar el juego será mucho más sencillo y menos costoso computacionalmente que detectar el solapamiento entre píxels de color diferente al del fondo del juego (píxels que se suelen llamar píxels “opacos”). Imaginaos esto con 4 fantasmas, o imaginaos por un momento un juego de disparar a cientos de marcianitos esquivando sus disparos…

Espero que os haya resultado interesante y que os sirva no sólo para aprender a mover cuadrados, sino también para entrever lo conveniente que es la programación orientada a objetos cuando de lo que se trata es de modelar un objeto, un cuadrado en este caso. Poco a poco iréis viendo de una forma mucho más clara la potencia de estos conceptos, a medida que programamos más ejemplos con Pygame… hasta que los podamos llamar videojuegos 🙂

No dudéis en dejarme vuestros comentarios en este artículo o a través del formulario de contacto.

]]>
http://pitando.net/2016/04/21/movimiento-y-colisiones-en-pygame/feed/ 1 1328
Episodio 21 – Microprocesadores, Microcontroladores y System on a Chip http://pitando.net/2016/04/21/episodio-21-microprocesadores-microcontroladores-y-system-on-a-chip/ Thu, 21 Apr 2016 05:00:45 +0000 http://pitando.net/?p=1324 Sigue leyendo Episodio 21 – Microprocesadores, Microcontroladores y System on a Chip ]]>  

En el episodio de hoy abordo tres conceptos que he dado por supuesto en el resto de los episodios y entre los que hay diferencias que conviene tener claras: microprocesadores, microcontroladores y lo que se ha venido a llamar System on a Chip, o SoC.

Es un capítulo de glosario, en el que hago mucho hincapié en el propósito de cada uno de estos tipos de sistemas programables: sistemas de propósito general en contraposición sistemas embebidos o empotrados, cuyo propósito es mucho más específico.

Espero tus comentarios en http://pitando.net y a través de twitter (https://twitter.com/pitandonet) y Facebook (https://facebook.com/pitandonet)

Foto del episodio: AMD Opteron “Barcelona”, de 4 núcleos, sin el encapsulado.
© American Micro Devices, Inc. (AMD), 2008

]]>
1324
Python y programación orientada a objetos http://pitando.net/2016/04/14/python-y-programacion-orientada-a-objetos/ http://pitando.net/2016/04/14/python-y-programacion-orientada-a-objetos/#comments Thu, 14 Apr 2016 09:00:12 +0000 http://pitando.net/?p=1308 Sigue leyendo Python y programación orientada a objetos ]]> Nuestra próxima incursión en la programación de videojuegos es una oportunidad fantástica para introducir la programación orientada a objetos en nuestra vida, de una forma sencilla y práctica con Python. Usaremos como ejemplos… galletas y bicicletas 🙂

Bowden Spacelander Bicycle - Museo de Brooklyn (licencia CC 3.0 - http://creativecommons.org/licenses/by/3.0)
Bowden Spacelander Bicycle

(Foto: Museo de Brooklyn – licencia CC 3.0)

La programación orientada a objetos es un paradigma (estilo, marco conceptual, filosofía) de programación que trata de organizar los programas modelando objetos, como si objetos del mundo real se tratasen. Las primeras nociones de objetos en un programa informático se dieron en el Instituto Tecnológico de Massachussets en las décadas de los 1950 y 1960. Por aquel entonces, un objeto era algo tan vago como “un elemento identificable con atributos asociados”. Este paradigma se formalizó en Oslo en el Centro de Cálculo Noruego, en el que intentaban simular movimientos navíos y sus cargas entre diferentes puertos usando Simula I. Como encontraban limitaciones en el lenguaje a la hora de plantear un modelo lo suficientemente completo, crearon una versión del lenguaje llamada Simula 67 que ya introducía muchos de los conceptos de lenguajes modernos orientados a objetos. Con este lenguaje ya podían declarar tipos concretos de barcos con propiedades y comportamientos muy detallados y específicos, creando así simulaciones mucho mejores. Anteriormente a este punto, los conceptos de la programación orientada a objetos eran difusos, no estaban explícitos en los lenguajes y eran más bien una forma de organizar el código que un modelo de programación establecido.

En este artículo vamos a ver los conceptos fundamentales de la programación orientada a objetos, y los aplicaremos a Python.

Conceptos básicos de la programación orientada a objetos

La programación orientada a objetos se basa en el concepto de objeto, como su nombre indica. Un objeto es una construcción de software que:

  1. Pertenece a una clase, o modelo, del cual es una representación concreta o instancia. Es como pensar en la idea general, abstracta, etérea… de bicicleta (clase) y ver tu bicicleta de montaña roja, con ruedas de 26 pulgadas y 24 marchas (objeto, instancia). Una clase sirve para crear objetos a partir de unos datos, de la misma forma que un molde de galletas crea galletas si le das la masa necesaria.
  2. Es totalmente identificable con respecto al resto. Es decir, tiene una identidad a través de la cual el programador (y el resto del programa) puede referirse a él
  3. Contiene datos en forma de variables que, en el contexto de un objeto, suelen llamarse atributos, o campos, que lo describen (en el caso de la bicicleta: color, talla, si es de montaña, de carreras, plegable,…). También contiene código, organizado en funciones que, en programación orientada a objetos, suelen llamarse métodos. Estos métodos ayudan a desempeñar su función o comportamiento

En cuanto a las clases, merecen su propio apartado. Una clase, como hemos dicho ya, es la definición del modelo que siguen todos los objetos que la representan.

  1. Una clase puede heredar propiedades de otra más general, especializándola. Por ejemplo, podemos tener una clase “bicicleta” con tres clases que heredan sus conceptos: bicicleta de montaña, bicicleta de carreras, bicicleta eléctrica. Son tres bicicletas más especializadas: más concretas o más específicas. En algunos lenguajes de programación como C++, una clase puede heredar de varias otras clases (herencia múltiple)
  2. En la definición de la clase es donde programamos los métodos y declaramos los atributos. Existen atributos de clase y atributos de instancia
    • Los atributos de clase se comparten entre todos los objetos de dicha clase. Es decir, todos pueden leer y escribir su valor
    • Los atributos de instancia,  o de objeto, sólo están accesibles en un objeto concreto
  3. Un programa orientado a objetos ejecuta, por lo general (hay excepciones), los métodos de los objetos, no de las clases. Por lo tanto, debemos pasar de una clase a un objeto mediante la creación de instancias. Hay quien llama a eso “instanciación”, pero a mí no me gusta nada

En cuanto a los métodos, conviene decir que se pueden redefinir, o sobreescribir: un determinado método del objeto bicicleta puede re-escribirse en las clases que heredan de bicicleta para, por ejemplo, tener un comportamiento diferente en la bicicleta eléctrica que en la bicicleta de montaña (particularmente en ese ejemplo, pedalear es bastante diferente). También se pueden sobrecargar: dos métodos con el mismo nombre (u “operador”) pero diferentes parámetros puede desempeñar comportamientos diferentes dentro de una misma clase.

Este tipo de discusiones pueden resultar densas, así que hasta aquí los conceptos de este artículo. ¡No son todos los que hay!, así que seguro que volveremos sobre este tema en artículos venideros. Vamos a practicar un poco con Python y ver algunos ejemplos que espero que os resulten aclaratorios.

Python orientado a objetos

Para crear una clase en Python usaremos la palabra clave class , de la siguiente forma:

class Nombre:
    'Documentación de la clase'
    Cuerpo de la clase

Por convenio, los nombres de clase empiezan por mayúscula. Igual que al definir funciones, la primera línea documenta la clase. El cuerpo de la clase son variables y funciones y van con un nivel de sangría, o “con un tabulador”. Vamos a hacer un ejemplo sencillo (y burdo) sobre el que ir comentando cosas.

class Bicicleta:
    'Clase base de bicicleta'

    def __init__(self):
        self.velocidad = 0

    def pedalear(self, rpm):
        self.velocidad = rpm*0.4

    def frenar (self, presion, tiempo):
        if self.velocidad > presion*tiempo/100 > 0:
            self.velocidad = self.velocidad - presion*tiempo/100
        else:
            self.velocidad = 0

En este ejemplo definimos una bicicleta con un sólo atributo: su velocidad. Asociados a ella tenemos dos métidos: pedalear, el cual incrementa la velocidad, y frenar, que la disminuye. Independientemente de lo simplificado y las muchas razones por las que pueden fallar estos dos métodos, fijaos en lo siguiente:

  • Existe un método llamado __init__ . Este método es el encargado de fijar las condiciones iniciales de un objeto, en el momento de su creación: es el constructor.
  • Todos los métodos tienen de primer parámetro uno que se llama self. Podía llamarse de cualquier otra forma e, incluso, podríamos llamarle de forma diferente en cada método; es una referencia a la instancia del objeto. Es decir, cuando la bicicleta esté creada,  self la representará, de tal forma que podremos referirnos a ella y consultar o modificar sus valores, o incluso llamar a sus métodos desde sí misma.
    • Para modificar la velocidad de la bicicleta hay que acceder a ella a través de  self , mediante  self.velocidad .
    • De la misma forma hemos declarado la variable  velocidad en el constructor __init__ .

Por lo demás, nada nuevo. Vamos a ver cómo se crea y se usa un objeto: abre IDLE y, en un mismo fichero, copia la declaración de la clase Bicicleta más estas líneas:

bicicleta1 = Bicicleta();

bicicleta1.pedalear(60)
print("Velocidad", bicicleta1.velocidad)
bicicleta1.frenar(4, 10)
print("Velocidad", bicicleta1.velocidad)
bicicleta1.frenar(100,100)
print("Velocidad", bicicleta1.velocidad)

Ten cuidado de no sangrar estas últimas líneas. Pulsa F5 desde el editor de IDLE. El resultado que deberías observar es el siguiente:

Nuestro primer objeto en acción
Nuestro primer objeto en acción

Para crear la instancia debemos llamar al constructor; no mediante su nombre __init__ , que es interno, sino usando el nombre de la clase: objeto = Nombre_clase(parametro1, parametro2,...). Además, self  se omite: el propio intérprete se encarga de incluirlo siempre. En nuestro ejemplo, el constructor no tiene argumentos, pero pronto le añadiremos alguno. Por otro lado los métodos y atributos del objeto se acceden mediante la construcción objeto.metodo(parametro1, parametro2,...) y objeto.atributo.

Realmente ya estás familiarizado con esta sintaxis, puesto que la hemos usado en muchas ocasiones en los artículos anteriores.

Ya tenemos una bicicleta básica; vamos a especializarla en una subclase, mediante herencia, creando la bicicleta de carreras. Tendrá un color, un número de platos y un número de piñones para poder cambiar de marchas.

Para crear una subclase en Python sólo tenemos que especificar el nombre de la clase de la cual hereda entre paréntesis, en la declaración. Así:

class BiciCarreras(Bicicleta):
    'Clase correspondiente a una bicicleta de velocidad'

Si dejásemos esa declaración así, podríamos crear instancias de la clase BiciCarreras  que, a efectos de datos y comportamiento, sería idéntica a la clase Bicicleta : puedes probarlo si quieres creando una instancia y ejecutando el método pedalear . Vamos a especializarla. Completa la definición de la clase para finalizar con este código que viene a continuación, y escríbelo al final de tu fichero Python de pruebas que has debido comenzar con la clase Bicicleta:

class BiciCarreras(Bicicleta):
    'Clase correspondiente a una bicicleta de velocidad'
    
    def __init__(self, color, platos, pinhones):
        self.color = color
        self.platos = platos
        self.pinhones = pinhones
        self.marchas = platos * pinhones
        self.plato_actual = math.floor(platos/2)
        self.pinhon_actual = math.floor(pinhones/2)

    def pedalear (self, rpm):
        self.velocidad = rpm*(self.pinhon_actual * self.plato_actual)/self.marchas

    def subir_pinhon (self):
        if self.pinhon_actual < self.pinhones:
            self.pinhon_actual = self.pinhon_actual + 1

    def bajar_pinhon (self):
        if self.pinhon_actual > 1:
            self.pinhon_actual = self.pinhon_actual - 1

    def subir_plato (self):
        if self.plato_actual < self.platos:
            self.plato_actual = self.plato_actual + 1

    def bajar_plato (self):
        if self.plato_actual > 1:
            self.plato_actual = self.plato_actual - 1
    
    def __str__ (self):
        return "Bicicleta de carreras de color " + self.color + " y " + str(self.marchas) + " marchas."

Vamos a ir identificando conceptos en el código:

  1. Estamos introduciendo un constructor con parámetros en la función __init__
  2. Estamos sobreescribiendo el método pedalear para diferenciar el comportamiento de esta clase de bicicleta respecto a la anterior, de la cual hereda, para hacerla más específica. Estamos además sobreescribiendo además el método __str__ , que sirve para particularizar el texto que se imprime cuando hacemos un print(instancia_bici_carreras)
  3. Definimos nuevos atributos y métodos propios de una bicicleta con más de una marcha.

En el fichero de Python que has empezado al comienzo de este artículo, importa el módulo math (lo tendrás que hacer en la primera línea), complétalo con esta clase BiciCarreras, y además con las siguientes:

print(bicicleta1)
bicicleta2 = BiciCarreras("rojo", 3, 8)
print(bicicleta2)
bicicleta2.pedalear(60)
print("Velocidad", bicicleta2.velocidad)
bicicleta2.subir_pinhon()
bicicleta2.subir_plato()
bicicleta2.pedalear(60)
print("Velocidad", bicicleta2.velocidad)

Fíjate que la primera línea imprime directamente el objeto instancia de la bicicleta más genérica. La salida que deberías ver ahora es la siguiente:

Salida por consola del ejemplo completo
Salida por consola del ejemplo completo

Localiza lo siguiente:

  • Imprimir la instancia de una clase en la que no sobreescribimos el método __str__  ofrece una información muy técnica (posición en la jerarquía de objetos dentro del intérprete de Python y dirección de memoria donde se ubica). Por otro lado, al sobreescribirlo podemos obtener una descripción más clara si podemos prescindir de la información “estándar”.
  • El comportamiento de la BiciCarreras  es totalmente diferente al pedalear y, como pretendíamos, reacciona a los cambios de marcha (piñón y plato).

Como introducción a la programación orientada a objetos, este contenido es suficiente. A medida que vayamos usando estos conceptos con los sucesivos artículos, sobre todo en los de videojuegos, irán apareciendo nuevos conceptos y técnicas más avanzados que acabaré reuniendo en una continuación de este artículo. Algunos de esos conceptos son los siguientes:

  • Comparación entre objetos.
  • Serialización de objetos: cómo guardar objetos en un fichero para luego recuperarlos.
  • Acceso a métodos de la clase de la cual heredamos (“superclase”).
  • Ocultación de información (encapsulamiento): cómo podemos definir algunos de los miembros de una clase (atributos y métodos) para que no puedan ser accedidos directamente.

Como ejercicio para vosotros, os propongo que reescribáis el ejemplo de la calculadora usando orientación a objetos.

 

]]>
http://pitando.net/2016/04/14/python-y-programacion-orientada-a-objetos/feed/ 2 1308
Videojuegos en Python: instalar y probar Pygame http://pitando.net/2016/04/07/videojuegos-en-python-instalar-y-probar-pygame/ http://pitando.net/2016/04/07/videojuegos-en-python-instalar-y-probar-pygame/#comments Thu, 07 Apr 2016 08:00:53 +0000 http://pitando.net/?p=1284 Sigue leyendo Videojuegos en Python: instalar y probar Pygame ]]> Llega abril y con él vuelven los videojuegos a PItando: vamos a empezar la andadura de programar algún juego en Python usando las librerías Pygame.

Pygame es un proyecto que nació en el año 2000 y que permite desarrollar muy rápidamente (si se tiene experiencia) juegos en dos dimensiones. De hecho, hay competiciones de juegos desarrollados con límite de tiempo basadas en el uso de este desarrollo. De todas formas, Python y Pygame no son Scratch, así que trabajar con estos recursos va a requerir soltura con Python y cierta madurez que Scratch no requiere.

Por hoy lo que haremos será instalar el entorno, probar que funciona y examinar minuciosamente un primer ejemplo tomado de la web oficial.

Vamos a empezar, entonces.

Instalar Pygame en Windows para Python 3.4

Para instalar Pygame en Windows debemos tener instalada una versión 3.4 de Python: la 3.5 no sirve y, si lo intentamos, no funcionará. Si no tienes instalado Python en Windows sigue las instrucciones del artículo correspondiente de PItando.

  1. Descargamos Pygame de la página de descargas, actualmente alojada en Bitbucket: https://bitbucket.org/pygame/pygame/downloads.
    • En esta página, escogeremos de la lista la última versión de Pygame, identificando el sistema operativo, arquitectura (x86 para 32 bits, amd64 para 64 bits) y la versión de Python. Por ejemplo, la última versión (1.9.2) para windows de 64 bits y para Python 3.4 es pygame-1.9.2a0-hg_8d9e6a1f2635+.winamd64py3.4.msi:
      • win indica Windows
      • amd64 indica 64 bits
      • py3.4 indica Python 3.4
  2. Una vez descargado hacemos doble click en el instalador y seguiremos un asistente que recojo en esta galería (fíjate en la numeración de los pasos, bajo las imágenes):
Haz click para ver el pase de diapositivas.
  • Con esto habremos terminado. Pasa a la sección “Probar Pygame y analizar un primer ejemplo” si no tienes más sistemas operativos donde instalarlo.

Instalar Pygame en Mac OS X para Python 3.4

Para Mac OS X la instalación es radicalmente diferente porque no existe un instalador preparado para los Mac modernos y compatible con Python 3.4. Así pues, lo que hay que hacer es instalarlo a mano compilándolo desde los archivos que manejan en pygame.org para desarrollar. De la misma forma, Pygame no va a funcionar en Python 3.5, por lo que asegúrate de instalar la versión 3.4.

Lo primero, para que no haya sorpresas durante el proceso, instalaremos (si no lo tenemos ya) XCode y XQuartz. XCode es la herramienta de desarrollo por excelencia del mundo Apple y nos hará falta para poder compilar Pygame desde un terminalXQuartz es un servidor de ventanas de tipo X11, muy parecido y totalmente compatible con el de cualquier Linux y, por supuesto, con el de la Raspbian de tu Raspberry Pi. Es un componente necesario para poder crear ventanas con Pygame.

Pantalla de descarga de XQuartz
Pantalla de descarga de XQuartz

La instalación es muy sencilla; sólo hay que avanzar por ella, aceptar la licencia e introducir la contraseña de Apple ID para otorgar permisos a hacer cambios en el Mac. Una vez termine tendremos que salir de la sesión y volver a entrar (o reiniciar el Mac) para que esta versión de X11 se ejecute por defecto con el sistema.

Si no lo has hecho, instala Python 3.4 usando el artículo de PItando dedicado a esta tarea.

Una vez hecho esto, instalaremos Homebrew, Mercurial y Git. Homebrew es un gestor de paquetes al estilo del apt-get de Raspbian; Mercurial y Git son sistemas de control de versiones de programas, usados para desarrollar en grupo. De repositorios Git y Mercurial es de donde nos tendremos que descargar el código de Pygame, las dependencias, y toda una suerte de cosas adicionales.

Cuanado hayas acabado de instalar Xcode, XQuartz y Python, escribe ahora estas sentencias en un terminal, de una en una. Si no encuentras el Terminal en el Launchpad, ábrelo desde Spotlight con ⌘ + Espacio, escribiendo “Terminal”.

usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)
brew install mercurial
brew install git
brew install sdl sdl_image sdl_mixer sdl_ttf smpeg portmidi
pip3 install hg+http://bitbucket.org/pygame/pygame

La primera de las sentencias anteriores instala Homebrew; las tres siguientes los prerrequisitos necesarios para Pygame, y la última descarga y compila Pygame usando pip3: un gestor de paquetes de Python que has instalado junto con Python 3.4.

Si todo ha ido bien, el proceso debería terminar con un mensaje “Successfully installed pygame-1.9.2a0 “. Con esto habremos terminado. Pasa a la sección “Probar Pygame y analizar un primer ejemplo” si no tienes más sistemas operativos donde instalarlo.

Pygame en la Raspberry Pi, para Python 3

En este caso, si tienes la distribución Raspbian Jessie actualizada, tendrás ya todo instalado. Pasa a la siguiente sección.

Probar Pygame y analizar un primer ejemplo

Vamos a copiar el ejemplo que hay en una de las páginas de introducción a Pygame, que hace rebotar una pelota en los bordes de una ventana. El código es el que pego a continuación, pero lo he modificado para limitar la velocidad del programa y para situarme en el directorio de trabajo, en donde tengo la imagen de la pelota. Además he puesto un control de excepciones (try:...except)para capturar el Ctrl + C con el que solemos interrumpir nuestros programas para terminar su ejecución.

En cualquier caso: si lo usas tal cual aparece en el listado del enlace superior, no verás gran cosa porque la pelota se mueve demasiado rápido. He marcado las líneas modificadas con comentarios.

# He incluido la librería "os" para manejar directorios
import sys, os, pygame
pygame.init()

# Me sitúo en el directorio de trabajo. Descomenta y edita la que corresponda
# según tu sistema operativo
# Windows
# os.chdir("c:\prog\python34\pitando")
# Mac OS X
# os.chdir("/Users/gvisoc/pitando")
# Raspberry Pi
os.chdir("/home/pi/pitando")

size = width, height = 320, 240
speed = [2, 2]
black = 0, 0, 0
screen = pygame.display.set_mode(size)
ball = pygame.image.load("ball.gif")
ballrect = ball.get_rect()

# Limitamos el número de cuadros por segundo de la animación
FRAMERATE = 60
# y cargamos un reloj que usará el valor anterior
clock = pygame.time.Clock()

try:
    while 1:
        # Esperamos al siguiente "tick", a intervalos de 1 / 60 segundos.
        clock.tick(FRAMERATE)
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT: sys.exit()
        ballrect = ballrect.move(speed)
        if ballrect.left < 0 or ballrect.right > width:
            speed[0] = -speed[0]
        if ballrect.top < 0 or ballrect.bottom > height:
            speed[1] = -speed[1]

        screen.fill(black)
        screen.blit(ball, ballrect)
        pygame.display.flip()
except KeyboardInterrupt:
    print ("User exits")
    sys.exit()

Copia el listado superior a un editor de texto y guárdalo como texto plano con extensión py en tu directorio de trabajo. Yo lo he guardado como bota_pelota.py 🙂

Aquí tienes la imagen de la pelota, también. Te hará falta: descárgala al directorio de trabajo en donde vayas a guardar el programa de arriba.

Descárgate la imagen de la pelota
Descárgate la imagen de la pelota

Tu instalación de Pygame estará bien si, al cargar el listado anterior en un editor de IDLE y ejecutarlo, observas una ventana parecida a la siguiente, con una pelota rebotando:

Programa de demostración
Programa de demostración

Para probarlo, abre el intérprete IDLE para Python 3.4. Pulsa Ctrl + O si estás en Windows o Raspbian, y ⌘ + O si estás en Mac, para que el programa te permita buscar y abrir el fichero del programa que has guardado anteriormente. Cuando lo tengas abierto, pulsa F5 e IDLE lo ejecutará directamente.

Análisis del ejemplo

Vamos paso a paso, empezando por las primeras líneas del programa:

# He incluido la librería "os" para manejar directorios
import sys, os, pygame
pygame.init()

# Me sitúo en el directorio de trabajo. Descomenta y edita la que corresponda
# según tu sistema operativo
# Windows
# os.chdir("c:\prog\python34\pitando")
# Mac OS X
# os.chdir("/Users/gvisoc/pitando")
# Raspberry Pi
os.chdir("/home/pi/pitando")

Lo que estamos haciendo es:

  • pygame.init() inicializa el motor Pygame que nos proporcionará una forma fácil de modelar sprites (¿recuerdas?, como en Scratch), moverlos y gestionar el “mundo” por el cual se mueven.
  • Nos movemos al directorio de trabajo con os.chdir("...").
  • Generamos el “mundo”: una pantalla de dimensiones 320 píxels de ancho y 240 de alto. Creamos una variable que representa el color negro para usar de fondo (el cómo lo veremos luego) y cargamos la imagen de la pelota.
size = width, height = 320, 240
speed = [2, 2]
black = 0, 0, 0
screen = pygame.display.set_mode(size)
ball = pygame.image.load("ball.gif")
  • Mención especial merece la sentencia ballrect = ball.get_rect() , que obtiene el rectángulo contenedor de la pelota, esto es, el menor rectángulo posible que contiene todos y cada uno de los píxels que forman la imagen de la pelota. Para que te suene en lo sucesivo, en inglés este rectángulo se suele llamar bounding box.
# Limitamos el número de cuadros por segundo de la animación 
FRAMERATE = 60 
# y cargamos un reloj que usará el valor anterior 
clock = pygame.time.Clock()
  • Aquí hemos generado los objetos de control de tiempos que nos ayudarán a limitar la velocidad del programa de forma independiente a la velocidad del microprocesador, del chip de gráficos y de la memoria del ordenador que estemos usando.

Ya conocemos las sentencias try... except que rodean el resto del programa, así que vamos con el bucle:

    while 1:
        # Esperamos al siguiente "tick", a intervalos de 1 / 60 segundos.
        clock.tick(FRAMERATE)
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT: sys.exit()
        ballrect = ballrect.move(speed)
        if ballrect.left < 0 or ballrect.right > width:
            speed[0] = -speed[0]
        if ballrect.top < 0 or ballrect.bottom > height:
            speed[1] = -speed[1]

        screen.fill(black)
        screen.blit(ball, ballrect)
        pygame.display.flip()

El bucle es infinito; en este caso, while 1:  es equivalente al más frecuente while True: .

  • Esperamos al siguiente tick, que Pygame nos proporciona a una frecuencia de 60 veces por segundo, con clock.tick(FRAMERATE)
  • Con el bucle que hay justo a continuación estaremos examinando los eventos que se hayan producido desde la anterior pasada del bucle. Si el usuario ha pulsado el aspa de la ventana (evento pygame.QUIT ), el programa se detendrá. Los eventos de Pygame son múltiples y variados, y los usaremos bastante en los artículos sucesivos.
  • Si no ha ocurrido eso, movemos la pelota con la simple sentencia ballrect = ballrect.move(speed) . Fíjate que para mover la pelota usaremos su bounding box, o rectángulo contenedor, y le proporcionaremos el objeto que hemos definido anteriormente con speed = [2, 2] . Como puedes imaginar, estamos dándole velocidades en los dos ejes: horizontal y vertical.
  • El siguiente fragmento realiza la detección de las paredes, comparando los límites del rectángulo contenedor con los límites de la ventana que hemos definido al principio del programa.
        if ballrect.left < 0 or ballrect.right > width:
            speed[0] = -speed[0]
        if ballrect.top < 0 or ballrect.bottom > height:
            speed[1] = -speed[1]
  • Observa lo sencillo que es hacer que la pelota rebote: sólo hay que cambiar de signo la velocidad. Esto quiere decir que si tenemos una velocidad 2 nos movemos “a ritmo 2” en sentido positivo del eje, horizontal o vertical, en el que nos movemos; velocidad -2 se moverá al mismo ritmo pero en dirección contraria.
  • El código del final actualiza la pantalla (incluyendo el relleno de fondo con el color negro) y permite que Pygame haga sus operaciones internas antes de pasar a la siguiente pasada del bucle. No te preocupes por sus significados concretos y por sus implicaciones; tendrán su turno en los artículos que vendrán a continuación.
        screen.fill(black)
        screen.blit(ball, ballrect)
        pygame.display.flip()

Y esto es todo por hoy. En breve publicaré en mi cuenta de Twitter el juego que vamos a programar “por fascículos” con esta librería, que espero que resulte interesante. ¡Estoy seguro de que aprenderemos bastantes cosas!

Puedes dejarme tus comentarios en este artículo, o bien escribirme a través del formulario de contacto.

]]>
http://pitando.net/2016/04/07/videojuegos-en-python-instalar-y-probar-pygame/feed/ 1 1284
Episodio 20 – BBC Micro:bit http://pitando.net/2016/04/07/episodio-20-bbc-microbit/ Thu, 07 Apr 2016 05:00:32 +0000 http://pitando.net/?p=1304 Sigue leyendo Episodio 20 – BBC Micro:bit ]]>  

En este episodio os hablo del BBC Micro:bit, un microcontrolador ofrecido por la BBC (sí: la misma British Broadcasting Corporation que lleva más de 50 años produciendo y emitiendo Dr. Who), en alianza con varios fabricantes de software y hardware entre los que se encuentra Microsoft. Hablaré de sus características y de su papel en el sistema educativo del reino unido, en donde la han enviado gratis a todos los niños de 7º curso o equivalente.

Enlaces de interés:

Espero vuestros comentarios en el blog, http://pitando.net y a través de twitter (https://twitter.com/pitandonet) y Facebook (https://facebook.com/pitandonet)

]]>
1304
Ejecuta programas al inicio de tu Raspberry Pi http://pitando.net/2016/03/31/ejecuta-programas-al-inicio-de-tu-raspberry-pi/ http://pitando.net/2016/03/31/ejecuta-programas-al-inicio-de-tu-raspberry-pi/#comments Thu, 31 Mar 2016 09:00:16 +0000 http://pitando.net/?p=1261 Sigue leyendo Ejecuta programas al inicio de tu Raspberry Pi ]]> Es posible que nos apetezca configurar uno de nuestros proyectos de tal forma que se ejecute automáticamente al encender la Raspberry Pi, o el PC si tenemos Linux. Esto puede servir, por ejemplo, para automatizar el proyecto del fotomatón, con el que tanto hemos trabajado estos últimos meses, para que comience a funcionar con el arranque de la Raspberry Pi. Aunque puede servir para cualquier cosa y en cualquier sistema Linux hay docenas de programas configurados al inicio del sistema, eso fue lo que me estuvo preguntando Alfonso García por correo electrónico.

Inicio de una variante de Raspbian (Minibian). Imagen de Luca Soltoggio (minibian: https://minibianpi.wordpress.com/)
Inicio de una variante de Raspbian (Minibian). Imagen de Luca Soltoggio (minibian: https://minibianpi.wordpress.com/)

Lo que haremos en este artículo va a ser ver paso a paso lo que tenemos que hacer para automatizar un programa al inicio de Linux usando el capítulo de inicialización del sistema de la norma Linux Standard Base (que podéis leer aquí, en inglés), que es la forma estándar que los Linux tipo Debian (como es Raspbian) tienen de interpretar los contenidos de un directorio llamado init.d para ir ejecutando todo lo que allí encuentren. Si usáis una distribución basada en Red Hat Linux podréis encontrar alguna diferencia.

Haremos nuestra tarea de una forma responsable y cumplidora con los requisitos de lo especificado por la LSB, pero también algo simplificada. Un primer paso para que luego, cada uno, profundice en este tema lo que desee o necesite.

Niveles de ejecución y subsistemas en Linux

Linux es un sistema operativo cuya vida discurre entre varios modos de ejecución, concretamente 6. Desde que arranca hasta que para, el sistema operativo va cambiando de uno a otro de forma secuencial. En cada nivel, que está orientado a unas determinadas tareas, uno o varios usuarios pueden ejecutar programas para sus propios fines, pero el sistema también arranca una serie de subsistemas específicos de forma automática. Un subsistema puede ser, por ejemplo, el sistema de sonido, una capa de seguridad para protegernos de ataques si nos conectamos a internet, o el entorno gráfico para mostrar un escritorio y que el usuario pueda interactuar con él mediante un ratón. Sin embargo, una aplicación interactiva como un navegador web o una hoja de cálculo no se consideran subsistemas.

Los niveles que hay son los siguientes, y vamos a ver para qué sirven de forma que quede claro el párrafo anterior:

  • 0: nivel de parada. Cuando el sistema pasa a este nivel, todos los subsistemas se paran de forma ordenada, dándoles tiempo a “recoger la casa y dejarla limpia”, para luego detener por completo el sistema operativo y la máquina. shutdown -h now como superusuario pone el sistema en nivel 0.
  • 1: modo de mantenimiento. En este modo de ejecución sólo se arrancan los subsistemas imprescindibles para que un usuario pueda hacer tareas de mantenimiento muy específicas. Si el ordenador se arranca en modo mantenimiento, el arranque desemboca en este modo de ejecución y se ofrece un terminal directamente, sin pedir usuario ni contraseña.
  • 2, 3: modo “normal” de funcionamiento. Todos los subsistemas se arrancan, incluyendo el de autenticación de usuarios (el que permite introducir usuario y contraseña). Son equivalentes, aunque el 2 tiene menos capacidades de red y se mantiene por razones históricas. Normalmente, si arrancas un ordenador con Linux en modo texto estarás en este modo. La inmensa mayoría de servidores que usan Linux terminan su arranque en el nivel de ejecución 3.
  • 4: sin uso.
  • 5: modo de escritorio. Es equivalente al 3, pero con el entorno gráfico arrancado por defecto. La configuración por defecto de la mayoría de ordenadores personales con Linux, como la Raspberry Pi.
  • 6: equivalente al 0, pero que en lugar de detener por completo el sistema y la máquina efectúa un reinicio. Es una de las cosas que ocurren cuando ejecutamos el comando shutdown -r now  como superusuario.

Podemos cambiar de nivel de ejecución manualmente y consultar la secuencia de niveles por los que ha pasado el sistema con los comandos telinit y runlevel, pero te los dejo a ti para que los consultes en el manual y experimentes con ellos: en este artículo no serán de gran interés.

Arrancar subsistemas en secuencia: directorios init.d y rc1.d, rc2.d,… rc5.d

Para arrancar un programa como subsistema debemos programar un guión (o script) en el directorio /etc/init.d, con unos permisos adecuados para que el sistema de arranque de Linux lo pueda ejecutar. 0755, o rwxr-xr-x, son suficientes. Los subsistemas allí presentes, una vez configurados en los niveles de ejecución adecuados para sus fines (normalmente 2,3,4 y 5 como hemos discutido arriba), aparecen como enlaces simbólicos en los directorios /etc/rcN.d, donde N es el nivel de ejecución. Un enlace simbólico es como se llama en Linux a un acceso directo un poco especial, con más capacidades que los que estamos acostumbrados a manejar como usuarios.

En estos directorios rcN.d, los enlaces simbólicos tienen un nombre cuya estructura indica al sistema si sirven para detener el subsistema, arrancarlo, pararlo y con qué prioridad; esto es: si debe arrancarlo antes o después que otro. Por ejemplo, en /etc/rc3.d solemos encontrar un enlace de nombre S12syslog. Éste indica que hay que arrancar (S, de start) el servicio syslog, que toma nota de todos los mensajes de estado del sistema (logs) y los pone a disposición del usuario en una ubicación concreta, con prioridad 12 (mayor prioridad cuanto menor valor). Si vemos que hay otro enlace, por ejemplo S56rawdevices, sabremos que arrancará el subsistema rawdevices con una prioridad menor (56) que syslog (12), y por lo tanto después.

¿Cómo se configura un subsistema en un nivel de ejecución y con una prioridad? Con la estructura de los guiones que colocaremos bajo init.d que, como ya os adelantaba, en Debian y Raspbian siguen un convenio recogido en el capítulo de inicialización del sistema de la “norma” Linux Standard Base.

A partir de este punto voy a simplificar, aunque sin caer en la chapuza. La estructura de init.d es algo muy completo y que hacerlo pulcra y correctamente constituye de por sí una capacidad profesional avanzada y bastante cotizada; en este blog lo que quiero es poner este tipo de cosas a vuestro alcance (y al mío, porque no os voy a engañar: no soy un experto en estos temas), pero no puedo ir hasta el último rincón. Os voy a presentar directamente un guión que sirve para arrancar el prototipo del fotomatón, cumpliendo con lo estrictamente necesario de la especificación de Linux Standard Base, y lo vamos a analizar paso a paso.

#!/bin/sh
### BEGIN INIT INFO
# Provides:     fotomatond
# Should-Start:
# Required-Start:   $local_fs $remote_fs
# Required-Stop:    $local_fs $remote_fs
# Default-Start:    2 3 4 5
# Default-Stop:     0 1 6
# Short-Description:    Fotomaton basado en Raspberry Pi
# Description:      Script de inicio para Debian para el fotomaton
#          basado en la Raspberry Pi, un prototipo y el modulo
#          de camara.
### END INIT INFO


. /lib/lsb/init-functions

DAEMON=/home/pi/pitando/fotomaton_dropbox.py
NOMBRE=fotomatond
LOGFILE=/var/log/fotomatond.log
ERRFILE=/var/log/fotomatond.err
DESCRIPCION="Servicio de fotomaton"

test -x $DAEMON || exit 0

set -e

case "$1" in

    start) 
        log_daemon_msg "Arrancando $DESCRIPCION" "$NOMBRE"
        start-stop-daemon --start --background --pidfile /var/run/$NOMBRE.pid --make-pidfile --quiet --startas /bin/bash -- -c "exec $DAEMON >$LOGFILE 2>$ERRFILE"
        log_end_msg $?
        ;;
    stop)
        log_daemon_msg "Parando $DESCRIPCION" "$NOMBRE"
        start-stop-daemon --stop --oknodo --quiet --pidfile /var/run/$NOMBRE.pid
        log_end_msg $?
        ;;
    restart)
        log_daemon_msg "Reiniciando $DESCRIPCION" "$NOMBRE"
        start-stop-daemon --stop --oknodo --quiet --pidfile /var/run/$NOMBRE.pid
        sleep 1
        start-stop-daemon --start --background --pidfile /var/run/$NOMBRE.pid --make-pidfile --quiet --startas /bin/bash -- -c "exec $DAEMON >$LOGFILE 2>$ERRFILE"
        log_end_msg $?
        ;;
    force-reload)
        restart
        ;;
    *)
        log_failure_msg "Uso: /etc/init.d/$NOMBRE {start|stop|restart|force-reload}"
esac

exit 0

De todas las operaciones obligatorias recogidas en la norma, nos dejamos la que todavía está en un estado de candidatura, aunque de facto ya es obligatoria, que es status. Status debería devolver un código que represente el estado de ejecución (arrancando, ejecutándose, parando, parada,…) del subsistema o servicio. En este artículo la dejaremos fuera, pero no descartéis una actualización o una continuación.

Usando este script vuestro fotomatón arrancará con el arranque del sistema y se detendrá correctamente con la parada del mismo, y lo mejor de todo es que sabréis por qué y podréis generalizarlo para cualquier otro propósito. Sin embargo, si queréis programar un servicio de modo profesional deberéis consultar la  documentación y practicar mucho. Vamos a ver su estructura a continuación.

Comentarios del inicio

Además del ya conocido comentario #!/bin/sh por el que comienza todo script de terminal, este fichero tiene una buen retahíla de comentarios al principio:

### BEGIN INIT INFO
# Provides:     fotomatond
# Should-Start:
# Required-Start:   $local_fs $remote_fs
# Required-Stop:    $local_fs $remote_fs
# Default-Start:    2 3 4 5
# Default-Stop:     0 1 6
# Short-Description:    Fotomaton basado en Raspberry Pi
# Description:      Script de inicio para Debian para el fotomaton
#          basado en la Raspberry Pi, un prototipo y el modulo
#          de camara.
### END INIT INFO

Aunque sean comentarios, son obligatorios. Indican lo siguiente por este orden:

  • El nombre del servicio que gestionan, en este caso fotomatond
  • Los servicios que deberían estar arrancados antes (should start), opcionalmente. Lo dejo en blanco porque no hay dependencias opcionales.
  • Los servicios que deben estar arrancados antes porque, si no, nuestro servicio no funcionaría (required start). En mi caso pongo las variables de sistema que hacen referencia al montaje de todos los sistemas de fichero ya que nuestro fotomatón almacena y lee fotografías en la tarjeta SD de la Raspberry Pi. Así pues,  $local_fs $remote_fs
  • Los servicios que deben parar después del nuestro ya que si no, nuestro fotomatón podría no ser capaz de terminar su ejecución de una manera ordenada (required stop). En nuestro caso y en la mayoría de los casos hay que poner lo mismo que en la opción anterior.
  • Los niveles de ejecución en los que este servicio se va a arrancar (default start), que en este caso son todos los que suponen una ejecución normal, con o sin entorno gráfico: 2 3 4 5 .
  • Los niveles de ejecución en los que este servicio se debe parar (default stop); para ser consistentes deberíamos poner todos los demás: 0 1 6
  • Descripciones corta (una línea) y larga (varias líneas que comienzan por un tabulador o dos espacios).

Con estos comentarios, el sistema de configuración del arranque creará correctamente los enlaces simbólicos que reseñaba en apartados anteriores (con la S, la prioridad,…)

Incorporar funciones, variables y comprobaciones preliminares

Examinamos ahora este bloque de código:

. /lib/lsb/init-functions

DAEMON=/home/pi/pitando/fotomaton_dropbox.py
NOMBRE=fotomatond
LOGFILE=/var/log/fotomatond.log
ERRFILE=/var/log/fotomatond.err
DESCRIPCION="Servicio de fotomaton"

test -x $DAEMON || exit 0

set -e
  •  La primera línea, . /lib/lsb/init-functions , incluye una serie de funciones de terminal que nos servirán principalmente para enviar mensajes a los logs del sistema: un lugar donde podemos grabar nuestra actividad con información útil.
  • Las siguientes 5 líneas definen unas variables que nos servirán para identificar correctamente el programa a arrancar.
    • Fijaos que la primera de ellas apunta directamente al directorio de usuario pi: esto es mejorable ya que un programa al inicio del sistema debería guardarse en un directorio de sistema.
    • Las dos siguientes definen dos ficheros de log diferentes al de sistema, que usaré para guardar los mensajes que imprimimos por pantalla desde Python con la función print (fichero de extensión .log), y los errores que vuelque el intérprete en caso de haberlos (fichero de extensión .err).
    • La variableDESCRIPCION incluye una descripción corta para reconocer fácilmente nuestros mensajes en los logs del sistema.
  • La sentencia test -x $DAEMON || exit 0 comprueba si el programa ya se encuentra en ejecución, y en caso afirmativo termina el guión sin errores (código de salida 0).
  • Por último, set -e configura el script para que, si cualquier cosa fallase devolviendo un código de salida diferente a 0 a partir de ese punto, el propio script se detendría devolviendo ese mismo valor. Es una forma abreviada de terminar ante un error, propagando aquello que nos hayan devuelto.

Arranque, parada, reinicio y recarga del subsistema

Son operaciones obligatorias (pero no las únicas posibles) para programar un subsistema al inicio de Linux: start , stop , restart  y force-reload. El guión que coloquemos bajo /etc/init.d debe contemplar las 4, y para eso suele usarse una estructura de control case:

case "$1" in

    start) 
        log_daemon_msg "Arrancando $DESCRIPCION" "$NOMBRE"
        start-stop-daemon --start --background --pidfile /var/run/$NOMBRE.pid --make-pidfile --quiet --startas /bin/bash -- -c "exec $DAEMON >$LOGFILE 2>$ERRFILE"
        log_end_msg $?
        ;;
    stop)
        log_daemon_msg "Parando $DESCRIPCION" "$NOMBRE"
        start-stop-daemon --stop --oknodo --quiet --pidfile /var/run/$NOMBRE.pid
        log_end_msg $?
        ;;
    restart)
        log_daemon_msg "Reiniciando $DESCRIPCION" "$NOMBRE"
        start-stop-daemon --stop --oknodo --quiet --pidfile /var/run/$NOMBRE.pid
        sleep 1
        start-stop-daemon --start --background --pidfile /var/run/$NOMBRE.pid --make-pidfile --quiet --startas /bin/bash -- -c "exec $DAEMON >$LOGFILE 2>$ERRFILE"
        log_end_msg $?
        ;;
    force-reload)
        restart
        ;;
    *)
        log_failure_msg "Uso: /etc/init.d/$NOMBRE {start|stop|restart|force-reload}"
esac

La estructura de control case superior examina el primer parámetro del guión. Si este es start ejecutará una secuencia de sentencias orientada a arrancar el programa del Fotomatón en Python; en caso de stop hará lo propio para pararla, y así sucesivamente. restart es una mezcla del contenido de start y stop, y force-reload, cuyo comportamiento es “recargar la configuración del subsistema y, si no es posible, reiniciarlo”, en este caso llama directamente a restart porque no hay configuración que recargar.

Vamos a examinar los bloques de sentencias que paran y arrancan el programa del fotomatón:

start) 
    log_daemon_msg "Arrancando $DESCRIPCION" "$NOMBRE"
    start-stop-daemon --start --background --pidfile /var/run/$NOMBRE.pid \ 
        --make-pidfile --quiet \ 
        --startas /bin/bash -- -c "exec $DAEMON >$LOGFILE 2>$ERRFILE"
    log_end_msg $?
    ;;

La primera línea define el caso en el que nos encontraríamos: que el primer argumento del guión fuese igual a start. La siguiente escribe un mensaje usando una de las funciones cargadas al principio del script mediante la librería init-functionslog_daemon_msg. A juzgar por el texto que escribimos, podemos esperar que el mensaje escrito tenga las variables sustituidas por su valor, y así será.

La siguiente línea es digna de comentar completamente: start-stop-daemon es un programa que ayuda a controlar la ejecución de subsistemas. Concretamente, lo que hacemos en esa línea es:

  • –start: arrancamos el programa.
  • –background: en segundo plano, es decir: el guión no espera a que el programa termine. Esto es muy conveniente porque nuestro programa, recordadlo, es un bucle infinito.
  • –pidfile /var/run/$NOMBRE.pid –make-pidfile va a asegurarse de crear un fichero de nombre /var/run/fotomatond.pid cuyo contenido va a ser un identificador de proceso en Linux, es decir: un número que identifica inequívocamente al programa del fotomatón cuando se esté ejecutando. Muy útil para pararlo, por ejemplo.
  • –quiet reduce la cantidad de información que esta orden devolverá por pantalla.
  • –startas /bin/bash — -c “exec $DAEMON >$LOGFILE 2>$ERRFILE” va a ejecutar el programa del fotomatón (contenido de la variable $DAEMON) de tal forma que la salida estándar que sacamos en el programa se almacene en el fichero /var/log/fotomatond.log y los errores, si los hubiese, en el fichero /var/log/fotomatond.err. Si nuestro programa estuviese mejor codificado y escribiese su información en un fichero, este último trozo de la sentencia sería notablemente más sencilla (–exec $DAEMON).

La última línea anota el resultado de la ejecución del comando start-stop-daemon.

Vamos ahora con la parada:

stop)
    log_daemon_msg "Parando $DESCRIPCION" "$NOMBRE"
    start-stop-daemon --stop --oknodo --quiet --pidfile /var/run/$NOMBRE.pid
    log_end_msg $?
    ;;

Realmente, la única diferencia es el uso de start-stop-daemon, mucho más sencillo. En este caso sólo hay que decirle que pare (–stop) el proceso cuyo identificador está almacenado en el fichero /var/run/fotomatond.pid, haciendo correcto uso de las variables definidas.

Por último, el programa termina correctamente con código 0, a no ser que se haya producido un error en algún punto, en cuyo caso habrá salido con un código diferente gracias a la sentencia set -e

exit 0

Unos retoques al programa del fotomatón antes de continuar

Para que el programa funcione correctamente hay que hacer un par de cambios: especificar las rutas de los ficheros de imagen de forma absoluta y modificar los mensajes que sacamos por consola para que se emitan inmediatamente. El código del programa quedaría como veis a continuación, y en él he marcado con comentarios las modificaciones:

#!/usr/bin/python3

import dropbox
import RPi.GPIO as GPIO
import time
import picamera

## Obtener una referencia a la cámara
camera = picamera.PiCamera()

## Conexión a la cuenta de Dropbox
# Sustuye estos valores por los que has copiado, conservando
# las comillas.
app_key = "APP_KEY" 
app_secret = "APP_SECRET"
# Las dos primeras claves no son en absoluto necesarias aquí, sirven
# para programar un flujo que generaría la siguiente (flujo de autori-
# zación OAuth2)
cuenta = "ACCESS_TOKEN"

print("Conectando a la cuenta de Dropbox...", flush=True) #Modificado
client = dropbox.client.DropboxClient(cuenta)
print("Cuenta de " + client.account_info().get("display_name") + " conectada.", flush=True) #Modificado
print("Oprime el pulsador para hacer la primera foto", flush=True) #Modificado

## Configuración GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings (False)
GPIO.setup(6, GPIO.IN)
GPIO.setup(12, GPIO.OUT)

## Bucle infinito del fotomatón
# Número total de fotos, para Dropbox
total = 0
# Número de fotos en la tarjeta SD, entre 0 y 10. Así no llenaré
# nunca el sistema de ficheros (sólo guardo 10 fotos en la
# Raspberry, "en local")
local = 0
try:
    while True:
        if (not GPIO.input(6)):
            # Modificado: rutas absolutas
            nombre_local = "/home/pi/pitando/captura_" + str(local) + ".jpeg"
            nombre_dropbox = "/home/pi/pitando/captura_" + str(total) + ".jpeg"
            
            # Uso del LED para guiar al "modelo" de la foto
            print ("Cuando se apague el LED se hará la foto", flush=True) #Modificado
            GPIO.output(12, GPIO.HIGH)
            for i in range(0, 3):            
                print(str(3 - i) + "...", flush=True) #Modificado
                time.sleep(1)
            GPIO.output (12, GPIO.LOW)
            print ("¡Foto!", flush=True) #Modificado
            # Hacemos la foto
            camera.capture(nombre_local)
                
            # Subir la foto a Dropbox
            f = open(nombre_local, 'rb')

            print("Subiendo la foto a Dropbox de " + client.account_info().get("display_name") + "...", flush=True) #Modificado
            response = client.put_file(nombre_dropbox, f)
            
            tamanho = str(round(int(response.get("bytes")) / 1024,1))
            print("Foto '" + nombre_dropbox + " cargada: " + tamanho + " kB.", flush=True) #Modificado

            # Incrementar contadores; 'local' siempre entre 0 y 10
            local = (local + 1) % 10 # resto de dividir local + 1 entre 10.
            total = total + 1
            print("Oprime el pulsador para hacer otra foto", flush=True) #Modificado
            
except dropbox.rest.ErrorResponse as e:
    print("Error al acceder a Dropbox: ", e.user_error_msg, flush=True) #Modificado
except KeyboardInterrupt:
    print("\nInterrupción del usuario, saliendo del programa", flush=True) #Modificado
finally:
    camera.close()
    GPIO.cleanup()
    print ("Fin del programa", flush=True) #Modificado

Configurar el servicio en el arranque de la Raspberry Pi

Para que el servicio arranque con la Raspberry Pi y se pare con ella, debemos hacer lo siguiente:

  • Una vez preparado el programa en Python como en el apartado anterior, asegúrate de que tiene permisos de ejecución 0755 ó rwxr-x .
  • Guarda en /etc/init.d/fotomatond el script que define el servicio fotomatond y que hemos estado discutiendo a lo largo de este artículo (etiquetado como Listado del servicio fotomatond). Otórgale los mismos permisos: 0755 ó rwxr-x .
  • Con la siguiente orden configurarás el servicio en el arranque con las prioridades adecuadas y creando los enlaces simbólicos bajo los directorios /etc/rcN.d, gracias a la cabecera de comentarios que analizamos:
sudo update-rc.d /etc/init.d/fotomatond defaults

Si quisiéramos desinstalarlo, ejecutaríamos su contrapartida:

sudo update-rc.d -f /etc/init.d/fotomatond remove

A partir de este momento y si todo ha ido bien, tan pronto reiniciemos la Raspberry Pi nuestro fotomatón debería funcionar.

Una vez reiniciada podríamos comprobar su funcionamiento (más allá de los leds de la cámara y de que las imágenes aparecen en el directorio correspondiente) mediante las siguientes órdenes que os dejo como ejercicio sencillo:

tail -n 100 /var/log/syslog
tail -n 100 /var/log/fotomatond.log
cat /var/run/fotomatond.pid
ps aux | grep fotomaton_dropbox.py | grep -v grep

Sobre las órdenes anteriores:

  1. Localiza los mensajes que emite el servicio de fotomatón
  2. Relaciona esos mensajes con sentencias concretas del script /etc/init.d/fotomatond y con sentencias de código Python
  3. Compara los identificadores de proceso de las órdenes 3ª y 4ª.

Y esto es todo. A partir de ahora podréis automatizar muchos de los programas que hagáis para que se ejecuten al encender la Raspberry Pi, porque lo que hemos visto aquí, aunque es básico, cubre la mayoría de las necesidades de los programas que podamos hacer con el objetivo de experimentar. Espero que lo encontréis útil. Recordad consultar la documentación para ampliar vuestro conocimiento:

Linux Standard Base – System Intialization

Podéis dejarme cualquier comentario en este mismo artículo o en el formulario de contacto, usando también la dirección de correo electrónico que hallaréis allí si lo preferís.

]]>
http://pitando.net/2016/03/31/ejecuta-programas-al-inicio-de-tu-raspberry-pi/feed/ 31 1261
Episodio 19 – Kojo, pensamiento computacional y Scala http://pitando.net/2016/03/24/episodio-19-kojo-pensamiento-computacional-y-scala/ Thu, 24 Mar 2016 06:00:04 +0000 http://pitando.net/?p=1256 Sigue leyendo Episodio 19 – Kojo, pensamiento computacional y Scala ]]> En este episodio os hablo de Kojo.

[Imagen en http://pitando.net] Ejemplo de un árbol fractal programado en Kojo. Podéis ver el código en el recuadro inferior izquierdo.
[Imagen en http://pitando.net] Ejemplo de un árbol fractal programado en Kojo. Podéis ver el código en el recuadro inferior izquierdo.
Kojo es un proyecto de la fundación Kogics (Play. Learn) que busca proporcionar herramientas que fomenten la curiosidad en los niños y que les hagan algo divertido del proceso de aprendizaje. Está basado en Processing, Logo y The Geometer’s Sketchpad y recorre las siguientes áreas:

  • Programación y pensamiento computacional
  • Matemáticas, ciencias,
  • Arte, música
  • Electrónica y robótica

Y además incorpora la capacidad de programar placas de Arduino y de aprender a programar en Scala como añadidos de gran valor.

Scala es un lenguaje que combina varias filosofías de programación y, hoy por hoy, es de los lenguajes de programación más vigentes y cotizados del mercado.

Espero vuestros comentarios en http://pitando.net y a través de twitter (https://twitter.com/pitandonet) y Facebook (https://facebook.com/pitandonet).

]]>
1256
Raspberry Pi Compute Module: ¿qué son? http://pitando.net/2016/03/17/raspberry-pi-compute-module-que-son/ Thu, 17 Mar 2016 10:00:09 +0000 http://pitando.net/?p=1250 Sigue leyendo Raspberry Pi Compute Module: ¿qué son? ]]> Estamos acostumbrados a ver la Raspberry Pi como un pequeño ordenador completo y listo para trabajar en cualquier proyecto o actividad en cuanto le insertemos un sistema operativo en una tarjeta de memoria y le conectemos un teclado, un ratón y una pantalla. Esto, en cualquiera de sus formatos: A, B, Zero; y lo mismo en cualquiera de sus versiones: 1, 2, 3…

Raspberry Pi Compute Module (cc 4.0 - Raspberry Pi Foundation)
Raspberry Pi Compute Module (cc 4.0 – Raspberry Pi Foundation)

Sin embargo, esto no es todo lo que ofrece la fundación Raspberry Pi en este sentido: nos falta por hablar de los compute module, o módulos de computación, que es un formato adicional de la Raspberry Pi, específicamente diseñado para aplicaciones industriales. Esto es: que se enchufan y ejecutan dentro de un proyecto más grande, como sistema embebido o empotrado.

Los módulos de computación de Raspberry Pi son unas placas con el formato y tamaño de los chips de memoria RAM de los ordenadores, es decir, encajan en un zócalo de tipo SODIMM DDR2 de 200 pines. Incorporan el microprocesador de la Raspberry Pi, una cierta cantidad de memoria RAM del orden al que nos tienen acostumbrados los modelos A (512 MB en los disponibles actualmente) y un chip integrado de memoria eMMC Flash de 4 GB donde podemos instalar un sistema operativo de la misma forma que si fuera una tarjeta SD. De hecho, el estándar eMMC es un subestándar de tarjetas de memoria multimedia.

Por el momento tenemos que quedarnos con el hecho fundamental de que no son ordenadores de propósito general, ni completos:

  • No incorporan tanta memoria RAM como otros formatos, por lo que no es a priori una configuración adecuada para hacer tareas personales.
  • La memoria que incorporan para la carga del sistema operativo es la mitad de la mínima recomendada para una instalación de Raspbian, por lo que los sistemas a los que dedicar este módulo deberían prescindir de entornos gráficos de trabajo y muchas aplicaciones de uso general.
  • No tiene puertos de alimentación, ni USB, ni de red, ni HDMI,…

Todo lo demás lo tenemos que aportar nosotros en nuestro proyecto, al que podremos enchufar un módulo de computación a través de un zócalo SODIMM DDR2 soldado a la placa que habremos diseñado y creado para la ocasión. Para facilitarlo, la fundación Raspberry Pi proporciona un kit de desarrollo con una placa de expansión especial para desarrollar sistemas embebidos basados en los módulos de computación.

Placa de expansión para desarrollo de proyectos industriales basados en el Compute Module. Puede verse uno enchufado en el zócalo SODIMM DDR2 (cc-4.0 Raspberry Pi Foundation)
Placa de expansión para desarrollo de proyectos industriales basados en el Compute Module. Puede verse uno enchufado en el zócalo SODIMM DDR2 (cc-4.0 Raspberry Pi Foundation)

A dicha placa enchufas tu módulo y te proporcionará nada menos que 120 pines GPIO (¡la Raspberry Pi 2 y la 3 tienen 40!), un puerto HDMI para conectar una pantalla, un puerto USB, un puerto de alimentación micro-USB que admite 5 V, dos puertos para cámara y dos puertos para pantalla. Eso no quiere decir que tu proyecto tenga que tener todo esto: esto lo usarás para desarrollar y depurar tu aplicación industrial. La placa donde insertarás este módulo probablemente tenga una pantalla, algunos sensores y algunos pulsadores o dispositivos sencillos de entrada, todo ello conectado al módulo a través de GPIO, pero nada más. De hecho, por razones como esta no incorpora aquello que es prescindible y susceptible de integrar por USB, como la tarjeta de red.

Los precios suelen rondar los 40 $ para el compute module y alrededor de 100 $ para el kit de desarrollo, aunque los he visto descontados ambos, en un kit, por unos 67 € IVA y gastos de envío incluidos, en la filial española de RS, que es uno de los vendedor oficiales de la fundación Raspberry Pi para este producto. ¡Importante!, de momento sólo he visto los correspondientes a la Raspberry Pi 1, es decir, microprocesador de un sólo nucleo y 700 MHz.

Si os interesa la obtención en lotes o por otros canales, podéis consultar precios y condiciones de distribución en la página de la fundación Raspberry Pi. Lo he visto también en Amazon, pero a un precio

]]>
1250
Sensor de aparcamiento: combina un LED RGB y un sensor de proximidad http://pitando.net/2016/03/10/sensor-de-aparcamiento-combinando-un-led-rgb-y-un-sensor-de-proximidad/ Thu, 10 Mar 2016 10:00:20 +0000 http://pitando.net/?p=1232 Sigue leyendo Sensor de aparcamiento: combina un LED RGB y un sensor de proximidad ]]> En el artículo de hoy vais a combinar el montaje del sensor de proximidad HC-SR04 con el del LED RGB que vimos la semana pasada, aprovechando que los prototipos que vamos probando son reutilizables como módulos para montajes más complejos. De esa forma, este artículo es una idea que os doy para que comprobéis cómo, yendo componente a componente, acabaréis teniendo una “librería” de soluciones que ir combinando para hacer proyectos más completos.

Si partís en este punto de cero, porque llegáis a este artículo directamente, os recuerdo lo que necesitáis saber:

Material que necesitarás:

Vamos a ello.

Montar el prototipo del sensor de aparcamiento

Con la Raspberry Pi desconectada, ensambla el prototipo del sensor de proximidad como te explico en este artículo, y asegúrate de que funciona correctamente con el código en Python que allí te propongo.

Una vez superado esa fase del experimento, desconecta la Raspberry Pi de nuevo para montar el LED RGB en una zona que no hayas usado de la placa, teniendo cuidado de distinguir el cátodo común (patilla más larga), que es el que debe ir conectado a tierra. Tienes instrucciones detalladas aquí.

En mi caso he conectado el LED RGB a los pines GPIO 16 (ánodo rojo), 20 (ánodo verde) y 26 (ánodo azul), de tal forma que mi prototipo queda de la siguiente forma:

Prototipo final. Si lo necesitas, haz click para ampliarlo en otra ventana de tu navegador.
Prototipo final. Si lo necesitas, haz click para ampliarlo en otra ventana de tu navegador.
Prototipo de nuestra imitación de un sensor de aparcamiento. Si lo necesitas, haz click para ampliarlo en otra ventana de tu navegador.
Prototipo de nuestra imitación de un sensor de aparcamiento. Si lo necesitas, haz click para ampliarlo en otra ventana de tu navegador.

Importante: date cuenta de que he colocado el LED RGB del revés con respecto al prototipo original, con lo que, repito: siempre identifica el cátodo común por la longitud mayor de la patilla, y luego a un lado tendrás el ánodo rojo, y al otro el verde y el azul.

Programar el sensor usando Python

Ya sólo queda combinar nuestros conocimientos de cómo programar esos dos dispositivos mediante Python. Mi propuesta (que no tienes por qué seguirla) es aprovechar el programa que hicimos en su día para probar el sensor, y añadirle unas cuantas sentencias de control del LED que puedes ver enmarcadas en comentarios:

#!/usr/bin/python3

import time
import RPi.GPIO as GPIO

## Sensor de aparcamiento
from gpiozero import RGBLED

led = RGBLED(16,20,26)
## Sensor de aparcamiento

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# trig (cable amarillo en el prototipo)
GPIO.setup(12, GPIO.OUT)
# echo (cable verde en el prototipo)
GPIO.setup(6, GPIO.IN)

try:
    while True:
        start = 0
        end = 0
        # Configura el sensor
        GPIO.output(12, False)
        time.sleep(2) # 2 segundos
        # Empezamos a medir
        GPIO.output(12, True)
        time.sleep(10*10**-6) #10 microsegundos
        GPIO.output(12, False)

        # Flanco de 0 a 1 = inicio 
        while GPIO.input(6) == GPIO.LOW:
            start = time.time()
        # Flanco de 1 a 0 = fin
        while GPIO.input(6) == GPIO.HIGH:
            end = time.time()

        # el tiempo que devuelve time() está en segundos
        distancia = (end-start) * 340 / 2
        #print ("Distancia al objeto =", str(distancia))

        ## Sensor de aparcamiento
        if distancia < 0.05:
            led.color = (1,0,0)
        elif 0.05 <= distancia < 0.15:
            led.color = (1,1,0)
        else:
            led.color = (0,1,0)
        ## Sensor de aparcamiento        
        
except KeyboardInterrupt:
    print("\nFin del programa")
    ## Sensor de aparcamiento
    led.off()
    ## Sensor de aparcamiento
    GPIO.output(12, False)
    GPIO.cleanup()

He usado 2 segundos de espera entre cada medición para poderlo probar cómodamente, y eso se ve en el vídeo de Vine que tenéis más abajo. En cuanto lo hayas depurado bien, podrías bajar ese tiempo de espera a un valor más práctico para la aplicación en la que estamos haciendo ensayos, como es el aparcar de un coche. Con una espera de dos segundos verías la luz roja demasiado tarde 🙂 También he comentado la línea que saca por el terminal la distancia al objeto, puesto que ya tenemos esa medición de distancia en el LED RGB.

Colores raros en mi LED, ¿por qué?

Podrás haber notado que el color amarillo sale un poco verdoso, como os planteaba la semana pasada. Eso ocurre porque no todos los componentes de color del LED RGB operan con los mismos valores de corriente y voltaje como podéis ver en las especificaciones de los LED y en muchos foros: al usar el mismo valor de resistencia en cada ánodo estamos limitando la corriente para protegerlo, sí, pero no estamos consiguiendo la misma luminosidad en cada componente de color. Además, el que el LED sea tan cristalino como en mi caso no ayuda: normalmente los LED RGB ofrecen un color mucho más uniforme si son de lente difusa (encapsulamiento traslúcido). Os enlazo aquí la hoja de especificaciones de un LED RGB de cátodo común que, aunque es de lente traslúcida, sirve para comprobar lo que os decía de los diferentes voltajes y luminancias: YSL-R596CR4G3B5W-F12.

Y esto es todo: espero que haya resultado interesante. Puedes dejarme cualquier comentario en esta misma entrada, enviándome cualquier comentario a través del formulario de contacto, y también a través de la dirección de correo que allí encontrarás.

]]>
1232
Episodio 18 – Raspberry Pi 3 y picaresca con Raspberry Pi Zero http://pitando.net/2016/03/10/episodio-18-raspberry-pi-3-y-picaresca-con-raspberry-pi-zero/ Thu, 10 Mar 2016 06:00:44 +0000 http://pitando.net/?p=1245 Sigue leyendo Episodio 18 – Raspberry Pi 3 y picaresca con Raspberry Pi Zero ]]>  

En el episodio de hoy comento las características y precio de la Raspberry Pi 3 Modelo B, que salió el pasado 29 de febrero de 2016 justo cuando se cumplen 4 años de la aparición de la primera Raspberry Pi en el mercado. También os hablo de los otros modelos que saldrán (A+ y Compute Module).

Con respecto a la Raspberry Pi Zero hago un poco de retrospectiva de las polémicas surgidas alrededor de su precio, su distribución y el volumen de producción. En resumen, actualmente sólo la podemos encontrar en 4 distribuidores oficiales cuyos gastos de envío a España están más o menos sobre los 36 $… por una placa de 5 $ que sólo podremos pedir de una en una (pagando un total 41 $).

Espero vuestros comentarios en el blog, http://pitando.net y a través de twitter (https://twitter.com/pitandonet) y Facebook (https://facebook.com/pitandonet)

]]>
1245
Programar un LED RGB con GPIO Zero http://pitando.net/2016/03/03/programar-un-led-rgb-con-gpio-zero/ http://pitando.net/2016/03/03/programar-un-led-rgb-con-gpio-zero/#comments Thu, 03 Mar 2016 10:00:39 +0000 http://pitando.net/?p=1213 Sigue leyendo Programar un LED RGB con GPIO Zero ]]> En este artículo veremos lo sencillo que es actuar sobre un prototipo formado únicamente por un LED RGB desde la Raspberry Pi. Necesitaremos lo siguiente:

  • Una Raspberry Pi con las librerías GPIO Zero para Python 3 instaladas.
  • Una placa de prototipado
  • Tres resistores de 220 Ω
  • Cuatro cables de interconexión
  • Un LED RGB de cátodo común
  • Opcional: placa de expansión.

Para programar este prototipo usaremos desde Python la librería GPIO Zero, de la que ya os hablé en su día en un artículo especialmente dedicado a ello. Veréis lo simple que queda el código.

LED GB de cátodo común

Un LED RGB de cátodo común es un dispositivo de 4 terminales: tres ánodos para los colores básicos (rojo, verde y azul) y un cátodo para tierra o masa. Cuando ponemos uno de los ánodos a nivel alto, “1” lógico o a 3,3 V en el caso de la Raspberry Pi, la parte del LED de ese color conduce la corriente y el LED brillará del color activado. Combinando valores altos en los ánodos de cada color, obtendremos un tono diferente.

Para distinguir cuál es el cátodo del LED buscaremos una pata más larga que las demás: ése es. A un lado tendremos una pata más corta (el ánodo rojo) y al otro lado del cátodo nos quedan otros dos ánodos: verde y azul, en ese orden.

LED RGB de cátodo común. La pata más larga es el cátodo; a su izquierda el rojo, y a la derecha el verde y el azul en ese orden, comenzando desde el cátodo.
LED RGB de cátodo común. La pata más larga es el cátodo; a su izquierda el ánodo rojo, y a la derecha los ánodos verde y azul en ese orden, comenzando desde el cátodo.

Lo que debemos tener en cuenta, como siempre, es que la corriente debe limitarse. Podemos limitarla de dos formas: con una resistor en cada ánodo, o bien uno sólo en el cátodo común. Yo he escogido la configuración recomendada por el fabricante de mi LED, que es usar 3 resistores de 220 Ω cada uno.

El prototipo

Si tenemos la placa de prototipado conectada a la Raspberry Pi a través de cualquier medio (como una placa de expansión, por ejemplo) la Raspberry Pi debe estar apagada.

Conectamos el LED a 4 filas consecutivas, vacías, de nuestra placa.

  • Conectaremos ahora a la fila del cátodo un cable negro que llevaremos a una de las hileras de masa, o tierra, de nuestra placa de prototipado.
  • A cada ánodo conectaremos un resistor de 220 Ω
  • En los otros extremos de los resistores, conectaremos cables rojo (al ánodo rojo), verde (al verde) y azul (al azul)

Por último conectaremos los cables a pines GPIO:

  • El cable rojo al pin GPIO 12, o a una fila conectada a dicho PIN si usamos una placa de expansión
  • El cable verde al pin GPIO 16, o a una fila conectada a dicho PIN si usamos una placa de expansión
  • El cable azul pin GPIO 20, o a una fila conectada a dicho PIN si usamos una placa de expansión
  • Si no estamos usando placa de expansión de las que alimentan las hileras de referencia (+, -) deberemos conectar el cable negro a uno de los pines etiquetados como GND.

El prototipo y el diagrama quedan como sigue:

Detalle del prototipo donde se pueden ver las conexiones a cada uno de los terminales del LED
Detalle del prototipo donde se pueden ver las conexiones a cada uno de los terminales del LED
Otra vista del prototipo. Sacad partido a las dos partes de la placa para trabajar con más comodidad.
Otra vista del prototipo. Sacad partido a las dos partes de la placa para trabajar con más comodidad.
Prototipo para experimentar con el LED RGB
Prototipo para experimentar con el LED RGB

Lo que haremos ahora será ponernos a experimentar con la librería GPIO Zero.

Jugando con el LED a través de GPIO Zero

Es el momento de conectar la Raspberry Pi a la red eléctrica y abrir una ventana de intérprete de Python 3.

En ella, importaremos el componente de la librería indicándole al objeto en qué pines GPIO está conectado, rojo primero, verde después y azul por último:

from gpiozero import RGBLED

led = RGBLED (12, 16, 20)

Ahora escribiremos las siguientes sentencias para ver cómo el LED se enciende en colores rojo, verde, azul, morado, amarillo, cyan…:

led.on ()
led.off ()
led.color = (1, 0, 0)
led.color = (0, 1, 0)
led.color = (0, 0, 1)
led.color = (1, 0, 1)
led.color = (1, 1, 0)
led.color = (0, 1, 1)

Como habéis visto hay funciones directas para encenderlos (en blanco, su terna de color sería (1, 1, 1)), y apagarlos completamente (equivaldría a fijar su color a la terna (0, 0, 0)).

Además, el objeto proporciona tres enteros llamados led.red, led.green y led.blue, con los que podemos consultar el estado de cada uno de los ánodos, y también fijar un valor para encenderlo y apagarlo. Ejecuta las siguientes líneas; observarás que además de encenderse de color azul los resultados que devuelve IDLE son los esperados:

led.blue = 1
led.blue
1
led.blue = 0
led.blue
0

Atención también a la sentencia led.blink, que hará parpadear el LED. Le podremos indicar el tiempo de encendido, el tiempo de apagado, el tiempo que tarda en encenderse, el tiempo que tarda en apagarse, el color en estado encendido, el color en estado apagado, el número de repeticiones (infinitas si se omite,…):

led.blink (1, 1, 0.5, 0.25, (0, 1, 0), (1, 0, 0))

El resultado es el de este Vine (¡tengo cuenta en Vine! → encontradme como PItando):

Referencias

Documentación del componente en GPIO Zero: http://gpiozero.readthedocs.org/en/v1.1.0/api_output.html#rgbled

Ejercicios

  1. Es posible que hayas visto que los colores no tienen la tonalidad esperada: el amarillo puede parecer un poco verdoso; el blanco un poco azulado, etc. Y, si te fijas en el Vine, verás que salen tonos de amarillo y cyan cuando realmente estoy intentando hacerlo parpadear entre verde y rojo. ¿Puedes dar una explicación a estas cosas?, ¿cómo se podría resolver? Os lo cuento el jueves que viene.
  2. Combina este montaje con el del sensor de proximidad para hacer un sensor de aparcamiento: si el objeto está más lejos de 15 cm, el LED se enciende verde; si está entre 15 y 5 cm, se enciende en amarillo; si está más cerca de 5 cm, que se encienda en rojo. El jueves que viene también.

¿Te animas con estos pequeños retos?


Espero que haya resultado interesante, además de por el propio dispositivo, como primer ejemplo de lo fácil que nos puede resultar programar un dispositivo con GPIO Zero. Puedes dejarme cualquier comentario en esta misma entrada, enviándome cualquier comentario a través delformulario de contacto, o bien a través de la dirección de correo que allí encontrarás.

 

]]>
http://pitando.net/2016/03/03/programar-un-led-rgb-con-gpio-zero/feed/ 1 1213
Permisos, usuarios y grupos en Linux http://pitando.net/2016/02/25/permisos-usuarios-y-grupos-en-linux/ http://pitando.net/2016/02/25/permisos-usuarios-y-grupos-en-linux/#comments Thu, 25 Feb 2016 10:00:06 +0000 http://pitando.net/?p=1199 Sigue leyendo Permisos, usuarios y grupos en Linux ]]> A lo largo de la andadura de PItando, el contenido del blog se ha centrado casi por entero en experimentos tanto a nivel de programación, como con unos primeros prototipos electrónicos sencillos. Hemos entrado lo básico en el uso del sistema operativo de referencia de la Raspberry Pi para poder cubrir nuestros objetivos: localizar y abrir programas, movernos un poquito por el Terminal, examinar el contenido de los directorios, editar un fichero de texto. Sin embargo, profundizar en los aspectos que el sistema operativo impone a los usuarios es necesario muchas veces para entender cómo van a afectar a nuestros programas.

Por el momento, de todas formas, hemos entrevisto ya tres de estos aspectos a la hora de hacer que un programa en Python sea ejecutable y a la hora de integrar nuestro fotomatón con una impresora a través de CUPS: los usuarios, los grupos y sus permisos sobre los ficheros. Tenéis que saber que en UNIX, y por lo tanto en todos los Linux y en Mac OS X, todo toma la forma de ficheros. Todo es todo: la pantalla, el teclado e incluso procesos del sistema, en ejecución, tienen una representación en forma de fichero donde se pueden aplicar permisos y privilegios de una forma muy poderosa.

Vista de ficheros mostrando usuarios propietarios, grupos y permisos en Linux
Vista de ficheros mostrando usuarios propietarios, grupos y permisos en Linux

Manejar correctamente estos tres aspectos harán nuestros programas más seguros y saludables: tanto para el resto del sistema operativo al ejecutarlos como debe ser, como ante otros programas y usuarios no demasiado bien intencionados.

Y, por supuesto, nos ayudarán a empezar a pensar en estos términos para entender conceptos más abstractos como la privacidad y la seguridad, desde un punto de vista práctico.

Un usuario en Linux representa a una “persona” que utiliza el sistema para ejecutar algún trabajo, ya sea interactivo a través del terminal (si soy yo, por ejemplo), o basado por entero en la ejecución de un programa. Entrecomillo “persona” porque no tiene que ser humana, sino es más bien una identidad responsable de algo: yo puedo crear un usuario en el sistema que sólo sirva para ejecutar un programa que, todos los viernes, me envíe a mí mismo un correo electrónico recordándome que el sábado no tengo que ir a trabajar. De hecho en su momento veremos cómo automatizar tareas en Linux y para eso volveremos sobre los usuarios.

Los usuarios se agrupan en grupos, para poder tratarlos en bloque cuando se trata de facilitarles permisos para realizar determinadas tareas sobre los ficheros del sistema.

Los ficheros tienen asociados permisos que, según “quien sea” el usuario que esté trabajando en el sistema, le permitirán hacer unas cosas u otras. Por ejemplo, hay ficheros que el usuario pi no va a poder modificar dentro de una instalación de Raspbian, pero sí leer. Y, como hemos visto muchas veces, no basta con escribir código Python dentro de un fichero de texto llamado “hola_mundo.py” para poderlo ejecutar desde el terminal: debemos darle permisos de ejecución escribiendo en el terminal chmod +x hola_mundo.py. Ningún programa, esté escrito como esté escrito, podrá ser ejecutado por un usuario si dicho fichero no tiene configurados permisos de ejecución para dicho usuario.

Usuarios y creación de usuarios

Hay fundamentalmente dos tipos de usuarios en un sistema UNIX como es Linux: los administradores, o súper usuarios, y los usuarios normales (regular user en inglés). Los administradores están liderados por un usuario llamado root, presente siempre en todos los sistemas Unix, que tiene permisos de escritura y facultad de cambiar absolutamente todo, ya sea para arreglarlo o para destrozarlo sin remedio ni vuelta atrás. Puede acceder a todos los documentos de todos los usuarios del sistema, a los archivos donde se almacenan los correos electrónicos, pueden escribirlos, borrarlos, borrar cualquier directorio… es, literalmente, omnipotente. La contraseña de este usuario es algo que debe mantenerse bajo el más estricto secreto y sólo al alcance de gente solvente tanto técnica como ética y moralmente.

Bajo la omnipotencia del usuario root hay dos clases más (al menos) de administradores: los sudoers y los administradores especializados en un tema específico sobre el cual son omnipotentes: impresoras, red, tal o cual servidor,…

Los sudoers son usuarios que pueden adquirir privilegios de administración personalmente: pertenecen a un grupo que puede ejecutar el comando sudo para activar aquellos privilegios que el usuario root les haya delegado. La orden sudo está protegida por la contraseña del usuario que es sudoer, para que nadie que se encuentre un terminal abierto pueda cometer tropelías.

Los privilegios que obtienen los sudoers al ejecutar una orden bajo el amparo de sudo pueden ser desde todos los privilegios (es lo más común en sistemas domésticos o personales), a sólo algunos pocos para una tarea concreta. La forma de configurar todo ésto e “hilar fino” en este sentido cae fuera de mis ambiciones para este artículo.

Os enlazo una lectura muy antigua que forma parte de los anales de la cultura UNIX y que todo el mundo que se acerque a esta familia de sistemas operativos debe leer tarde o temprano. Tiene más de 20 años y sigue vigente como el primer día: Administración del sistema. Especial atención al apartado “Sobre raíces, sombreros y la sensación de poder“, que llama la atención al usuario que ha obtenido recientemente privilegios de súper usuario acerca de ciertos principios morales y conservadores que debe tener muy claros. Incluyendo en esto último, por supuesto, al dueño de un Macintosh compartido. Mac OS X es, a todos los efectos, un sistema operativo de la familia UNIX (sea su parentesco lo lejano que sea).

Este señor ejemplifica bastante bien lo que sería un usuario root de un sistema (Matrix en este caso) © Warner Bros.
Este señor ejemplifica bastante bien lo que sería el usuario root de un sistema, además de su creador, aunque eso sea otra historia (The Matrix  © Warner Bros.)

Creación y destrucción de usuarios

Existen, que yo sepa, tres formas de crear un usuario en Linux. A saber:

  • Mediante la herramienta gráfica de turno de gestión de usuarios. Varía con cada distribución o, mejor dicho, con cada entorno de ventanas. Generalmente está orientada a crear usuarios humanos solamente. Por estas dos razones no profundizaré más en ella, y queda descartada para este artículo.
  • Mediante la orden de terminal adduser [identificador de usuario] . Esta orden está destinada a la creación de usuarios humanos, y por ello:
    • Crea un directorio personal bajo /home: /home/[identificador de usuario]
    • Copia a dicho directorio los ficheros y directorios básicos, o “plantilla”, desde el directorio /etc/skel (de skeleton, esqueleto).
    • Solicita, de forma interactiva, una contraseña para este usuario (contraseña UNIX) que habrá que repetir.
    • Pide también interactivamente los datos personales básicos para saber quién es: nombre, teléfono,… Este paso es opcional.
  • Mediante la orden de terminal useradd [identificador de usuario] . Esta herramienta está destinada a la creación de usuarios automáticos, o usuarios lógicos: no son humanos y se usan exclusivamente para ejecutar programas bajo su identidad. Por ejemplo, el servidor web apache se ejecuta por defecto con un usuario lógico llamado www-data .
    • Esta herramienta no es interactiva: no solicita nada.
    • El usuario así creado no tiene contraseña, habría que asignársela automáticamente con la orden passwd [identificador de usuario]  si fuese necesario.
    • No tiene directorio personal, ni nada parecido, porque no está pensado para que sea una persona que use el ordenador en su trabajo.

Por defecto, cada usuario tiene un grupo individual y exclusivo llamado de la misma forma que él. Su utilidad básica es compartir ficheros: si incluyo a otros usuarios en mi grupo puedo controlar su acceso a mis carpetas de documentos, como veremos en el siguiente apartado.

Los usuarios se borran con la orden deluser [identificador de usuario]. Hay que tener precaución antes de usarla porque no pide confirmación.

Todas estas herramientas se deben ejecutar con permisos de superusuario: o bien conectándonos como el usuario root, o bien ejecutando el comando sudo si está disponible en el sistema y tenemos permisos para utilizarlo. No dudes en utilizar el manual de Linux, como veíamos en el artículo de uso básico del terminal, para saber más sobre todos estos comandos.

Grupos y asignaciones a grupos

Un grupo en UNIX es, como era esperable, un conjunto de usuarios relacionados entre sí. Los grupos se pueden crear, poblar (asignándole usuarios) y borrar, y sirven para otorgar facultades sobre el sistema o partes del mismo a todos los usuarios que estén dentro del grupo. Más adelante veremos cuáles; de momento vamos a ver cómo crear y borrar grupos, y las asignaciones que podemos hacer.

Para crear un grupo ejecutaremos la orden groupadd [nombre del grupo], mientras que para borrarlo haremos groupdel [nombre del grupo]. Ambas órdenes deben ejecutarse como root o mediante sudo.

Ninguna de las dos órdenes va a pedir confirmación, sin importar el número de usuarios que hubiera asociados al grupo en el caso del borrado. Por eso, es vital pensar mucho antes de ejecutar un borrado de grupo del sistema: sus integrantes pueden sufrir consecuencias inesperadas como la imposibilidad de acceder a discos duros externos, a dispositivos de vídeo, a ficheros… como veremos a continuación.

Asignar un usuario a un grupo

Para añadir un usuario a un grupo lo haremos mediante la orden usermod. Esta orden tiene muchas opciones que os dejo para consultar en el manual: la que nos interesa para la inmensa mayoría de los casos es usermod -aG [nombre del grupo] [nombre del usuario]. Con ella, añadimos un usuario al grupo sin importar (ni eliminarle de) los grupos a los que ya perteneciese anteriormente.

Para consultar los grupos a los que pertenece un usuario podemos usar la orden groups.

Para eliminar un usuario de un grupo ejecutaremos, también como súper usuarios, deluser [nombre de usuario] [nombre de grupo]. Ojo con esta orden pues es la misma que se usa para borrar totalmente un usuario, sólo que con otra opción más como es el nombre del grupo del que retirar al usuario. Si os confundís, le liáis la del pulpo al usuario víctima de vuestra confusión, porque esta orden no pide confirmación.

Asignar un fichero (o un directorio) a un grupo

Aquí empieza la potencia de los grupos. Podemos definir que ficheros o directorios pertenezcan a un grupo de usuarios: todo lo que existe en un sistema UNIX es propiedad de un grupo. Dicho de otra forma, a un grupo se le puede asociar un usuario mediante una relación de pertenencia (el usuario forma parte del grupo) pero también se le pueden asociar ficheros mediante una relación de propiedad (un fichero es propiedad de un grupo de usuarios).

Así, todo ese grupo de usuarios tendrán las mismas facultades sobre un fichero.

Para asociar un fichero a un grupo se ejecuta la ordenchgrp [nombre de usuario] /ruta/de/ficheros/o/directorios, o bien anteponiendo sudo, o bien como el usuario  root. Para consultar los permisos, propietarios y grupos (co-)propietarios de los ficheros, tenemos modificadores en la orden ls que nos ayudan, como es el modificador -l (ls -l)

NOTA: a diferencia de un usuario, que puede pertenecer a muchos grupos, un fichero sólo puede ser propiedad de un grupo. Lo mismo con un directorio, pero sin embargo podemos hacer que un directorio pertenezca a un grupo pero su contenido (directorios o ficheros) a otros. No confundáis un directorio y los permisos sobre éste, con su contenido y los permisos sobre su contenido.

El usuario propietario de un fichero

Cuando un usuario crea un fichero, tanto desde el terminal como a través de haber ejecutado un programa, es su propietario por pleno derecho. El propietario de un fichero, siempre y cuando pueda acceder al mismo, está siempre facultado a cambiarle los permisos. Ahora llegamos a los permisos 🙂

Sin embargo, un fichero puede llegar a ser propiedad de un usuario y al, mismo tiempo, propiedad de un grupo, pertenezca el usuario propietario del fichero a él, o no. En este caso, siempre prevalece la propiedad del usuario sobre la del grupo. Cuando el usuario pertenece al mismo grupo al que pertenece el fichero, el grupo es tratado como “el resto de usuarios que pertenecen al grupo aparte del propietario”.

El propietario de un fichero no puede cambiar el grupo al que pertenece un fichero, pero el usuario root sí, mediante la orden chown (cuyo modo de uso te dejo que consultes tú en el manual).

Para consultar los permisos, propietarios y grupos (co-)propietarios de los ficheros, tenemos modificadores en la orden ls que nos ayudan, como es el modificador -l (ls -l)

El fondo de la cuestión: los permisos

A todo fichero o directorio en un sistema Linux se le asocian permisos en tres aspectos:

  • Permisos de lectura: para acceder al contenido y verlo, leerlo o procesarlo.
  • Permisos de escritura: para modificarlo.
  • Permisos de ejecución: para ejecutar el contenido de un fichero, si es un programa.

Cuando se trata de directorios, los permisos de lectura permiten al usuario listar su contenido, los de escritura crear archivos dentro, y los de ejecución atravesarlo, por ejemplo haciendo cd, o por otro lado escribiendo una ruta que lo atraviese: /esta/ruta/por/ejemplo atraviesa el directorio “por”.

Estos permisos se asocian en tres niveles:

  • Usuario propietario del fichero
  • Grupo co-propietario del fichero
  • El resto de usuarios del sistema

La prioridad va de arriba a abajo en el sentido de que el propietario de un fichero es quien tiene más poder, y por lo tanto sus permisos prevalecen, el grupo co-propietario es el siguiente, y el resto es el menos prioritario.

Los permisos se representan de dos formas: mediante una cadena de 10 letras, en la cual la primera que pueden ser d (o nada) y cada una de las otras 9 r, w o x.

  • d  significa que el elemento cuyos permisos estamos viendo es un directorio, y por lo tanto aplica lo que he discutido arriba. Si no hay d, es que es un archivo.

Para el resto de permisos, la ausencia indica que no se concede el permiso, la presencia indica que sí se concede el permiso.

  • r significa lectura.
  • w significa escritura
  • x significa ejecución

Del orden de las letras obtenemos el nivel del permiso: las tres primeras a la izquierda aplican al usuario propietario del fichero o del directorio, las tres siguientes al grupo propietario, y las tres últimas, a la derecha, al resto de usuarios del sistema. Como hemos discutido, los permisos se aplican en orden de prioridad en el mismo sentido en que se leen de izquierda a derecha; primero el propietario, luego el grupo y luego el resto de usuarios del sistema.

Un ejemplo sería el siguiente:

-r-xr--r--

En este caso estamos ante un fichero cuyo propietario puede leer y ejecutar, los usuarios del grupo propietario pueden leer solamente (excepto si el propietario es también parte del grupo, caso en el que lo podría ejecutar porque sus permisos tienen más peso), y el resto de usuarios del sistema también.

Otro ejemplo:

drwxr-x---

Ahora estamos viendo un directorio cuyo propietario puede listar su contenido (con ls), crear ficheros dentro y atravesarlo; el grupo al que pertenece el directorio puede examinarlo y atravesarlo, pero no crear ficheros dentro. Por último, el resto de usuarios del sistema no puede hacer nada… excepto root, claro.

¡Nada de esto puede detener al usuario root!

Para consultar los permisos, propietarios y grupos (co-)propietarios de los ficheros, tenemos modificadores en la orden ls que nos ayudan, como es el modificador -l (ls -l)

Los permisos se cambian con la orden chmod, a la que se puede especificar lo siguiente:

  • El nivel o niveles de aplicación:
    • u para el usuario propietario.
    • g para el grupo propietario
    • o, de otros para el resto de usuarios del sistema: otros.
    • a  engloba a los tres anteriores al mismo tiempo.
  • Un + para otorgar, o un – para quitar.
  • El permiso, recordemos:
    • r significa lectura.
    • w significa escritura
    • x significa ejecución

Serían construcciones válidas chmod a+x[fichero] , chmod a-x [fichero] , chmod u+rw [fichero] y chmod go-w [fichero] , es decir: podemos agrupar tanto niveles sobre los que actuar como permisos que otorgar.

Además puedo combinar separados por comas varias modificaciones en una misma orden, separándola por comas. Por ejemplo, chmod a+x,u+rw,go-w [fichero]  otorga permisos de ejecución a todos los usuarios, al propietario le garantiza permisos de lectura y escritura, y tanto al grupo como a otros usuarios les quita permisos de escritura.

Vamos a ver todo esto con un ejemplo práctico sencillo: planificar los permisos de un aula de informática.

Ejercicio: el aula de informática

En el aula de informática existen dos profesores que enseñan Python a un grupo de 6 alumnos. Tanto los profesores como los alumnos pueden entrar en todos los ordenadores del aula con sus nombres de usuario, que son pepe  y paco para los profesores y alba, alicia, marta, marcos, pedro y juan  para los alumnos. Cada uno tiene su contraseña y su directorio personal.

A ti, que eres el encargado de montar el aula de informática, te piden lo siguiente:

  • Los profesores deben poder leer y ejecutar los programas de los alumnos.
  • Los alumnos no pueden leer ni ejecutar los programas de los otros alumnos.
  • Todos los alumnos deben poder leer los archivos de los profesores, pero no ejecutarlos ni modificarlos.

Solución

Crearemos los usuarios con la orden adduser [usuario] , pues es la interactiva que permite crear usuarios humanos. Eso creará directorios de inicio bajo /home donde poder practicar.

Empezaremos restringiendo los permisos de los alumnos entre ellos, pero garantizando el acceso de lectura de los profesores a todos los ficheros de los alumnos. Me centraré en alba como alumna y en paco como profesor para el ejemplo, aunque habría que extenderlo a todos los demás.

  • Que los directorios raíz de los alumnos (por ejemplo, /home/alba) pertenezcan a un grupo ajeno a los alumnos, como puede ser profesores , y no otro grupo, ni siquiera al suyo propio aunque ellos sean los propietarios.
    • chgrp profesores /home/alba
  • Los profesores deben pertenecer también a ese grupo, profesores.
  • Los alumnos no pueden pertenecer al grupo de profesores.
  • El directorio raíz de  alba , en este ejemplo, debe tener los permisos drwxr-x---
    • chmod u+rwx,g+rx,g-w,o-rwx /home/alba
    • Es un directorio: d
    • Escritura, lectura y ejecución, para  albarwx
    • Lectura y ejecución para el grupo, que es  profesores:r-x
    • Ninguno para el resto de usuarios:---

De esta forma, sólo alba  y el grupo profesores podrán entrar en su directorio raíz y, por lo tanto, acceder a su información: da igual los permisos individuales que tengan los ficheros y los directorios bajo ~alba.: al no poder atravesarlo, no podrán leerlo ni aunque todos los usuarios tuvieran permisos totales. Pero es que, además, el grupo deprofesores no podrá escribir información en sus ficheros aunque sí leerlos y ejecutarlos para evaluarlos.

De la misma forma,  alba no podrá entrar en el directorio de  juan si le aplicamos los mismos permisos a su carpeta de inicio, ~juan.

Si alba no quita permisos a los ficheros que crea, los profesores podrán leerlos sin ningún problema ya que los ficheros se crean por defecto con el grupo de permisos -rw-r--r--, es decir: lectura y escritura para ella misma, lectura para el grupo (que al haberlo creado ella, será ella misma y no los profesores) y lectura para todos los demás, incluyendo a los profesores.

Sin embargo, alba tendrá que asegurarse personalmente que los profesores pueden ejecutar sus trabajos dándoles permisos de ejecución. Lo hará ejecutando chmod uo+x [fichero a convertir en ejecutable]en lugar de simplemente chmod +x [fichero a convertir en ejecutable]. De esa forma, el conjunto de permisos quedará como sigue:

-rwxr--r-x

El efecto que tiene esto es que podrán ejecutarlo solamente:

  • Ella misma.
  • Cualquier otro usuario con permiso de acceso a todos los directorios que conducen hasta el fichero ejecutable, incluyendo su directorio personal (al que el resto de los alumnos no pueden acceder)

Es decir, podrán ejecutarlo ella misma y los profesores, pero no el resto de los alumnos.

El acceso de los alumnos al programa de sus profesores es fácil: los profesores sólo tienen que dar permisos de lectura a “otros” sobre los ficheros que quieren que se puedan leer y, además, a todos los directorios que conduzcan hasta ellos, y no de escritura ni de ejecución. Por defecto, los ficheros se crean con acceso de lectura para todos los usuarios (el propietario, los del grupo al que pertenece y el resto) y de escritura para el dueño.

Así pues, lo que tienen que hacer los profesores es asegurarse de que cuando den permisos de ejecución sobre un fichero lo hagan exclusivamente para ellos, o para ellos y el resto de profesores: chmod u+x [fichero]  o chmod ug+x [fichero] , pero no al resto simplemente con el modificador +x. Si no han restringido ningún otro permiso, ya debería funcionar.

Conclusiones y recomendaciones

Podréis decir: “Vale, pero esto es para ficheros. No es tan potente, porque hay muchas otras cosas en un ordenador”. Lo bonito de todo esto es lo que os contaba al principio del artículo y es que, en UNIX, absolutamente todo en el sistema, desde el teclado a la pantalla, pasando por todas las unidades de almacenamiento conectadas, los joysticks, el sistema de sonido… toma forma de fichero para facilitar su gestión. Es decir, uno puede restringir el que un usuario use el DVD-ROM con tan sólo cambiar los permisos sobre un fichero especial que representa a un dispositivo físico.

Lo que hemos visto en este artículo es muchísimo más de lo que vais a necesitar en la mayoría de las ocasiones. Sin embargo, conviene tener algún “poso” de todos estos temas aunque sólo sea de pasada, por si en alguna ocasión tenéis que echar mano de algo de esto.

Mi recomendación es que, si no tenéis nada mejor que hacer, creéis unos cuantos usuarios y unos cuantos grupos para despejar cualquier incertidumbre, palpar bien todos estos temas y crearos vuestras propias sensaciones e inquietudes. Os propongo, por poner un ejemplo, provocar de varias formas que el usuario propietario de un fichero sea incapaz de acceder a él.

Si tenéis cualquier duda no dudéis en contactar por los canales habituales: comentarios y formulario de contacto, donde además encontraréis una dirección de correo electrónico para escribirme siempre que queráis.

]]>
http://pitando.net/2016/02/25/permisos-usuarios-y-grupos-en-linux/feed/ 5 1199
Episodio 17 – con Luís del Valle y Alfonso Contreras, de “La tecnología para todos” y programarfacil.com http://pitando.net/2016/02/25/episodio-17-con-luis-del-valle-y-alfonso-contreras-de-la-tecnologia-para-todos-y-programarfacil-com/ Thu, 25 Feb 2016 06:00:02 +0000 http://pitando.net/?p=1208 Sigue leyendo Episodio 17 – con Luís del Valle y Alfonso Contreras, de “La tecnología para todos” y programarfacil.com ]]>  

En este episodio tengo el placer de charlar con Luís del Valle (https://twitter.com/ldelvalleh) y Alfonso Contreras (https://twitter.com/acontreraslopez) del podcast “La Tecnología para todos”, que pertenece al blog http://programarfacil.com, en twitter como https://twitter.com/programarfacilc.

Hemos hablado de Arduino y de sus proyectos, entre los que destaca un campus online donde podréis suscribiros a los cursos que tienen programados, y del estado de todo este mundillo de la tecnología en la educación en España. Os dejo aquí enlaces mencionados en el audio, y más:

Recuerda que puedes dejarme tus comentarios en el blog http://pitando.net. También te invito a que valores el podcast en iTunes (https://itunes.apple.com/es/podcast/pitando/id1012568958?l=en&mt=2) y comentes en iVoox (http://www.ivoox.com/pitando_sq_f1177092.51643729_1.html) si te gusta, pues es una forma de hacer que llegue a más usuarios con un sencillo gesto.

]]>
1208
Controla un sensor de proximidad desde tu Raspberry Pi http://pitando.net/2016/02/18/controla-un-sensor-de-proximidad-desde-tu-raspberry-pi/ http://pitando.net/2016/02/18/controla-un-sensor-de-proximidad-desde-tu-raspberry-pi/#comments Thu, 18 Feb 2016 10:00:56 +0000 http://pitando.net/?p=1174 Sigue leyendo Controla un sensor de proximidad desde tu Raspberry Pi ]]> En este artículo vamos a jugar con un componente muy sencillo: el sensor de proximidad HC-SR04. Es este componente de aquí:

Sensor HC-SR04 de proximidad por ultrasonidos
Sensor HC-SR04 de proximidad por ultrasonidos

Seguro que, si os interesa el mundillo del cacharreo y el prototipado, os suena porque parecen unos ojos. En cierto modo lo son, ya que gracias a él nuestra Raspberry Pi puede detectar objetos en su proximidad, y ofrecer a nuestros programas la distancia a la que se encuentra el componente detectado.

Su principio de funcionamiento es el del SONAR, y es el que usan los murciélagos para detectar obstáculos y presas. Por uno de los “ojos”, el que está marcado como T de transmisor, nuestro sensor emitirá ultrasonidos cuyo eco, fruto del rebote con un obstáculo, captará con el que aparece rotulado con una R, de receptor. Al recibir ese eco, nuestro sensor emitirá una señal eléctrica a través del pin etiquetado como Echo que, correctamente interpretada, nos ofrecerá la distancia al obstáculo.

En este artículo vamos a configurarlo para imprimir por pantalla las distancias al objeto más cercano, a través de un programa en Python.

Acerca del HC-SR04

Este sensor se encuentra disponible a un precio muy asequible en Amazon España (enlace de afiliado), aunque como en todo lo de Amazon los precios fluctúan según la demanda. Su funcionamiento está basado en ultrasonidos y funciona con una corriente de 15 mA y unos niveles de tensión de alimentación y referencia de 5 V, en contraposición a la tensión de referencia que utiliza la Raspberry Pi, es de 3,3 V. A priori, esto es un problema que se puede resolver usando sabiamente nuestros conocimientos de circuitos eléctricos y resistores, como veremos.

Tiene 4 terminales, a saber:

  • Vcc, que es el terminal de alimentación al que deberemos conectar un voltaje continuo de 5 V.
  • Trig, de trigger (gatillo, o disparador), que es una señal de control que nosotros enviaremos desde la Raspberry Pi para pedirle al sensor que mida la distancia al objeto que tenga enfrente. Desde el punto de vista del sensor es un PIN de entrada, que deberemos conectar a un GPIO configurado como salida. Funciona por tensión y si le aplicamos un voltaje de 3,3 V de forma directa funcionará correctamente.
  • Echo es un PIN a través del cual el sensor nos contestará una vez le pidamos una medida, haciendo variar su salida entre 0 y 5 V.
  • Gnd es un terminal al que deberemos aplicar el voltaje de referencia de 0 V desde nuestra placa de prototipos.

El sensor tiene un alcance de 4 metros y la distancia más corta que es capaz de detectar es de 2 centímetros, todo ello con una precisión de 3 milímetros.

Funcionamiento

El funcionamiento del sensor responde a este mecanismo:

  • Para hacer una medida nos tenemos que asegurar de que, previamente, el PIN Trig esté a nivel bajo (0 V, 0, GPIO.LOW) durante al menos 2 μs (microsegundos).
  • Posteriormente, debemos enviar un nivel alto (3,3 V, 1, GPIO.HIGH) al PIN Trig durante 10 μs, intervalo tras el cual lo volveremos a poner a nivel bajo.

Esto hace que el sensor emita 8 pulsos de ultrasonidos a una frecuencia de 40 kHz. El oído humano es capaz de oir frecuencias de hasta 22 kHz en general (habrá alguien que pueda superarlo, pero por poco), por eso es inaudible y se llama ultrasonido porque está por encima (ultra) de la máxima frecuencia perceptible.

  • Tras ello, al detectar un objeto en el rango de medición (menor a 4 metros), el PIN Echo se pondrá a un nivel alto de 5 V.
  • Se mantendrá con ese voltaje durante un tiempo que representa el tiempo T que ha tardado el sonido en viajar al obstáculo desde el transmisor, rebotar, y ser detectado en el receptor.
Funcionamiento del sensor. El pulso en Echo tendrá una duración T igual al instante de recepción, tR, menos el instante de emisión, tI.
Funcionamiento del sensor. El pulso en Echo tendrá una duración T igual al instante de recepción, tR, menos el instante de emisión, tI.

 

  • Lo que tendremos que hacer con esa medida de tiempo, en segundos, será dividirla entre 2 (sólo queremos contar un camino) y multiplicarla por la velocidad del sonido, que a 20 ºC es de 343 metros por segundo. Así:

D (m) = (T/2) · 343 m/s

Y el resultado, como es lógico según la fórmula, vendrá dado en metros.

Montaje del prototipo

Para montar el prototipo voy a estrenar mi placa de prototipado larga que viene con el kit de Sunfounder que os enseñaba hace unas semanas, junto con la placa de expansión de GPIO que trae; la otra que tengo del kit de Vilros no encaja. Como veréis en la foto tengo que cablear la alimentación sacándola de los terminales GPIO de 5V0 (5 V) y de GND (0 V).

Prototipo con HC-SR04
Prototipo con HC-SR04
Detalle de las conexiones del sensor. Cable rojo: Vcc (5 V). Cable amarillo: Trig. Cable verde: Echo. Cable negro: Gnd (0 V)
Detalle de las conexiones del sensor. Cable rojo: Vcc (5 V). Cable amarillo: Trig. Cable verde: Echo. Cable negro: Gnd (0 V)

Diagrama de conexiones:

Prototipo con el esquema básico de conexiones - haz click para ampliarlo
Prototipo con el esquema básico de conexiones – haz click para ampliarlo
  1. Conectaremos un PIN 5V0 (5 V) desde las filas conectadas a la placa de expansión a una de las columnas laterales rotuladas con un signo + rojo. Usad un cable rojo, por convenio (negro es masa y rojo es tensión de alimentación).
  2. Conectaremos un PIN rotulado como GND, 0 V, masa o tierra, desde su fila a la columna lateral marcada con un signo – azul. Usad un cable negro, por convenio (negro es masa y rojo es tensión de alimentación).
  3. El sensor hay que conectarlo ocupando 4 filas de la placa de prototipado: como recordaréis del artículo y el vídeo en el que os enseñaba este tipo de placas con un ejemplo más pequeño, las columnas de una misma fila están conectadas entre sí. En mi caso he ocupado las 4 últimas: de la 60 a la 63, incluidas.
  4. Una vez hecho eso, alimentamos el sensor:
    1. Tiramos un cable rojo desde la fila de tensión roja, de 5 V (a la que hemos conectado el cable en el punto 1), directamente a la fila que se conecta con el terminal Vcc del sensor.
    2. Tiramos un cable negro desde la fila azul, de 0 V, a la que hemos conectado un cable desde un PIN GND (punto 2), directamente a la fila que se conecta con el terminal Gnd del sensor.
  5. Sacamos un cable amarillo de la fila que se conecta al terminal Trig del sensor y lo conectamos directamente a la fila que se conecta al PIN GPIO 12. Este GPIO 12 estará configurado como salida, y la corriente que extraerá el sensor será su corriente de trabajo (15 mA), dentro de los niveles seguros para la Raspberry.
    1. Recordad que la Raspberry Pi soporta a través de GPIO “un máximo de 16 mA por cada PIN, no superando entre todos 50 mA“: os lo contaba en el artículo sobre análisis de circuitos eléctricos.
    2. Esto quiere decir que en este prototipo no necesitamos limitar la corriente con un resistor, pero si usásemos más terminales GPIO como salida en otro montaje más complejo, sí deberíamos hacerlo para no superar el máximo de 50 mA entre todos ellos.
  6. Sacaremos un cable verde del terminal Echo del sensor, que conectaremos a un divisor de tensión formado por tres resistores de 330 Ω como podéis ver en el esquema y, con otro cable verde, lo llevaremos hasta el PIN GPIO 6. El divisor de tensión merece una sección aparte.
  7. El divisor de tensión debe ir conectado a tierra como en el esquema, por lo que si lo estáis conectando en la sección inferior de la placa de prototipado, como yo, deberéis conectarlo a la fila azul rotulada con un -, y ésta a un puerto GPIO rotulado como GND mediante un cable negro.

Pasar de 5 V a 3,3 V: el divisor de tensión

Un divisor de tensión es un sistema de resistores que sirve para fijar la tensión a un nivel intermedio entre el nivel de alimentación del conjunto (por ejemplo, 5 V) y el nivel de tierra. Normalmente se configura con 2 resistencias como en la siguiente imagen:

Divisor de tensión
Divisor de tensión

Si suponemos que Vd no se conecta a nada y por lo tanto por esa derivación no sale ninguna corriente, este montaje funciona como sigue:

Vd = I · R2

Echo = I · R1 + I · R2

Dividiendo una expresión por otra, se tiene que:

Vd / Echo = I · R2 / (I · R1 + I · R2)

Simplificando y despejando Vd, se llega a que:

Vd = Echo · R2 / (R1 + R2)

En el circuito que estamos montando, Echo es 5 V y queremos conseguir en Vd haya un valor de 3,3 V, puesto que la Raspberry Pi trabajo con ese nivel de referencia. Por lo tanto:

3,3V = 5V * R2/ (R1 + R2) ⇒

3,3V · R1 + 3,3V · R2 = 5V * R2 ⇒

R2 ≅ 2 · R1

Es decir: si configuramos un divisor de tensión con una resistencia R2 conectada a tierra que valga el doble que otra resistencia R1, conectada a la tensión de referencia de 5 V, en medio de ambas resistencias tendremos aproximadamente 3,3 V. Nos vale cualquier par de resistencias, pero en función de su valor la corriente que las recorrerá será, lógicamente, diferente.

En el caso que nos ocupa este sencillo análisis es válido, ya que Vd irá directamente conectado a un PIN GPIO configurado como entrada: los GPIO de entrada se comportan como un circuito abierto (o una resistencia infinita), y por lo tanto no drenan corriente del circuito. Pero ojo: si Vd lo conectásemos a un circuito que presentase una resistencia global menor que infinito, habría que hacer un análisis más completo. Te propongo que completes el circuito poniendo una resistencia R3 entre Vd y tierra, y que apliques las leyes de Kirchoff y de Ohm para comprobarlo. En este caso, además, como la corriente que aceptaría el resto del circuito no sería nula, tendríamos que considerar dos resistencias que fijasen no sólo la tensión, sino también la corriente, de forma que el resto del circuito no se dañase o funcionase como se pretende.

Como se da la mala suerte de que no tengo ninguna resistencia en mis kits que valga exactamente el doble que otra, lo que haré será colocar 2 resistencias de 330 Ω en serie en lugar de una de 660 Ω para hacer las veces de R2:

Divisor de tensión final
Divisor de tensión final

Las resistencias en serie son equivalentes a una única resistencia cuyo valor es la suma de sus valores individuales, como podrás comprobar si repites el análisis.

Si ahora vuelves a consultar el diagrama podrás reconocer un divisor de tensión en la configuración de resistencias, el PIN Echo del sensor y el GPIO 6 de entrada a la Raspberry Pi: los elementos recorridos por los cables verdes.

El divisor de tensión dentro del prototipo
El divisor de tensión dentro del prototipo

Programa de pruebas

El programa de pruebas, de acuerdo a lo explicado en el apartado Funcionamiento, es el siguiente:

#!/usr/bin/python3

import time
import RPi.GPIO as GPIO
<br># Configurar el GPIO con convenio de numerado BCM
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# trig (cable amarillo en el prototipo)
GPIO.setup(12, GPIO.OUT)
# echo (cable verde en el prototipo)
GPIO.setup(6, GPIO.IN)

try:
    while True:
        start = 0
        end = 0
        # Configura el sensor
        GPIO.output(12, False)
        time.sleep(2) # 2 segundos para hacer el programa usable
        # Empezamos a medir
        GPIO.output(12, True)
        time.sleep(10*10**-6) #10 microsegundos
        GPIO.output(12, False)

        # Flanco de 0 a 1 = inicio 
        while GPIO.input(6) == GPIO.LOW:
            start = time.time()
        # Flanco de 1 a 0 = fin
        while GPIO.input(6) == GPIO.HIGH:
            end = time.time()

        # el tiempo que devuelve time() está en segundos
        distancia = (end-start) * 343 / 2
        print ("Distancia al objeto =", str(distancia))
        
except KeyboardInterrupt:
    print("\nFin del programa")
    GPIO.output(12, False)
    GPIO.cleanup()

En este listado, lo único que tenéis que tener en cuenta es que para hacer el programa usable y que os dé tiempo a ver en pantalla los resultados he puesto una pausa de 2 segundos, que no microsegundos, entre cada medida. Ese intervalo es igualmente válido para configurar el sensor (forzando un nivel bajo en el terminal Trig, conectado al GPIO 12).

Para medir el ancho del pulso que el sensor estoy usando dos bucles de espera activa, técnica de la que no me siento orgulloso y que por lo tanto, en su debido momento, será objeto de estudio para entenderla, ver sus defectos y hablar sobre cómo los podemos solventar:

        # Flanco de 0 a 1 = inicio 
        while GPIO.input(6) == GPIO.LOW:
            start = time.time()
        # Flanco de 1 a 0 = fin
        while GPIO.input(6) == GPIO.HIGH:
            end = time.time()

Fijando esos dos instantes de tiempo con ayuda de esos bucles, ya tendremos la medida, en segundos, del tiempo que tarda el (ultra)sonido en ir hasta el obstáculo y volver al sensor. Dicha medida la podremos multiplicar por la velocidad del sonido para obtener la distancia, en metros.

El resultado lo podéis ver en la siguiente imagen:

Resultado del ejercicio
Resultado del ejercicio

No olvidéis dar permisos de ejecución al programa con chmod +x ultrasonidos.py  (si lo habéis llamado así).

Y esto es todo: espero que haya resultado interesante. Puedes dejarme cualquier comentario en esta misma entrada, enviándome cualquier comentario a través del formulario de contacto, o bien a través de la dirección de correo que allí encontrarás.

]]>
http://pitando.net/2016/02/18/controla-un-sensor-de-proximidad-desde-tu-raspberry-pi/feed/ 6 1174
Análisis básico de circuitos eléctricos http://pitando.net/2016/02/11/analisis-basico-de-circuitos-electricos/ http://pitando.net/2016/02/11/analisis-basico-de-circuitos-electricos/#comments Thu, 11 Feb 2016 10:00:36 +0000 http://pitando.net/?p=1151 Sigue leyendo Análisis básico de circuitos eléctricos ]]> Hasta el momento hemos jugado con algún prototipo electrónico para aprender distintos conceptos de programación, obteniendo como recompensa un efecto físico: un LED parpadeando, por ejemplo. También a la inversa: hemos obtenido un efecto en un programa a raíz de un estímulo físico como pulsar un botón en una placa.

Durante estos primeros meses, nuestros prototipos hacen uso de resistores para fijar niveles de tensión y limitar corriente, de tal forma que además de contribuir a que las cosas funcionen, protegen al resto de los componentes. Pero, ¿cómo podemos decidir qué valor de resistencia utilizar en cada caso? Analizar un circuito formado por resistencias es algo sencillo que sólo exige resolver ecuaciones de primer grado, dispuestas de acuerdo a ciertas leyes eléctricas que vienen de la física.

En este artículo intentaré explicar de una forma clara y sencilla esas tres leyes, muy simples, que rigen los circuitos eléctricos y que nos ayudarán a analizarlos y diseñarlos.

Relación entre voltaje y corriente: resistencia y la ley de Ohm

Vamos a empezar por el principio y a tratar de explicar cómo se produce la conducción de corriente a través de un cable conductor, físicamente hablando.

Si tenemos un cable conductor y le aplicamos un voltaje a sus extremos, una corriente eléctrica lo recorre porque los electrones se mueven a lo largo del cable, entre los extremos del mismo. Los electrones son partículas con carga negativa que viajan desde el extremo de menor potencial (por ejemplo, 0 V) al extremo de mayor potencial. Por convenio, aunque para muchos es por complicar las cosas, la corriente se representa en el sentido contrario al movimiento de los electrones (de mayor a menor potencial).

Los electrones se moverán en mayor medida cuanto mayor sea el voltaje. La cantidad de electrones que pasan por un determinado punto del cable en cada segundo es la intensidad de la corriente. Uniendo las dos frases anteriores, podemos decir que la intensidad de corriente (I) es proporcional al voltaje (V) aplicado. Pero, ¿en qué medida? En la medida en que la resistencia (R) al paso de corriente que presente el cable lo permita, de acuerdo a la Ley de Ohm:

V = I · R

El voltaje se mide en Voltios (V),
la intensidad en Amperios (A)
y la resistencia en Ohmios (Ω)

Elementos de la Ley de Ohm
Elementos de la Ley de Ohm

Por ejemplo: si aplicásemos 3,3 V a una resistencia de valor desconocido, y con un multímetro midiésemos una corriente de 10 mA, el resistor tendría un valor de…

V = I · R ≡ 3,3 V = 10 mA · R ⇒ R = V / I = 3,3 V / 0,01 A = 330 Ω

Aunque sin estudiar algún que otro curso de física os lo tendréis que creer simplemente, la intensidad de corriente que puede conducir un cable metálico de un largo determinado depende del metal del que está hecho y de la sección del cable. Cuanto más gordo sea el cable, más corriente conduce; cuanto más estrecho, menos. Exactamente igual que una autopista: a más carriles, más coches la pueden recorrer al mismo tiempo.

Gracias a este hecho podréis pensar que una forma de regular la intensidad de corriente que atraviesa un cable es usar, en algún lugar, una sección más estrecha: de esta forma, igual que cuando quitan un carril de la autopista se produce un atasco y el tráfico va más lento, cuando quitas espacio reduces el paso de electrones por esa sección.

Sobre el papel es cierto, pero en la práctica el cable se calentará, y antes de que hayas conseguido reducir la corriente sensiblemente, la sección más estrecha se pondrá al rojo vivo y se romperá: has fabricado un fusible.

Para regular la corriente deberemos usar resistores, que son componentes diseñados para presentar un valor de resistencia (R) lo suficientemente elevados como para que nada se rompa ni arda.

¿Qué es un resistor?

Un resistor es un componente eléctrico hecho de un material que, aunque conduce la corriente, presenta una resistencia  significativa al paso de corriente a través de sí, muchísimo mayor que la que presenta un cable. En lugar de estar hecho de metal, los más comunes están hecho de una mezcla de cerámica y carbón. La cerámica actúa de aislante, y el carbón presenta buenas propiedades conductoras, por lo que la resistencia dependerá de la proporción de estas dos sustancias y de las dimensiones del resistor. Sencillo: para un tamaño determinado, cuanta más cerámica haya y menos carbón, más resistencia presentará al paso de corriente.

Existen otras formas de fabricar resistores, pero una de las más comunes a la hora de hacer circuitos electrónicos es la que hemos visto, por su bajo coste y por su fiabilidad en condiciones ambientales “domésticas”.

Voltajes e intensidades en un circuito: las leyes de Kirchoff

Hasta ahora, con la ley de Ohm, podemos determinar qué corriente recorrerá un resistor al que se le aplica un voltaje. Pero, ¿qué debemos tener en cuenta para calcular corrientes en montajes más complejos? Las leyes de Kirchoff de la tensión y la corriente.

La ley de Kirchoff de la corriente dice que en un nodo del circuito, la suma de todas las corrientes que confluyen al mismo es igual a cero. Un nodo del circuito es un punto de conexión, es decir: un punto en el que se conectan varias ramas, o componentes: una confluencia. En esta ley, tenemos que entender como que la suma de las corrientes que entran en el nodo es igual a la suma de las corrientes que salen del nodo.

I1 + I2 + … + In = 0

Recordemos: se considera que la corriente recorre el circuito desde puntos de mayor voltaje a puntos de menor voltaje.

La Ley de Kirchoff de la tensión dice que en un lazo del circuito, la suma de todas las tensiones es igual a cero. Un lazo cerrado el circuito es algo muy intuitivo y literal: se trata de una serie de componentes que se cierra sobre sí misma: luego lo veremos en un dibujo. Aquí lo que cabe señalar es que las fuentes de tensión aportan tensión, y por lo tanto suman, y el resto de componentes presentan caídas de tensión, por lo que restan.

V1 + V2 + … + Vn = 0

Pongamos un ejemplo sencillo:

Nuestro primer circuito para analizar
Nuestro primer circuito para analizar

Lo primero que me vais a decir es que no hay ningún lazo cerrado. Sí, pero no: tiene truco. Esta forma de representar los circuitos de una forma simplificada es muy común y reconozco que la he usado en el blog para describir los prototipos, porque en la placa de prototipado tengo una línea en un lado que me aporta 3,3 V, y otra línea que es la tierra, ó 0 V. Pero en realidad, 3,3 V y 0 V pueden considerarse los dos extremos de un componente: una fuente de tensión. Una pila, vaya.

A efectos de análisis, una fuente de tensión es algo que:

  1. Presenta una tensión fija en sus extremos.
  2. No presenta resistencia, es decir, puede entregar una intensidad de corriente infinita.

Esta última frase es lapidaria y destructiva: la intensidad de corriente de un circuito se determina con la tensión de alimentación y la resistencia del conjunto. Si no pusiésemos resistencias, la corriente no estaría limitada. Las cosas podrían quemarse.

Lo primero que vamos a hacer es cerrar lazos, entonces, tomando el punto de 3,3 V y el nivel de tierra como los dos terminales de una batería, o fuente de tensión.

Cerrando mallas con ayuda de una fuente de tensión.
Cerrando mallas con ayuda de una fuente de tensión.

El siguiente paso es interpretar el circuito tanto con mallas como con flechas para decidir en qué sentidos vamos a interpretar voltajes y corrientes.

Circuito con los lazos y las corrientes representadas
Circuito con los lazos y las corrientes representadas

La tensión de la fuente es mayor en la línea horizontal más larga (marcada con un “+”) y menor en la menor. Por lo tanto, en la línea horizontal corta tendremos 0 V, y en la larga, 3,3 V. Más cosas acerca de este dibujo: las flechas azules son intensidades de corriente, pero las flechas verdes no lo son. Las flechas verdes son simplemente una guía visual que dibujaremos (o nos imaginaremos) para interpretar correctamente las tensiones. En mi caso, asociaré el sentido de la flecha al sentido de la corriente, de forma que en mi análisis la dirección de la flecha la tensión caerá y por lo tanto la fuente, cuya tensión crece en el sentido de la flecha, estará en otro miembro de la igualdad. Ahora lo veréis claro con las cuentas que vamos a hacer.

Vamos ahora a plantear las “cuentas” que tenemos que hacer. Según la ley de Kirchoff de las corrientes, el nodo A cumple que:

I1 = I2 + I3

Es decir: la corriente que entra en A, que es I1, es igual a la suma de las corrientes que salen: I2 e I3.

Según la ley de Kirchoff de las tensiones y la ley de Ohm, la malla M1 cumple que:

3,3 V = I1 · 330 Ω + I2 · 330 Ω

Es decir: la tensión aportada por la fuente de alimentación es igual a la caída de tensión en la primera resistencia de 330 Ω, más la caída de tensión en la segunda resistencia de 330 Ω. Las colocamos así, en la igualdad, porque si interpretamos el lazo en el sentido de las agujas del reloj, cuando la fuente aporta tensión, en las resistencias esa tensión cae, según mi discusión de unos pocos párrafos más arriba. Por último para terminar de interpretar esta igualdad, las resistencias de 330 Ω están recorridas por las corrientes I1 e I2, respectivamente.

La malla M2 cumple, según la ley de Kirchoff de las tensiones y la ley de Ohm, que:

I2 · 330 Ω = I3 · 470 Ω

Si despejamos I2 de esta última igualdad, se tiene que:

I2 = (470/330) · I3 = 1,42 · I3

Vamos a sustituir I1 por I2 + I3 en la ecuación de la malla 1:

3,3 V = (I2 + I3) · 330 Ω + I2 · 330 Ω

Y ahora sustituimos I2 por su valor en función de I3, que es 1,42 · I3:

3,3 V = (1,42* I3 + I3) · 330 Ω + 1,42 · I3 · 330 Ω

Despejando I3 y combinándola con las igualdades en negrita anteriores, se tiene que:

I3 = 2,6 mA
I2 = 3,7 mA
I1 = 6,3 mA

Y por lo tanto, las caídas de tensión en las diferentes resistencias son

V1 = 6,3 mA · 330 Ω = 2,079 V
V2 = 3,7 mA · 330 Ω = 1,221 V
V3 = 2.6 mA · 470 Ω = 1,222 V

Salvo diferencias en el tercer decimal, fruto de los redondeos, todo cuadra. V1 + V2 = 3,3 V, como obliga la ley de las mallas, y V2 = V3 por la misma razón.

Analizando el prototipo del pulsador y el LED. Comportamiento eléctrico del GPIO.

Para esta sección no desarrollaré el análisis por mallas y por nodos de forma detallada, pero te animo a que tú sí lo hagas.

Con las leyes de Kirchoff y de Ohm podemos analizar eléctricamente cualquier circuito. Vamos a ver cómo funcionan estas leyes en la práctica usando el prototipo del pulsador y el LED, y cómo nos sirven para calcular corrientes y caídas de tensión. Cuando metemos una Raspberry Pi en el medio, tenemos que conocer dos cosas:

  1. Un pin GPIO configurado como entrada no acepta ninguna corriente.
  2. Un pin GPIO configurado como salida actúa como una fuente de tensión que puede presentar 3,3 V ó 0 V con respecto al nivel de masa, dependiendo de su estado (alto o bajo; 1 ó 0). Aunque no va a entregar una intensidad infinita, si no limitamos la corriente con resistencias, la intensidad subirá por encima de los límites seguros para el microprocesador y éste se quemará.

Además, se considera que el pulsador, cuando se oprime, es un conductor perfecto (no presenta resistencia). El LED presentará cierta caída de tensión que luego explicaré.

Recordad el montaje:

Esquema del montaje con el pulsador. La fuente de alimentación de 3,3 V representa la línea de alimentación de la placa de prototipado.
Esquema del montaje con el pulsador. La fuente de alimentación de 3,3 V representa la línea de alimentación de la placa de prototipado.

Estudiemos eléctricamente cada rama.

Rama del LED

Recordad como funcionaba: el pin GPIO número 12 estaba configurado como salida. Esta salida funciona siempre por voltaje, colocando un nivel 3,3V para el valor alto (1) de los pines GPIO la Raspberry Pi. A dicho pin conectamos un LED y, a continuación (en serie), una resistencia de 330 Ω conectada directamente a tierra, masa, o al nivel de referencia de 0 V. Cuando el pin GPIO se ponga a 3,3V, la rama del circuito drenará corriente de la Raspberry Pi como si de una pila se tratase, y deberemos limitar su intensidad para proteger nuestro preciado ordenador.

Veamos hasta qué punto limitará la corriente una resistencia de 330 Ω, suponiendo por el momento que el LED no presentase resistencia y que por lo tanto entre sus conectores no cayese tensión (cosa que no es cierta). La corriente que recorrería esa rama del circuito sería, como máximo:

I = V / R = 3,3 V / 330 Ω = 0,01 A = 10 mA

La corriente que recorre una rama del circuito
conectada a un puerto GPIO “puesto a 1”
será de un máximo de 10 mA si ponemos
en serie una resistencia de 330 Ω.

La máxima intensidad de corriente que soporta la Raspberry Pi a través de sus puertos GPIO es de “16 mA por pin, no excediendo entre todos los pines un máximo de 50 mA” (fuente —en inglés), por lo que una resistencia de 330 Ω es segura para usar en todos aquellos dispositivos que queramos activar al poner un pin GPIO configurado como salida, aunque no presentasen resistencia alguna o, visto de otro modo, aunque no restasen ningún voltaje a la rama del circuito.

El caso es que todos los dispositivos restan tensión, presentan una cierta resistencia. En el caso de los LED, cuando se encienden consumen cierta potencia que hace que presenten una caída de tensión de alrededor de 2V entre sus conectores (aunque varía según el color y el brillo). Esta tensión la debemos restar a los 3,3 V, por lo que la corriente sería incluso más baja y por lo tanto más segura:

I = V / R = (3,3 – 2) V / 330 Ω = 1,3 V / 330 Ω = 3,94 mA

Es decir, la corriente que recorre el LED es de casi 4 mA. Esta corriente, aunque pueda parecer pequeña, es suficiente para que un LED común, de los que normalmente se usan como indicadores en los circuitos, se encienda.

Rama del pulsador

En este caso, el pin GPIO 6 estaba configurado como entrada. Cuando un pin GPIO se configura como entrada no acepta corriente. Entre el pin 6 y la línea de 3,3 V de la placa de prototipado habíamos colocado una resistencia de 10 kΩ, es decir, 10.000 Ω.

El pulsador es un dispositivo que no presenta resistencia: cuando lo oprimimos se comporta como un cable. Por ello, el nivel de tensión que percibía el pin GPIO número 6 eran 0 V. Como comentábamos en el artículo del prototipo, al accionar el pulsador se produciría una corriente que atravesaría la resistencia y el pulsador, no entrando a través del pin GPIO ninguna corriente apreciable. Si no colocamos una resistencia en serie con el pulsador, se produciría un cortocircuito y se dañaría: debíamos proteger el pulsador reduciendo en todo lo posible la corriente a un valor seguro. Veamos qué valor conseguíamos para ese montaje con una resistencia de 10 kΩ:

I = V / R = 3,3 V / 10.000 Ω = 0,00033 A = 0,33 mA = 330 μA

Una corriente de 330 micro amperios resulta segura
si lo que queremos hacer es colocar un pin GPIO 

configurado como entrada a 0 V

Y hasta aquí el artículo de hoy. He intentado que no fuese denso, y sé que resolver ecuaciones y aprender leyes físicas no es del gusto de todos, pero creo que es necesario, de vez en cuando, redactar este tipo de textos. Así, siempre tendremos cerca una referencia que nos ayudará a entender lo que muestra un multímetro, determinando valores de corriente y tensión de sus circuitos en caso de necesidad. Depurar un circuito es una tarea importante cuando está en juego la seguridad de nuestro equipo, como la Raspberry Pi.

Y el saber no ocupa lugar, ¿no?


Puedes dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto, o bien a través de la dirección de correo que allí encontrarás.

]]>
http://pitando.net/2016/02/11/analisis-basico-de-circuitos-electricos/feed/ 1 1151
Episodio 16: Actualización del proyecto Astro Pi http://pitando.net/2016/02/11/episodio-16-actualizacion-del-proyecto-astro-pi/ Thu, 11 Feb 2016 06:00:52 +0000 http://pitando.net/?p=1164 Sigue leyendo Episodio 16: Actualización del proyecto Astro Pi ]]>  

En este episodio os cuento tres noticias acerca de Astro Pi que se han producido recientemente:

  1. Arranque de Astro Pi (Ed; Izzy se arrancará en unos días)
  2. Desafíos de programación 2016, para los alumnos del reino unido:
  3. Instrucciones para imprimir una caja de vuelo de Astro Pi (hoja de trabajo)

Cuentas de Twitter de las dos Astro Pi:

Episodios relacionados:


Podéis dejarme cualquier comentario en esta misma entrada, o enviándome
cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast.

Si queréis también podéis dejar una revisión del podcast en las página de Ivoox e iTunes, lo que además de proporcionarme comentarios sin duda valiosos, ayudará a que otros oyentes encuentren este podcast:

]]>
1164
Prepara tu Mac para programar Minecraft desde Python http://pitando.net/2016/02/04/prepara-tu-mac-para-programar-minecraft-desde-python/ Thu, 04 Feb 2016 10:00:03 +0000 http://pitando.net/?p=1130 Sigue leyendo Prepara tu Mac para programar Minecraft desde Python ]]> Es turno de preparar nuestro Mac OS X para poder programar el mundo de Minecraft mediante Python como os enseñaba en el artículo correspondiente a Minecraft Pi, en este vídeo:

Hace algunas semanas le llegaba el turno a Windows, artículo del que os recomiendo su lectura para saber un poco la controversia que hay alrededor de los programas que vais a configurar en este artículo. Ahora, por fin, le toca al Mac.

De esta forma, una vez todos listos, en los siguientes artículos de la serie empezaremos a experimentar con estos medios para automatizar nuestras construcciones.

Qué necesitamos

  • Un Mac con el Mac OS X, y Java. Minecraft es un juego hecho en Java y por lo tanto es total y enteramente multiplataforma.
  • Minecraft, la versión comercial, perfectamente funcional. Cuesta 20 €, es un juegazo que merece la pena para jugadores de cualquier edad, y lo podéis obtener en https://minecraft.net/. No lo he dicho en los otros artículos, pero al comprar Minecraft podéis usarlo en cuantos ordenadores queráis, sin importar si son ordenadores con Windows, Linux, Mac,… eso sí, uno sólo de cada vez. Además, comprando esta versión obtendréis acceso gratuito a la beta para Windows 10.
  • El Kit de desarrolladores de Java 8 que podemos descargar de esta web de Oracle. Es gratuito. Escogeremos la versión para Mac OS X del paquete que está etiquetado como “JDK“. La instalación es trivial (siguiente, siguiente,…)
  • Git, que es software libre y además gratuito, que descargaremos de https://git-scm.com/. Lo instalaremos también dejando todos los valores por defecto (siguiente, siguiente,…)
Descargar GIT
Descargar GIT

 

  • El programa de instalación a través de la construcción de CraftBukkit desde su código fuente, llamado BuildTools.jar, disponible en este enlace. Este enlace pertenece a la web de un derivado de Craftbukkit, llamado Spigot, que aunque es objeto también de investigación por los asuntos que os contaba en el artículo de Windows, todavía no se ha visto afectada. Gratuito
  • El plugin, o modificación, Raspberry Juice (zumo de frambuesa) que podremos descargar de la página web Bukkit.org mediante este enlace, para la versión 1.8.8 de craftbukkit. Gratuito
  • Python 2.7. Este desarrollo no es compatible con Python 3, así que deberás ejecutar Python 2.7 (está disponible por defecto en Mac OS X).

Empecemos ahora para preparar nuestros Macs.

Construcción de Craftbukkit

Una vez descargado Java 8 y GIT, e instalados, descargamos BuildTools.jar a un directorio de nuestro Mac, por ejemplo Descargas/craftbukkit bajo nuestro directorio de usuario. Abriremos un terminal (escribiendo simplemente “terminal” en Spotlight). Una vez en el directorio de la descarga (~/Downloads/craftbukkit), escribiremos en el terminal java -jar BuildTools.jar . Igual que en el artículo de Windows, comenzará un proceso automático que terminará al poco tiempo con estos mensajes:

Construcción finalizada con éxito
Construcción finalizada con éxito

Como vemos ahí, habrá creado en la carpeta Descargas/craftbukkit dos productos, craftbukkit-1.8.8.jarspigot-1.8.8.jar. Podremos comprobarlo escribiendo ls  en el terminal.

Instalación de CraftBukkit

Lo mismo que en el caso de Windows, este paso llevará a confusión casi con total seguridad, así que pon especial cuidado en seguir los pasos a pies juntillas.

  • Copia craftbukkit-1.8.8.jar a una carpeta de tu Mac nueva, para alojar y preparar CraftBukkit antes de modificarlo para trabajar con Python. Por ejemplo, ~/juegos/craftbukkit, esto es: un subdirectorio del directorio personal de tu usuario, que podrás crear directamente en el terminal mediante el comando mkdir (tanto para juegos como para craftbukkit)
  • Crea un archivo llamado “craftbukkit.sh” en esa misma carpeta, con el siguiente contenido:
#!/bin/sh
java -Xms512M -Xmx1024M -jar craftbukkit-1.8.8.jar
  • Otórgale permisos de ejecución introduciendo en la consola chmod +x craftbukkit.sh
  • Ejecútalo escribiendo ./craftbukkit.sh  en el terminal. Verás que el proceso crea una serie de carpetas y ficheros, y termina con este mensaje:
Resultado de lanzar craftbukkit.sh por primera vez
Resultado de lanzar craftbukkit.sh por primera vez

 

  • En la carpeta donde lo hemos ejecutado, localizamos el fichero eula.txt y la editamos con un editor de textos, que puede ser nano si lo prefieres usar en la consola (recuerda: para grabar usa ctrl-o y para salir ctrl-x), o bien TextEdit desde el Finder o con Spotlight.
  • Dentro de este fichero cambiaremos la línea que pone eula=false  por eula=true  . Lo guardaremos y cerraremos el editor.
  • Volveremos a ejecutar ./craftbukkit.sh , y veremos cómo avanza más:
Segunda ejecución de craftbukkit.sh
Segunda ejecución de craftbukkit.sh

 

  • Escribiremos stop e intro (↵)

Instalación de Raspberry Juice en CraftBukkit

En este momento, si listamos el contenido del directorio en el que nos encontramos desde el terminal veremos un directorio llamado plugins. Deberemos copiar ahí dentro el fichero RaspberryJuice.jar que nos hemos descargado anteriormente, mediante el comando cp  si no queremos salir al Finder.

Tras hacerlo, volveremos a ejecutar CraftBukkit. Al hacerlo, veremos en la ventana de comandos que aparece el mensaje “[19:46:45 INFO]: [RaspberryJuice] Loading RaspberryJuice v1.7 “, además de alguna otra línea de traza que habla de este plugin; eso indica que el plugin se ha cargado correctamente.

CraftBukkit ya ampliado con Raspberry Juice
CraftBukkit ya ampliado con Raspberry Juice

Ya tenemos listo CraftBukkit y ejecutándose en nuestro Mac. No lo cerraremos, ya que lo que tendremos que hacer será, una vez preparado el intérprete de Python 2.7, conectarnos a CraftBukkit a través del menú de Minecraft. Como os contaba en el artículo correspondiente a Windows, CraftBukkit proporciona un servidor multijugador que contiene y proporciona “las reglas de juego” del mundo de Minecraft para una partida en concreto. Tanto solos como acompañados, para poder programar Minecraft en Python jugaremos allí dentro.

Preparar el intérprete de Python 2.7

Si has instalado Python 3, desde Spotlight no podrás arrancar el intérprete interactivo IDLE de Python 2.7, que deberías tener incluido en Mac OS X por defecto: Mac OS X utiliza Python 2 internamente para ciertas tareas, y al irlo actualizando, deberías ir incrementando las versiones hasta tener la última de Python 2.7. Para poderlo arrancar, abre una nueva ventana del terminal y escribe idle2.7

Necesitamos instalar un módulo en Python que es complementario al plugin de Raspberry Juice que hemos instalado en CraftBukkit. Para ello, vamos a la página de GitHub donde se aloja, https://github.com/zhuowei/RaspberryJuice, y descargaremos todo el contenido usando el botón Download Zip si no lo hemos hecho al principio del proceso.

Descargar el

Una vez descargado, por ejemplo en la carpeta de Descargas:

  • Descomprimimos el archivo haciendo doble click sobre él en el Finder.
  • Navegamos dentro hasta que veamos la carpeta mcpi del subdirectorio RaspberryJuice-master\src\main\resources\mcpi\api\python\original.
  • La arrastramos a nuestro directorio personal de usuario (en mi caso, gvisoc); es el icono que tienen nuestro nombre de usuario de Mac OS X. Suele aparecer en los favoritos del Finder con un icono de una casa:
Directorio de usuario en el Finder
Directorio de usuario en el Finder

Arrancaremos desde un terminal el intérprete de Python 2.7 (IDLE) mediante idle2.7 para probar el juego en el siguiente paso, ¡por fin!

Probar el interfaz de programación contra Minecraft

Arrancamos el juego desde Spotlight o desde Launchpad, y en el lanzador crearemos un nuevo perfil, que igual que hacíamos en el artículo de Windows será una copia de nuestro perfil habitual. La única diferencia que configuraremos será para que ejecute la versión del juego igual a la versión del servidor CraftBukkit (en este momento es la 1.8.8) que hemos construido al principio del artículo. Esta versión la configuraremos en la lista desplegable Release:

Nuevo perfil, para la versión 1.8.8
Nuevo perfil, para la versión 1.8.8

Al guardar este perfil tendremos dos: el normal, que seguiremos usando para jugar a la última versión disponible de Minecraft, y el que usaremos para programar Python en Minecraft mediante CraftBukkit.

Una vez seleccionado este nuevo perfil, y no habiendo cerrado la ventana del terminal que está ejecutando craftbukkit.sh, lanzamos el juego. Recomiendo poner el juego en una ventana, no a pantalla completa.

Si entramos en Multijugador, veremos que hay un servidor con el nombre por defecto, “Servidor de Minecraft“. Si no lo hubiese, prueba a pulsar el botón Añadir servidor e introduce como dirección del servidor el valor 127.0.0.1, que representa tu propio Mac. Este servidor multijugador es local a nuestro Mac, y es el propio CraftBukkit que hemos dejado funcionando. Si no te apareciese ni siquiera añadiéndolo, comprueba que CraftBukkit se está ejecutando y, si lo hubieras cerrado por alguna razón, ejecuta craftbukkit.sh en ~/juegos/craftbukkit desde un Terminal y repite los pasos que has dado en este párrafo.

Ventana de Minecraft mostrando el servidor de CraftBukkit local al Mac
Ventana de Minecraft mostrando el servidor de CraftBukkit local al Mac

Entramos al servidor seleccionándolo y usando el botón Entrar al servidor.

Ejemplo del arcoiris: probemos el puente Python – Minecraft

Fuera del juego, en Mac OS X, abrimos un nuevo fichero en IDLE para Python 2.7, de forma que podamos escribir un programa. En él copiamos el ejemplo siguiente, tomado de la página de Raspberry Juice, que escribirá un arcoiris en el mundo de Minecraft.

import mcpi.minecraft as minecraft
import mcpi.block as block
from math import *

colors = [14, 1, 4, 5, 3, 11, 10]

mc = minecraft.Minecraft.create()
height = 60

mc.setBlocks(-64,0,0,64,height + len(colors),0,0)
for x in range(0, 128):
        for colourindex in range(0, len(colors)):
                y = sin((x / 128.0) * pi) * height + colourindex
                mc.setBlock(x - 64, y, 0, block.WOOL.id, colors[len(colors) - 1 - colourindex])

Para ejecutarlo lo guardaremos en el disco duro (en nuestra carpeta de experimentos de PItando, por ejemplo) y pulsaremos la tecla F5. Esto lo ejecutará directamente dentro de IDLE.

El arcoiris aparecerá justamente encima de nuestras cabezas, con lo que tendremos que apartarnos unos cuantos metros para poderlo ver.

Un arcoiris en Minecraft generado mediante Python
Un arcoiris en Minecraft generado mediante Python

Ahora puedes intentar tú mismo algo, como trasladar el ejemplo del Hombre de Hielo, o tratar de crear estructuras a tu alrededor (muros, vallas,…).

¡Disfruta, pocas combinaciones tan creativas hay a tu alcance!


Notas acerca de los números de versión de este artículo

En todo el artículo estoy manejando la versión 1.8.8 porque es la versión de CraftBukkit que se construye con BuildTools.jar. Si llegas a este artículo meses después de que haya aparecido, la versión habrá cambiado. Sé consecuente a lo largo de este texto y, donde veas 1.8.8, piensa realmente en la versión de CraftBukkit que hayas obtenido al construirlo.

En cuanto a RaspberryJuice, ten en cuenta que existe una versión para cada versión de CraftBukkit. La 1.7 se corresponde con la versión de CraftBukkit 1.8.1, pero en mi experiencia es compatible con cualquier versión 1.8. La tabla de correspondencias se encuentra en esta web: http://dev.bukkit.org/bukkit-plugins/raspberryjuice/, en donde pone Facts (abajo a la derecha).


Podéis dejarme cualquier comentario en esta misma entrada, o enviándome
cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico.

]]>
1130
Fotomatón basado en la Raspberry Pi, conectado a tu impresora http://pitando.net/2016/01/28/fotomaton-basado-en-la-raspberry-pi-conectado-a-tu-impresora/ http://pitando.net/2016/01/28/fotomaton-basado-en-la-raspberry-pi-conectado-a-tu-impresora/#comments Thu, 28 Jan 2016 10:00:13 +0000 http://pitando.net/?p=1098 Sigue leyendo Fotomatón basado en la Raspberry Pi, conectado a tu impresora ]]> Justo antes de las vacaciones de Navidad os contaba un proyecto con el que podíais conectar vuestra Raspberry Pi a Dropbox para subir allí las fotos que hicieseis con su módulo de cámara: el fotomatón. En este artículo os cuento cómo hacerlo para que, en lugar de subir la foto a Dropbox, se imprima a través de una impresora conectada a la Raspberry Pi (y reconocida por Raspbian). Esta variante viene motivada por mi primera interacción de verdad con los lectores del blog.

Common Unix Printing System
Common Unix Printing System

Pocos días antes de sacar el fotomatón basado en Dropbox me escribía un lector, llamado Pedro J, que estaba al tanto de mis intenciones de publicar un proyecto de tipo fotomatón paracido al que usé en el Pabellón de la Caza durante el directo. Me preguntaba si lo haría conectado a una impresora. Mi contestación fue que por el momento lo sacaría conectado a Dropbox, pero que era buena idea y trataría de cubrir esta variante de alguna forma y pese a que no tengo impresora. En el episodio 13 del podcast, del 22 de diciembre, hice una llamada a la acción para que compartieseis experiencias a la hora de imprimir desde Python en Linux, de tal forma que pudiese cubrir este apartado con vuestras colaboraciones. Casi inmediatamente, Antonio Hernán (@AH3rn4n) enviaba un material que me ponía sobre la pista de la impresión desde Python via CUPS (Common Unix Printing System).

Desde aquí mi agradecimiento a los dos, y… ¡al lío!

CUPS – Common Unix Printing System

CUPS es el acrónimo de Common Unix Printing System, sistema de impresión común a Unix. Es un software libre para impresión creado en 1999 por Apple, la misma compañía que hace y vende iPhones y Macbooks. Como su nombre indica, permite imprimir en todas las variantes de Unix, incluyendo todas las distribuciones de Linux y también Mac OS X: de ahí el común en su nombre. Además, permite imprimir tanto directamente a una impresora local a un ordenador como a través de una red ya sea ésta local o la misma internet.

El funcionamiento de CUPS es sencillo e intuitivo: crea un servicio en el ordenador que, siempre en ejecución, detecta y proporciona acceso a las impresoras conectadas al ordenador y a través de la red a la que tiene acceso. Proporciona un panel de control basado en una página web local, es decir, a través del navegador, a través del cual se pueden añadir impresoras, consultar los documentos que tiene en cola esperando a ser impresos, y un largo etcétera.

Veamos cómo instalarlo e instalar la impresora. Como no tengo impresora física, instalaré la clásica y siempre útil impresora virtual PDF.

Instalación de CUPS en la Raspberry Pi

Esta sección la enfocaré “en modo receta”, yendo directamente a listaros las órdenes que nos permitirán instalar CUPS en Raspbian, a través del terminal.

Lo primero que debemos hacer es actualizar los repositorios y los paquetes disponibles en Raspbian:

sudo apt-get update
sudo apt-get upgrade

Tardará un rato. Tras ello, instalamos los paquetes cupscups-pdf: el primero es el sistema propiamente dicho, y el segundo es una impresora en PDF que habremos de configurar.

Además, añadiremos al usuario actual (pi, a no ser que hayamos creado otro) al grupo de administradores de CUPS, lpadmin, es decir: nos daremos permiso para añadir y gestionar impresoras en el sistema.

sudo apt-get install cups cups-pdf
sudo usermod -a -G lpadmin pi

Instalar y probar la impresora

En esta sección instalaré una impresora virtual que guardará copias de los documentos que queramos imprimir, pero en formato PDF. Para tu impresora física, solamente deberás localizar la marca y modelo en las capturas de pantalla que verás en este apartado. Ten en cuenta, claro, que el resultado será una impresión física en lugar de un nuevo documento PDF en un subdirectorio del usuario actual. También sé coherente con el nombre que configures en estos paneles, ya que te deberás referir a la impresora mediante su nombre.

Abriremos un navegador y escribiremos en la barra de direcciones http://127.0.0.1:631. Veremos que se abre una página web de bienvenida de CUPS, ¡en español!

Página de bienvenida de CUPS
Página de bienvenida de CUPS

Vamos a hacer click en la pantalla Administración:

Panel de administración de CUPS
Panel de administración de CUPS

Una vez dentro, haremos click en el botón Añadir impresora, tras lo que el sistema nos pedirá que nos identifiquemos.

Identificación como administradores ante CUPS
Identificación como administradores ante CUPS

Introduciremos el usuario y la contraseña del usuario que hemos introducido en el grupo lpadmin, es decir, usuario pi y contraseña raspberry a no ser que hayáis cambiado algo desde la instalación de Raspbian. Es decir: debes introducir el usuario y la contraseña de acceso al sistema en Linux, o Raspbian, del usuario que usas normalmente.

Una vez hecho eso, encontraremos la siguiente secuencia de páginas, donde iremos configurando las opciones que os detallo en los pies de foto.

Primero, creamos una nueva impresora seleccionando la opción local, CUPS-PDF (Virtual PDF Printer).

Creamos una nueva impresora seleccionando la opción local, CUPS-PDF
Creamos una nueva impresora seleccionando la opción local, CUPS-PDF

A continuación, dejamos el nombre por defecto.

Daremos el nombre que se nos sugiere y pulsaremos "Siguiente"
Daremos el nombre que se nos sugiere y pulsaremos “Siguiente”

Seleccionamos marca Generic.

Seleccionaremos la marca como genérica (Generic) y pulsaremos "Siguiente"
Seleccionaremos la marca como genérica (Generic) y pulsaremos “Siguiente”

El modelo es Generic CUPS-PDF Printer (en).

Seleccionaremos el modelo indicado (Generic Cups-PDF Printer (en)) y pulsaremos "Añadir impresora"
Seleccionaremos el modelo indicado (Generic Cups-PDF Printer (en)) y pulsaremos “Añadir impresora”

Estableceremos las opciones predeterminadas de la impresora consultándoselas a ella misma.

Por último, el sistema nos pedirá que introduzcamos las opciones predeterminadas. Pulsaremos "Consultar a la impresora las opciones predeterminadas".
Por último, el sistema nos pedirá que introduzcamos las opciones predeterminadas. Pulsaremos “Consultar a la impresora las opciones predeterminadas“.

Si todo ha ido bien, veremos una página parecida a ésta, en donde CUPS nos ofrece la vista de gestión de la impresora recién configurada.

Vista de gestión de la impresora
Vista de gestión de la impresora

Tras ello podremos probarla desde el terminal. Navegaremos a un directorio donde tengamos algún tipo de fichero legible, ya sea una fotografía o un texto, y escribiremos la orden siguiente:

lp -d Virtual_PDF_Printer <nombre de fichero>

Por ejemplo:

lp -d Virtual_PDF_Printer clon2.jpeg

Esto emitirá un mensaje del tipo a

la id solicitada es Virtual_PDF_Printer-1 (1 archivo(s))

De ahí a unos instantes creará un archivo de igual nombre, pero extensión PDF, en el directorio /home/pi/PDF :

Directorio de trabajo de la impresora CUPS-PDF
Directorio de trabajo de la impresora CUPS-PDF

Lógicamente, el fichero PDF recién creado deberá contener el texto o la imagen que habéis impreso; lógicamente también, si tenéis una impresora física, el resultado debería ser una copia impresa.

Este señor, ya de sobra conocido por estos lares, es quien se escode tras la fotografía "clon2.jpeg" del ejemplo. Esta foto es del visor PDF, y muestra la foto encuadrada en un folio tamaño DIN A4.
Este señor, ya de sobra conocido por estos lares, es quien se escode tras la fotografía “clon2.jpeg” del ejemplo. Esta foto es del visor PDF, y muestra la foto encuadrada en un folio tamaño DIN A4.

Acceder a la impresora desde tus programas en Python

Para poder acceder a la impresora deberás instalar unas librerías. Tras ello os introduciré las líneas esenciales para imprimir un fichero y el código fuente del fotomatón completo.

Instalaciones necesarias: librerías de desarrollo de CUPS y pycups

Deberemos instalar la librería libcups2-dev para poder acceder a CUPS desde otros lenguajes de programación, ya sea Python o cualquier otro. Para ello ejecutamos en la consola:

sudo apt-get install libcups2-dev

Tras ello, deberemos instalar el módulo pycups en Python 3 mediante el uso del programa pip, que deberemos ejecutar con permisos de super usuario ya que debe realizar instalaciones adicionales en directorios del sistema:

sudo pip3 install pycups

Es posible que el comando superior falle, sobre todo recientemente (a mí me pasó) con un mensaje de error que termina con el texto “ImportError: cannot import name IncompleteRead “. En ese caso, deberás actualizar pip con el siguiente comando:

sudo easyinstall3 -U pip

Y después de ello, repetir la instalación de pycups.

Prueba de la impresión en Python y programa final

Para imprimir en Python basta con ejecutar las siguientes órdenes que puedes probar directamente desde IDLE para Python 3:

import cups
conexion = cups.Connection()
conexion.printFile ('Virtual_PDF_Printer',fichero, titulo, dict())

En este listado, la última sentencia es la que imprime; la segunda es solamente para conectarnos al servidor de impresión CUPS. En la sentencia de impresión deberemos indicar el nombre de la impresora; fichero  es la ruta al fichero que queremos imprimir, y titulo  el nombre del archivo PDF generado. El último parámetro, dict(), es una estructura de datos que alojaría las opciones de impresión. La enviaremos vacía, para imprimir con las opciones por defecto.

Con esta información y la experiencia en Python que ya hemos ganado es muy fácil modificar el programa del fotomatón (recuerda que utiliza el prototipo del pulsador) para obtener esta nueva versión:

#!/usr/bin/python3

import cups
import RPi.GPIO as GPIO
import os
import time
import picamera

## Obtener una referencia a la cámara
camera = picamera.PiCamera()

## Conexión al sistema CUPS
print("Conectando a CUPS")
conexion = cups.Connection()

print("Oprime el pulsador para hacer la primera foto")

## Configuración GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings (False)
GPIO.setup(6, GPIO.IN)
GPIO.setup(12, GPIO.OUT)

## Bucle infinito del fotomatón
total = 0
# Número de fotos en la tarjeta SD, entre 0 y 10. Así no llenaré
# nunca el sistema de ficheros (sólo guardo 10 fotos en la
# Raspberry, "en local")
local = 0
try:
    while True:
        if (not GPIO.input(6)):
            nombre_local = "captura_" + str(local) + ".jpeg"
            nombre_impresora = "captura_" + str(total) + ".jpeg"
               
            # Uso del LED para guiar al "modelo" de la foto
            print ("Cuando se apague el LED se hará la foto")
            GPIO.output(12, GPIO.HIGH)
            for i in range(0, 3):            
                print(str(3 - i) + "...")
                time.sleep(1)
            GPIO.output (12, GPIO.LOW)
            print ("¡Foto!")
            # Hacemos la foto
            camera.capture(nombre_local)

            # Imprimir el fichero
            idcups = conexion.printFile('Virtual_PDF_Printer',nombre_local,nombre_impresora,dict())
            print ('Foto enviada a la impresora (id trabajo = ' + str(idcups) + ')')
            # Incrementar contadores; 'local' siempre entre 0 y 10
            local = (local + 1) % 10
            total = total + 1
            print("Oprime el pulsador para hacer otra foto")
except KeyboardInterrupt:
    print("\nInterrupción del usuario, saliendo del programa")
finally:
    camera.close()
    GPIO.cleanup()
    print ("Fin del programa")

exit (0)

Espero que haya sido interesante. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto entwitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

]]>
http://pitando.net/2016/01/28/fotomaton-basado-en-la-raspberry-pi-conectado-a-tu-impresora/feed/ 1 1098
Episodio 15: RasPiO Pro Hat de @RasPiTV, en Kickstarter http://pitando.net/2016/01/28/episodio-15-raspio-pro-hat-de-raspitv-en-kickstarter/ http://pitando.net/2016/01/28/episodio-15-raspio-pro-hat-de-raspitv-en-kickstarter/#comments Thu, 28 Jan 2016 06:00:20 +0000 http://pitando.net/?p=1124 Sigue leyendo Episodio 15: RasPiO Pro Hat de @RasPiTV, en Kickstarter ]]> En este episodio os traigo el proyecto RasPiO Pro Hat de Alex Eames, el dueño de webs como RasPi.TV y Rasp.IO. ¿Qué es el Pro Hat?

  • Es un HAT (Hardware Attached On Top) que se conecta a los pines GPIO de la Raspberry Pi
  • Posiciona los pines GPIO en orden numérico alrededor de una mini breadboard, que no oculta los puertos es decir: puedes usar la protoboard incorporada, o sacar cables a otra placa.
  • Protege todos los pines ante sobrecorriente y sobretensión tanto positiva como negativa.
  • No necesitas resistores para conectar LEDs.
  • Funciona por defecto con GPIO Zero, de Ben Nutall.
  • No requiere instalación de ninguna librería, ni soldadura.
    Compatible con cualquier Raspberry Pi de 40 Pines (Modelo B+, Raspberry Pi 2)
  • Perfecto para principiantes y profesionales que necesitan hacer un prototipado rápido y directo

Enlaces:

Entradas y episodios relacionados:

]]>
http://pitando.net/2016/01/28/episodio-15-raspio-pro-hat-de-raspitv-en-kickstarter/feed/ 1 1124
Programar Minecraft desde Python, para Windows http://pitando.net/2016/01/21/programar-minecraft-desde-python-para-windows/ http://pitando.net/2016/01/21/programar-minecraft-desde-python-para-windows/#comments Thu, 21 Jan 2016 10:00:20 +0000 http://pitando.net/?p=1071 Sigue leyendo Programar Minecraft desde Python, para Windows ]]> Hace unas semanas os mostraba en un vídeo el funcionamiento de Minecraft Pi Edition, la edición de Minecraft que viene preinstalada en Raspbian. Esta versión del popular juego tiene como peculiaridad que incorpora un mecanismo que nos permite programar el mundo de Minecraft mediante Python. Entre otras cosas, por ejemplo, podremos crear edificios y teletransportar al personaje. En la entrada que os enlazaba antes podíais ver cómo convertía a Steve, el protagonista del juego, en el Hombre de Hielo de la Patrulla X. Os lo vuelvo a ofrecer aquí:

En este artículo os explico cómo hacer para poder programar Minecraft mediante Python, como en el vídeo de arriba, pero usando Windows y la versión comercial de Minecraft en lugar de la Raspberry Pi y su edición específica. Es decir: sin limitaciones, ya que esto podremos hacerlo tanto si jugamos en modo supervivencia como en modo creativo y, lo mejor, es que se hace a través de un servidor multijugador llamado CraftBukkit que tendremos disponible para invitar a nuestros amigos. No sé vosotros pero a mí me encanta la idea de convertirme en algo parecido al arquitecto de Matrix 😀

Pero empezaré por una historia alrededor de esta cuestión que la hace más complicada de lo que nos gustaría.

CraftBukkit y una historia de licencias

En el libro Aventuras en Minecraft (enlace de afiliado), además de cubrir el segmento de lectores que poseen una Raspberry Pi, proporcionaban un enlace a una descarga que instalaría las herramientas para programar el mundo de Minecraft mediante Python, pero las de Windows y Mac OS X. Además, con dichas herramientas y al ser para Windows y Mac OS X, podrías programar sobre una versión comercial de Minecraft (no la “Pi Edition”).

Aunque hable en pasado, estas herramientas todavía existen y están basadas en un proyecto llamado CraftBukkit que ya he mencionado, desarrollado en Bukkit.org. Una turbia historia de desencuentros entre un programador de CraftBukkit, la propia comunidad, ¿él mismo?, y Mojang, que es la empresa que desarrolla Minecraft, hizo que la distribución de estas herramientas se complicase legalmente. Como consecuencia, esos descargables asociados al libro desaparecieron… ¿temporalmente? La cuestión es que este programador se dio cuenta de que al distribuirlo “listo para usar”, es decir, compilado, estaba infringiendo la licencia de Minecraft al incluir parte de su código de alguna forma que no he encontrado claramente explicada. Así pues, por absurdo que pudiera parecer, él mismo denunció el proyecto a la autoridad competente para tratar de llamar la atención sobre el problema y sentar a todas las partes a resolverlo. En ese mismo momento, las distribuciones compiladas de CraftBukkit desaparecieron de los servidores de descarga y, me imagino que como consecuencia de ello, los kits descargables asociados al libro no se encuentran disponibles en la página de la editorial.

Como consecuencia de todo esto, Mojang es consciente del problema y aunque no han participado en esta retirada de las descargas de CraftBukkit al no haber sido ellos los denunciantes, el comunicado de la compañía es que no se oponen al proyecto y que, al parecer están poniendo de su parte para deshacer el entuerto involuntario. Sin embargo, de esto último no he encontrado fuentes oficiales. Como ya digo, esta es una historia turbia y podría cometer errores si intentase ahondar más.

Pese a todo, existen formas públicas y notorias de obtener un CraftBukkit compilando el proyecto a partir del código fuente, y existe una web llamada GetSpigot.org que, por el momento, permite descargar alguna versión de CraftBukkit. Estos mecanismos no han sido bloqueados por la denuncia y, como tal, siguen disponibles y nadie ha tratado de ocultarlos. Voy a tratar de explicaros paso a paso cómo conseguir hacerlo funcionar compilando el proyecto desde cero: no he probado las descargas de GetSpigot.

Qué necesitamos

  • Un ordenador con Windows, capaz de ejecutar Java 8 y capaz de ejecutar Minecraft.
  • Minecraft, la versión comercial, perfectamente funcional. Cuesta 20 €, es un juegazo que merece la pena para jugadores de cualquier edad, y lo podéis obtener en https://minecraft.net/. Además, comprando esta versión obtendréis acceso gratuito a la beta para Windows 10
  • El Kit de desarrolladores de Java 8 que podemos descargar de esta web de Oracle. Es gratuito. Escogeremos la versión para Windows que corresponda (32 ó 64 bits) del paquete que está etiquetado como “JDK“. La instalación es trivial (siguiente, siguiente,…)
  • Git, que es software libre y además gratuito, que descargaremos de https://git-scm.com/. Lo instalaremos también dejando todos los valores por defecto (siguiente, siguiente,…)
Descargar GIT
Descargar GIT

 

  • Un programa de instalación a través de la construcción de CraftBukkit desde su código fuente, llamado BuildTools.jar, disponible en este enlace. Este enlace pertenece a la web de un derivado de Craftbukkit, llamado Spigot, que aunque es objeto también de investigación por los asuntos que antes os contaba, todavía no se ha visto afectada. Gratuito
  • El plugin, o modificación,  Raspberry Juice (zumo de frambuesa) que podremos descargar de la página web Bukkit.org mediante este enlace, para la versión 1.8.8 de craftbukkit. Gratuito
  • Python 2.7. Este desarrollo no es compatible con Python 3, así que si no tienes Python 2.7 deberás descargarlo e instalarlo como ya hicimos con Python 3

Empecemos entonces.

Construcción de Craftbukkit

Una vez descargado todo, instalado Java 8 y Git, iremos a la carpeta donde hemos descargado BuildTools.jar (por ejemplo, Descargas). Sobre ella misma, en el explorador de Windows, haremos click con el botón derecho para seleccionar la opción Guit Bash Here (“[intérprete de] Bash de Git aquí“).

Guit Bash Here sobre la carpeta de Descargas
Guit Bash Here sobre la carpeta de Descargas

Dentro de la ventana de Terminal que se abre al hacerlo, escribiremos java -jar BuildTools.jar . Con ello, un proceso automático que durará varios minutos comenzará a descargar todo lo necesario para construir nuestro CraftBukkit, y a construirlo. Si funciona bien veréis lo siguiente al final de todo:

Construcción finalizada con éxito
Construcción finalizada con éxito

Esto habrá dejado en la carpeta de trabajo los dos productos que queríamos, en este caso craftbukkit-1.8.8.jar y spigot-1.8.8.jar.

Resultado de la construcción
Resultado de la construcción

Instalación de CraftBukkit

El siguiente paso puede llevar a confusión, así que pon especial cuidado en seguir los pasos escrupulosamente:

  • Copia craftbukkit-1.8.8.jar a una carpeta de tu PC nueva, para alojar y preparar CraftBukkit antes de modificarlo para trabajar con Python. Por ejemplo, c:\juegos\craftbukkit
  • Crea un archivo llamado “craftbukkit.bat” en esa misma carpeta, con el siguiente contenido:
java -Xms512M -Xmx1024M -jar craftbukkit-1.8.8.jar
pause
  • Guárdalo poniendo atención en que la extensión sea “.bat” y no “.bat.txt”, sobre todo si usas el Bloc de Notas.
  • Ejecútalo haciendo doble click sobre él. Verás que el proceso crea una serie de carpetas y ficheros, y termina con este mensaje:
Resultado de ejecutar craftbukkit.bat por primera vez
Resultado de ejecutar craftbukkit.bat por primera vez

 

  • En la carpeta donde lo hemos ejecutado (c:\juegos\craftbukkit en este ejemplo), localizamos el fichero eula.txt y lo abriremos con el Bloc de Notas.
Eula.txt
Eula.txt

 

  • Dentro de este fichero cambiaremos la línea que pone eula=false  por eula=true . Lo guardaremos y cerraremos el Bloc de Notas.
  • Volvemos a ejecutar con doble click el fichero craftbukkit.bat. Lo que veremos ahora será que se crean muchas más carpetas y ficheros, y que la ventana se queda así:
Segunda ejecución de craftbukkit.bat
Segunda ejecución de craftbukkit.bat

 

  • Escribimos stop  e Intro (↵). Cuando leamos “Pulse una tecla para continuar…”, presionamos Intro (↵) de nuevo.

Instalación de Raspberry Juice en CraftBukkit

En este punto veremos que dentro de la carpeta donde estamos ejecutando CraftBukkit (c:\juegos\craftbukkit) se ha generado una carpeta llamada plugins. Deberemos copiar ahí dentro el fichero RaspberryJuice.jar que nos hemos descargado anteriormente. Simplemente lo arrastramos y lo soltamos allí.

Tras hacerlo, sólo nos queda volver a ejecutar craftbukkit.bat. Al hacerlo, veremos en la ventana de comandos que aparece el mensaje “[21:18:15 INFO]: [RaspberryJuice] Loading RaspberryJuice v1.7“. Eso quiere decir que el plugin ha sido instalado con éxito.

Ejecutamos craftbukkit ya ampliado con el plugin Raspberry Juice
Ejecutamos craftbukkit ya ampliado con el plugin Raspberry Juice

Ya tenemos listo CraftBukkit y ejecutándose en nuestro PC con Windows. No lo cerraremos, ya que lo que tendremos que hacer será, una vez preparado el intérprete de Python 2.7, conectarnos a CraftBukkit a través del menú de Minecraft. Como os contaba al principio de todo, CraftBukkit proporciona un servidor multijugador que contiene y proporciona “las reglas de juego” del mundo de Minecraft para una partida en concreto. Tanto solos como acompañados, para poder programar Minecraft en Python jugaremos allí dentro.

Preparar el intérprete de Python 2.7

El siguiente paso exige instalar Python 2.7, ya que es la versión de Python para la que todo esto es compatible. Hazlo con este proceso si no lo tienes.

Necesitamos instalar un módulo en Python que es complementario al plugin de Raspberry Juice que hemos instalado en CraftBukkit. Para ello, vamos a la página de GitHub donde se aloja, https://github.com/zhuowei/RaspberryJuice, y descargaremos todo el contenido usando el botón Download Zip si no lo hemos hecho al principio del proceso.

Descargar el

Una vez descargado, por ejemplo en la carpeta de Descargas:

  • Descomprimimos el archivo
  • Navegamos dentro hasta que veamos la carpeta mcpi del subdirectorio RaspberryJuice-master\src\main\resources\mcpi\api\python\original.
  • La copiamos al portapapeles.
  • La pegaremos dentro del subdirectorio lib de nuestra instalación de Python 2.7 (en mi caso, c:\prog\Python27\lib).

Arrancaremos el intérprete de Python 2.7 (IDLE) para probar el juego en el siguiente paso, ¡por fin!

Probar el interfaz de programación contra Minecraft

Ejecutamos el lanzador de Minecraft como si fuéramos a jugar, pero antes crearemos un nuevo perfil. Este perfil será una copia de nuestro perfil habitual, que modificaremos para que ejecute la versión del juego correspondiente al servidor CraftBukkit que tenemos en ejecución (en este caso es la 1.8.8). La encontraremos en la lista desplegable Release:

Perfil para la versión 1.8.8
Perfil para la versión 1.8.8

Al guardar este perfil tendremos dos: el normal, que seguiremos usando para jugar a la última versión disponible de Minecraft, y el que usaremos para programar Python en Minecraft mediante CraftBukkit.

Una vez seleccionado este nuevo perfil, y no habiendo cerrado la ventana de craftbukkit.bat, lanzamos el juego. Recomiendo poner el juego en una ventana, no a pantalla completa.

Si entramos en Multijugador, veremos que hay un servidor con el nombre por defecto, “A Minecraft Server“. Este servidor multijugador es local a nuestro PC con Windows, y es el propio CraftBukkit que hemos dejado funcionando. Si no te aparece, comprueba que CraftBukkit se está ejecutando y, si lo hubieras cerrado por alguna razón, ejecuta craftbukkit.bat en c:\juegos\craftbukkit para que aparezca al cabo de unos segundos.

A Minecraft Server
A Minecraft Server

Entramos al servidor seleccionándolo y usando el botón Entrar al servidor.

Nuestro “Hola, Mundo”: un arcoiris

Fuera del juego, en Windows, abrimos un nuevo fichero en IDLE para Python 2.7, de forma que podamos escribir un programa. En él copiamos el ejemplo siguiente, tomado de la página de Raspberry Juice, que escribirá un arcoiris en el mundo de Minecraft.

import mcpi.minecraft as minecraft
import mcpi.block as block
from math import *

colors = [14, 1, 4, 5, 3, 11, 10]

mc = minecraft.Minecraft.create()
height = 60

mc.setBlocks(-64,0,0,64,height + len(colors),0,0)
for x in range(0, 128):
        for colourindex in range(0, len(colors)):
                y = sin((x / 128.0) * pi) * height + colourindex
                mc.setBlock(x - 64, y, 0, block.WOOL.id, colors[len(colors) - 1 - colourindex])

Para ejecutarlo lo guardaremos en el disco duro (en nuestra carpeta de experimentos de PItando, por ejemplo) y pulsaremos la tecla F5. Esto lo ejecutará directamente dentro de IDLE.

El arcoiris aparecerá justamente encima de nuestras cabezas, con lo que tendremos que apartarnos unos cuantos metros para poderlo ver.

Un arcoiris generado mediante Python. ¿No te sientes como un personaje de Matrix?
Un arcoiris generado mediante Python. ¿No te sientes como un personaje de Matrix?

Ahora puedes intentar tú mismo algo, como trasladar el ejemplo del Hombre de Hielo, o tratar de crear estructuras a tu alrededor (muros, vallas,…).

¡Disfruta, pocas combinaciones tan creativas hay a tu alcance!


 

Notas acerca de los números de versión de este artículo

En todo el artículo estoy manejando la versión 1.8.8 porque es la versión de CraftBukkit que se construye con BuildTools.jar. Si llegas a este artículo meses después de que haya aparecido, la versión habrá cambiado. Sé consecuente a lo largo de este texto y, donde veas 1.8.8, piensa realmente en la versión de CraftBukkit que hayas obtenido al construirlo.

En cuanto a RaspberryJuice, ten en cuenta que existe una versión para cada versión de CraftBukkit. La 1.7 se corresponde con la versión de CraftBukkit 1.8.1, pero en mi experiencia es compatible con cualquier versión 1.8. La tabla de correspondencias se encuentra en esta web: http://dev.bukkit.org/bukkit-plugins/raspberryjuice/, en donde pone Facts (abajo a la derecha).


Podéis dejarme cualquier comentario en esta misma entrada, o enviándome
cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico.

]]>
http://pitando.net/2016/01/21/programar-minecraft-desde-python-para-windows/feed/ 1 1071
Abriendo el Sunfounder SuperKit para Raspberry Pi 2 http://pitando.net/2016/01/16/abriendo-sunfounder-superkit-raspberrypi2/ http://pitando.net/2016/01/16/abriendo-sunfounder-superkit-raspberrypi2/#comments Sat, 16 Jan 2016 15:36:08 +0000 http://pitando.net/?p=1067 Sigue leyendo Abriendo el Sunfounder SuperKit para Raspberry Pi 2 ]]>

En este vídeo os muestro el Kit que me he comprado recientemente para hacer experimentos y prototipos. Podéis encontrar este Kit, que usaré en los sucesivos artículos de electrónica en Amazon siguiendo este enlace (de afiliado): Sunfounder Superkit para Raspberry Pi 2.

¡Pronto nuevos experimentos!

Incluye lo siguiente:

  • Temporizador 555
  • Registro con desplazamiento 74HC595 (2)
  • Módulo de control de potencia para motores L293D
  • Optoacoplador 4N35 (2)
  • Aceletrómetro ADXL345
  • Codificador de giro
  • Potenciómetro (50k)
  • Módulo de alimentación
  • Display LCD – LCD1602
  • Display de 7 segmentos (2)
  • Matriz 8×8
  • Motor de contínua
  • Hélice
  • Transistor NPN S8050 (2)
  • Transistor PNP S8550 (2)
  • Condensadores cerámicos 100 nF (4)
  • Condensadores cerámicos 10 nF (4)
  • Diodos rectificadores (4)
  • Timbre
  • Interruptor
  • Botones (5)
  • LEDs (16 rojos, 2 verdes, 2 amarillos, 2 blancos)
  • LED RGB
  • 40 pines para soldar
  • Placa de expansión para la Raspberry Pi 2, GPIO de 40 pines. Podéis ver aquí cómo se monta.
  • Una placa de prototipado / breadboard / protoboard de 64 filas
  • Cable de expansión de 40 conexiones
  • Cables hembra – macho (20)
  • Cables macho – macho (65)
]]>
http://pitando.net/2016/01/16/abriendo-sunfounder-superkit-raspberrypi2/feed/ 1 1067
GPIO Zero: simplifica el control de prototipos desde Python http://pitando.net/2016/01/14/gpio-zero-simplifica-el-control-de-prototipos-desde-python/ http://pitando.net/2016/01/14/gpio-zero-simplifica-el-control-de-prototipos-desde-python/#comments Thu, 14 Jan 2016 10:00:19 +0000 http://pitando.net/?p=1054 Sigue leyendo GPIO Zero: simplifica el control de prototipos desde Python ]]> El pasado mes de noviembre tuve la oportunidad de grabar un podcast en directo con Diógenes Digital, en el que hablamos largo y tendido de Arduino, Raspberry Pi y niños aprendiendo robótica y programación. En aquel programa discutíamos las diferencias, los usos y enfoques de estas dos plataformas, y una de las cuestiones que comentaba era el control de motores, prototipos y dispositivos comunes a través de los pines GPIO de la Raspberry Pi.

La librería estándar a través de la cual venimos controlando nuestros prototipos desde Raspbian, RPi.GPIO, exige al programador pensar en términos de pines y de tensiones:

  • Configurar un determinado pin como entrada o como salida
  • Poner tal o cual pin a nivel alto (3,3 V) o a nivel bajo (0 V)

Es decir, ofrece una solución de muy bajo nivel en el sentido de que obliga a pensar en esos términos (pines GPIO), en lugar de los componentes que queríamos controlar (LED, pulsador, motor,…). Por lo tanto, la distancia que hay entre manejar los voltajes de los pines que usas en tus proyectos y el efecto final (que un LED se encienda, que un motor haga girar un brazo 90 grados,…) la debes asumir tú y resolver por tus propios medios. Al empezar con estas cosas, cuanto más trabajemos con pines y voltajes en lugar de con motores y componentes, más esfuerzo debemos hacer al diseñar nuestros programas y por lo tanto más riesgo de frustrarnos corremos en nuestro aprendizaje.

En este artículo os voy a introducir una alternativa a RPi.GPIO, aparecida el pasado mes de noviembre, y que os facilitará el trabajo con prototipos: GPIO Zero (inglés). Con ella, podrás enseñar a quien tengas a tu alrededor los fundamentos básicos de la programación relacionada con objetos del mundo real de una forma más amigable que hablándole de pines y tensiones.

GPIO Zero es una librería escrita para Python por Ben Nutall, de la fundación Raspberry Pi, partiendo RPi.GPIO, de Ben Cronston, y con ayuda de Dave Jones (el autor de la librería de la cámara de la Raspberry Pi) y otros. La versión estable es del mes de noviembre de 2015, y trata de proporcionar objetos de Python que modelen el comportamiento de los componentes que usamos en nuestros prototipos, atendiendo a sus montajes más comunes. Por ejemplo, si queremos usar un LED en el pin GPIO número 12, en lugar de configurar el pin como salida primero y después enviarle un nivel alto a dicho pin:

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(false)
GPIO.setup(12, GPIO.OUT)

GPIO.output(12, GPIO.HIGH)

Lo que haremos será importar un LED de la librería, declarar que está conectado al pin número 12 y encenderlo, en un lenguaje mucho más directo y natural (aunque en inglés):

from gpiozero import LED

led = LED(12)
led.on()

Esto reduce muchísimo tanto el riesgo de configurar erróneamente un pin, como el código previo de configuración, y además usa características que mejoran el uso del procesador por parte de nuestros programas, como ya veremos más adelante.

Pero, sobre todo y como comenta Ben Nutall en RaspberryPi.org, la librería está escrita con la educación en mente. Un niño aprenderá lo mismo con los dos programas de arriba, pero con el segundo lo hará de una forma mucho más rápida, amigable y progresiva; y si tras aprender conceptos de programación automatizando tareas con LED usando esa semántica tan sencilla, decide ir a un nivel de detalle mucho más bajo y pensar en pines y tensiones para crear sus propios prototipos, podrá hacerlo usando RPi.GPIO.

Además de LED, GPIO Zero proporciona modelos de muchos otros dispositivos:

  • LED, LED multicolor RGB
  • Timbre, o buzzer
  • Motor
  • Pulsador
  • Sensor de movimiento
  • Sensor de luminosidad
  • Conversores analógico a digital MCP3004 y MCP3008

Obtener GPIO Zero

GPIO Zero será un recurso que usaremos mucho en nuestros prototipos a partir de ahora, y está disponible por defecto en las últimas imágenes de Raspbian, la distribución de Linux proporcionada por la fundación Raspberry Pi. Si la tuya no la tiene porque la has instalado antes de noviembre de 2015, puedes obtenerla escribiendo esto en el terminal:

sudo apt-get update
sudo apt-get install python3-gpiozero python-gpiozero

La documentación completa, en inglés, se encuentra en esta web: http://pythonhosted.org/gpiozero/

¿Te animas a probarlo?

Una idea que dejo aquí por si queréis probarlo ya, y no podéis esperar, es que tratéis de reescribir todos los experimentos que hayáis hecho con PItando usando GPIO Zero en lugar de RPI.GPIO como hasta ahora. Iré publicando esas nuevas versiones, así que estad atentos.


Puedes dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto, o bien a través de la dirección de correo que allí encontrarás.

]]>
http://pitando.net/2016/01/14/gpio-zero-simplifica-el-control-de-prototipos-desde-python/feed/ 4 1054
Episodio 14 – Processing y las artes visuales http://pitando.net/2016/01/14/episodio-14-processing-y-las-artes-visuales/ Thu, 14 Jan 2016 06:00:49 +0000 http://pitando.net/?p=1062 Sigue leyendo Episodio 14 – Processing y las artes visuales ]]> En este programa, el primero de 2016 (¡feliz año!), os hablo del entorno Processing, para programar en el contexto de las artes visuales. Se trata de una aplicación que nos permite codificar una obra visual, tanto estática como animada, e interactiva. Para hacerlo, nos proporciona modos que ofrecen sintaxis de lenguajes conocidos. Entre estos modos, los tres más respaldados son el de Java, Javascript y Python.

En un tiempo, Processing llegará a PItando ya que además de las bondades que os he contado es multiplataforma y está disponible tanto para Windows como para Mac OS X y Linux. Dentro de Linux, la última versión (3.0.1) está disponible para Raspberry Pi y además con soporte para los pines GPIO.

Más información en http://processing.org, donde podréis descargar los instaladores y en la página de la Processing Foundation en https://processingfoundation.org/. Ambos enlaces están en inglés, así como la documentación de referencia, pero sin embargo el interfaz de la aplicación está traducida al español.

Para instalarlo desde una Raspberry Pi (yo lo he probado desde Raspbian) deberéis introducir lo siguiente en el terminal:

curl https://processing.org/download/install-arm.sh | sudo sh

Cuando aparezca el mensaje “Done! You can start processing by running "processing" in the terminal, or through the applications menu (might require a restart).” podréis reiniciar la Raspberry para después ejecutar el entorno tanto desde el terminal como desde el menú de Programación.


Podéis dejarme cualquier comentario en esta misma entrada, o enviándome
cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast.

Si queréis también podéis dejar una revisión del podcast en las página de Ivoox e iTunes, lo que además de proporcionarme comentarios sin duda valiosos, ayudará a que otros oyentes encuentren este podcast:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the resistors” (https://soundcloud.com/eric-skiff/were-the-resistors)

]]>
1062
Fotomatón conectado a Dropbox con la Raspberry Pi http://pitando.net/2015/12/22/fotomaton-conectado-a-dropbox-con-la-raspberry-pi/ http://pitando.net/2015/12/22/fotomaton-conectado-a-dropbox-con-la-raspberry-pi/#comments Tue, 22 Dec 2015 10:00:27 +0000 http://pitando.net/?p=1034 Sigue leyendo Fotomatón conectado a Dropbox con la Raspberry Pi ]]> En el artículo de hoy quiero ofreceros un proyecto sencillo, útil y vistoso: se trata de montar un dispositivo que, cuando oprimas un pulsador en la placa de prototipado, te saque una fotografía y la suba a tu cuenta de Dropbox. Así, los invitados a tus fiestas podrán retratarse y dejar una estampa en una carpeta de tu espacio en la nube lista para compartir con ellos.

Para eso vamos a usar el siguiente material:

  • Una cuenta de Dropbox, valen las gratuitas
  • Una Raspberry Pi con conexión a internet
  • El módulo de la cámara de la Raspberry Pi, acerca de la cual puedes leer y aprender a montarla y configurarla en su propio artículo. El soporte es opcional, pero de alguna forma tendrás que sujetar la cámara en la posición deseada.
  • Una placa de prototipado con un prototipo como el que ya conocemos, que podéis ver en la imagen inferior y que consta de:
    • Una placa de prototipado, también llamada breadboard o protoboard
    • Una resistencia de 330 Ω
    • Una resistencia de 10 kΩ
    • Un LED
    • Un pulsador
    • 3 cables de interconexión, para el propio circuito
    • Una placa de expansión de GPIO con su cable plano, o bien los cables necesarios para alimentar el prototipo (+3,3 V y  0 V) y llevar a la placa una salida y una entrada por GPIO
    • Puedes leer sobre este prototipo aquí
Prototipo con pulsador. Puncha en la imagen para ampliarla.
Prototipo con pulsador que usaremos para disparar las fotos. Pincha en la imagen para ampliarla.

En el artículo veremos la configuración necesaria para la cuenta de Dropbox y el programa en Python que coordinará todas las acciones. En lo referente al montaje del prototipo y de la cámara os remito a los artículos correspondientes, pues son cosas que ya hemos hecho:

Configurar la cuenta de Dropbox para que sea posible cargar archivos desde un programa en Python

Para poder acceder a Dropbox desde nuestros programas deberemos primero crear la definición de nuestra aplicación en nuestra cuenta de Dropbox. Este proceso es muy sencillo: se trata de registrar nuestro programa (que es la aplicación) y obtener unas determinadas claves que la identifiquen y le concedan acceso a nuestra cuenta. Esta identificación permite a Dropbox, entre otras cosas, asignar y restringir permisos a nuestra aplicación así como relacionarla a ella y a su creador con las operaciones que realiza sobre los ficheros de lo usuarios (ojo: lo que hace, no la información que maneja). De esa forma disuade a cualquier delincuente de la realización de programas maliciosos o que roben información.

Antes de continuar, puedes estar tranquilo: la aplicación que vas a hacer sólo podrá acceder a una de tus carpetas que se creará a tal efecto, nadie más podrá hacerlo y tu información estará en todo momento en tu poder. En cualquier caso, si tienes cualquier duda puedes escribirme o dejar un comentario en esta entrada, y te responderé lo antes posible.

Para empezar, ve a tu cuenta de Dropbox y haz click en los tres puntos de abajo del todo, a la derecha de Privacidad, para desplegar el menú inferior que te muestro en esta imagen:

Menú de opciones adicionales. Desarrolladores.
Menú de opciones adicionales. Desarrolladores.

Una vez ahí haz click en Desarrolladores. A partir de este momento, la página sólo está disponible en inglés, pero te guiaré y te explicaré cada paso por si no manejas bien dicho idioma.

Haz click en Create your app, “Crear tu aplicación“.

Crear tu aplicación
Crear tu aplicación.

En la siguiente pantalla debes escoger lo siguiente:

  • Donde pone 1. Choose an API, “1) Escoge tu API” debes escoger Dropbox API. Es la opción que se corresponde con las cuentas de Dropbox para usuarios particulares, no empresas.
  • Donde pone 2. Choose the type of access you need, “2) Escoge el tipo de acceso que necesitas“, escoge App folder – Access to a single folder created specifically for your app, “Aplicación de carpeta – acceso a una única carpeta creada específicamente para tu aplicación“. De esta forma, nuestro fotomatón sólo podrá escribir en una carpeta específica que, además, se creará automáticamente. La otra opción daría acceso a toda tu cuenta de Dropbox, pero no es necesario.
  • Donde pone 3. Name your app 3) Da nombre a tu aplicación“debes introducir un nombre. Este nombre debe ser único en todo el conjunto de aplicaciones que puedan existir. Como ves en la imagen, yo he escogido “PItandoFoto“, que nadie ha solicitado anteriormente. Como sugerencia, puedes escoger un nombre que empiece por “PItandoFoto” y añadirle cualquier otra cosa, como tu nombre, un número o tu dirección de correo electrónico.
  • Cuando hayas terminado pulsa el botón Create app, “Crear la aplicación
Formulario para crear la aplicación
Formulario para crear la aplicación

El siguiente paso es copiar las claves de acceso. Tan pronto hayas pulsado el botón anterior verás un formulario como el siguiente:

Edición de la aplicación.
Edición de la aplicación.

Pulsa en el letrero azul Show, “Mostrar“, a la derecha de App secret para mostrar la clave secreta de la aplicación. Apunta el valor que sale además del inmediatamente superior, rotulado a la izquierda como App key. Pulsa también el botón Generate para Generar una clave permanente de acceso a la aplicación para tu usuario. De esta forma, tu programa no necesitará más que estos códigos para funcionar (realmente no necesitarás los tres, como veremos más adelante):

  • App key, que es una clave que identifica a la aplicación de forma pública en el ecosistema de aplicaciones de Dropbox. La podrías distribuir. En el programa la llamaré APP_KEY.
  • App secret, que es una clave secreta y que no podrías distribuir, y que identifica al programa que vas a escribir. No debes compartirla a través de internet, ya que permitirías a cualquier otro programador hacer un programa que suplantase al tuyo. En el programa la llamaré APP_SECRET.
  • Access token es una clave de acceso a tu cuenta de usuario que es equivalente, a todos los efectos, a haber autorizado a la aplicación a acceder a tu cuenta de Dropbox. Bajo ningún concepto deberías compartirla a través de internet, ya que te podrían suplantar a ti y acceder a tu cuenta de igual manera que si te hubiesen robado la contraseña. En el programa la llamaré ACCESS_TOKEN .

Una vez creada la clave secreta para acceder desde tu propia cuenta en el tercer paso, en ese preciso momento, en tu cuenta de Dropbox se crearán dos carpetas. Es posible que recibas alguna notificación. Son las siguientes:

  1. Aplicaciones. Es una carpeta de sistema dentro de la cual se crearán todas las carpetas de las aplicaciones que hayan sido creadas con la opción que ya hemos visto, App folder.
  2. Dentro de Aplicaciones, una carpeta de nombre igual al que has escogido: en mi caso, PItandoFoto.

Comprueba que dichas carpetas existen.

Carpetas creadas
Carpetas creadas

Si todo ha ido bien, hemos terminado por aquí. Puedes volver todas las veces que quieras a la página de desarrolladores y hacer click en My Apps para ver todas las aplicaciones que hayas creado, y editarlas o eliminarlas.

Descargar la librería de acceso a Dropbox para Python 3

La librería de acceso a las funciones de Dropbox está disponible en un repositorio de librerías de Python llamado Pip, que significa Python Package Index, traducido es más o menos “Índice (o directorio) de paquetes de Python“. Se trata de una red de servidores de ficheros del cual podemos descargar muchas librerías para muchos propósitos a través del programa de igual nombre, pip. Este programa se incluye siempre en las distribuciones de Python como las que incorpora la Raspberry Pi, con las siguientes consideraciones:

  • Si usas Python 2, el programa se llama pip
  • Si usas Python 3, deberás usar pip3

La librería que está disponible en Pip se corresponde con la versión 1 de lo que se conoce como API. Un API, o Interfaz del programador de aplicaciones, es un conjunto de funciones y estructuras de datos que se proporcionan a los programadores de aplicaciones para, de una forma fácil, acceder a una cierta cantidad de funcionalidades de un producto. En el caso de Dropbox, este API permite leer el contenido de la carpeta de Dropbox, subir y descargar ficheros, mantener directorios sincronizados,… todo lo que el cuerpo necesita, vaya.

Aquí iremos al grano para subir cada foto, pero si quieres consultar las posibilidades que tienes, puedes consultar la página de la versión 1 del API de Dropbox para Python.

Para descargar el cliente de Dropbox para Python 3, ya desde la Raspberry Pi encendida e iniciada la sesión con el usuario pi, debemos ejecutar la siguiente orden en el terminal:

sudo pip3 install dropbox

Con esto ya podremos empezar a usar el cliente para Dropbox en nuestros programas con sólo importar la librería con un import dropbox.

Programa del fotomatón

Una vez completados los pasos anteriores, pasaremos a escribir el programa. Vamos a usar cosas muy básicas, sabiamente combinadas, con la única novedad de la carga de un archivo en Dropbox. Esta carga se hace de la siguiente forma:

import dropbox

# Sustituye estos valores por los que has apuntado, conservando
# las comillas
app_key = 'APP_KEY'
app_secret = 'APP_SECRET'
cuenta = 'ACCESS_TOKEN'

cliente = dropbox.client.DropboxClient(cuenta)
print ('Cuenta autorizada: ', cliente.account_info())

f = open('gvisoc.jpg', 'rb')
respuesta = cliente.put_file('/gvisoc-subida.jpg', f)
print ('Subida:', respuesta)

La primera línea, claramente, sirve para importar la librería instalada mediante pip. A partir de ahí y usando las claves que hemos apuntado (líneas 5, 6 y 7) , obtenemos un objeto que nos permite acceder a Dropbox (línea 9). Realmente sólo estamos usando la tercera; las dos primeras nos servirían para programar un flujo de autorización entre nuestro programa y Dropbox de forma que la tercera de ellas se generase durante el proceso… pero está fuera de nuestras ambiciones de momento. Podéis dejar las tres claves o escribir sólo la tercera (cuenta = 'ACCESS_TOKEN'). Yo las dejaré como documentación, o recordatorio si lo preferís, de este último comentario.

Lo que queda por hacer es sencillamente subir un fichero local que podremos guardar en nuestra carpeta de aplicación con un nombre diferente (líneas 12 y 13).

Aunque no se muestra, cabe destacar que podremos controlar las excepciones en la subida capturando dropbox.rest.ErrorResponse. Si lo asignamos a una variable e, podremos inspeccionar el mensaje de error mediante e.user_error_msg. Así:

except dropbox.rest.ErrorResponse as e:
    print("Error al acceder a Dropbox: ", e.user_error_msg)

Entendido esto, lo único que tenemos que hacer es combinarlo mediante un bucle con la entrada mediante el botón, el encendido del led y la captura de una fotografía. Debemos repasar también, si es que lo hemos olvidado, el acceso a ficheros y los programas ejecutables.

#!/usr/bin/python3

import dropbox
import RPi.GPIO as GPIO
import time
import picamera

## Obtener una referencia a la cámara
camera = picamera.PiCamera()

## Conexión a la cuenta de Dropbox
# Sustuye estos valores por los que has copiado, conservando
# las comillas.
app_key = "APP_KEY" 
app_secret = "APP_SECRET"
# Las dos primeras claves no son en absoluto necesarias aquí, sirven
# para programar un flujo que generaría la siguiente (flujo de autori-
# zación OAuth2)
cuenta = "ACCESS_TOKEN"

print("Conectando a la cuenta de Dropbox...")
client = dropbox.client.DropboxClient(cuenta)
print("Cuenta de " + client.account_info().get("display_name") + " conectada.")
print("Oprime el pulsador para hacer la primera foto")

## Configuración GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings (False)
GPIO.setup(6, GPIO.IN)
GPIO.setup(12, GPIO.OUT)

## Bucle infinito del fotomatón
# Número total de fotos, para Dropbox
total = 0
# Número de fotos en la tarjeta SD, entre 0 y 10. Así no llenaré
# nunca el sistema de ficheros (sólo guardo 10 fotos en la
# Raspberry, "en local")
local = 0
try:
    while True:
        if (not GPIO.input(6)):
            nombre_local = "captura_" + str(local) + ".jpeg"
            nombre_dropbox = "captura_" + str(total) + ".jpeg"
            
            # Uso del LED para guiar al "modelo" de la foto
            print ("Cuando se apague el LED se hará la foto")
            GPIO.output(12, GPIO.HIGH)
            for i in range(0, 3):            
                print(str(3 - i) + "...")
                time.sleep(1)
            GPIO.output (12, GPIO.LOW)
            print ("¡Foto!")
            # Hacemos la foto
            camera.capture(nombre_local)
                
            # Subir la foto a Dropbox
            f = open(nombre_local, 'rb')

            print("Subiendo la foto a Dropbox de " + client.account_info().get("display_name") + "...")
            response = client.put_file(nombre_dropbox, f)
            
            tamanho = str(round(int(response.get("bytes")) / 1024,1))
            print("Foto '" + nombre_dropbox + " cargada: " + tamanho + " kB.")

            # Incrementar contadores; 'local' siempre entre 0 y 10
            local = (local + 1) % 10 # resto de dividir local + 1 entre 10.
            total = total + 1
            print("Oprime el pulsador para hacer otra foto")
            
except dropbox.rest.ErrorResponse as e:
    print("Error al acceder a Dropbox: ", e.user_error_msg)
except KeyboardInterrupt:
    print("\nInterrupción del usuario, saliendo del programa")
finally:
    camera.close()
    GPIO.cleanup()
    print ("Fin del programa")

Es hora de ejecutar el proyecto desde el terminal dando permisos de ejecución al programa previamente. Cuando hayas comprobado que todo está bien, puedes desconectar el monitor o la TV, el teclado y el ratón, y observar el funcionamiento del proyecto directamente desde otro ordenador, tu tableta o tu smartphone viendo cómo las fotos aparecen en tu cuenta de Dropbox a medida que las haces.

Podéis dejarme cualquier duda o comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

Espero que os hayáis divertido en el proceso y que saquéis partido al “chiringuito” 🙂

Anexo: borrar la aplicación

Puedes hacerlo volviendo a la página de Desarrolladores de Dropbox, y seleccionando My Apps, “Mis aplicaciones” en el menú de la izquierda. En la pantalla que aparece, si haces click en tu aplicación podrás acceder al menú de edición de la misma. Desplazándote hacia abajo hasta el final, verás el botón Delete app, “Borra la aplicación“.

Menú MyApps
Menú MyApps

Tendrás que confirmar el borrado (botón Delete), y ten en cuenta que la carpeta de tu aplicación permanecerá en tu cuenta. Pese a todo, recomiendo ser cauto y copiar las fotos que hayas sacado antes de confirmar el borrado, por si acaso en algún momento cambiasen este comportamiento o ocurriese cualquier error.

]]>
http://pitando.net/2015/12/22/fotomaton-conectado-a-dropbox-con-la-raspberry-pi/feed/ 5 1034
Episodio 13 – Python es una cosa muy seria http://pitando.net/2015/12/22/episodio-13-python-es-una-cosa-muy-seria/ Mon, 21 Dec 2015 23:41:40 +0000 http://pitando.net/?p=1048 Sigue leyendo Episodio 13 – Python es una cosa muy seria ]]> En este episodio encontraréis…

  • PItando volverá el 14 de enero
  • Agradecimientos y reseñas
  • Últimas noticias para cerrar el año.
  • Retrospectiva Python:
    • Anticipo del artículo de hoy
    • Python y servicios web: Dropbox
    • Imprimir desde la Raspberry mediante Python: llamada a la acción.
    • Aventuras en Minecraft, Minecraft Pi y programaremos Minecraft desde Python en nuestros PC o Mac.
  • Balance de los primeros 6 meses de PItando

¡Felices vacaciones y próspero cacharreo!


Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast.

Si queréis también podéis dejar una revisión del podcast en las página de Ivoox e iTunes:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the resistors” (https://soundcloud.com/eric-skiff/were-the-resistors)

]]>
1048
Resistencias: cómo leer su valor http://pitando.net/2015/12/17/resistencias-como-leer-su-valor/ Thu, 17 Dec 2015 10:00:04 +0000 http://pitando.net/?p=1023 Sigue leyendo Resistencias: cómo leer su valor ]]> Llevamos un cierto tiempo jugando con prototipos electrónicos, y hay un tema que he dejado de lado porque ahora mismo no era del todo imprescindible: leer el valor de la resistencia de un vistazo al propio componente, interpretando sus bandas de colores.

"Resistor" por Nunikasi. Licencia CC BY-SA 3.0, vía Wikipedia
“Resistor” por Nunikasi. Licencia CC BY-SA 3.0, vía Wikipedia

Hasta ahora me he aprovechado de nuestro estatus de novatos porque para coger una resitencia de un determinado valor, lo que hacíamos era irnos a una bolsa etiquetada. Pero si somos lectores de PItando es porque queremos aprender y por lo tanto queremos saber qué estamos haciendo y, en el caso que nos ocupa, saber leer una resistencia nos acercará más a nuestro objetivo.

Siendo rigurosos, un resistor es un componente electrónico que proporciona una determinada resistencia. Sí, decir “una resistencia” es, si nos ponemos puristas, una forma demasiado coloquial de hablar: el componente se llama resistor y la funcionalidad electrónica que proporciona es la resistencia. El resistor tiene una serie de bandas de colores, de un ancho fijo, sobre su cuerpo que generalmente es de cerámica, y que expresan la resistencia del componente.

Lo que debemos hacer para leer la resistencia del componente es lo siguiente:

  • Localizar la banda de tolerancia. Lo primero que tenemos que hacer es localizar en uno de sus extremos una banda más alejada del resto que las demás. Esa banda expresa la tolerancia que va a proporcionar un determinado componente, a saber:
    • Banda verde (0,5%): el valor efectivo de la resistencia del componente puede estar comprendido entre el valor nominal que leamos del resto de las bandas, y ±0,5 %: por ejemplo, si leemos 100 Ω ±0,5 %, el valor efectivo estará entre 99,5 Ω y 100,5 Ω
    • Banda marrón (1 %): el valor efectivo de la resistencia del componente puede estar comprendido entre el valor nominal que leamos del resto de las bandas, y ±1 %: por ejemplo, si leemos 100 Ω ±1 %, el valor efectivo estará entre 99 Ω y 101 Ω
    • Banda roja (2%): el valor efectivo de la resistencia del componente puede estar comprendido entre el valor nominal que leamos del resto de las bandas, y ±2 %: por ejemplo, si leemos 100 Ω ±2 %, el valor efectivo estará entre 98 Ω y 102 Ω
    • Banda dorada (5%): el valor efectivo de la resistencia del componente puede estar comprendido entre el valor nominal que leamos del resto de las bandas, y ±5 %: por ejemplo, si leemos 100 Ω ±5 %, el valor efectivo estará entre 95 Ω y 105 Ω. En este caso puede que la banda no esté más separada de las demás, ya que la banda de más a la izquierda nunca será dorada.
    • Banda plateada (10 %): el valor efectivo de la resistencia del componente puede estar comprendido entre el valor nominal que leamos del resto de las bandas, y ±10 %: por ejemplo, si leemos 100 Ω ±10 %, el valor efectivo estará entre 90 Ω y 110 Ω. En este caso puede que la banda no esté más separada de las demás, ya que la banda de más a la izquierda nunca será plateada.
    • No hay banda, es decir, el resto de bandas de colores están claramente más cerca de un borde que de otro: tolerancia de ± 20 %.
  • Una vez localizada la banda de tolerancia, colocamos el resistor con ésta hacia la derecha. Así, podremos interpretar el resto del valor leyendo de forma natural, de izquierda a derecha.
  • Tengamos en cuenta el siguiente código de colores para interpretar las bandas:
    • Negro: 0
    • Marrón: 1
    • Rojo: 2
    • Naranja: 3
    • Amarillo: 4
    • Verde: 5
    • Azul: 6
    • Púrpura: 7
    • Gris: 8
    • (Blanco): 9
  • De izquierda a derecha, cada banda aporta un dígito del valor que indica su color.
  • La banda de más a la derecha (sin llegar a ser la de tolerancia) es el número de ceros en que termina el valor nominal, y se llama “multiplicador”. Esta banda puede ser de los colores de arriba, pero también dorada o plateada:
    • Si fuese dorada, en vez de añadir ceros dividiríamos entre 10.
    • Si fuese plateada, dividiríamos entre 100.

Ejemplos:

  1. naranja, verde, marrón oro = 350 Ω ± 5 %
  2. verde, verde, negro, rojo y, más alejado, verde: 55000 Ω ± 0,5 %

Existen resistencias de 6 bandas, en cuyo caso la última expresa sensibilidad a la temperatura, y que trataremos llegado el caso.

Aquí tenéis las resistencias que incluye mi kit de iniciación, donde se ve claramente este código de colores y su valor. Para poderla ver bien al natural, debido a su tamaño, deberéis usar una lupa.

Para leer esta resistencia he dejado más a la derecha la banda de tolerancia, y he leído "naranja=3", "naranja=3", "marrón=1 cero". Total, 330 Ohm +-5%
Para leer esta resistencia he dejado más a la derecha la banda de tolerancia, que es la dorada, y he leído “naranja=3”, “naranja=3”, “marrón=1 cero”. Total, 330 Ohm +- 5 %.
Para leer esta resistencia, dejo a la derecha la banda de tolerancia (la dorada), y leo "marrón=1", "negro=0", "naranja=3 ceros", por lo que tengo 10.000 Ohm +- 5 %
Para leer esta resistencia, dejo a la derecha la banda de tolerancia (la dorada), y leo “marrón=1”, “negro=0”, “naranja=3 ceros”, por lo que tengo 10.000 Ohm +- 5 %

Fijaos que la banda de tolerancia no está especialmente separada del resto porque tiene un color, el dorado, que nunca tendré al otro extremo de la resistencia.

Existen a lo largo y ancho de la web muchos sitios en donde podréis imprimir una referencia rápida de esta notación. Yo os recomiendo que os construyáis vuestra propia referencia y que, por supuesto, practiquéis siempre que podáis para tener soltura con ella. Y, por supuesto, recomiendo encarecidamente tener un multímetro a mano para comprobar el valor de la resistencia siempre que tengamos la menor sombra de duda. Podéis consultar el vídeo de esta entrada para repasar cómo hacerlo.

 

]]>
1023
Escribe programas interactivos en Python http://pitando.net/2015/12/10/escribe-programas-interactivos-en-python/ http://pitando.net/2015/12/10/escribe-programas-interactivos-en-python/#comments Thu, 10 Dec 2015 10:00:12 +0000 http://pitando.net/?p=1005 Sigue leyendo Escribe programas interactivos en Python ]]> Anteriormente hemos estado haciendo programas ejecutables en Python que aceptaban parámetros por línea de comandos, es decir, en el momento de invocarlos le pasábamos todos los datos que necesitaban para comenzar a ejecutarse.

Este tipo de programas son muy adecuados para crear utilidades para el usuario cuando está usando un terminal, o cuando quiere integrarlos en otro sistema mayor que tenga la posibilidad de invocar estas utilidades. Sin embargo, en otras ocasiones es necesario dotar al programa de la capacidad de ser interactivo, como a la hora de pedir confirmación al usuario y recibir una respuesta (el clásico “¿Desea borrar el fichero? [S/n]”), o incluso llegar a crear nuestros propios intérpretes de órdenes.

En este artículo veremos lo sencillo que es creando un juego interactivo que proponga al usuario adivinar un número entre cero y un parámetro que recibirá por línea de comandos.

Cosas que debes repasar:

Obtener datos de modo interactivo

En Python 3 la entrada de datos se lleva a cabo mediante la función estándar input() , que permite escribir un mensaje a mostrar por la pantalla invitando al usuario a responder a una pregunta. En el momento de ejecutar input() el programa se detendrá, mostrando la frase que le hemos pasado como parámetro, y se quedará a la espera de una respuesta del usuario que podrá introducir por teclado, terminando su respuesta mediante la tecla intro (↵). Una vez lo haga, la función devolverá como resultado el texto introducido por el usuario pero sin el carácter de retorno de carro final.

Haz la prueba tú mismo escribiendo en IDLE (el entorno de desarrollo de Python 3) lo siguiente:

x = input ("Dime tu nombre: ")

Verás que IDLE imprime ese mensaje pero no vuelve a mostrar el cursor de Python: está esperando a que introduzcas texto. Introduce tu nombre y pulsa la tecla intro (↵): en ese momento, IDLE volverá a mostrar el cursor que permite escribir sentencias en Python. Escribe ahora:

print ("Hola", x)

y observa el resultado.

Funcionamiento de la función input() de Python 3
Funcionamiento de la función input() de Python 3

En Python 2 existen dos funciones: raw_input(), que tiene exactamente el mismo comportamiento que hemos visto con la función input() de Python 3, e input(). Input en Python 2 es una función algo peliaguda porque además de recoger la entrada por teclado del usuario la interpreta como una sentencia en Python. No se debe usar porque puede dar lugar a que el usuario introduzca código malintencionado en nuestro programa. Siempre debemos usar raw_input() en Python 2 o input() en Python 3 y realizar la interpretación de datos en nuestro programa, de forma muy controlada y calculada.

En cualquier caso, yo recomiendo usar siempre Python 3 ya que es la versión que está actualmente evolucionando y por lo tanto será donde aprovechemos los avances en el lenguaje. Python 2 ya solamente recibe actualizaciones de seguridad y de mantenimiento: no evoluciona.

¡Adivina el número!

Vamos a hacer un programa ejecutable que acepte por línea de comandos un número arbitrario. Con ese número, el programa propondrá adivinar al usuario, de modo interactivo, un número obtenido al azar entre el 0 y el número obtenido por línea de comandos. Para facilitar la tarea, el programa irá contestando si el usuario se ha pasado por lo alto, si se ha quedado corto y, por supuesto, si ha acertado.

Para obtener un número al azar entre otros dos usaremos la función random() del módulo random, que ofrece un número entre 0 y 1, con decimales. Esos decimales los redondearemos con round() y el resultado lo convertiremos a entero mediante int().

Para empezar, introduciremos el bloque que interpretará el parámetro por línea de comando. Lo que haré será tratar de interpretar el primer parámetro como un número mayor que 0. Si no se introduce un número, o éste es menor que 0, el programa termina. Si no se introduce ningún parámetro el programa tomará como valor por defecto el 100. El código es el siguiente:

import sys

try:
    arg = sys.argv[1]
    techo = int (arg)
    if (techo < 0):
        print ("Error 1: el número introducido, " + arg + " es menor que 0.")
        exit (1) # Error de línea de comandos.
except (IndexError):
    techo = 100 # por defecto
except (ValueError):
    print("Error 2: el parámetro introducido, '"+ arg + "', no es un número.")
    exit(2) # Error de línea de comandos.

El paso siguiente es generar un primer número secreto entre 0 y el techo  introducido por línea de comandos. Para eso:

  • Importaremos el módulo random dándole un nombre opcionalmente, tras la línea que importa el módulo sys:
import sys
import random as generador
...
  • Hecho esto, al final de lo que llevamos de programa generaremos un primer número secreto: multiplicaremos el restultado de generador.random() por el número máximo, recibido por línea de comandos, y lo convertiremos a un número entero redondéandolo y aplicándole la conversión de tipos.
secreto = int (round (techo * generador.random()))

A continuación hay que escribir la mecánica del juego. Lo que haremos será un bucle que:

  1. Pedirá un intento al usuario.
  2. Si el intento es válido (un número entre 0 y el techo  introducido por línea de comandos), lo comparará con el secreto.
  3. Informará al usuario del resultado y, si ha acertado, ofrecerá la oportunidad de jugar otra vez, generando un nuevo número secreto.

Para convertir esto en realidad escribiremos un bucle sobre una variable booleana (cuyo valor puede ser True  o False ), que modificaremos sólo si el jugador decide abandonar el juego. El resto del código es sencillo de escribir si se tiene en cuenta lo aprendido hasta ahora en la serie de Python.

continuar = True
while continuar:
    
    # Captura de la entrada
    s_intento = input ("Adivina el número entre 0 y " + str(techo) + ":")

    # Control de errores: formato numérico y fuera de rango.
    # Variable para controlar el error en la entrada del usuario
    error = False
    try:
        intento = int (s_intento)        
        if intento > techo or intento < 0:
            print ("Intento incorrecto: debe ser un número entre 0 y " + str(techo))
            error = True
    except (ValueError):
        error = True
        print ("Error: '" + s_intento + "' no es un número")
    

    # Si no se han detectado errores, se evalúa el intento del jugador.
    if not error:

        # Vemos si ha acertado, quedado por arriba o por abajo.
        if intento == secreto:

            # Vemos si quiere jugar de nuevo.
            respuesta = ''
            while respuesta.lower() not in ['s', 'n']:
                respuesta = input ("¡Has acertado!, ¿quieres jugar otra vez? [s/n]")
            if respuesta.lower() == "s":
                print ("Generando nuevo número secreto")
                secreto = int (round (techo * generador.random()))
            else:
                continuar = False # Esto detiene el juego.
        
        elif intento < secreto:
            print ("Te has quedado corto, prueba con un número más alto")
        else:
            print ("Te has pasado: prueba con un número más bajo")

El código fuente completo quedará como sigue:

import sys
import random as generador

try:
    arg = sys.argv[1]
    techo = int (arg)
    if (techo < 0):
        print ("Error 1: el número introducido, " + arg + " es menor que 0.")
        exit (1)
except (IndexError):
    techo = 100 # por defecto
except (ValueError):
    print("Error 2: el parámetro introducido, '"+ arg + "', no es un número.")
    exit(2) # Error de línea de comandos.


secreto = int (round (techo * generador.random()))

continuar = True
while continuar:
    
    # Captura de la entrada
    s_intento = input ("Adivina el número entre 0 y " + str(techo) + ":")

    # Control de errores: formato numérico y fuera de rango.
    # Variable para controlar el error en la entrada del usuario
    error = False
    try:
        intento = int (s_intento)        
        if intento > techo or intento < 0:
            print ("Intento incorrecto: debe ser un número entre 0 y " + str(techo))
            error = True
    except (ValueError):
        error = True
        print ("Error: '" + s_intento + "' no es un número")
    

    # Si no se han detectado errores, se evalúa el intento del jugador.
    if not error:

        # Vemos si ha acertado, quedado por arriba o por abajo.
        if intento == secreto:

            # Vemos si quiere jugar de nuevo.
            respuesta = ''
            while respuesta.lower() not in ['s', 'n']:
                respuesta = input ("¡Has acertado!, ¿quieres jugar otra vez? [s/n]")
            if respuesta.lower() == "s":
                print ("Generando nuevo número secreto")
                secreto = int (round (techo * generador.random()))
            else:
                continuar = False # Esto detiene el juego.
        
        elif intento < secreto:
            print ("Te has quedado corto, prueba con un número más alto")
        else:
            print ("Te has pasado: prueba con un número más bajo")
        
exit(0) # código de no error.

Ejecución del programa

Este programa, así escrito, puede ser guardado en un fichero (por ejemplo, adivina.py) y es ejecutable directamente en IDLE pulsando F5 en la ventana de edición:

Ejecución en el propio IDLE usando F5 desde el editor de programas
Ejecución en el propio IDLE usando F5 desde el editor de programas

También se puede ejecutar en Windows desde el símbolo de sistema, que puedes encontrar pulsando [Tecla de Windows] + R y escribiendo cmd. Una vez en el símbolo del sistema deberás ejecutarlo anteponiendo la ruta completa al intérprete de Python, o python si has seguido todas las instrucciones del final del artículo que trataba la línea de comandos.

Ejecución en Windows
Ejecución en Windows

Para poderlo ejecutar en tu PC con Linux o en la Raspberry Pi, deberás escribir al principio de todo, como primera línea, #!/usr/bin/python3 ; en el Mac OS X deberás escribir por su parte algo similar (puede cambiar la versión de Python) a #!<strong>/Library/Frameworks/Python.framework/Versions/3.4/bin/python3</strong>. En cualquier caso, puedes obtener la ruta del ejecutable de Python 3, que sería lo que va después del signo de admiración, ejecutando en un terminal which python3. Para ejecutarlo en cualquiera de estas tres alternativas recuerda darle permisos de ejecución al fichero donde guardes el programa (por ejemplo, adivina.py) con la sentencia chmod +x adivina.py desde el terminal.


Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

]]>
http://pitando.net/2015/12/10/escribe-programas-interactivos-en-python/feed/ 2 1005
Episodio 12 – Las Astro Pi lanzadas, ¡al fin! http://pitando.net/2015/12/08/episodio-12-las-astro-pi-lanzadas-al-fin/ Tue, 08 Dec 2015 16:34:58 +0000 http://pitando.net/?p=1020 Sigue leyendo Episodio 12 – Las Astro Pi lanzadas, ¡al fin! ]]> En este breve episodio, que adelanto 2 días aprovechando que hoy, 8 de diciembre, es festivo, resumo los contínuos retrasos que sufrió el cohete Atlas V, encargado de llevar a la órbita de encuentro con la Estación Espacial Internacional a la cápsula Cygnus que contiene las dos Raspberry Pi del proyecto Astro Pi.

Si nada ocurre este será el penúltimo audio en el que hable de despegues de cohetes ya que los dos eventos que quedan, por ahora, relacionados con despegues y órbitas de Astro Pi son:

  • El encuentro de la Cygnus con la Estación (mañana miércoles 9)
  • El despegue, martes 15 de diciembre, y posterior llegada de los astronautas a la Estación Espacial Internacional.

Posteriormente a eso, comenzarían los 6 meses de ejecución de los proyectos de los estudiantes que han sido seleccionados mediante concurso, cosa que espero ir comentando en cuanto haya novedades.

]]>
1020
Minecraft Pi: programando el mundo de Minecraft con Python http://pitando.net/2015/12/03/programando-en-minecraft-pi-con-python/ http://pitando.net/2015/12/03/programando-en-minecraft-pi-con-python/#comments Thu, 03 Dec 2015 10:00:54 +0000 http://pitando.net/?p=995 Sigue leyendo Minecraft Pi: programando el mundo de Minecraft con Python ]]> Hoy os traigo un vídeo que hace mucho tiempo que quería grabar, pero que no fue posible por dificultades técnicas. Es una demostración de cómo podemos obtener resultados visibles en un videojuego programando en Python. De forma parecida a cuando, mediante Scratch, conseguimos influir en la “vida” que un gato lleva en nuestra pantalla, mediante Python y la versión de Minecraft que incluye Raspbian conseguiremos transformar el mundo de Minecraft en nuestra Raspberry Pi.

¿Qué es Minecraft?

Pero, ¿quién no lo conoce a estas alturas? En cualquier caso: es un videojuego famosísimo que permite jugar de dos formas: de modo creativo y en modo supervivencia.

  • El modo creativo propone al jugador un mundo abierto donde es todopoderoso y posee una cantidad ilimitada de materiales para construir estructuras y mecanismos cualesquiera.
  • El modo supervivencia propone al jugador un mundo abierto donde puede morir, por la noche es atacado por distintos monstruos, y donde parte sin nada: debe sobrevivir y construir por sus propios medios hasta viajar entre varias dimensiones tratando de vencer a un Dragón (o, al menos, ese era el objetivo el año pasado).

Minecraft Pi

La Raspberry Pi incorpora una versión muy reducida del modo creativo del juego, llamada Minecraft PI Edition, con un complemento que permite programar en el mundo de Minecraft mediante Python. En este vídeo, que os recomiendo ver a pantalla completa, os muestro cómo:

Recursos

Podéis encontrar información sobre las librerías que proporciona este juego en este enlace (en inglés), pero estad seguros de que seguiré publicando contenido acerca de este videojuego tan particular, y ejemplos escritos en Python sobre él. Para que exploréis, os dejo también el enlace a la hoja de trabajo (en inglés) disponible en la página de la fundación Raspberry Pi. Incluye varios ejercicios muy parecidos a los que os he mostrado y, de hecho, fue mi primer recurso de consulta y me he basado por entero en él. Os dejo aquí el código Python que he usado en el vídeo:

from mcpi.minecraft import Minecraft
from mcpi import block
import time

# Crea el objeto que nos permitirá acceder a Minecraft
mc = Minecraft.create()

# Obtiene la posición
x, y, z = mc.player.getPos() 

# Teletransporte vertical
mc.player.setPos( x, y+10, z) 

# Hombre de Hielo
while True:
    # Posición del jugador
    x, y, z = mc.player.getPos()
    
    #Si el bloque que piso no es de hielo, pongo un bloque de hielo
    if mc.getBlock(x, y, z) != block.ICE:
        mc.setBlock†(x, y - 1, z, block.ICE)
        
    # Aunque aparentemente no haga falta siempre es más responsable
    # ceder tiempo a Minecraft para atender a otros aspectos del juego.
    time.sleep (0.1) 

No dudéis en dejarme comentarios al respecto, ¡incluyendo propuestas de experimentos con este juego! 🙂

]]>
http://pitando.net/2015/12/03/programando-en-minecraft-pi-con-python/feed/ 1 995
Episodio 11 – Astro Pi: ¡se lanza el jueves 3! Raspberry Pi Zero http://pitando.net/2015/12/01/episodio-11-astro-pi-se-lanza-el-jueves-3-raspberry-pi-zero/ Tue, 01 Dec 2015 18:30:09 +0000 http://pitando.net/?p=992 Sigue leyendo Episodio 11 – Astro Pi: ¡se lanza el jueves 3! Raspberry Pi Zero ]]> En este episodio, totalmente inesperado esta semana, os cuento principalmente dos cosas:

  • Que me he equivocado en la fecha que os comentaba para el lanzamiento de las Raspberry Pi protagonistas del proyecto Astro Pi: se lanzan pasado mañana jueves 3 de diciembre, a las 22:30 hora española desde Cabo Cañaveral, y lo podéis ver en la TV de la NASA: http://www.nasa.gov/multimedia/nasatv/
  • Comentar las características y el lanzamiento de la Raspberry Pi Zero, y pistas sobre cómo conseguirla por unos gastos de envío algo caros pero que, al menos, si nos juntamos con más gente, se nos hagan medianamente asumibles para el bolsillo.

Espero que os guste. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast.

Si queréis también podéis dejar una revisión del podcast en las página de Ivoox e iTunes:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the resistors” (https://soundcloud.com/eric-skiff/were-the-resistors)

]]>
992
Episodio 10 – Directo en “Cervezas y Podcasts, segunda edición” con @micropakito de Diógenes Digital http://pitando.net/2015/11/29/episodio-10-directo-en-cervezas-y-podcasts-segunda-edicion-con-micropakito-de-diogenes-digital/ http://pitando.net/2015/11/29/episodio-10-directo-en-cervezas-y-podcasts-segunda-edicion-con-micropakito-de-diogenes-digital/#comments Sun, 29 Nov 2015 16:09:49 +0000 http://pitando.net/?p=986 Sigue leyendo Episodio 10 – Directo en “Cervezas y Podcasts, segunda edición” con @micropakito de Diógenes Digital ]]> Ayer sábado nos reunimos unos cuantos alrededor de unos micrófonos en El Pabellón de la Caza, en Alcobendas, y nos pusimos a grabar podcasts en directo: éramos Diógenes Digital (http://diogenesdigitalpodcast.blogspot.com.es/), Invita la Casa (http://invitalacasapodcast.blogspot.com.es/), Histocast (http://www.histocast.com/) y yo. Fue mi primer directo y conté al micrófono con Sergio, uno de los integrantes de Diógenes Digital (@micropakito en twitter ).

Hablamos un buen rato (casi una hora) de la Raspberry Pi y el Arduino aplicados a la educación de los pequeños en programación y electrónica y, la verdad: me lo pasé muy bien, creo que grabar con alguien al lado hace que todo quede mucho más dinámico y divertido, y creo que quedó un programa bastante chulo.

Os dejo los enlaces a los recursos que os contábamos en directo:

Una foto del fotomatón, cortesía de dove (https://twitter.com/doveamore). Detrás se ve la placa del montaje de Arduino de Sergio:

CU6GnxGWUAA-mmJ.jpg large

(Foto en twitter: https://twitter.com/doveamore/status/670621453390381058)


Espero que os guste. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast.

Si queréis también podéis dejar una revisión del podcast en las página de Ivoox e iTunes:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the resistors” (https://soundcloud.com/eric-skiff/were-the-resistors)

]]>
http://pitando.net/2015/11/29/episodio-10-directo-en-cervezas-y-podcasts-segunda-edicion-con-micropakito-de-diogenes-digital/feed/ 3 986
Cámara para la Raspberry Pi: instalarla y probarla http://pitando.net/2015/11/26/modulo-de-camara-para-la-raspberry-pi/ http://pitando.net/2015/11/26/modulo-de-camara-para-la-raspberry-pi/#comments Thu, 26 Nov 2015 10:00:06 +0000 http://pitando.net/?p=974 Sigue leyendo Cámara para la Raspberry Pi: instalarla y probarla ]]> Existen dos módulos con capacidad de captura de imágenes disponibles para la Raspberry Pi: una cámara de visión normal y una cámara de visión infrarroja. Ambos se conectan de la misma forma a la placa, y en este artículo os voy a enseñar el módulo de la cámara de visión normal.

Podéis ver cómo se conecta a la placa en este vídeo:

En el resto del artículo veremos cómo preparar la Raspberry Pi para tomar fotos tanto desde Python como desde un Terminal de Linux, y verificar así que hemos conseguido instalar correctamente la cámara. En otros artículos que están por llegar veremos más opciones de captura fotográfica y las capacidades de grabar vídeo que tiene (a 1080p, dicho sea de paso).

Una vez conectada la cámara como os enseñaba en el vídeo, lo que haréis será entrar en el programa de configuración de la Raspberry Pi mediante un terminal, escribiendo lo siguiente:

sudo raspi-config

Una vez dentro, localizaréis la 5ª opción, Enable Camera.

Menú principal de raspi-config. La opción para activar la cámara aparece resaltada.
Menú principal de raspi-config. La opción para activar la cámara aparece resaltada.

Dentro de esta opción el proceso es sumamente sencillo, solamente hay que desplazarse hacia la opción Enable mediante el uso del tabulador y los cursores del teclado.

Pantalla de activación de la cámara. Desplázate hasta "enable" mediante el uso de tabulador y cursores.
Pantalla de activación de la cámara. Desplázate hasta “enable” mediante el uso de tabulador y cursores.

Una vez pulsemos la tecla Intro en la pantalla que os he mostrado, volveremos al menú principal, en donde saldremos de la herramienta desplazándonos mediante las teclas del tabulador y cursores hasta la opción Finish. Al hacerlo, el programa nos dará la opción de reiniciar la Raspberry Pi, puesto que activar la cámara necesita un reinicio;  responderemos que sí para terminar.

Probar la cámara usando el Terminal (Bash)

Para hacerlo, simplemente abre una ventana del terminal y escribe

raspistill -o test.jpeg

El resultado será la aparición de un fichero llamado test.jpeg en el directorio donde estuvieras en ese momento.

En otros artículos veremos más comandos para grabar vídeo y más opciones.

Probar la cámara usando un programa en Python

Desde el intérprete de Python importamos la librería necesaria para manejar la cámara y obtenemos un objeto con la funcionalidad ofrecida para la cámara.

import picamera

camara = picamera.PiCamera()

Posteriormente, y cuantas veces necesitemos, ejecutaremos el código siguiente para capturar imágenes:

camara.capture("test_python.jpeg")

Por último, antes de salir del entorno o del programa en Python debemos liberar la cámara y los recursos de la librería ejecutando la siguiente sentencia:

camara.close()

Resultado

Aquí podéis ver el programa Visor de Imágenes mostrando la captura hecha:

Captura de prueba con la cámara
Captura de prueba con la cámara

Si en vuestro caso sale del revés, deberéis modificar vuestro código ejecutando la siguiente órden desde la consola para reflejarla horizontal y verticalmente:

camera -vf -hf -o test.jpeg

O bien la siguiente sentencia desde Python, previa a capturar la fotografía, para rotarla 180º en vertical (no es necesario doble reflexión en este caso):

camara.vflip = True

Enlaces de interés

Más información en la página del producto de la fundación Raspberry Pi (en inglés).


La cámara de la Raspberry Pi ofrece muchas posibilidades a la hora de hacer experimentos, como veremos en lo sucesivo. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

]]>
http://pitando.net/2015/11/26/modulo-de-camara-para-la-raspberry-pi/feed/ 8 974
Episodio 9 – Cacharreo en “Cervezas y Podcasts, segunda edición” http://pitando.net/2015/11/25/episodio-9-cacharreo-en-cervezas-y-podcasts-segunda-edicion/ Wed, 25 Nov 2015 20:38:49 +0000 http://pitando.net/?p=983 Sigue leyendo Episodio 9 – Cacharreo en “Cervezas y Podcasts, segunda edición” ]]> En este episodio, muy breve, os cuento qué tengo preparado para la jornada de podcasting en directo que en Diógenes Digital (http://diogenesdigitalpodcast.blogspot.com.es) han organizado para el sábado 28 de noviembre de 2015 en Alcobendas, y en la que participamos Invita la Casa (http://invitalacasapodcast.blogspot.com.es), Histocast (http://www.histocast.com), Diógenes Digital y yo mismo.

Como escucharéis, podréis subir una foto desde las jornadas a mi cuenta de twitter, pitandonet, a través de mi Raspberry Pi usando un pulsador físico. La librería para poder emitir tuits (y hacer otras muchas cosas) desde la Raspberry usando Python es TweePy. La idea es que podáis acercaros y ver in situ un montaje práctico y, si queréis, participar en las jornadas también a través de twitter dejando vuestro retrato bajo un hashtag que configuraré para la ocasión.

Detalles de la jornada: http://diogenesdigitalpodcast.blogspot.com.es/2015/11/cervezas-y-podcast-segunda-edicion.html


Espero que os guste. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast.

Si queréis también podéis dejar una revisión del podcast en las página de Ivoox e iTunes:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the resistors” (https://soundcloud.com/eric-skiff/were-the-resistors)

]]>
983
Solución al ejercicio del prototipo con pulsador http://pitando.net/2015/11/19/solucion-al-ejercicio-del-prototipo-con-pulsador/ http://pitando.net/2015/11/19/solucion-al-ejercicio-del-prototipo-con-pulsador/#comments Thu, 19 Nov 2015 10:00:00 +0000 http://pitando.net/?p=968 Sigue leyendo Solución al ejercicio del prototipo con pulsador ]]> Hace un par de semanas nos divertíamos con un prototipo que contaba nada menos que con un LED y un pulsador, aprendiendo a controlar el LED gracias a la señal proveniente del pulsador que introducíamos por un PIN GPIO.

Al final del artículo os proponía un ejercicio que decía así:

Tomando como base los programas de este artículo, consigue variar su funcionamiento de tal forma que pulsar el botón cambie su estado. Es decir, una pulsación lo enciende, otra lo apaga; si dejamos el dedo oprimiendo el pulsador, el LED parpadea cambiando de estado cada segundo. Como en este vídeo:

Consideraciones para Python: puedes usar un bucle infinito en Python (while True:), y terminar el programa usando la combinación de teclas CTRL + C (pulsar a la vez las teclas Control y C).

Lo mejor, en cualquier caso, sería que usases un bloque try: ... finally: ... como los que vimos en el artículo de excepciones, para hacer cosas como liberar recursos y apagar el LED antes de finalizar el programa.

Vamos a ver la solución.

Para este ejercicio monta el prototipo del artículo “Vuelve la electrónica: recibir información a través del GPIO de la Raspberry Pi“.

Solución en Python

Tomando como base el programa que enciende el LED durante un segundo, lo que debemos hacer es dotar de memoria al bucle, para que en una pasada invierta el estado en el que se puso con la anterior.

Para eso usaremos una variable llamada estadoen la que almacenaremos el valor presente en el pin GPIO número 12, que es el que usábamos como salida. Como inicialmente el LED estará apagado, la inicializaremos a False. Dentro del bucle, pondremos una sentencia de control ifque, en función del valor de estado, pondrá el contrario y lo almacenará en la misma variable.

Prueba el siguiente código:

#!/usr/bin/python3

import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(12, GPIO.OUT)
GPIO.setup(6, GPIO.IN)

estado = False
GPIO.output(12, GPIO.LOW)

try:
    while True:
        if (not GPIO.input(6)):
        
            if (not estado):
                # LED Apagado
                print ("LED encendido")
                GPIO.output(12,GPIO.HIGH)
            else:
                # LED encendido
                print ("LED apagado")
                GPIO.output(12, GPIO.LOW)
            estado = not estado
finally:
    print ("Haciendo limpieza")
    GPIO.cleanup()
    print ("Hasta luego")

Habrás notado que tenemos un comportamiento extraño en el LED, y es que en ocasiones se queda encendido, otras veces se queda apagado, algunas parpadea un poco.¿Por qué ocurre esto? A la vista del código, no debería fallar.

Esto ocurre cuando combinamos la programación con el mundo físico: las cosas no funcionan de forma perfecta. El pulsador es un dispositivo físico ruidoso, es decir, al oprimirlo y soltarlo, emite variaciones aleatorias al PIN GPIO, y eso hace que el LED tenga esos transitorios extraños. Al entrar esa señal en el programa, en ocasiones éste registra más pulsaciones de las que hemos hecho, y es por culpa de ese ruido de origen mecánico.

Para evitarlo vamos a poner un temporizador corto, que nos permita retirar el dedo del pulsador antes de dejar que el bucle entre en la siguiente pasada:

time.sleep(0.5) # Filtro de rebotes

Lo pondremos antes de salir de una pasada del bucle. El código queda como sigue:

#!/usr/bin/python3

import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(12, GPIO.OUT)
GPIO.setup(6, GPIO.IN)

estado = False
GPIO.output(12, GPIO.LOW)

try:
    while True:
        if (not GPIO.input(6)):
        
            if (not estado):
                # LED Apagado
                print ("LED encendido")
                GPIO.output(12,GPIO.HIGH)
            else:
                # LED encendido
                print ("LED apagado")
                GPIO.output(12, GPIO.LOW)
            estado = not estado
            time.sleep(0.5) # Filtro de rebotes
finally:
    print ("Haciendo limpieza")
    GPIO.cleanup()
    print ("Hasta luego")

Pruébalo ahora para comprobar que, ahora sí, el programa funciona como debe. Puedes experimentar con el valor del temporizador de espera para obtener resultados distintos y encontrar el umbral mínimo a partir del cual el prototipo deja de funcionar.

Solución en Scratch

Las explicaciones son básicamente las mismas.

Solución en Scratch al ejercicio del LED (cambio de estado)
Solución en Scratch al ejercicio del LED (cambio de estado)

Espero que haya sido interesante. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

]]>
http://pitando.net/2015/11/19/solucion-al-ejercicio-del-prototipo-con-pulsador/feed/ 5 968
Episodio 8bis – Cervezas y podcasts, segunda edición http://pitando.net/2015/11/12/episodio-8bis-cervezas-y-podcasts-segunda-edicion/ http://pitando.net/2015/11/12/episodio-8bis-cervezas-y-podcasts-segunda-edicion/#comments Thu, 12 Nov 2015 18:51:19 +0000 http://pitando.net/?p=965 Sigue leyendo Episodio 8bis – Cervezas y podcasts, segunda edición ]]> Muy muy rápido, os hablo para contaros que el próximo 28 de noviembre participo en la segunda edición de las jornadas de “Cervezas y Podcasts”, invitado por los organizadores Diógenes Digital podcast (http://diogenesdigitalpodcast.blogspot.com.es), a quienes hay que dar las gracias por organizar eventos como éste, tan divertidos 🙂

Será en el pabellón de la Caza, en el número 137 de la calle Marqués de Valdavia, en Alcobendas (Madrid, España)

En ellas, además de grabar un podcast en directo con ellos intentaré por todos los medios llevar algo de cacharreo para verlo en directo.

También participan Invita la Casa (http://invitalacasapodcast.blogspot.com.es) e Histocast (http://www.histocast.com) y el día se cerrará con una mesa redonda.

Enlace al anuncio de las jornadas con todos sus detalles: http://diogenesdigitalpodcast.blogspot.com.es/2015/11/cervezas-y-podcast-segunda-edicion.html

¡Espero veros a alguno por allí!

]]>
http://pitando.net/2015/11/12/episodio-8bis-cervezas-y-podcasts-segunda-edicion/feed/ 1 965
Nuestro primer videojuego con Scratch (y 4): sistema de puntuación sencillo http://pitando.net/2015/11/12/nuestro-primer-videojuego-con-scratch-y-4-sistema-de-puntuacion-sencillo/ Thu, 12 Nov 2015 09:00:15 +0000 http://pitando.net/?p=944 Sigue leyendo Nuestro primer videojuego con Scratch (y 4): sistema de puntuación sencillo ]]> Como sabéis, tengo en curso una serie en PItando en la que trato de crear con vosotros un videojuego muy básico en Sratch. Se divide en cuatro partes:

  1. Mover al gato en las cuatro direcciones y hacer que rebote en los bordes del escenario. Conseguido en la primera entrega.
  2. Programar la lógica de rebote para cuando el gato se encuentre con una pared roja. Conseguido en la entrega anterior.
  3. Programar la lógica de “gato encuentra a ratón”. Lo podéis leer aquí.
  4. Proponer un esquema de puntuación para poder competir con nuestros amigos.

En esta entrada de hoy, la cuarta de la serie y que cierra todos los puntos básicos del juego, vamos a recorrer ese paso que nos queda por dar, y aplicar puntuación al juego.

Si te animas, que espero que sí, sigue leyendo 🙂

Como venimos haciendo y para dar cabida a todos los lectores, tengáis o no una Raspberry Pi, haremos este ejercicio sobre el navegador web. Abre la página del editor on-line de Scratch y sube el fichero que guardábamos la semana pasada. Si no lo tienes, ¿qué mejor momento el de ahora para hacer las tres primeras partes del ejercicio? 😉

Para ello, ve al menú Archivo y pulsa la opción Subir de tu computadora.

Opción para subir un proyecto al editor Web de Scratch
Opción para subir un proyecto al editor Web de Scratch

Localiza el fichero y confirma la selección, tras lo cual el editor te pedirá confirmación en inglés (Replace contents of the current project?). Confirma la carga con el botón OK y, si todo ha ido bien, verás el editor cargado con el estado en el que lo dejamos en la ocasión pasada.

Dibuja un mapa más complejo

Para poder presentar un desafío a los jugadores, el escenario debería tener más que dos paredes. Abre el editor de escenarios de Scratch y, sobre tu escenario de pruebas, dibuja alguna pared más. Yo he dibujado este escenario:

Escenario del juego; puedes descargártelo si quieres pinchando en la imagen primero para ampliarlo.
Escenario del juego; puedes descargártelo si quieres pinchando en la imagen primero para ampliarlo.

En este escenario, el gato saldrá de la esquina inferior izquierda y el ratón estará en la esquina inferior derecha. Sube el escenario como hacíamos en el primer artículo de la serie.

Lógica de la puntuación y retoques finales al fin del juego

Lo primero que vamos a hacer una vez subido ese escenario es recolocar al gato una vez toca al ratón y, en general, como posición de partida, para que aparezca en la esquina inferior izquierda:

Gato recolocado a la esquina inferior izquierda.
Gato recolocado a la esquina inferior izquierda.

He reducido también el tiempo durante el cual el mensaje se queda en la pantalla, porque será más cómodo.

Vamos ahora a preguntar al jugador, tras esos dos segundos, si quiere volver a jugar. Para eso debemos combinar dos bloques del grupo Sensores y una condición del grupo Control:

Pregunta al usuario.
Pregunta al usuario.

Así, cuando el gato toque al ratón, recibiremos una pregunta como esta:

Podremos responder a la pregunta escribiendo "sí" (con tilde) en la caja de texto
Podremos responder a la pregunta escribiendo “sí” (con tilde) en la caja de texto

A partir de ahora ya sólo queda establecer un mecanismo de puntuación. El que os propongo en este artículo está basado en el tiempo en que tardamos en llegar al ratón. Para eso, dentro de Sensores existe un bloque llamado cronómetro que siempre está funcionando.

Lo que haré será poner el cronómetro a cero cuando el usuario comience a jugar pulsando una tecla por primera vez, y empezaré a restar los segundos transcurridos de un valor máximo, por ejemplo, 100. Es decir: se parte de 100, y se pierden puntos con el transcurso del tiempo.

Esa puntuación estará almacenada en una variable llamada puntuación, que deberemos dar de alta en el grupo Datos.

Os enseño el código completo de esta pequeña porción del ejercicio para luego explicároslo:

Puntuación y control del juego
Puntuación y control del juego

Como veis, tengo una variable que se llama jugando. Me sirve para que el juego sólo cuente puntuación a partir de que el jugador pulse una tecla por primera vez, para mover el gato. Cuando el juego está en transcurso, pongo jugando a 1; desde que el jugador gana hasta que pulsa la primera tecla tras responder sí a la pregunta que acabamos de programar, pongo jugando a 0.

También veréis que redondeo la resta con la que calculo la puntuación: lo hago porque el cronómetro tiene varios decimales de precisión, y no necesitamos tanta.

Por otro lado, antes de reiniciar el cronómetro cuando el jugador pulsa una tecla por primera vez escondo las dos variables de control, parar y jugando, para que sólo se muestre la de puntuación.

Y esto es todo, en realidad. Con este bloque tenemos ya un pequeño juego que permite a los pequeños de la casa (y a nosotros) experimentar con un entorno de programación muy interesante, como es Scratch, y también echar alguna partida. No te quedes aquí, y haz cuantas pruebas quieras. Por ejemplo, cosas que puedes intentar hacer por ti mismo son las siguientes:

  • Añade movimiento automático al ratón para que sea más difícil de capturar.
  • Haz que el gato pierda más puntos si toca una pared.
  • Coloca algún objeto extra en el escenario que, por ejemplo, permita al gato atravesar paredes.
    • Complícalo haciendo que la posición del objeto sea aleatoria, usando el bloque sacar un número al azar entre … y … del bloque operadores.

Y paro, pero podría seguir.


Espero que haya sido interesante. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

 

]]>
944
Episodio 8 – Proyecto “Astro Pi” http://pitando.net/2015/11/12/episodio-8-proyecto-astro-pi/ http://pitando.net/2015/11/12/episodio-8-proyecto-astro-pi/#comments Thu, 12 Nov 2015 05:15:09 +0000 http://pitando.net/?p=957 Sigue leyendo Episodio 8 – Proyecto “Astro Pi” ]]> En este episodio os cuento en qué consiste el proyecto Astro Pi: enviar nada menos que a la Estación Espacial Internacional a dos Raspberry Pi ampliadas con una placa de expansión llena de sensores y dispositivos de entrada / salida. En el audio os explico cómo se ha planteado la parte didáctica del tema, un concurso para los estudiantes de primaria y secundaria del Reino Unido, y cómo se han preparado las dos unidades del kit conocido como Astro Pi para su periplo.

Enlaces de interés:


Espero que os guste. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto (http://pitando.net/contacto) y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter (https://twitter.com/pitandonet), como en Facebook (https://www.facebook.com/pitandonet), y en Google+ (https://plus.google.com/+PitandoNet) también podéis seguir mis publicaciones, incluyendo las del podcast en:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the resistors” (https://soundcloud.com/eric-skiff/were-the-resistors)

]]>
http://pitando.net/2015/11/12/episodio-8-proyecto-astro-pi/feed/ 1 957
Vuelve la electrónica: recibir información a través del GPIO de la Raspberry Pi http://pitando.net/2015/11/05/vuelve-la-electronica-recibir-informacion-a-traves-del-gpio-de-la-raspberry-pi/ http://pitando.net/2015/11/05/vuelve-la-electronica-recibir-informacion-a-traves-del-gpio-de-la-raspberry-pi/#comments Thu, 05 Nov 2015 07:00:56 +0000 http://pitando.net/?p=865 Sigue leyendo Vuelve la electrónica: recibir información a través del GPIO de la Raspberry Pi ]]> Ha llegado el momento de retomar la electrónica en PItando: hoy vamos a ver cómo se pueden usar pulsadores para introducir información en la Raspberry Pi, a través de los terminales GPIO.

Para eso, en este artículo he ampliado el prototipo que nos viene acompañando en los últimos artículos de electrónica para demostrar conceptos, de la siguiente forma:

Prototipo con pulsador. <strong>Pincha en la imagen</strong> para ampliarla. Pincha, por favor. En serio: pincha.
Prototipo con pulsador. Pincha en la imagen para ampliarla. Pincha, por favor. En serio: pincha.

Como ves, he movido el prototipo conocido a la parte derecha de la placa, y en la izquierda he montado el pulsador. Necesitarás:

  • Una Raspberry Pi
  • Un kit de prototipado (placa de expansión, placa de prototipado o breadboard y el cable de conexión plano)
  • Un diodo emisor de luz, o LED
  • Una resistencia de 330 Ω y otra de 10 kΩ
  • Tres cables de prototipado, cortos
  • Un pulsador

Si tienes un kit como el que uso en PItando, lo tendrás todo.

A continuación viene un vídeo en el que explicaré el montaje y lo podremos ver en acción, tanto mediante Python como mediante ScratchGPIO. En el resto del artículo está toda la información de interés y, como casi siempre, ejercicios al final 🙂

GPIO como entrada

Los terminales (o pines) GPIO se pueden configurar como entrada, de ahí la I en GPIO: input. Cuando se configura un pin GPIO como entrada, la Raspberry Pi será capaz de distinguir dos niveles de tensión en dicho puerto: 3,3 V ó 0 V. Así pues, si queremos actuar sobre el microprocesador podemos escoger entre dos opciones:

  • Que lo significativo sea que en el pin se ponga el nivel de tensión alto (3,3 V)
  • Que lo significativo sea que en el pin se ponga el nivel bajo (0V)

Una par de cosas muy importantes: los terminales GPIO usan los voltajes de referencia de la Raspberry Pi como señales de entrada, es decir: 3,3 V y 0 V. Usar un nivel de 5 V a la entrada GPIO de la Raspberry Pi estropearía el microprocesador.

El prototipo

En el montaje de hoy quiero hacer que la Raspberry Pi detecte una señal externa, de tal forma que cuando apriete el pulsador, un pin GPIO se ponga a 0 V. Por lo tanto, en reposo, el puerto debe estar a 3,3 V. Esto lo conseguiré conectando el puerto GPIO que escogeré a la línea de alimentación de 3,3 V de la placa de protipado, pero no directamente, sino a través de una resistencia de 10 kΩ.

Si observáis el pulsador veréis que tiene cuatro patas, dos en una cara y otras dos en otra cara opuesta. Las patas de cada una de las caras son las que se conectan entre sí al pulsar el botón, y las patas opuestas de cara una de las caras están conectadas permanentemente.

Colocaré el pulsador conectado directamente entre el terminal GPIO de entrada y la línea de tierra de la placa de prototipado, de tal forma que cuando pulse el botón, habrá una conexión directa entre la línea de 0 V y el pin GPIO. En reposo (sin pulsarlo) y no habiendo conexión a 0 V, el pin estará a 3,3 V. Como discutía antes, para que funcione debo usar una pareja de patas ubicadas en una misma cara, que son las que hacen la función de pulsador.

Esquema del montaje con el pulsador. La fuente de alimentación de 3,3 V representa la línea de alimentación de la placa de prototipado.
Esquema del montaje con el pulsador. La flecha con valor de tensión de 3,3 V representa la línea de alimentación de la placa de prototipado.

En el otro lado de la placa voy a repetir el montaje que sirve para encender un LED a través de otro pin GPIO configurado como salida. Es el mismo prototipo que venimos usando en otros artículos, pero en el otro lado de la placa.

Lo que viene a continuación son los programas de control que, ante pulsaciones en el botón, enciendan durante un tiempo un LED.

Programas de control

Los vimos por partida doble y en acción en la segunda parte del vídeo; en esta sección resumo los detalles del código.

Python

Aquí tenéis el código en Python, en el que aprovecho para aplicar parte del artículo de manejo de excepciones y control de errores.

#!/usr/bin/python3

import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(12, GPIO.OUT)
GPIO.setup(6, GPIO.IN)

try:
    while True:
        if (not GPIO.input(6)):
            print ("LED encendido")
            GPIO.output(12,GPIO.HIGH)
            time.sleep(1)
            print ("LED apagado")
            GPIO.output(12, GPIO.LOW)
finally:
    GPIO.output(12, GPIO.LOW) # por seguridad
    print ("Haciendo limpieza")
    GPIO.cleanup()
    print ("Hasta luego")

Vamos a verlo en detalle:

  • En primer lugar hay que importar las librerías que nos harán falta para programar el comportamiento deseado: RPi.GPIO  bajo el alias GPIO, y time .
  • Se configura la numeración de la placa en modo BCM (Broadcom).
  • Se elimina la salida por consola de los avisos con GPIO.setwarnings(False)
  • Configuro el pin GPIO 12 como salida, y el GPIO 6 como entrada. Este punto es crucial, no nos podemos confundir ya que si usásemos el terminal del pulsador como salida en lugar de como entrada, podríamos dañar el microprocesador si éste sacase un nivel alto de tensión y usásemos el pulsador: en ese caso lo estaríamos poniendo a masa con una intensidad de corriente muy alta.
  • El funcionamiento del programa se lleva a cabo en un bucle infinito que comprueba el valor del pin GPIO 6:
    • GPIO.input(6) devuelve True si el nivel de tensión es alto, cercano a 3,3 V, en el puerto número 6; y False si es bajo, cercano a 0 V.
    • Por lo tanto, la sentencia if (not GPIO.input(6) ) valdrá True cuando accionemos el pulsador, poniendo un valor bajo, cercano a 0 V en la entrada número 6 en numeración BCM.
    • Si esto ocurre, encendemos el LED durante 1 segundo para después apagarlo.
  • El bucle infinito está controlado por un bloque de control de excepciones, cuya intención es capturar la señal de interrupción que el sistema operativo envía cuando pulsamos CTRL + C. En el bloque finally  apagamos el LED (por si estuviera encendido), liberamos los recursos del sistema de control de GPIO y terminamos.

Scratch

Y aquí tenéis el código Scratch.

Programa en Scratch para encender el LED durante 1 segundo usando el pulsador.
Programa en Scratch para encender el LED durante 1 segundo usando el pulsador.

Como veis, los pasos son los mismos, salvo por el hecho de que podemos omitir la configuración de los puertos, que corre a cargo de ScratchGPIO, y además podemos parar el programa con el botón con forma de stop, como os enseñaba en el vídeo.

Lo único malo es que ScratchGPIO no usa la notación BCM para los terminales GPIO, por lo que tendremos que usar la numeración secuencial con ayuda de esta imagen (“la chuleta”):

Conexiones GPIO de la Raspberry Pi 2, modelo B, con esquema BCM. La imagen es de www.raspberrypi-spy.co.uk
Conexiones GPIO de la Raspberry Pi 2, modelo B, con esquema BCM. La imagen es de www.raspberrypi-spy.co.uk

Ejercicios

Tomando como base los programas de este artículo, consigue variar su funcionamiento de tal forma que pulsar el botón cambie su estado. Es decir, una pulsación lo enciende, otra lo apaga; si dejamos el dedo oprimiendo el pulsador, el LED parpadea cambiando de estado cada segundo. Como en este vídeo:

Consideraciones para Python: puedes usar un bucle infinito en Python (while True:), y terminar el programa usando la combinación de teclas CTRL + C (pulsar a la vez las teclas Control y C).

Lo mejor, en cualquier caso, sería que usases un bloque try: ... finally: ... como los que vimos en el artículo de excepciones, para hacer cosas como liberar recursos y apagar el LED antes de finalizar el programa.


Espero que haya sido interesante. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

]]>
http://pitando.net/2015/11/05/vuelve-la-electronica-recibir-informacion-a-traves-del-gpio-de-la-raspberry-pi/feed/ 6 865
Excepciones en Python I: Control básico de excepciones http://pitando.net/2015/10/29/excepciones-en-python-i-control-basico-de-excepciones/ http://pitando.net/2015/10/29/excepciones-en-python-i-control-basico-de-excepciones/#comments Thu, 29 Oct 2015 09:00:25 +0000 http://pitando.net/?p=921 Sigue leyendo Excepciones en Python I: Control básico de excepciones ]]> Hay situaciones en las que un programa se comporta de manera inesperada, incorrecta o, directamente interrumpe su ejecución de forma incontrolada. Tratar esas situaciones es el día a día de todos los que jugamos, profesional o personalmente, con la programación.

Muy por delante de saber escribir programas en un lenguaje de programación, el primer paso para convertirte en un buen programador es pensar que las cosas fallan y que hay que hacer todo lo posible para controlarlas si eso ocurre. La experiencia me ha demostrado que, si alguien se considera un buen programador, de todo el tiempo que invierta en un programa la mayoría se dedicará en tratar situaciones inesperadas para que el programa funcione bien en la mayoría de las circunstancias. Este concepto es esencial: un programa que funciona bien, funciona bien incluso cuando algo falla. Es más: debe hacerlo. 

En este artículo vamos a iniciarnos en el control de este tipo de situaciones cuando programamos con el lenguaje Python, de una forma un tanto básica pero que será de mucho provecho en los artículos que vienen a continuación en PItando.

En Python se distinguen dos tipos de problemas: errores y excepciones en el funcionamiento normal de un programa.

Errores

La documentación de Python define los errores como aquellos fallos humanos que hacen que un programa no cumpla su función. Ya sea porque el programa no funciona en absoluto (ni por un momento), o bien porque funciona… mal. Cuando me refiero a ellos como humanos es porque se producen durante el diseño y la construcción del programa, no es algo que entre dentro de la idea original de lo que debe hacer. Destacan, por ser los más obvios, los errores de sintaxis.

Por ejemplo, el siguiente código tiene un error sintáctico que lo hace inviable:

cadenas = ["piedra", "palo", "hoja"]
for cadena in cadenas print (cadena)

El error está en que falta un “:” entre el conjunto a recorrer por el bucle y la sentencia a repetir. La sentencia errónea si quiera se ejecuta, ni por un momento, y el programa termina porque no tiene sentido continuar.

Por otro lado, el siguiente programa tiene un error, también de sintaxis, que hace que el programa no haga lo que se desea (pruébalo en IDLE):

cadenas = ["palo", "piedra", "hoja"]
for cadena in cadenas:
    print(cadena)
    print("Se han terminado las cadenas")

Este ejemplo es muy sencillo. Obviamente y con sólo dar un vistazo al código, está claro que el programador quería recorrer las palabras de una lista, imprimirlas todas y terminar el ejercicio con un mensaje que indicase al usuario que había terminado. Sin embargo, un error de sintaxis como el de este caso, que es de sangrado (poner un tabulador donde no es), hace que el programa funcione, pero mal. Parece un ejemplo absurdo, pero ilustra bien problemas más complejos, como los que pueden surgir al ejecutar por primera vez un programa de más de 100 líneas, con bucles anidados y condiciones complejas.

Tener errores de sintaxis en el código puede tener dos efectos: que la escritura de un programa sea algo tedioso porque fallamos con la sintaxis y tenemos que editarlo varias veces antes de conseguir ejecutarlo, o que se produzcan comportamientos no esperados o erráticos, como en el segundo ejemplo. Evitar errores como el primero (que hace que el programa no se ejecute en absoluto) es algo que se consigue con la práctica, a base de conocer muy de cerca la sintaxis de Python. Evitar errores como el que vimos de sangrado, también. Sin embargo, como el programa va a ejecutarse (mal) a fin de cuentas, no siempre se pueden ver todos los errores antes de ejecutar el programa. Para evitar sorpresas eso es extremadamente importante probar bien el programa antes de darlo por terminado.

El apartado de probar los programas es tan amplio que ocupará varios artículos de este blog en lo sucesivo. Quédate con el mensaje de momento, y en lo sucesivo iré dando claves en ese proceso, medida que lo que hagamos en PItando nos lo vaya pidiendo.

Por suerte, la sintaxis de Python es sencilla, con lo que es fácil conseguir dominarla pronto.

Excepciones. Cómo mantenerlas bajo control.

Las excepciones son harina de otro costal. Son comportamientos anómalos que se producen porque las cosas fallan. Así de simple: todo falla. Porque un fichero de texto está corrupto. Porque el disco duro tiene un sector defectuoso. Porque el usuario interrumpe de forma abrupta la ejecución del programa. Porque el usuario introduce una letra cuando tu programa está esperando un número. Porque hay un cable flojo en un prototipo electrónico. Tu programa puede estar perfectamente escrito, y que algo pase: porque las cosas pasan, y si algo puede pasar, en algún momento ten por seguro que pasará.

Una vez asumido que, hagas lo que hagas, siempre llegará un momento en el que algo falle, llega el momento de ver cómo podemos actuar si eso ocurre. Como en la vida real, cuando algo falla está en nuestra responsabilidad evitar que las cosas se salgan de madre, y para eso lo que tenemos que hacer es controlar estas situaciones cuando se producen, teniendo las excepciones bajo control.

Vamos a hacer unas pruebas y explicar lo básico para poder empezar a aplicar estas técnicas de manejo de excepciones en nuestros programas, de ahora en adelante.

Como primer ejemplo, vamos a ver qué ocurre cuando itentamos dividir una letra por un número diferente de cero:

"C" / 3

 El resultado que verás es el siguiente:

Nuestra primera excepción
Nuestra primera excepción

Lo que nos dice el intérprete de Python es, básicamente, que la combinación de operandos que hemos intentado usar con el operador “división” no está soportado. En detalle, lo que ocurre es que el operando de la división intenta dividir los dos operandos, pero al recibir una letra no puede ofrecer un resultado correcto. Por ello, termina elevando (o lanzando) una excepción de tipo TypeError directamente al intérprete: lo puedes ver al inicio de la última línea de texto rojo. Toda vez que el intérprete de Python reciba una excepción va a detener el programa, es decir: no va a ejecutar nada que esté escrito después de la línea que provoca la excepción.

Controlar las excepciones significa dos cosas: (1) hacer algo para que el intérprete no reciba una excepción, y (2): hacer que el programa reaccione a la excepción de forma coherente. Es decir, si puede continuar, que continúe. Si no puede continuar, que termine su ejecución de forma ordenada: cerrando los ficheros que haya abiertos, emitiendo al sistema operativo códigos de salida en consecuencia, informando al usuario del problema, etcétera.

Para controlar las excepciones, Python nos ofrece la sentencia try... except... else... finally; parece compleja pero no lo es tanto, es más: es de las más intuitivas. Vamos a verla en detalle con un ejemplo.

try:
	res = x / y
except (TypeError):
	print ("Se ha producido una excepción")
else:
	print ("el resultado es ", res)
finally:
	print ("Esta sentencia se ejecuta en cualquier caso")

 El código del recuadro superior puede leerse de la siguiente forma:

Intenta:
    dividir x entre y
Para las excepciones de tipo "TypeError":
    imprime un mensaje
Si no se ha producido excepción alguna:
    imprime el resultado
En cualquier caso:
    imprime otro mensaje

Prueba a ejecutar el código de arriba con y = 3  y asignando a x valores arbitrarios, como True, 3, "y". Verás que, o bien se imprime el mensaje “Se ha producido una excepción” y “Esta sentencia se ejecuta en cualquier caso”, o bien se imprime un número resultado de la división y “Esta sentencia se ejecuta en cualquier caso”.

Resultado en caso de excepción.
Resultado en caso de excepción.
Funcionamiento del ejemplo en el caso en el que no hay ninguna excepción
Funcionamiento del ejemplo en el caso en el que no hay ninguna excepción

Ahora prueba a asignar x = 3 e y = 0:

Funcionamiento del ejemplo en el caso en el que hay una excepción no controlada
Funcionamiento del ejemplo en el caso en el que hay una excepción no controlada

Como ves, el código en el bloquefinally siempre se ejecuta: vaya el resultado bien con el flujo de ejecución progresando por la salida else , se capture una excepción mediante except , o se produzca algo totalmente inesperado y no capturado. Un hecho especialmente significativo es que finally  se ejecuta antes de que se eleve la excepción: el intérprete percibe la excepción sólo cuando el bloque contenido bajo finally  ha terminado. Así pues, el valor clave de finally  reside en que, pase lo que pase, podremos realizar tareas de limpieza y seguridad como podría ser intentar cerrar el fichero, guardar datos del usuario…

Vamos ahora a terminar el ejemplo capturando la excepción recién descubierta, que como se ve en el mensaje de la última imagen, es ZeroDivisionError: prueba el código siguiente con los valores que quieras en las dos variables.

try:
	res= x / y
except (TypeError):
	print ("Se ha producido una excepción")
except (ZeroDivisionError):
	print ("No se puede dividir entre cero en Python")
else:
	print ("el resultado es ", res)
finally:
	print ("Esta sentencia se ejecuta en cualquier caso")

El programa superior también se puede escribir de una forma más compacta aunque, en caso de error, mostrará menos información al usuario ya que no distingue el tipo de excepción en el mensaje:

try:
	res= x / y
except (TypeError, ZeroDivisionError):
	print ("Excepción")
else:
	print ("el resultado es ", res)
finally:
	print ("Esta sentencia se ejecuta en cualquier caso")

Puedes volverlo a probar si no defines una de las dos variables (en lugar de x/y, escribe t/y y ejecútalo sin asignar valor a t), tratar de identificar el error que se emitirá y escribir el bloque de control.

Conclusiones

Con lo que hemos visto en este artículo podemos coincidir en que es importante:

  1. Escribir bajo try el código que es susceptible de provocar una excepción. Este tipo de código es muy amplio y normalmente se identifica consultando la documentación de una función escribiendo help(funcion), aunque tristemente la documentación de Python disponible a través de IDLE está en inglés. Ejemplos fácilmente identificables hasta ahora son los siguientes:
    1. Operaciones aritméticas sobre variables
    2. Operaciones con ficheros (apertura, escritura, lectura)
    3. Que el usuario pulse Control y C, a la vez, cuando ejecutamos un programa por línea de comandos.
  2. Las excepciones se capturan en bloques except
    1. Si el tratamiento de varios tipos de excepciones es el mismo, se pueden pasar todas al mismo bloque: except(TypeError, KeyboardInterrupt)
    2. Si no, se pone uno detrás de otro:
      try:
          ...
      except (TypeError):
          # Gestionar tipo incorrecto
          ...
      except (KeyboardInterrupt):
          # Gestionar interrrupción de usuario
          ...
      
  3. Todo el código que dependa del correcto resultado del bloque bajo try, lo escribimos bajo else. De esta forma, sólo ejecutará cuando no se produzca excepción alguna. Es una forma de escribir buen código Python, ya que de esa forma no capturaremos ninguna excepción que no nos corresponda tratar a nosotros. En otros artículos de excepciones y control de las mismas, más avanzados, incidiré en este aspecto; de momento piensa que, igual que en la vida real: cada uno es responsable de gestionar los problemas que él mismo se arriesga a provocar, pero no arreglar los de los demás 😀
  4. Las sentencias de limpieza y liberación de recursos van dentro de finally: ya que siempre se ejecuta, es el mejor lugar para hacer cosas como, por ejemplo, liberar memoria, cerrar ficheros o dejar el sistema de GPIO en un estado de reposo con la función GPIO.cleanup(). finallyes muy valioso, y mi recomendación es que siempre escribas un bloque finally.
Ejemplo de la ayuda integrada en IDLE. Como ves, está en inglés, pero si buscas la palabra "Raise", que significa "eleva" (elevar una excepción), es fácil localizar la información de interés.
Ejemplo de la ayuda integrada en IDLE. Como ves, está en inglés, pero si buscas la palabra “Raise”, que significa “eleva” (elevar una excepción), es fácil localizar la información de interés.

Espero que haya sido interesante. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

]]>
http://pitando.net/2015/10/29/excepciones-en-python-i-control-basico-de-excepciones/feed/ 1 921
Episodio 7 – Proyecto global “Weather Station For Schools”. Especial “cómo hago PItando” http://pitando.net/2015/10/29/episodio-7-proyecto-global-weather-station-for-schools-especial-como-hago-pitando/ Thu, 29 Oct 2015 04:35:18 +0000 http://pitando.net/?p=906 Sigue leyendo Episodio 7 – Proyecto global “Weather Station For Schools”. Especial “cómo hago PItando” ]]> En este episodio os voy a contar un proyecto interesantísimo llamado “Weather Station For Schools”, Estación meteorológica para escuelas, que organiza un experimento meteorológico a lo largo de 1.000 institutos de todo el mundo para realizar mediciones y analítica de datos meteorológicos entre todos ellos. Las mediciones se realizarán con una estación meteorológica basada, cómo no, en la Raspberry Pi.

El proyecto está llevado por la Fundación Raspberry Pi y apoyado financiera y tecnológicamente por Oracle. Destaca el hecho de que un instituto español, más concretamente el Instituto de Educación Secundaria Cervantes de Madrid, va a participar en el proyecto.

Enlaces de interés:

En la segunda parte del programa hago un especial acerca de algún truquillo, más que una técnica en sí, y medios con los que hago PItando: blog, fotografías, vídeos y podcast. De esta parte del programa  os dejo una colección de enlaces por si os queréis descargar alguno de los programas y servicios que uso:

Y, de propina, un par de fotos del rincón de grabación (“el chiringuito“), sólo visibles en las notas del programa en el blog – http://pitando.net/2015/10/29/episodio-7-proyecto-global-weather-station-for-schools-especial-como-hago-pitando:

Fotografía del detalle de un prototipo, usando trípode y cámara con zoom óptico.
Fotografía del detalle de un prototipo, usando trípode y cámara con zoom óptico.
Rincón de grabación: dos trípodes con sus patas entrelazadas, con el móvil para grabar vídeo y el micrófono para grabar el sonido, todo en directo. Es fácil imaginar lo sencillo que es tirarlo todo con los cables del multímetro, o al manipular el prototipo.
Rincón de grabación: dos trípodes con sus patas entrelazadas, con el móvil para grabar vídeo y el micrófono para grabar el sonido, todo en directo. Es fácil imaginar lo sencillo que es tirarlo todo con los cables del multímetro, o al manipular el prototipo.

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.

]]>
906
Soluciones a los ejercicios de ficheros http://pitando.net/2015/10/22/soluciones-a-los-ejercicios-de-ficheros/ Thu, 22 Oct 2015 09:00:43 +0000 http://pitando.net/?p=834 Sigue leyendo Soluciones a los ejercicios de ficheros ]]> Hace tres semanas os proponía dos ejercicios en el artículo de trabajo con ficheros en Python. Decían así:

  1. Crea un programa ejecutable por línea de comandos que reciba el nombre de un fichero (ruta relativa al directorio actual) como parámetro, y que devuelva el número de caracteres total, y el número total de palabras.
    1. Sólo puedes abrir el fichero una vez.
    2. Sólo puedes hacer una pasada al fichero (no vale volver a la posición inicial usando la función seek 
    3. No está permitido leer el fichero con read  ni con readlines 
  2. Crea otro programa ejecutable por línea de comandos que reciba el nombre de un fichero (ruta relativa al directorio actual) y que produzca otro, de igual nombre pero con extensión .pitando, con el contenido del primero fichero pero en mayúsculas.
    1. Sólo puedes abrir el fichero original una vez.
    2. Sólo puedes hacer una pasada al fichero (no vale volver a la posición inicial usando la función seek 
    3. No está permitido leer el fichero con read  ni con readlines 

Vamos a ver las soluciones paso a paso.

Contador de caracteres y palabras de ficheros

En este ejercicio debemos contar las palabras de un fichero y el total de los caracteres, de una sola pasada y sin leerlo a memoria completamente de una sola vez.

Lo primero, vamos a ver qué significa “una palabra”. Una palabra es un conjunto de letras del alfabeto que están separadas de otro conjunto semejante mediante espacios, signos de puntuación o separadores de línea. Por simplificarlo un poco, para resolver este ejercicio entenderemos “palabra” como un conjunto de letras del alfabeto separado de otro mediante cualquier otra cosa.

Para contar palabras debemos encontrar el primer caracter de la misma (la primera letra), y detectar el fin de la palabra. Saber si un caracter es una letra del alfabeto lo podemos hacer mediante comparación, como en la siguiente función:

def alfabetico(caracter):
    # Función que devuelve True si el caracter pasado como
    # parámetro es alfabético, False en otro caso

    alfabeto = ['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']
    
    if caracter.lower() in alfabeto:
        return True
    else:
        return False

Para saber si estamos en una palabra, te propongo el siguiente código:

# Variable de trabajo: total de palabras
palabras = 0

# Indicador de si estamos dentro de una
# palabra, o no
en_palabra = False

# Supón por el momento que la variable linea contiene
# una línea de texto
for caracter in linea:
    if alfabetico(caracter):
        if not en_palabra:
            en_palabra = True
            palabras = palabras + 1
        else:
            en_palabra = False

En él, lo que hago es para cada línea es lo siguiente:

  • Examino la línea caracter por caracter.
    • Si el caracter es alfabético,
      • Si no estoy dentro de una palabra ya, es que he encontrado una de ellas.
        • Sumo una palabra
        • Indicador de palabra a true 
      • Si estoy dentro de una palabra no hago nada, puesto que ya la he encontrado y por lo tanto ya la he contado.
    • Si no es alfabético, no estoy dentro de una palabra
      • Indicador de palabra a false 

Te recomiendo que pongas a prueba este algoritmo con un papel y lápiz, si no lo entiendes a la primera. También lo puedes probar en IDLE

Lo único que hay que hacer ahora es contar los caracteres además de las palabras, e integrar esta solución en la lectura de un fichero. Haré la lectura por líneas:

# Supón que nombreFichero ya tiene un nombre de fichero
# válido
fichero = open(nombreFichero)

# Variables de trabajo: totales 
palabras = 0
caracteres = 0

# Indicador de si estamos dentro de una
# palabra, o no
en_palabra = False

# Analizamos línea a línea
for linea in fichero:
    
    for caracter in linea:
        caracteres = caracteres +1

        # Para contar palabras debemos contar el número de
        # veces que nos encontramos un grupo de caracteres
        # alfabéticos
        if alfabetico(caracter):
            if not en_palabra:
                en_palabra = True
                palabras = palabras + 1
        else:
            en_palabra = False

    #Para la siguiente línea
    en_palabra = False

# Cerrar el fichero 
fichero.close()

Observa dónde he puesto el recuento de caracteres. Fíjate también que, antes de avanzar a la siguiente línea, reinicializo la variable que indica si estoy en una palabra: en_palabra = False 

Por último, debemos empaquetar todo este programa en forma de programa ejecutable. Para eso recuerda que necesitamos la librería sys  para poder acceder a la línea de comandos, y para poder salir del programa entregando un resultado al sistema operativo.

Este es el código fuente del programa final, para Mac OS X (para los otros sistemas operativos sólo cambia la primera línea):

#!/Library/Frameworks/Python.framework/Versions/3.4/bin/python3

import sys

def alfabetico(caracter):
    # Función que devuelve True si el caracter pasado como
    # parámetro es alfabético, False en otro caso

    alfabeto = ['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']
    
    if caracter.lower() in alfabeto:
        return True
    else:
        return False


# Analizar la línea de comandos y abrir el fichero
if len(sys.argv) != 2:
    print('Uso:', sys.argv[0], '')
    exit(1)

nombreFichero = sys.argv[1]
fichero = open(nombreFichero)


# Variables de trabajo: totales 
palabras = 0
caracteres = 0

# Indicador de si estamos dentro de una
# palabra, o no
en_palabra = False

# Analizamos línea a línea
for linea in fichero:
    
    for caracter in linea:
        caracteres = caracteres +1

        # Para contar palabras debemos contar el número de
        # veces que nos encontramos un grupo de caracteres
        # alfabéticos
        if alfabetico(caracter):
            if not en_palabra:
                en_palabra = True
                palabras = palabras + 1
        else:
            en_palabra = False

    #Para la siguiente línea
    en_palabra = False

# Cerrar el fichero 
fichero.close()

print ('Palabras:', palabras, '
Caracteres:', caracteres)

exit(0)

Pasar el contenido de ficheros a mayúsculas

Este ejercicio es mucho más fácil de lo que parece. Lo primero que tenemos que hacer, con respecto a lo que ya sabemos, es construir el nombre del fichero de salida encontrando el último punto. Para eso existe una función extremadamente útil que, aplicada a una cadena de texto, encuentra el primer índice de una subcadena empezando por el final: es la función rfind. Una vez encontrado el índice donde se encuentra el punto, usaremos una notación de Python llamada slicing (“loncheado”) que, aplicada sobre una cadena, sirve para trocearla. En su forma más básica, que de momento es la que nos interesa, funciona de la siguiente forma:

cadena[inicio:fin] # selecciona la subcadena formada por los caracteres que se
                   # encuentran entre la posición inicio (incluida) y la fin,
                   # no incluida

Por ejemplo, "hola caracola"[2:12] dará como resultado la cadena “la caracol”.

Poniendo todas estas ideas juntas, para obtener el nombre del fichero de salida tendremos el siguiente código:

# Construir el nombre de fichero de salida
# Primero, encontramos el último punto
punto = nombreFichero.rfind('.')

# Copiamos los caracteres hasta el punto, más la extensión '.pitando',
# a una nueva variable.
nombreFicheroSalida = nombreFichero[0:punto] + '.pitando'

El proceso de los ficheros es muy, muy fácil. Sólo tenemos que abrir los dos, y escribir en el segundo una versión pasada a mayúsculas de cada línea del primero. Para pasar a mayúsculas una cadena usaremos la función upper, que es complementaria a la ya conocida lower.

# Abrimos los dos ficheros
fichero = open(nombreFichero)
ficheroSalida = open(nombreFicheroSalida, 'x')

# Vamos línea a línea usando la función que ya conocemos
for linea in fichero:
    ficheroSalida.write(linea.upper())

# Cerramos los dos ficheros
fichero.close()
ficheroSalida.close()

Juntando todos estos pequeños exctractos es fácil componer la solución final como a continuación:

#!/Library/Frameworks/Python.framework/Versions/3.4/bin/python3

import sys

# Analizar la línea de comandos
if len(sys.argv) != 2:
    print('Uso:', sys.argv[0], '')
    exit(1)

nombreFichero = sys.argv[1]

# Construir el nombre de fichero de salida
# Primero, encontramos el último punto
punto = nombreFichero.rfind('.')

# Copiamos los caracteres hasta el punto, más la extensión '.pitando',
# a una nueva variable.
nombreFicheroSalida = nombreFichero[0:punto] + '.pitando'

# Abrimos los dos ficheros
fichero = open(nombreFichero)
ficheroSalida = open(nombreFicheroSalida, 'x')

# Vamos línea a línea usando la función que ya conocemos
for linea in fichero:
    ficheroSalida.write( linea.upper())

# Cerramos los dos ficheros
fichero.close()
ficheroSalida.close()

print("Resultado escrito en " + nombreFicheroSalida)
exit(0)

Y esto es todo, que no es poco.

Recuerda que, como siempre, puedes dejarme comentarios o dudas en los comentarios de este artículo, o a través del formulario de contacto o de la dirección de correo que allí se encuentra.

]]>
834
Raspbian Jessie – notas http://pitando.net/2015/10/17/raspbian-jessie-notas/ http://pitando.net/2015/10/17/raspbian-jessie-notas/#comments Sat, 17 Oct 2015 09:45:37 +0000 http://pitando.net/?p=861 Sigue leyendo Raspbian Jessie – notas ]]> Si instaláis Raspbian Jessie mediante Noobs, tenéis que tener en cuenta que el proceso de configuración cambia un poco. En lugar de abrirse el programa de configuración una vez se reinicia el sistema por primera vez, iréis al escritorio. Allí podréis escoger si abrir un terminal y lanzar el conocido programa de configuración con sudo raspi-config, para el que podéis seguir las instrucciones detalladas de este post, o bien configurar la Raspberry Pi visualmente con un panel de control muy sencillito (aunque en inglés). El panel de control está en Menú (Menu) → Preferencias (Preferences) → Raspberry Pi Configuration

RaspiConf-1RaspiConf-2

Os interesan:

  • La opción de Disable dentro de Overscan en la pestaña System: si veis áreas negras en vuestra pantalla o televisor.
  • Las correspondientes a la zona horaria (Timezone), idioma global (Locale) y teclado (Keyboard) en la pestaña Localisation.

Las opciones a configurar son muy fáciles de hacer si recordáis lo que configurábamos con la primera puesta en marcha (hay una lista bien detallada), pero no dudéis en poneros en contacto conmigo mediante los comentarios, el formulario de contacto o el correo electrónico que allí tenéis

]]>
http://pitando.net/2015/10/17/raspbian-jessie-notas/feed/ 1 861
Nuestro primer videojuego con Scratch (3): el gato encuentra al ratón http://pitando.net/2015/10/15/nuestro-primer-videojuego-con-scratch-3-el-gato-encuentra-al-raton/ Thu, 15 Oct 2015 09:00:04 +0000 http://pitando.net/?p=837 Sigue leyendo Nuestro primer videojuego con Scratch (3): el gato encuentra al ratón ]]> El mes pasado comenzaba una serie en PItando en la que trataba de crear con vosotros un videojuego muy básico en Sratch. Constaba de, a priori, cuatro partes:

  1. Mover al gato en las cuatro direcciones y hacer que rebote en los bordes del escenario. Conseguido en la primera entrega.
  2. Programar la lógica de rebote para cuando el gato se encuentre con una pared roja. Conseguido en la entrega anterior.
  3. Programar la lógica de “gato encuentra a ratón”.
  4. Proponer un esquema de puntuación para poder competir con nuestros amigos.

En esta entrada de hoy, la tercera de la serie, vamos a ir un paso más allá y vamos a programar sobre un escenario básico la detección del ratón, además de resolver los ejercicios que os planteaba en la última entrada.

Si te animas, que espero que sí, sigue leyendo 🙂

Recuperar el proyecto del ordenador

Como venimos haciendo y para dar cabida a todos los lectores, tengáis o no una Raspberry Pi, haremos este ejercicio sobre el navegador web. Abre la página del editor on-line de Scratch y sube el fichero que guardábamos la semana pasada. Si no lo tienes, ¿qué mejor momento el de ahora para hacer las dos primeras partes del ejercicio? 😉

Para ello, ve al menú Archivo y pulsa la opción Subir de tu computadora.

Opción para subir un proyecto al editor Web de Scratch
Opción para subir un proyecto al editor Web de Scratch

Localiza el fichero y confirma la selección, tras lo cual el editor te pedirá confirmación en inglés (Replace contents of the current project?). Confirma la carga con el botón OK y, si todo ha ido bien, verás el editor cargado con el estado en el que lo dejamos en la ocasión pasada.

Diseñar el ratón

Para detectar un ratón, primero hay que disponer de un ratón en el proyecto. Como lamentablemente, la librería de objetos de Scratch no tiene ratones, vamos a aprovechar la ocasión para diseñar uno.

En Scratch el ratón va a ser un objeto o sprite y, como tal, necesita un disfraz. Todo esto lo aprendíamos en la primera de las apariciones de Scratch en este blog que, si no recuerdas, te invito a repasar.

Para empezar un objeto, hay que usar la opción Dibujar un objeto del panel de control de objetos:

Opción para comenzar a dibujar un nuevo objeto
Opción para comenzar a dibujar un nuevo objeto

Al hacer click en ese pincel, se nos abrirá un editor donde intentaremos dibujar un ratón a mano alzada con el pincel:

Un ratón bien feo.
Un ratón bien feo.

Si os fijáis en el escenario, veréis que el ratón ha aparecido en él y, en comparación con el gato naranja protagonista de nuestro experimento, es enorme.

Así visto, el ratón surgido de nuestras pesadillas podría comerse al gato de un bocado.
Así visto, el ratón surgido de nuestras pesadillas podría comerse al gato de un bocado.

Para encoger al ratón:

  • Lo que haremos será usar el botón Convertir a vector que hay en la esquina inferior derecha de la pantalla.
  • Una vez hecho esto, seleccionamos el ratón haciendo click y arrastrando el ratón de forma que describa la diagonal de un rectángulo que abarque al ratón entero. Si lo hacemos bien, veremos que el rectángulo desaparece y es sustituido por otro rectángulo de trazos más gruesos y con varios puntos de interacción:
Ratón seleccionado
Ratón seleccionado
  • Una vez seleccionado, hacemos click en una de las esquinas, y arrastramos de tal forma que el ratón merme a unas proporciones que sean coherentes con el tamaño del gato:
Proporciones mejoradas
Proporciones mejoradas

No hay que salvar explícitamente el ratón como un objeto, sino que podemos pasar directamente a la pestaña de Programas para programar el resto del ejercicio.

Antes, colocamos el ratón en la esquina inferior derecha del escenario, simplemente arrastrándolo.

Programar la detección del ratón por parte del gato

No hay más que comprobarlo de una forma muy parecida a la detección de paredes:

Detección del ratón
Detección del ratón

En este caso, la novedad estriba en el bloque decir, que se encuentra en el grupo de controles de apariencia.

Si pruebas el resultado, verás que el gato dice “He ganado” al encontrar al ratón, y en 5 segundos vuelve a la posición de partida.

Resultado final
Resultado final

En el siguiente capítulo complicaré un poco esta solución para que el programa se detenga y pregunte al usuario si quiere repetir el desafío. Además, nos queda por realizar el esquema de puntuaciones y convendrá tener un escenario algo más elaborado, con más paredes.

Recuerda guardar el proyecto en tu ordenador como ya vimos en otras ocasiones.

Soluciones a los ejercicios de la entrega anterior

Los ejercicios decían así:

Si experimentas lo suficiente verás que puedes hacer que una pared se sitúe en zonas “angostas” del Sprite. Por ejemplo, que un extremo de una pared se cuele entre las orejas o los bigotes de nuestro protagonista. También ocurre que, si dejas demasiado tiempo pulsada una misma tecla, el programa acaba por fallar 😉 . Se comporta mejor que cuando pusimos la solución de la variable, sí… pero no del todo bien, y te toca explicar por qué y encontrar una solución.

Ejercicio 1: reproduce esos comportamientos y ofrece una explicación a los mismos.

Ejercicio 2: propón y construye al menos dos soluciones a estos comportamientos.

Ejercicio 3: Si, al detectar una pared, paramos el gato en vez de hacer que rebote, el programa funciona mal. ¿Por qué?

En primer lugar, podemos situar esquinas de las paredes en “zonas angostas” del objeto porque éste se diseña sobre un fondo transparente, y la detección se basa en colores opacos. Así, el gato no tiene fondo realmente, aunque en la paleta de objetos lo parezca.

Para entender correctamente los siguientes razonamientos es esencial haber realizado el ejercicio por tu cuenta; no es una lectura fácil si no viene respaldada por la práctica.

Abusar de las teclas tiene un efecto perjudicial en el resultado final por dos razones:

  • Todos los bloques del programa se ejecutan al mismo tiempo: concurrentemente. Deliberadamente he programado un rebote de la misma igual al desplazamiento que provocamos con las teclas.  El rebote se compensa por una pulsación de una tecla si la variable parar se pone a valor 0, es decir: el gato avanza lo mismo rebotando que pulsando una (1) vez una flecha. Por lo tanto, si mantenemos pulsada la tecla, cualquier desequilibrio en el sistema de reparto de proceso por los distintos hilos que maneja Scratch nos acerca a una situación inestable.
  • Además, deliberadamente también, el programa está hecho de tal forma que tan pronto comienza el desplazamiento del rebote, el gato deja de tocar la pared y automáticamente el valor de parar pasa de ser 1 (tocando pared) a 0, lo cual permite al resto de los bloques interpretar las pulsaciones del teclado.

Podemos modificar el bloque de rebote de, al menos, dos formas:

  • Evitar que el resto de los programas interpreten las teclas durante un tiempo suficiente para que el rebote se ejecute (introduciríamos una espera de 1 segundo o más antes de poner parar a 0)
  • Movernos más distancia en el rebote (por ejemplo, 15 puntos) en el mismo tiempo (1 segundo), lo cual haría que el gato rebotase el triple de distancia que la que avanza con el teclado, y a mayor velocidad. Eso nos sacaría del “umbral de inestabilidad” que hemos estado experimentando.

Por último, si en vez de rebotar hacemos que el gato deje de moverse al tocar una pared, el programa funcionará mal porque, salvo contadísimas excepciones, la variable parar nunca podrá tomar el valor 0. Estaremos siempre en una situación de contacto con la pared en la que, tal y como hemos programado el juego, impedimos que el programa procese las teclas para que el gato pueda alejarse de la misma.


Espero que haya sido interesante. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

]]>
837
Episodio 6 – Especial menciones y Raspbian Jessie http://pitando.net/2015/10/14/episodio-6-especial-menciones-y-raspbian-jessie/ http://pitando.net/2015/10/14/episodio-6-especial-menciones-y-raspbian-jessie/#comments Wed, 14 Oct 2015 20:50:10 +0000 http://pitando.net/?p=855 Sigue leyendo Episodio 6 – Especial menciones y Raspbian Jessie ]]> En este episodio contesto las amables revisiones y comentarios que tenía acumulados y desatendidos (mea culpa y pido disculpas por ello) como los de LuisDelValle y Argades en iTunes, y en iVoox lechuzo y el podcast Diogenes Digital (RSSiTunesiVooxblog), tanto por escrito como en su último episodio al que acabo de acceder nada más grabar este audio (¡una cosa menos en la montaña de deudas y cosas por hacer, oiga!).

Planteo, además, mis peripecias actualizando la Raspberry a Raspbian Jessie, la nueva versión de Raspbian basada en Debian 8.

¿Qué ocurre con Jessie?

Lo que ocurre a grandes rasgos es que el proceso estándar de actualizar una distribución de Linux no lo hace bien en el caso de Raspbian. El proceso es el siguiente:

1) Actualizar el sistema actual con:

# sudo apt-get update
# sudo apt-get upgrade
# sudo apt-get dist-upgrade

3) Editar el archivo /etc/apt/sources.list :

# sudo nano /etc/apt/sources.list

3bis) Cambiar su contenido para que quede como sigue, de forma que apunte al nuevo repositorio:

deb http://mirrordirector.raspbian.org/raspbian jessie main firmware contrib non-free
#deb http://archive.raspberrypi.org/debian wheezy main

4) Volver a ejecutar los siguientes comandos:

# sudo apt-get update
# sudo apt-get upgrade
# sudo apt-get dist-upgrade

5) Reiniciar.

Lo que ocurre es que, como comento, se pierden los programas Wolfram y Mathematica, y aparecen unos menús que no vienen a cuento.

Además, perdemos novedades como entornos de programación en Java y una versión de LibreOffice.

Por todo esto, termino recomendando salvaguardar todo lo que hayáis modificado o creado dentro de la Raspberry Pi y realizar una instalación limpia con Noobs siguiendo las ya conocidas instrucciones: http://pitando.net/2015/06/18/puesta-en-marcha-raspberrypi/

Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones.

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.

]]>
http://pitando.net/2015/10/14/episodio-6-especial-menciones-y-raspbian-jessie/feed/ 1 855
Uso de un multímetro http://pitando.net/2015/10/08/uso-de-un-multimetro/ http://pitando.net/2015/10/08/uso-de-un-multimetro/#comments Thu, 08 Oct 2015 09:00:21 +0000 http://pitando.net/?p=820 Sigue leyendo Uso de un multímetro ]]> Como en cualquier caso en el que coqueteamos con la electricidad, hacer prototipos con la Raspberry Pi pone en riesgo el equipamiento que usamos. En el caso de este microordenador no estamos en riesgo nosotros, claro está, por los bajos valores de tensión (3,3 voltios ) y de corriente (algunos miliamperios). Sin embargo, los puertos GPIO de la Raspberry Pi no cuentan con protección eléctrica para picos de corriente o de tensión: una mala conexión puede dañar o estropear la Raspberry Pi.

Al mismo tiempo que aprenderemos a calcular los valores de tensión y corriente que harán funcionar correctamente nuestros prototipos, las leyes que lo rigen y demás, es muy conveniente aprender a usar instrumentos de medida que permitan comprobar que nuestro montaje se corresponde con el circuito que hemos ideado, o que estamos poniendo en práctica con el experimento de turno.

Este fin de semana he preparado un vídeo en el canal de YouTube de PItando en el que trato de explicaros cómo medir corriente y voltaje en escenarios de corriente contínua, resistencia, y usar el test de continuidad. Todo esto usando un multímetro (o también polímetro) muy asequible.

El resumen del vídeo, en sus conceptos más importantes, viene a continuación.

  • En este multímetro, para medir corrientes de más de 200 mA hay que hacerlo usando la entrada azul, y:
    • podemos realizar la medida por un máximo de 10 segundos,
    • y debemos dejar 15 minutos de “reposo” al multímetro entre cada dos medidas.
  • Para cualquier medida, hay que encender el multímetro sin tocar el circuito con las puntas de medida, y colocando el dial en la posición de mayor valor dentro de la magnitud a medir:
    • 2.000 kΩ para la resistencia.
    • 250 V para la tensión
    • 200 mA para la corriente si medimos con la entrada roja
    • 10 A para la corriente si medimos con la entrada azul.
  • Una vez midiendo con el dial situado en el máximo valor, iremos girando el dial posición a posición (en sentido descendente dentro de una misma magnitud) hasta que la escala del multímetro sea tal que la pantalla muestre valores coherentes.
  • El voltaje ha de medirse colocando el multímetro en paralelo al componente en el que queremos comprobar la caída de tensión.
  • La corriente ha de medirse colocando el multímetro en serie dentro de la rama en la cual queremos hacer la medida.
  • La resistencia se debe medir siempre con el circuito sin alimentación: en nuestro caso lo mejor es apagar y desenchufar la Raspberry Pi. Esto es porque, como discuto en el vídeo, para medir esta magnitud, el multímetro funciona como una fuente de alimentación con un voltaje de prueba.
  • La continuidad, al ser un test que trata de comprobar que dos puntos están conectados, debe hacerse también con el prototipo apagado. Raspberry Pi apagada y desenchufada.

Si no tienes multímetro y este te parece un buen momento para comprar uno, usando este enlace para comprar el Hama EM393B contribuirás, sin esfuerzo, a PItando ya que es un enlace de afiliado de Amazon. Como puedes consultar aquí, uso estos enlaces para conseguir subvencionar parte de los componentes, aparatos y equipos que me compro para hacer PItando; lo mejor de todo es que el precio es el mismo para ti: me ayudarás a hacer de PItando un blog más completo por el mero hecho de usar el enlace anterior.

Espero que os guste y os parezca interesante. Podéis dejarme cualquier comentario en esta misma entrada, en el vídeo en YouTube (a raíz del cual, si os gusta, podéis suscribiros al canal), o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

 

]]>
http://pitando.net/2015/10/08/uso-de-un-multimetro/feed/ 1 820
Ficheros de texto en Python http://pitando.net/2015/10/01/ficheros-de-texto-en-python/ http://pitando.net/2015/10/01/ficheros-de-texto-en-python/#comments Thu, 01 Oct 2015 09:00:29 +0000 http://pitando.net/?p=771 Sigue leyendo Ficheros de texto en Python ]]> Cuando se trabaja con un programa, lo más normal del mundo es que queramos guardar el estado del mismo entre dos sesiones de trabajo. Es decir, cuando escribimos un artículo o un documento, no lo escribimos del tirón para luego imprimirlo “y se acabó”, sino que lo guardamos en el disco duro para, en otro momento, continuar trabajando con él. O, simplemente, para enviarlo por correo electrónico, archivarlo…

El trabajo con ficheros es una de las técnicas fundamentales que necesitamos dominar para escribir programas útiles con cualquier lenguaje de programación. En este artículo de PItando os voy a transmitir con ejemplos la forma de hacerlo con Python 3, como siempre, a través de ejemplos interactivos que podemos ir haciendo para navegar por las explicaciones. En el apartado de ejercicios os plantearé programas ejecutables para el siguiente artículo de la serie.

Para este artículo, además, me centraré fundamentalmente en ficheros de texto plano dejando los formatos binarios para otra entrada. El objetivo es proporcionaros una referencia rápida dentro de PItando a la hora de tener que trabajar con ficheros (guardar, leer información o configuración) durante nuestros proyectos.

Introducción a los ficheros. Modos de apertura y cierre del fichero

Un fichero es, para Python, un objeto que se crea con una operación de apertura, y sobre el que se pueden ejecutar funciones de lectura, escritura y cierre. Refleja un fichero en el disco duro, y dependiendo del modo de apertura, podremos hacer unas cosas u otras, incluyendo la pérdida total de la información del mismo 😉 Por eso, es muy importante comprender bien las consideraciones que vienen a continuación, y después con los ejemplos.

Para poder trabajar a lo largo del artículo vamos a crear un fichero de texto manualmente, con el editor de texto del sistema operativo con el que estés trabajando. En mi caso lo voy a crear con nano porque estoy trabajando en Linux (valdría igual para el Mac OS X); si estás trabajando en Windows puedes utilizar el Bloc de Notas.

Escribiremos el siguiente texto:

Ésta es la línea 0.
Línea 1 por aquí.
Pera
Manzana
Plátano

Guárdalo con el nombre que tú quieras, en el directorio de tu elección. Yo lo guardaré como ejemplo.txt en ~/pitando , es decir, en el subdirectorio pitando dentro de mi directorio de usuario en Linux. En Windows lo podrías guardar, por ejemplo, en una carpeta llamada pitando dentro de tu directorio de instalación de Python (en mi caso, c:\prog\Python34, con lo que el directorio quedaría c:\prog\Python34\pitando).

Ahora abre IDLE (en la Raspberry Pi, es el icono Python 3 del menú de Programación), y sitúate en el directorio donde has guardado el fichero con las siguientes sentencias, escribiéndolas o copiándolas sobre IDLE, de una en una (no todas a la vez):

import os 
os.chdir('pitando')

El código de arriba funciona si estás en Mac o Linux (incluyendo la Raspberry Pi) y has creado el directorio pitando dentro de tu directorio personal, como decíamos antes. Si estás en Windows:

import os
os.chdir('c:\prog\Python34\pitando')

Una vez hecho esto, y si hemos grabado el fichero anterior con el nombre ejemplo.txt, prueba las siguientes sentencias (recuerda: una línea de cada vez):

fichero = open ('ejemplo.txt')
fichero.read()

El efecto que verás es que Python ha leído el fichero entero, sin ningún tipo de pudor, y lo ha transcrito en la pantalla traduciendo los retornos de carro por combinaciones '\n'.

Lectura completa de un fichero
Lectura completa de un fichero

Prueba a repetir la última de las sentencias, fichero.read(): lo que verás ahora es que no sale nada. ¿Qué ha ocurrido aquí?, ¿he perdido la información del fichero? No, lo que ocurre es que ya se ha avanzado hasta el final del fichero y no queda información por leer: el fichero se ha abierto de forma secuencial, se ha leído desde el principio hasta el final, y por esta vez no se puede hacer más.

Cierra el fichero con la sentencia de Python fichero.close() en IDLE, y comprueba si quieres en la consola que tu información sigue estando donde esperas, usando la orden cat en Linux o Mac OS X, o type en Windows.

cat en Mac OS X
cat en Mac OS X

Por supuesto, esto no es todo. Podrás haberte preguntado cosas como “¿Puedo leerlo poco a poco?, es decir, ¿qué hubiera ocurrido si el fichero ocupase un tamaño demasiado grande?, ¿puedo rebobinar el fichero de alguna forma, para volverlo a leer?, ¿qué pasa si quiero escribir?“. Vamos a tratar de responderlas todas, al menos a un nivel suficiente para que empieces a trabajar por tu cuenta.

Modos de apertura de ficheros

Lo primero que debes conocer es el modo de apertura del fichero, y escoger el que más te convenga. Este modo es el segundo parámetro de la función que ya vimos, y su modo por defecto, si no se especifica nada, es en modo lectura de texto. Siguiendo con el ejemplo, en el que abríamos el fichero con la sentencia fichero = open ('ejemplo.txt'), podríamos haber escrito fichero = open ('ejemplo.txt', 'r') o fichero = open ('ejemplo.txt', 'rt') para obtener exactamente los mismos resultados (read, o read text sería la traducción de los modos 'r' y 'rt' , respectivamente).

Puedes probarlo: el siguiente código tendrá exactamente el mismo efecto que el del ejemplo anterior (usando IDLE, copia una línea de cada vez):

fichero = open ('ejemplo.txt', 'rt')
fichero.read()
fichero.close()

En esta tabla que viene a continuación tienes todos los modos posibles que nos pueden interesar por el momento, con comentarios sobre su efecto.

Modo de apertura Efecto
'r', 'rt' Lectura en modo texto, en la codificación por defecto. Abre el fichero en la posición inicial para leerlo entero. Éste es el modo por defecto, es decir, si no se especifica ningún modo se abrirá en modo lectura y para texto.
'rb' Lectura en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Abre el fichero en la posición inicial para leerlo entero.
'r+' Lectura y escritura en modo texto, en la codificación por defecto. Abre el fichero en la posición inicial para leerlo entero, o para actualizarlo.
'w', 'wt' Escritura en modo texto, en la codificación por defecto. Crea un fichero vacío, borrando su contenido anterior si ya existía.
'wb' Escritura en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Crea un fichero vacío, borrando su contenido anterior si ya existía.
'x', 'xt' Modo de creación exclusiva en modo texto, en la codificación por defecto. Crea un fichero vacío, y si existe previamente emitirá un error (y no borrará el fichero previamente existente).
'xb' Modo de creación exclusiva en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Crea un fichero vacío, y si existe previamente emitirá un error (y no borrará el fichero previamente existente).
'a', 'at' Escritura en modo texto, en la codificación por defecto. Abre el fichero en la última posición, para añadirle contenido.
'ab' Escritura en modo binario, para ficheros que no contienen texto (ficheros de audio, de imagen,…). Abre el fichero en la última posición, para añadirle contenido.

Si quisiésemos crear un fichero en el disco duro, debemos abrirlo en modo de escritura, pero con mucho cuidado. Como hemos visto en la tabla, los modos 'w' y 'wb' crean un fichero vacío, y si existía un fichero con el mismo nombre, lo borrará en el proceso. Por eso, a no ser que nuestro fichero sea de trabajo o queramos borrar cualquier versión anterior, es mucho más seguro en general usar el modo 'x' en vez de 'w', ya que su funcionamiento es tal que si ya existía emitirá un error. De hecho, si escribes en este punto la sentencia fichero = open ('ejemplo.txt', 'x'), el sistema te devolverá el siguiente error, que indica precisamente que el fichero existe:

Traceback (most recent call last):
  File "", line 1, in 
    fichero = open ('ejemplo.txt', 'x')
FileExistsError: [Errno 17] File exists: 'ejemplo.txt

En la siguiente sección, que ya trata sobre operar con el contenido de un fichero a través de los distintos modos, probaremos también a destruir ficheros usando los modos derivados de  'w'.

Vamos a trabajar en cierto detalle con estos modos, pero teniendo en cuenta que en este artículo no experimentaremos con formatos binarios (quedan para otro día), centrándonos exclusivamente en ficheros de texto.

Cierre del fichero

Es extremadamente importante cerrar siempre un fichero después de trabajar con él con la sentencia que ya hemos experimentado:

fichero.close()

Si hemos estado leyendo un fichero, cerrarlo libera memoria del ordenador; si estábamos escribiendo en él, cerrar el fichero vuelca (o consolida) todos los cambios en el disco duro y libera la memoria que Python tenía reservada para el trabajo con él.

¿Qué es eso de volcar todos los cambios en el disco duro? ¿Acaso los ficheros no están ya en el disco duro? No mientras trabajas con ellos. Todos los programas informáticos que trabajan con fichero lo hacen en memoria: de forma autónoma (sin hacer tú nada) copian una sección pequeña del fichero, bien definida, a la memoria RAM. Después trabajan con ella, e igualmente de forma autónoma (transparente para ti) la escriben en el disco duro cuando la edición progresa a otras áreas del fichero. Cerrar el fichero fuerza a que todos los cambios pendientes de escribir al disco se escriban, acción que en inglés (y por lo tanto en muchos lenguajes) recibe el nombre de flush.

Trabajando con el contenido de un fichero

En este apartado vamos a ver todas las formas que tenemos de abrir, leer y escribir un fichero, usando cadenas de texto normales.

Distintas formas de leer el contenido de un fichero

Ya hemos visto que mediante la sentencia fichero.read() leemos todo el contenido del fichero, sea lo largo que sea. Existen otros métodos para leer un fichero de texto, que son los siguientes:

  • Lectura de un número preciso de caracteres: se usa para controlar la memoria que usa nuestro programa a la hora de leer un fichero, y se obtiene pasando como argumento a la función read  el número de bytes a leer.
    • Por ejemplo, si quisiésemos leer un fichero de 8 kB en 8 kB, usaríamos la sentencia fichero.read(8192) .
    • La función devuelve una cadena cuyo tamaño en bytes es igual o menor al valor del argumento. Si llega al final del fichero en una ejecución, puede devolver menos.
    • Si se estuviera en el final del fichero, al ejecutarla devolvería una cadena vacía, es decir, ''. Esto es lo que ocurrió en el ejemplo del apartado anterior.
    • El número de caracteres devuelto depende de la codificación de los mismos. Si la codificación es ASCII, coincide con el número de bytes, pero si es UTF de 16 bit, será la mitad.
  • Lectura de una línea: se trata de la función fichero.readline()  y se usa para obtener líneas, es decir, cadenas de texto separadas por retornos de carro (caracter '\n' ).
    • Por cada línea incluirá su retorno de carro.

El tipo devuelto por todas las funciones de lectura es str  siempre que el modo de apertura es de texto.

Vamos a ver unos ejemplos. Nuestro fichero de ejemplo, ejemplo.txt, tiene exactamente 64 bytes si se consulta el tamaño según el sistema operativo. Veamos el comportamiento de la función de lectura por bytes cuando le pasamos como argumento 45. Escribe lo siguiente en IDLE:

fichero = open ('ejemplo.txt')

bloque_1 = fichero.read(45);
bloque_2 = fichero.read(45);
bloque_3 = fichero.read(45);

bloque_1
len(bloque_1)

bloque_2
len(bloque_2)

bloque_3
len(bloque_3)

 Verás lo siguiente, en Windows:

Funcionamiento de la lectura por bloques en Python
Funcionamiento de la lectura por bloques en Python

En Windows la codificación por defecto para los ficheros de texto es tal que cada caracter ocupa 1 byte excepto algunos no visibles, como por ejemplo el retorno de carro. Si te fijas, la suma de todos los caracteres es 59, y no 64 como dice Windows. Python representa el caracter retorno de carro como '\n', y lo almacena en un único caracter, pero Windows realmente usa dos caracteres: retorno de carro (CR) y nueva línea (LF), de un byte cada uno. De ahí la diferencia de 5 caracteres en 5 líneas de texto. En Mac OS X ocurre exactamente lo mismo. En general, en todas las plataformas ocurrirá lo mismo si la codificación de caracteres del sistema es de 8 bits.

Por último, fíjate que el tercer bloque es una cadena vacía y contiene 0 caracteres. Es importante darse cuenta de esto porque esos dos hechos nos dan una condición de salida para un bucle (es decir, cosas que comprobar para salir de un bucle) para el caso en  que quisiéramos escribir un programa que procesase un fichero por bloques de caracteres.

Veamos ahora la lectura de líneas, tanto separadamente como en bucle: cierra para ello el fichero con fichero.close()  para después transcribir el siguiente código:

fichero = open ('ejemplo.txt')

fichero.readline();
fichero.readline();

for linea in fichero:
    print(linea, ' ** longitud:', len(linea));

 El resultado debería ser éste:

Lectura de ficheros por líneas
Lectura de ficheros por líneas

Las dos primeras líneas no deberían sorprendernos, pero la sentencia for  merece atención:

  • Los ficheros se ofrecen al programa, en parte, como listas de líneas, de tal forma que pueden recorrerse usando un bucle como en el programa, y no hay que preocuparse de cuándo se alcanza el final de fichero.
    • “en parte” ya que si escribimos fichero  en IDLE no se imprime la lista de las líneas del fichero, sino una cadena que describe al fichero: un descriptor.
    • Si queremos obtener todas las lineas del fichero como una lista, podemos ejecutar fichero.readlines() o list(fichero)
Representación de un descriptor de un fichero.
Representación de un descriptor de un fichero.
  • Como era de esperar, cuando la función print  se encuentra un retorno de carro, lo imprime. Es decir, cuando simplemente evaluamos una variable, IDLE imprime los retornos de carro como su representación '\n' , pero si una cadena se imprime mediante la función print , el retorno de carro se imprime como lo que es.

Movernos por el contenido de un fichero: seek y tell en ficheros de texto

Las funciones seek y tell permiten movernos por los contenidos de un fichero de texto, hasta cierto punto. Digo hasta cierto punto, porque los ficheros de texto tienen serias limitaciones con la función seek. Veamos cuáles por qué.

A priori, seek admite dos parámetros: el desplazamiento y el punto de partida.

fichero.seek(desplazamiento, desdeDonde)

Ambos parámetros son variables numéricas, y el comportamiento es de la siguiente forma:

  • desdeDonde es opcional y puede tomar los siguientes valores:
    • 0 comienza el desplazamiento desde el inicio del fichero. Este es el valor por defecto, es decir, el valor si no se especifica este parámetro.
    • 1 comienza el desplazamiento desde la posición actual.
    • 2 comienza el desplazamiento desde el final del fichero.
  • desplazamiento puede ser un entero positivo o negativo, en este último caso sólo si desdeDonde es 1 ó 2.

Sin embargo, los ficheros de texto tienen un comportamiento especial, no demasiado bien documentada en python.org, y la función seek sólo permite realizar desplazamientos relativos al inicio del fichero. Además, los valores del desplazamiento sólo sirven si son los que devuelve la otra función que trataré en este apartado, que es tell

tell es una función que devuelve la posición de lectura o escritura en un fichero, en bytes si el fichero se abre en modo binario, y de forma opaca si el fichero se abre en modo texto. Es decir, la posición de lectura o escritura en un fichero de texto es un número cuya forma de calcularse o determinarse está deliberadamente oculta, y debe tratarse como un valor sin significado pero que, de alguna forma, funciona. Representa una posición, una dirección dentro de un espacio de posiciones, que no tiene que corresponderse con lo que nosotros entenderíamos por una secuencia de caracteres (el primero, el segundo,…).

Por este especial funcionamiento de los ficheros de texto en Python (cuyos detalles nos están ocultos), la función seek  tendrá un comportamiento arbitrario si intentamos operar con un desplazamiento que no haya sido generado por la función tell. Esto implica una serie de complicaciones a la hora de tratar un fichero que exploraremos en a través de los ejercicios.

Al final del artículo usaré estas dos funciones cuando tratemos de actualizar un fichero con el modo 'r+'.

Escritura de ficheros

Para escribir un fichero de texto tenemos los modos 'a', 'x', 'w' y 'r+'. El último de ellos permite leer y escribir al mismo tiempo para actualizar un fichero. Vamos a verlos precisamente por ese orden para comprender su efecto, una vez entendamos el uso de la función write , que es la encargada de las operaciones de escritura sobre el contenido.

write acepta como parámetro la cadena a escribir. Es una función estricta en el sentido de que cualquier cosa que queramos escribir debe ser convertida primero a una cadena, mediante la función str . Por ejemplo, fichero.write('Otra línea más\n')  es una función de escritura válida, pero fichero.write(42)  no lo es; deberíamos escribir fichero.write(str(42)). Como es de esperar, write  no añade el caracter de nueva línea, por lo que hay que tenerlo en cuenta y añadirlo si lo que queremos es añadir una nueva línea a un fichero. Por último, mencionar que devuelve el número de caracteres escritos.

Prueba los siguientes ejemplos.

Añadir líneas a un fichero

La apertura del fichero en modo “ampliación” se realiza pasando como parámetro de modo el 'a'. El siguiente código,

fichero = open ( 'ejemplo.txt', 'a' ) 
fichero.write('Línea añadida como ejemplo.\n') 
fichero.close()

 al ser escrito en IDLE ofrece como resultado el número de caracteres escritos antes de ejecutar el cierre del fichero, 28. Si examinamos el contenido del fichero veremos lo siguiente:

Ésta es la línea 0.
Línea 1 por aquí.
Pera
Manzana
Plátano
Línea añadida como ejemplo.

Creación exclusiva de un fichero

Como ya vimos, abrir un fichero en modo 'x', provoca un error si éste existe. Por lo demás, funciona exactamente igual que una escritura como la que hemos visto pero… si el fichero estuviese vacío. El siguiente código,

fichero = open ( 'ejemplo_2.txt', 'x' ) 
fichero.write('Línea añadida como ejemplo.\n') 
fichero.close()

 al ser escrito en IDLE ofrece como resultado el número de caracteres escritos antes de ejecutar el cierre del fichero, 28. Si examinamos el contenido del fichero ejemplo_2.txt veremos lo siguiente:

Línea añadida como ejemplo.

Sin embargo, si escribimos fichero = open ( 'ejemplo.txt', 'x' ) obtendremos el ya conocido error, que indica precisamente que el fichero existe:

Traceback (most recent call last):
  File "", line 1, in 
    fichero = open ('ejemplo.txt', 'x')
FileExistsError: [Errno 17] File exists: 'ejemplo.txt

Creación de un fichero existente destruyendo su contenido previo

Como ya os he avisado, abrir un fichero en modo 'w', provoca la destrucción de su contenido anterior si éste existe. El siguiente código,

fichero = open ( 'ejemplo_2.txt', 'w' ) 
fichero.write('Nuevo contenido')
fichero.close()

 al ser escrito en IDLE ofrece como resultado el número de caracteres escritos antes de ejecutar el cierre del fichero, 15. Si examinamos el contenido del fichero ejemplo_2.txt veremos lo siguiente:

Nuevo contenido

en lugar del contenido del apartado anterior: Línea añadida como ejemplo. ya no existe.

Actualización de un fichero

Abrir un fichero con el modo 'r+'  permite leerlo y escribir valores. Vamos a ver cómo funciona con un poco más de detalle. Ejecuta las siguientes sentencias, una a una, en IDLE:

fichero = open ('ejemplo.txt', 'r+')
fichero.write('Prueba')
fichero.seek(0)
fichero.read()
fichero.seek(0)
fichero.write('Esta e')
fichero.read()
fichero.write('CONTENIDO ADICIONAL')
fichero.seek(0)
fichero.read()
fichero.close()

Estas sentencias que has escrito demuestran el comportamiento del modo de actualización de un fichero, y permite sacar como conclusiones que:

  1. Lo que hacemos al escribir en este modo es sobreescribir, y no insertar, la información existente.
  2. Además, deja la posición de lectura o escritura tras el último caracter escrito.
  3. Por último, si estamos en el final del fichero, además, funciona como el modo anteriormente comentado 'a' 
Modo de actualización de ficheros
Modo de actualización de ficheros

El contenido del fichero resultante es el siguiente:

Esta es la línea 0.
Línea 1 por aquí.
Pera
Manzana
Plátano
Línea añadida como ejemplo.
CONTENIDO ADICIONAL

Voy a plantearos a continuación una serie de ejercicios, tras el resumen de las consideraciones más importantes.

Consideraciones

  1. Escoge muy cuidadosamente el modo de apertura de los ficheros en tu programa: de ello puede depender que tus usuarios (los usuarios de tu programa) no pierdan información. Los ordenadores son buenos siguiendo instrucciones, no leyendo las mentes (esa frase abría mi proyecto de fin de carrera hace casi 11 años, y es de Donald Erving Knuth).
  2. Cierra siempre el fichero: sé económico con los recursos que usa tu programa. Siempre que termines de leer o escribir un fichero, ciérralo: es una forma de devolver memoria al sistema operativo para poderla usar más adelante.
  3. A la hora de leer un fichero de una línea  en una línea, el ejemplo que hace uso del bucle for es el mejor: muy limpio en lo que a código se refiere, y muy eficiente en uso de recursos.
  4. Leer todo un fichero de golpe (fichero.read() ) o leer todas las líneas de golpe para tener una lista de líneas (fichero.readlines() ) puede dejar tu ordenador sin memoria. Evítalo siempre que puedas, o, si lo usas, intenta que siempre sea por una buena razón y sólo en casos en los que tú y sólo tú seas quien define el tamaño máximo de los ficheros.

Ejercicios

  1. Crea un programa ejecutable por línea de comandos que reciba el nombre de un fichero (ruta relativa al directorio actual) como parámetro, y que devuelva el número de caracteres total, y el número total de palabras.
    1. Sólo puedes abrir el fichero una vez.
    2. Sólo puedes hacer una pasada al fichero (no vale volver a la posición inicial usando la función seek 
    3. No está permitido leer el fichero con read  ni con readlines 
  2. Crea otro programa ejecutable por línea de comandos que reciba el nombre de un fichero (ruta relativa al directorio actual) y que produzca otro, de igual nombre pero con extensión .pitando, con el contenido del primero fichero pero en mayúsculas.
    1. Sólo puedes abrir el fichero original una vez.
    2. Sólo puedes hacer una pasada al fichero (no vale volver a la posición inicial usando la función seek 
    3. No está permitido leer el fichero con read  ni con readlines 

Recuerda que puedes repasar conceptos como las rutas relativas al directorio actual y cómo hacer programas ejecutables por línea de comandos en los propios artículos de PItando.

Recuerda, también, que puedes dejarme comentarios o dudas en los comentarios de este artículo, o a través del formulario de contacto o de la dirección de correo que allí se encuentra.

]]>
http://pitando.net/2015/10/01/ficheros-de-texto-en-python/feed/ 2 771
Episodio 5 – Raspberry Pi 5 M, RPi.GPIO rootless, Raspbian Jessie y miscelánea de PItando http://pitando.net/2015/10/01/episodio-5-raspberry-pi-5-m-rpi-gpio-rootless-raspbian-jessie-y-miscelanea-de-pitando/ Thu, 01 Oct 2015 04:00:37 +0000 http://pitando.net/?p=811 Sigue leyendo Episodio 5 – Raspberry Pi 5 M, RPi.GPIO rootless, Raspbian Jessie y miscelánea de PItando ]]> En el episodio de hoy, en primer lugar, quiero agradecer a Emilio Cano (@emilcar) , de Emilcar.fm, el espacio en forma de entrevista que dedicó en Promopodcast a PItando. Fue un rato muy divertido y todo un honor.

Comparto con vosotros tres noticias del entorno de la Raspberry Pi:

Además os adelanto los derroteros que tomará PItando en las próximas semanas:

  • Vuelve la electrónica, introduciendo señales en la Raspberry Pi mediante pulsadores.
  • Haré un vídeo explicativo del uso de un multímetro.
  • No nos olvidaremos de nuestro videojuego en Scratch.
  • Casi al mismo tiempo que este episodio, sale en PItando un especial bastante largo sobre trabajo con ficheros, que espero que os sirva también para repasar los conocimientos de Python que vimos antes de las vacaciones.

¡Espero que os guste!

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.

 

]]>
811
Nuestro primer videojuego con Scratch (2): detección de paredes http://pitando.net/2015/09/24/nuestro-primer-videojuego-con-scratch-2-deteccion-de-paredes/ http://pitando.net/2015/09/24/nuestro-primer-videojuego-con-scratch-2-deteccion-de-paredes/#comments Thu, 24 Sep 2015 09:00:56 +0000 http://pitando.net/?p=755 Sigue leyendo Nuestro primer videojuego con Scratch (2): detección de paredes ]]> La semana pasada comenzaba una serie en PItando en la que trataba de crear con vosotros un videojuego muy básico en Sratch. Constaba de, a priori, cuatro partes:

  1. Mover al gato en las cuatro direcciones y hacer que rebote en los bordes del escenario. Conseguido la semana pasada.
  2. Programar la lógica de rebote para cuando el gato se encuentre con una pared roja.
  3. Programar la lógica de “gato encuentra a ratón”
  4. Proponer un esquema de puntuación para poder competir con nuestros amigos.

En esta entrada de hoy, segunda de la serie, vamos a ir un paso más allá y vamos a programar sobre un escenario básico la detección de paredes que, además, consistía en el ejercicio de la semana pasada.

Si te animas, que espero que sí, sigue leyendo 🙂

Recuperar el proyecto del ordenador

Como venimos haciendo y para dar cabida a todos los lectores, tengáis o no una Raspberry Pi, haremos este ejercicio sobre el navegador web. Abre la página del editor on-line de Scratch y sube el fichero que guardábamos la semana pasada. Si no lo tienes, ¿qué mejor momento el de ahora para hacer la primera parte del ejercicio? 😉

Para ello, ve al menú Archivo y pulsa la opción Subir de tu computadora.

Opción para subir un proyecto al editor Web de Scratch
Opción para subir un proyecto al editor Web de Scratch

Localiza el fichero y confirma la selección, tras lo cual el editor te pedirá confirmación en inglés (Replace contents of the current project?). Confirma la carga con el botón OK y, si todo ha ido bien, verás el editor cargado con el estado en el que lo dejamos la semana pasada; en mi caso es el de la siguiente imagen.

Proyecto cargado
Proyecto cargado

Detección de pared

Para detectar una pared hay que colocar un sensor que se active si el gato, esto es, el Sprite1, toca una de ellas. Para hacerlo, Scratch proporciona un sensor de detección de colores. Esto es muy conveniente porque permite detectar patrones en el escenario de fondo sin tener que definir las paredes como objetos propiamente dichos, o Sprites.

Como ya nos dice la experiencia, los sensores son bloques que encajan como sentencias para ser evaluadas en bloques condicionales y bucles. Por lo tanto, un enfoque que podemos seguir es que, siempre que se pulse una tecla, comprobar si se toca un color. 

Sensor de detección de color en un bloque condicional activado por la pulsación de cualquier tecla
Sensor de detección de color en un bloque condicional activado por la pulsación de cualquier tecla

Configurar el bloque de comprobación de si se toca un color es sencillo: al colocar el sensor en el bloque condicional, haremos click con el botón izquierdo en el recuadro que expresa el color a detectar. En ese momento, el cursor cambia de forma a una mano (normalmente es una flecha), lo que significa que Scratch está esperando a que le señalemos el color a evaluar. Lo que haremos será hacer click con el botón izquerdo en una pared, y veremos que el recuadro cambia al color de la pared.

Reacción a la detección de una pared

Lo que queda por hacer ahora es codificar la reacción a la colisión con una pared. Se nos pueden ocurrir dos alternativas:

  1. Parar de mover el gato
  2. Programar un rebote

La primera de las alternativas no va a funcionar, o al menos todo lo bien que quisiéramos, y formará parte de “los deberes” tratar de razonar por qué. La segunda la programaremos ahora, pero será laboriosa porque Scratch no proporciona un bloque “rebotar” genérico, por lo que tendremos que detectar la dirección en la que el gato se está moviendo y actuar en cada caso específico, deslizando al gato una determinada distancia en dirección contraria.

Veamos en detalle el código Scratch para el rebote en dirección abajo. Como es lógico, dentro del bloque condicional deberemos detectar que la dirección de desplazamiento es hacia abajo, hecho que demuestra que la tecla que se está pulsando es “flecha abajo”, ↓. Una vez claro esto, lo que habrá que hacer será desplazarlo hacia arriba una distancia prudencial.

Programación de rebote en dirección descendente.
Programación de rebote en dirección descendente.

Vamos a probarlo moviendo al gato de forma que podamos “atacar a una pared” pulsando la flecha hacia abajo: verás que funciona como debería pero… ahora hazlo de una forma exigente: mantén la tecla pulsada todo el tiempo, ¡sin piedad!, ¡EL GATO TIENE QUE ATRAVESAR LA PARED!

Verás que en algunas ocasiones, el gato (en mi caso, la cabeza del gato) consigue penetrar en la pared, quedando enmedio:

A veces el gato, a fuerza de cabezonería, consigue hacer mella en la pared :-)
A veces el gato, a fuerza de cabezonería, consigue hacer mella en la pared 🙂

Una explicación profunda de este fenómeno que, si estudiamos con detenimiento el código sabremos que no debería suceder, puede llevarnos varias páginas. En resumen, lo que ocurre en este tipo de sistemas es que Scratch reparte el código de nuestro programa en unos recursos especiales del ordenador, llamados hilos. Además de nuestro código, hay hilos ocupados en actualizar la pantalla para reflejar la posición de los Sprites, e interpretar eventos como los del teclado. El intérprete de Scratch, de una forma más o menos literal, reparte su atención a partes iguales por todos los hilos, pero desafortunadamente ese reparto hace aguas cuando se trata de atender a eventos del teclado si éstos vienen en grupo.

¿Por qué los eventos de teclado vienen en grupo a veces?

Seguro que te has fijado que en ocasiones, si el ordenador va lento, puedes seguir tecleando porque, de alguna forma, el teclado tiene cierta cantidad de memoria llamada buffer, que almacena las teclas pulsadas. Gracias a ello,  cuando el ordenador vuelve en sí, de repente aparecen todas las teclas que habías pulsado o, al menos, hasta cierto punto.

Este comportamiento se vuelve en nuestra contra cuando pulsamos demasiado tiempo la tecla “flecha abajo”. Lo que ocurre es que el buffer de teclado se llena, y cuando eso ocurre, el sistema operativo intenta enviar a Scratch todas las teclas almacenadas, de golpe y porrazo. Cuando eso ocurre, y debido a cómo Scratch está programado internamente, de alguna forma el control de eventos se satura y pueden ocurrir efectos no deseados como éste.

¿Cómo podemos evitarlo?

Lo que tenemos que hacer es que, la primera vez que se detecta una pared, paremos de mover al gato en dirección hacia abajo mientras se produce el rebote. Eso lo podremos hacer con una variable, que llamaremos mover, y que se comprobará en el bloque de movimiento hacia abajo. Como una imagen vale más que mil palabras, os enseñaré primero el código y luego lo analizamos.

Bloqueando el procesado de la tecla "flecha hacia abajo" mientras se produce el rebote
Bloqueando el procesado de la tecla “flecha hacia abajo” mientras se produce el rebote

Vamos a ver lo que he hecho.

  1. Bloque de movimiento (el superior): condiciono el movimiento hacia abajo al estado de una variable llamada parar. Si parar vale 0, el gato se mueve. Es decir, asocio 0 al valor “no“, y “si no hay que parar”, muevo al gato. Esto tiene truco y es que por defecto, en Scratch, las variables numéricas tienen como valor inicial el 0.
  2. Bloque de detección de pared (el inferior):
    1. Si el gato toca la pared, fijo la variable parar a 1. Esto hace que el bloque de movimiento, el superior, deje de funcionar. A la siguiente pulsación de tecla que llegue a Scratch, el bloque de movimiento encontrará la condición desfavorable y no se ejecutará.
    2. Cuando termino de “hacer que el gato rebote”, vuelvo a fijar la variable parar a 0. Esto, como estoy seguro de que podréis intuir, deshace lo hecho y consigue que el bloque de movimiento vuelva a funcionar. Fíjate que he puesto una espera del mismo tiempo en el que el rebote tarda en ejecutarse: lo hago para asegurarme de que la variable cambia su valor sólo cuando el gato deja de moverse.

Prueba esto ahora. Verás que el gato rebota tan pronto como la punta de sus orejas roza la pared. Las orejas, porque en mi caso el gato va de cabeza debido al “efecto rebote” contra los bordes del escenario y con el que experimentaba la semana pasada.

Esto sí que es lo que queremos. Ya sólo queda generalizarlo a las cuatro direcciones.

Código Scratch para generalizarlo a las cuatro direcciones

Sin más, voy a dejar aquí el código, pero es bueno que intentes recrearlo por ti mismo, para coger soltura.

Solución al artículo de hoy. Pincha en la imagen para verlo en todos su esplendor.
Solución al artículo de hoy. Pincha en la imagen para verlo en todo su esplendor.

Propina: hacer que el gato no ande de cabeza al rebotar en los bordes del escenario

Esto es opcional, porque el que el gato gire al rebotar le da un aspecto curioso al resultado. Sólo tienes que reorientarlo después del bloque de rebote, así:

Enderezar al gato
Enderezar al gato

Consideraciones y ejercicios

Si experimentas lo suficiente verás que puedes hacer que una pared se sitúe en zonas “angostas” del Sprite. Por ejemplo, que un extremo de una pared se cuele entre las orejas o los bigotes de nuestro protagonista. También ocurre que, si dejas demasiado tiempo pulsada una misma tecla, el programa acaba por fallar 😉 . Se comporta mejor que cuando pusimos la solución de la variable, sí… pero no del todo bien, y te toca explicar por qué y encontrar una solución.

Ejercicio 1: reproduce esos comportamientos y ofrece una explicación a los mismos.

Ejercicio 2: propón y construye al menos dos soluciones a estos comportamientos.

Ejercicio 3: Si, al detectar una pared, paramos el gato en vez de hacer que rebote, el programa funciona mal. ¿Por qué?

]]>
http://pitando.net/2015/09/24/nuestro-primer-videojuego-con-scratch-2-deteccion-de-paredes/feed/ 1 755
Episodio 4 – Tecnología, Programación y Robótica en las aulas. Videojuegos con Scratch http://pitando.net/2015/09/17/episodio-4-tecnologia-programacion-y-robotica-en-las-aulas-videojuegos-con-scratch/ Thu, 17 Sep 2015 18:57:51 +0000 http://pitando.net/?p=750 Sigue leyendo Episodio 4 – Tecnología, Programación y Robótica en las aulas. Videojuegos con Scratch ]]> En este episodio me hago eco de la nueva asignatura de Enseñanza Secundaria Obligatoria para los institutos de la Comunidad de Madrid, en España, que es “Tecnología, Programación y Robótica“. Lo hago aportando información y opinión, y en contacto con PItando, destacando aspectos positivos y también negativos.

Para terminar el episodio, repaso los últimos contenidos en Scratch relacionándolos también con la nueva asignatura: 
  • ScratchGPIO para relacionar la programación y la electrónica, especialmente adecuado para niños de menor edad que la adecuada para empezar con Python. Entrada relacionada.
  • Programar videojuegos con Scratch, una serie de, al menos, 4 episodios, que comienza con la entrada de hoy.
¡Espero que os guste!
 
 
Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.
]]>
750
Nuestro primer videojuego con Scratch (1) http://pitando.net/2015/09/17/nuestro-primer-videojuego-con-scratch-1/ http://pitando.net/2015/09/17/nuestro-primer-videojuego-con-scratch-1/#comments Thu, 17 Sep 2015 06:05:07 +0000 http://pitando.net/?p=731 Sigue leyendo Nuestro primer videojuego con Scratch (1) ]]> Hoy vamos a entrar en harina seria con Scratch para hacer el germen de lo que será nuestro primer videojuego. Lo haremos en varios artículos: en cada artículo os enseñaré a resolver una parte del videojuego, y al mismo tiempo os propondré un desafío. En los siguientes artículos de la serie iré resolviendo el desafío anterior, y proponiendo otro. Y así sucesivamente.

El videojuego final consistirá en un sencillo laberinto en el que moveremos al gato naranja de Scratch para que consiga llegar a un ratón. Los artículos serán cuatro, en principio:

  1. Mover al gato en las cuatro direcciones y hacer que rebote en los bordes del escenario.
  2. Programar la lógica de rebote para cuando el gato se encuentre con una pared roja.
  3. Programar la lógica de “gato encuentra a ratón”
  4. Proponer un esquema de puntuación para poder competir con nuestros amigos.

Estaos atentos a la cuenta de twitter de PItando para seguir esta serie y saber cuándo publicaré cada uno de las entradas.

Este artículo lo resolveré en el navegador usando el editor de la página web de Scratch, pero es igualmente abordable en la Raspberry Pi.

Movimiento

Para mover en una dirección al gato naranja tendremos que colocar en el área de programas del editor de Scratch la detección de un evento de pulsación de tecla. Vamos a usar las teclas de las flechas del cursor, ←, ↑, →, ↓, aunque los nostálgicos del Spectrum como yo podéis usar otras combinaciones que os traigan recuerdos de vuestra infancia (o, p, q y a, por ejemplo). Para eso, tendremos que preparar cuatro bloques de detección de teclas como el de la captura siguiente:

Bloque básico de detección de tecla. No funcionará, todavía tiene trabajo que hacer. Con la opción "duplicar" del botón derecho del ratón, crea tres copias más.
Bloque básico de detección de tecla. No funcionará, todavía tiene trabajo que hacer.

Para seleccionar la tecla de “flecha arriba” (en principio el bloque detecta la barra espaciadora) usa la flecha negra que apunta hacia abajo: es de las primeras opciones. Puedes crear las tres copias que te faltan usando el botón derecho del ratón y la opción “duplicar”. Una vez hecho eso, está claro lo que hay que hacer:

  1. Configurar las cuatro teclas: una en cada bloque.
  2. Programar cada bloque de deslizamiento para que avance en lugar de ir a una posición concreta.

Configurar las cuatro teclas es fácil, solamente hay que buscar cada una en la lista desplegable de su bloque. Hazlo ahora para evitar interferencias en la prueba que haremos dentro de unas líneas.

La programación del bloque de deslizamiento tiene algo más de ciencia, pero no mucha. Lo que deberemos hacer, por ejemplo para la flecha de arriba, es deslizar al gato una distancia corta en un intervalo de tiempo dado, de forma que su posición en el eje vertical se incremente a partir de su valor actual. Vamos a ver los bloques que necesitamos.

Ve a la sección de bloques titulada Sensores y localiza uno que dice “posición X de Sprite 1”: 

Detección de posición en un eje
Detección de posición en un eje

Ese bloque nos permite detectar la posición de un sprite en un eje (x o y: horizontal o vertical respectivamente). El Sprite1 es el gato naranja. 

Ve ahora a la sección de bloques titulada Operadores y localiza uno que suma dos números:

Bloque de suma
Bloque de suma

Con estos bloques ya podemos construir un deslizamiento tal que el gato no se mueva en el eje horizontal (el Sprite1 mantiene su posición en el eje x) pero que avance un cierto número de puntos en vertical, en sentido ascendente. Quedaría así:

Bloque de movimiento vertical ascendente.
Bloque de movimiento vertical ascendente.

Pruébalo ahora. Si has cambiado las teclas de los otros bloques para que sólo una de ellos reconozca la flecha arriba, el gato debería moverse un poco hacia arriba al pulsar la flecha hacia arriba. Si la mantienes pulsada, el movimiento debería ser más o menos continuo.

Hacer el resto de los bloques debería ser fácil con los conocimientos que tienes. Puedes usar el bloque de suma sumando un número negativo para el desplazamiento de las flechas izquierda y abajo, o buscar el bloque de resta en Operadores. También puedes cambiar la operación aritmética del bloque con el menú que aparece al hacer click con el botón derecho.

Una vez hecho, deberías tener los siguientes programas:

Bloques de movimiento.
Bloques de movimiento.

 

Rebotar al llegar al final del escenario

Lo que nos queda por hacer para esta semana antes de proponer el ejercicio es programar un pequeño rebote en los bordes del escenario. Esto es sumamente fácil, pero está bien hacerlo para que el gato no se salga del escenario. Examina los bloques de movimiento para encontrar uno que dice “rebotar si toca un borde”: ésa es la clave. Para integrarlo en nuestra versión inicial del juego lo que haremos será un quinto programa que:

  • Se active al pulsar cualquier tecla: en el evento “al presionar tecla” tendremos que poner el valor any, que es una palabra inglesa que significa “cualquiera”.
  • Use el bloque “rebotar si toca un borde”
  • Se detenga.

Así:

Bloque de rebote
Bloque de rebote

Pruébalo: verás un efecto raro… resolverlo es opcional y forma parte del ejercicio que os plantearé dentro de unas pocas líneas.

Guardar el proyecto en el ordenador

En este punto, descargaos el programa para poder continuar en otro momento usando el menú Archivo del editor web de Scratch:

Opción de menú para descargar a tu ordenador el proyecto en el que hemos estado trabajando.
Opción de menú para descargar a tu ordenador el proyecto en el que hemos estado trabajando.

Esto es todo por hoy, ya sólo queda plantear el ejercicio.

Recordad que podéis dejarme comentarios o dudas en los comentarios de este artículo, o a través del formulario de contacto o de la dirección de correo que allí se encuentra.

Ejercicio

En el siguiente artículo de esta serie lo que publicaré será una versión de este programa que hará que el gato salga despedido hacia atrás una determinada distancia al tocar paredes rojas. Además, resolveremos el “efecto secundario” que tiene el rebote del gato contra el final del escenario.

Para crear un escenario con paredes rojas podéis dibujarlas con cualquier programa de dibujo sobre un fondo de 480 puntos de ancho por 360 de alto, o bajaros este escenario de pruebas, que será el que yo use:

Escenario con dos paredes rojas
Escenario con dos paredes rojas, en fondo blanco. Pincha en la imagen para abrirlo en otra ventana y descargarlo a tamaño completo.

Una vez hecho eso, deberéis subirlo al editor de Scratch usando el botón en forma de carpeta que hay en el panel de abajo a la izquierda, Objetos, bajo la etiqueta “Fondo nuevo”:

Cargar fondo desde archivo.
Cargar fondo desde archivo.

Al pinchar en el icono se os abrirá un cuadro de diálogo para localizar el escenario de fondo y subirlo al editor. Si lo hacéis bien, veréis que se seleccionará automáticamente y se os abrirá en un editor:

Escenario recién subido. Podéis volver al editor de programas pinchando en la pestaña "Programas".
Escenario recién subido. Podéis volver al editor de programas pinchando en la pestaña “Programas”.

Si queréis, podéis editarlo. Una vez terminado, continuad con el ejercicio volviendo a la pestaña “Programas”.

 

]]>
http://pitando.net/2015/09/17/nuestro-primer-videojuego-con-scratch-1/feed/ 4 731
ScratchGPIO: control de prototipos con Scratch http://pitando.net/2015/09/10/scratchgpio-control-de-prototipos-con-scratch/ http://pitando.net/2015/09/10/scratchgpio-control-de-prototipos-con-scratch/#comments Thu, 10 Sep 2015 09:00:07 +0000 http://pitando.net/?p=722 Sigue leyendo ScratchGPIO: control de prototipos con Scratch ]]> ScratchGPIO es un proyecto de software libre desarrollado por Simon Walters (Cymplecy) que consiste en un añadido a Scratch que lo capacita para manejar los puertos GPIO de la Raspberry Pi. Lógicamente, no es compatible con la versión de Scratch de navegador, por razones obvias: un PC o un Mac no tienen puertos GPIO.

Este proyecto me gusta mucho porque Scratch supone una alternativa muy asequible a Python en función de la experiencia programando y la capacidad de abstracción de la persona que intente programar una placa a través de los puertos. Por ejemplo, si lo que queréis es introducir en estos temas a un niño de corta edad (8 a 12 años), probablemente se incline más por usar Scratch que Python. En este sentido, ScratchGPIO abre la puerta de la electrónica y de la programación a una audiencia mucho más amplia, en edad, que Python.

Actualmente en la versión 7, es compatible con los modelos 1 y 2 de la Raspberry Pi, y es muy fácil de instalar. En este artículo y en el vídeo que lo acompaña lo instalaremos y haremos una prueba rápida con el prototipo del artículo anterior.

Qué necesitas

Conecta todo como se explica en el artículo de la semana pasada y enciende la Raspberry Pi.

Instalar ScratchGPIO

Para instalar ScratchGPIO, abre un terminal y escribe lo siguiente:

wget http://bit.ly/1wxrqdp -O instalarScratchGPIO7.sh
chmod +x instalarScratchGPIO7.sh
sudo ./instalarScratchGPIO7.sh

Lo que estamos haciendo con esas sentencias de terminal es lo siguiente:

  • Descargamos de Github la última versión del programa de instalación.
  • Le damos permisos de ejecución
  • Lo ejecutamos con permisos de superusuario.

El resultado debería ser que aparecen dos nuevos iconos en el escritorio, etiquetados como ScratchGPIO 7 y ScratchGPIO 7Plus. ScratchGPIO 7 sirve para programar una placa de prototipos, mientras que ScratchGPIO 7Plus está pensado para usar con placas ya prefabricadas, como alguna que veremos en breve.

Resultado de instalar ScratchGPIO7
Resultado de instalar ScratchGPIO 7

A día de hoy, el enlace acortado de la primera línea (la de wget ), “http://bit.ly/1wxrqdp”, enlaza con la versión 7 de ScratchGPIO en Github. Sin embargo, a medida que pase el tiempo, irán saliendo versiones y este artículo, aunque lo revisaré e iré actualizando, puede que quede algo atrasado. Consulta siempre en la página de instalación la versión y el enlace que tienes que poner en el comando wget .

Probar ScratchGPIO con el prototipo del LED

Una vez instalado, ya toca probarlo. He grabado un vídeo que publiqué en el canal de YouTube hace unos días a este respecto:

Del vídeo, lo más importante es que la numeración de los puertos es secuencial, no BCM. De hecho, se veía en el vídeo al consultar la parte de sensores para localizar los pines configurados como entrada, por defecto.

Pines detectados como sensor en ScratchGPIO
Pines detectados como sensor en ScratchGPIO: la numeración es sencuencial.

Esto no supone un problema: en nuestro prototipo, para poder colocar un valor alto en el GPIO 6 como salida, tendremos que consultar la imagen de correspondencia y convertir de BCM (GPIO 6) a numeración secuencial, de entre 1 y 40:

Conexiones GPIO de la Raspberry Pi 2, modelo B, con esquema BCM. La imagen es de www.raspberrypi-spy.co.uk
Conexiones GPIO de la Raspberry Pi 2, modelo B, con esquema BCM. La imagen es de www.raspberrypi-spy.co.uk

Consultando la imagen anterior, está claro: el GPIO 6 es el pin 31.

Otra cosa importante es la sintaxis que tienen que tener los mensajes para que ScratchGPIO los reconozca:

  • pin
  • un número entre el 1 y el 40, para la RaspberryPi 2, entre el 1 y el 26 para la Raspberry Pi 1.
  • Un valor:
    • high u on, indistintamente, colocan un valor alto en el pin correspondiente, y si estaba configurado como entrada, lo configura automáticamente como salida.
    • low u off, indistintamente, colocan un valor bajo en el pin correspondiente, y de nuevo: si estaba configurado como entrada, lo configura automáticamente como salida.

Ejemplo: pin6on, pin7low

Es decir, pin31high configuraría el pin 31 como salida y colocaría 3,3V en el pin nº 31 en numeración secuencial, es decir, en el GPIO 6 en numeración BCM. Si quisiéramos configurar un pin como entrada deberíamos escribir un mensaje formado como sigue:

  • config
  • un número entre el 1 y el 40, para la RaspberryPi 2, entre el 1 y el 26 para la Raspberry Pi 1.
  • in

Ejemplo: config31in.

Esto es todo por esta semana. Como comentaba al final del vídeo, siempre que haga algo con un prototipo intentaré poner siempre dos programas equivalentes en el artículo: uno con Python y otro con Scratch; en estos últimos aprovecharé para ir introduciendo más elementos del lenguaje y más mensajes proporcionados por ScratchGPIO. Espero que os guste la idea.

Recordad que podéis dejarme comentarios o dudas en los comentarios de este artículo, o a través del formulario de contacto o de la dirección de correo que allí se encuentra.

]]>
http://pitando.net/2015/09/10/scratchgpio-control-de-prototipos-con-scratch/feed/ 3 722
Nuestro primer prototipo con la Raspberry Pi http://pitando.net/2015/09/03/nuestro-primer-prototipo-con-la-raspberry-pi/ http://pitando.net/2015/09/03/nuestro-primer-prototipo-con-la-raspberry-pi/#comments Thu, 03 Sep 2015 08:00:04 +0000 http://pitando.net/?p=671 Sigue leyendo Nuestro primer prototipo con la Raspberry Pi ]]> Esta semana vamos ya a meter las manos en la masa. Montaremos un prototipo básico que programaremos desde la Raspberry Pi, gracias al kit de inicio orientado a la electrónica que os enseñaba unas semanas atrás. Este kit incluye una placa de prototipado, en la que podemos montar circuitos electrónicos de una forma muy fácil y sin necesidad de soldadura ni herramientas especiales: con nuestras propias manos.

Lo que haremos será un programa en Python con el que poder encender y apagar un LED (Light-Emitting Diode, diodo emisor de luz) a nuestro antojo.

Conectar la placa de prototipado a la Raspberry Pi

Vamos a ir viendo los componentes que vamos a necesitar, y cómo se conectan, a través de un vídeo:

  • Una Raspberry Pi. En el vídeo usaré la Raspberry Pi 2, modelo B.
  • Una placa de prototipado.
  • Un cable de conexión y una placa de expansión para conectar la placa de prototipado a los puertos GPIO de la Raspberry Pi

Con esto, podremos conectar la placa de prototipado a la Raspberry Pi (siempre apagada y desenchufada) como explico en el siguiente vídeo:

Montar el prototipo

Para probarla y descubrir cómo será, en esencia, la tarea de programar circuitos físicos desde Python, se necesita:

  • Un LED.
  • Un cable de conexión; si es corto, mejor.
  • Una resistencia de 330 Ω.

Mención especial merece el LED. Si nos fijamos en la imagen siguiente, veremos que uno de los conectores del LED es más largo que el otro. Ese conector más largo es el ánodo, o el borne positivo: el que deberemos enchufar a valores positivos de tensión. El conector más corto es el cátodo, es decir, el borne negativo, y debe ir a valores de voltaje menores, o a tierra directamente. 

LED con los bornes sin cortar. El borne positivo es ligeramente más largo que el negativo
Un LED es un diodo emisor de luz. El borne positivo es ligeramente más largo que el negativo, y será el que debamos conectar a voltajes positivos si queremos que brille.

Usando nuestros dedos, doblaremos la pata más larga en algún punto intermedio, lejos del plástico que envuelve al diodo. Así, las dejaremos a nivel:

LED con el borne positivo doblado, para que sea más cómodo conectarlo a la placa de prototiposLED con el borne positivo doblado, para que sea más cómodo conectarlo a la placa de prototipos
LED con el borne positivo doblado, para que sea más cómodo conectarlo a la placa de prototipos

De nuevo, con la Raspberry Pi apagada y desenchufada, y con la placa de prototipos conectada como en el vídeo, haremos lo siguiente:

  • Conectamos a la fila 16, que está alineada con el pin GPIO nº 6 según la placa de expansión, un extremo del cable de conexión. El otro extremo, a la fila 23. Por ejemplo, podemos usar la columna “a” en este caso.
  • Conectamos la pata larga del LED a la fila 23, por ejemplo, a la columna “e”. Recordad que, para una misma fila, tenemos en todos sus conectores hembra la misma tensión.
  • Conectamos la pata corta del LED a la fila 25, por ejemplo, a la columna “e”.
  • Conectamos un extremo de una resistencia de 330 Ω a la fila 25, en una columna que resulte cómoda. Yo lo he hecho en la columna “b”.
  • El otro extremo de la resistencia irá conectado a la hilera azul de tensión, al negativo de la placa de prototipos del lado de 3,3V.

Así:

El primer prototipo con la Raspberry Pi se corresponde con este montaje, que ha sido descrito unas líneas más arriba de esta imagen.
El primer prototipo con la Raspberry Pi se corresponde con este montaje, que ha sido descrito unas líneas más arriba de esta imagen.

El prototipo del circuito que hemos montado se corresponde con este esquema que ahora explicaré para que podamos interpretarlo y usarlo asíduamente en el blog:

Esquema manuscrito del prototipo que vamos a probar
Esquema manuscrito del prototipo que vamos a probar. ¿A que tengo buena letra?

De arriba a abajo:

  • El puerto GPIO 6 ofrece 3,3 V cuando el microprocesador programa en éste el valor alto; como veremos, en Python se programa fijando el valor GPIO.HIGH. Así pues, GPIO.HIGH  ⇒ 3,3 V. En otro caso, este puerto tiene como valor de tensión 0 V.
  • El diodo es el triángulo invertido con una línea en su vértice. La base del triángulo, que en este caso está arriba, es el borne positivo, y la línea en el vértice que en este caso está abajo, el negativo. Los LED se distinguen de otros diodos porque se dibujan con dos flechas a un lado que simbolizan la emisión de luz.
  • La línea en forma de dientes de sierra es la resistencia, de 330 Ω en este caso.
  • Las dos líneas paralelas inferiores significan “tierra” o “masa” y se dibujan si el valor de tensión es 0. Si fuese negativo, o menor que el de 3,3 V, se dibujaría una flecha hacia abajo.

Estamos usando el conector GPIO número 6 para alimentar el circuito a 3,3 V, tensión que alimentará el borne positivo del diodo cuando el microprocesador ponga el GPIO 6 en valor “alto”. Cuando eso ocurra, el diodo conducirá la corriente través de la resistencia, emitiendo luz. 

La resistencia de 330 Ω está ahí para limitar la corriente a unos parámetros que la Raspberry Pi aguante, dado que el diodo por sí mismo supondría muy poca resistencia, lo que proporcionaría una corriente muy alta al conjunto y dañaría el microprocesador. Con esa resistencia, la corriente queda limitada a unos 3,6  mA (miliamperios), que está dentro del rango de “salud” del microprocesador. En próximos artículos trataré estos temas con más profundidad de forma que tengamos la máxima información posible.

La relación entre corriente y voltaje y las reglas necesarias para diseñar estos circuitos serán el objetivo de otro artículo; de momento, montad este circuito así y aseguraos de que todas las patitas de los elementos (cable, resistencia y diodo) quedan enchufadas hasta el fondo.

Probando el prototipo con Python

Una vez hecho esto, conectad a la Raspberry Pi su teclado y su ratón, ésta a la TV y a la corriente, y… no ocurre nada con el LED. Pues claro, el GPIO no está inicializado y hace falta programarlo. Actualmente el GPIO nº 6 tiene 0 V a su salida, y necesitamos ponerlo a 3,3 V para que el diodo emita luz. Eso lo haremos con Python.

Abrid un nuevo programa en Python 3 con el IDLE (está en la sección de Programación en el Menú de la Raspberry Pi, etiquetado como “Python 3”), y escribid el siguiente código:

#!/usr/bin/python3

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(6, GPIO.OUT)

print ("LED encendido")
GPIO.output(6,GPIO.HIGH)
time.sleep(5)
print ("LED apagado")
GPIO.output(6, GPIO.LOW)
GPIO.cleanup()

print ("Hasta luego")

Como ya sabéis, estamos importando dos librerías:

  • RPi.GPIO es la librería encargada de proporcionarnos un modo de programar los puertos GPIO de la Raspberry Pi en Python. Le estamos asignando un alias, GPIO, para podernos referir a ella con un nombre corto.
  • time  es una librería que nos permitirá hacer que el programa se quede suspendido un tiempo en segundos, entre otras cosas.

Las siguientes tres líneas configuran el experimento: estamos diciendo a la librería que use una numeración de conectores llamada BCM (numeración de Broadcom, que es el fabricante del microprocesador), y que se corresponde con la leyenda que tiene nuestra placa de expansión, y que en la Raspberry Pi se refleja de este modo teniendo en cuenta que el pin número 1 es el de arriba a la izquierda si sujetamos la Raspberry Pi orientando los puertos USB y Ethernet hacia abajo:

Conexiones GPIO de la Raspberry Pi 2, modelo B, con esquema BCM. La imagen es de www.raspberrypi-spy.co.uk
Conexiones GPIO de la Raspberry Pi 2 modelo B, con esquema BCM. La imagen es de www.raspberrypi-spy.co.uk. “Ground” es tierra, o 0V y los pines con otros voltajes (3,3V y 5V) no los usaremos en nuestros prototipos; usaremos todos los demás, y  a su debido tiempo.

Las dos siguientes lo que hacen es eliminar información de aviso de la salida estándar, que para este ejemplo no hace falta, y declarar el uso del puerto GPIO 6 como salida (GPIO.OUT ), es decir, que el microprocesador fija la tensión en ese puerto enviando información así.

El resto del programa realiza lo siguiente:

  • Escribe un mensaje por pantalla, “Led encendido”.
  • Enciende el LED enviando el valor “alto”, GPIO.HIGH , por el puerto 6. Esto aplica una tensión de 3,3 V en el borne negativo del diodo, haciendo que emita luz.
  • Suspende el programa durante 5 segundos.
  • Escribe un mensaje por pantalla, “Led apagado”.
  • Apaga el LED enviando el valor “bajo”, GPIO.LOW , por el puerto 6. Esto hace que el valor entre ambos bornes del diodo sea de 0 V, como al encender la placa.
  • Limpiamos la configuración de los puertos GPIO con GPIO.cleanup() 
  • Escribe un mensaje por pantalla, “Hasta luego”.

Para ejecutar este programa hay que grabarlo con un nombre (por ejemplo, ledtest.py) y darle permisos de ejecución en la Raspberry Pi a través del terminal, y ejecutarlo con privilegios de superusuario. Eso se hace con la orden sudo, así:

chmod +x ledtest.py
sudo ./ledtest.py

Esto hará que el LED se encienda durante 5 segundos, para luego apagarse, dando una luz roja bastante brillante aunque no lo suficiente para hacernos daño. En cualquier caso no conviene mirarlo fijamente demasiado rato.

LED encendido por nuestro programa en Python
LED encendido por nuestro programa en Python

En la pantalla del terminal veremos estos mensajes:

Ejecución del programa en Python
Ejecución del programa en Python

He preparado un segundo vídeo donde podéis ver el montaje del prototipo y las precauciones explicadas de primera mano, y además podéis ver cómo el código Python anterior produce una iluminación del LED de una duración de 5 segundos:

Y esto es todo por hoy. Os animo a que lo probéis y que probéis cuantas variaciones se os ocurran en el programa. En sucesivos artículos del blog iré probando más cosas y complicando un poco más los montajes.

No olvidéis dejar en los comentarios cualquier duda que tengáis, así como en el formulario de contacto o en la dirección de correo que está publicada en esa misma página.

]]>
http://pitando.net/2015/09/03/nuestro-primer-prototipo-con-la-raspberry-pi/feed/ 6 671
Episodio 3 – Vuelta de vacaciones, Scratch y el primer prototipo http://pitando.net/2015/09/02/episodio-3-vuelta-de-vacaciones-scratch-y-el-primer-prototipo/ Wed, 02 Sep 2015 21:52:53 +0000 http://pitando.net/?p=714 Durante las vacaciones de PItando que han tenido lugar en agosto he estado migrando el blog, principalmente, y cambiando el feed del Podcast.

Cambiar un feed de un podcast se ha convertido en una tarea bastante sencilla cuando se tiene un hosting propio, y se trata de modificar un archivo llamado .htaccess para que, si un lector o servicio suscrito al feed antiguo lo intenta refrescar, el servidor le conteste que el feed se ha trasladado permanentemente a una nueva ubicación, junto con los mecanismos necesarios para que sea capaz de encontrar la nueva dirección. Esto, por ejemplo, es suficiente para que iTunes corrija la ubicación del feed automáticamente y así me lo confirmaron en la página de soporte de Apple.

Lo que hay que poner en el archivo .htaccess del servidor que aloja el feed antiguo es lo siguiente, dentro del bloque ... :

RewriteBase /
RewriteRule ^category/podcast/feed http://pitando.net/feed/podcast [L,R=301]

Así, lo que se consigue es redirigir la URL antigua (category/podcast/feed colgando de la raíz del dominio) a la nueva (http://pitando.net/feed/podcast) a través de una respuesta HTTP sea un código 301, que indica “trasladado permanentemente”.

También he mejorado algunas características de forma del propio blog, que se supone que me van a ayudar a llegar mejor a vosotros, por ejemplo con el artículo introductorio a Scratch, y el que sale mañana, que será el primer prototipo electrónico de PItando. Este último artículo, aunque muy básico, es una muestra del detalle al que quiero llegar en las entradas experimentales del blog, tanto a nivel teórico explicando qué hay detrás de los principios que queremos aprender, como a nivel audiovisual para demostrar tanto procedimiento como resultados.

¡Espero que os guste!

Enlaces de interés:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.

]]>
714
El lenguaje de programación Scratch http://pitando.net/2015/08/27/el-lenguaje-de-programacion-scratch/ http://pitando.net/2015/08/27/el-lenguaje-de-programacion-scratch/#comments Thu, 27 Aug 2015 07:00:41 +0000 http://pitando.net/?p=656 Sigue leyendo El lenguaje de programación Scratch ]]> Scratch es un lenguaje de programación completamente visual que permite crear animaciones, juegos y cortos de animación interactivos de una forma muy sencilla. Es un proyecto del laboratorio de medios del Instituto Tecnológico de Massachusetts, MIT Media Lab, y dentro de éste, de un grupo llamado Lifelong Kindergarten Group, que podría ser traducido de una forma un poco grosera como Grupo de la guardería de / para toda la vida. Empezó en 2003 como un proyecto para dar soporte tecnológico a las actividades extraescolares, principalmente de las comunidades más desfavorecidas, de tal forma que los niños de todo el mundo tuvieran acceso a la educación tecnológica sin importar sus circunstancias. Está pensado para niños de entre 8 y 16 años, pero eso no lo hace inválido para un adulto que se quiera acercar a la programación sin conocimientos previos. Y es totalmente gratis

Desde 2007 el proyecto está abierto a todo el público de internet en general, a través de su web, http://scratch.mit.edu. Allí, además de que podemos programar directamente en la propia página y descargar un editor local, existe una comunidad en la que nos podemos dar de alta para compartir nuestro proyecto y examinar los de los demás. Actualmente esa comunidad comparte casi diez millones y medio de proyectos

Los proyectos compartidos en la comunidad de Scratch llegan casi a los 10 millones y medio
Los proyectos compartidos en la comunidad de Scratch llegan casi a los 10 millones y medio. Pincha en la imagen para ir a la web de Scratch

Como era de esperar, viene de serie con la Raspberry Pi, y está acompañado de ciertas extensiones que lo hacen muy interesante para combinar con la electrónica y que usaré dentro de poco en el blog.

Esta es la pinta que tiene el editor de Scratch en la Raspberry Pi:

Este es el aspecto de Scratch en el escritorio de la Raspberry Pi
Este es el aspecto de Scratch en el escritorio de la Raspberry Pi. ¡No está mal!

Lo que editemos en la Raspberry Pi puede subirse al editor de la página web, así que escoge tu opción preferida. Al poder programar directamente en la web, a diferencia de otros entornos de programación, no voy a entrar en detalles sobre cómo instalarlo en Windows o en Mac ya que no es necesario.

Y poco más: ¡vamos a verlo en acción!

Scratch se basa en el concepto de sprite, que representa un objeto, que puede tener uno o varios costumes, o disfraces, que son la forma que tiene ese objeto de representarse en la pantalla en un momento dado. Esos objetos existen en un área de dos dimensiones llamada escena, dentro de la cual podemos animarlo y someter esas animaciones a todo tipo de variaciones. 

Colocando en el área de trabajo bloques de diferentes grupos, como ControlMovimiento, Apariencia, Sonido,… podremos dar forma a nuestros desarrollos y conseguir que nuestros objetos respondan a eventos de teclado, de ratón, de chocar con otros objetos para dotar al programa de interactividad. Realmente, podemos componer casi cualquier escena usando sentencias de control que, muchas de ellas, como los bucles y las condiciones, están presentes en los lenguajes que ya conocemos (Python y Sonic Pi). La diferencia que presenta fundamentalmente con respecto a, por ejemplo, Python, es la forma en la que la ejecución del programa tiene lugar: Scratch es un lenguaje dirigido por eventos en el que, para que tu programa haga algo debes disponer elementos que detecten eventos y disparen unas acciones sobre ellos. Si no hay al menos un evento de inicio, no hay nada.

Realmente el entorno es muy completo y está diseñado para el aprendizaje autónomo, es decir, sin ayuda o, más bien, con poca. El proceso es divertido e intuitivo, como podéis ver en este vídeo que he grabado y que os enseña las posibilidades y la mecánica básica de este lenguaje, en 20 minutos:

Espero que os guste la idea, ya que además de divertido, es fácil y muy rápido de aprender, y tanto nosotros como los niños que se interesen por estos temas veremos progresos muy rápidamente. Podéis probarlo ya mismo a través de la propia página del proyecto.

Os dejo aquí una captura de pantalla de los bloques que he ido combinando en el vídeo, para que intentéis reproducirlo si queréis. No os quiero dejar el archivo del proyecto para que os lo descarguéis porque conviene que os familiaricéis con las características del editor, y cómo los bloques se combinan. Ya poco queda por decir, así que a divertirse.

Secuencia de bloques de Scratch resultante del vídeo de este artículo
Secuencia de bloques de Scratch resultante del vídeo de este artículo

Si os animáis a probarlo, que espero que sí, podéis enviarme cualquier duda a través de los comentarios de este artículo o a través del formulario de contacto, en el que también encontraréis una dirección de correo electrónico.

]]>
http://pitando.net/2015/08/27/el-lenguaje-de-programacion-scratch/feed/ 1 656
Episodio 2 – Sonic Pi y mi falta de formación musical. Vacaciones. http://pitando.net/2015/07/30/episodio-2-sonic-pi-y-mi-falta-de-formacion-musical-vacaciones/ Thu, 30 Jul 2015 06:09:55 +0000 http://pitando.net/?p=578 logo_sonicPiEn este episodio os cuento el “detrás de las cámaras” de mi entrada en el mundo musical con Sonic Pi y las ideas que tengo en este aspecto para el blog. Sonic Pi es una herramienta fantástica para el aprendizaje musical, y que ofrece una intersección muy atractiva entre música y tecnología, tanto para aquellas personas interesadas en los dos campos, como para los que tenemos carencias en uno de ellos… como es mi caso. ¡Menudo desastre!

También os cuento que voy a poner el proyecto en “remojo vacacional” durante unas pocas semanas (2 ó 3) que me servirán para enfocar una fase 2 más orientada a dar contenido de complejidad y valor creciente. En estas vacaciones, migraré el blog a un hosting propio, además de tratar de profundizar en temas de electrónica que quiero actualizar para poder hacer mejores artículos, con más valor para vosotros). De esta forma, podremos todos aprender un poco más, cada vez más.
 
También procuraré posicionarlo un poco mejor en las redes sociales y en los buscadores.
 
Enlaces de interés / relacionados:
 

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.

]]>
578
Kit de inicio de Raspberry Pi 2 orientado a electrónica http://pitando.net/2015/07/30/kit-de-inicio-de-raspberry-pi-2-orientado-a-electronica/ http://pitando.net/2015/07/30/kit-de-inicio-de-raspberry-pi-2-orientado-a-electronica/#comments Thu, 30 Jul 2015 06:08:00 +0000 http://pitando.net/?p=573 Sigue leyendo Kit de inicio de Raspberry Pi 2 orientado a electrónica ]]> En este artículo os voy a enseñar un kit de Raspberry Pi 2 (Modelo B) que he escogido recientemente. Es uno de los llamados “Kit de inicio”, y como veréis por su contenido y en el vídeo, está orientado específicamente para iniciarse en la electrónica programable por medio de la Raspberry Pi.

Mi Raspberry Pi de toda la vida (el Modelo B original) fue un regalo de Reyes, y gracias a ella he podido empezar a trastear y concebir un tiempo después la idea de este blog, y todo lo que le rodea. Y es muy suficiente para enseñar a programar (y enseñar música relacionada con la programación) a un niño o a un adulto que se interese por el tema.

Sin embargo, una Raspberry Pi no es suficiente, por si sola, si se trata de hacer un experimento que incluya un prototipo electrónico. Hacen falta componentes electrónicos, una placa donde poderlos conectar, cables, y medios para conectar dicho prototipo a la Raspberry y poder interactuar con él.

Este kit incluye todo lo necesario para empezar a hacer experimentos de electrónica básicos, incluida la Raspberry Pi 2 (es decir, la nueva) Modelo B.

La caja contiene:

  • Lo básico para usar una Raspberry Pi 2, excepto teclado, ratón y monitor / televisor:
    • Una Raspberry Pi 2, Modelo B (1 GB y 4 cores)
    • Un adaptador de corriente de 5 Voltios y 2 Amperios
    • Un cable HDMI
    • Un adaptador de red WiFi USB.
    • Una tarjeta Micro-SD de 8 GB marca Kingston, con Noobs preinstalado, y un adaptador a SD.
    • Una caja para la Raspberry Pi.
  • Algunos extras:
    • Dos disipadores de calor, para el microprocesador y para la tarjeta Ethernet, adhesivos
  • Lo básico para montar los primeros prototipos electrónicos:
    • Una placa de pruebas / prototipado de tipo “Breadboard” y cables de conexión.
    • 10 LED rojos y otros 10 LED amarillos
    • 45 resistencias de 330 Ω (ohmios) y otras 45 de 10 kΩ (10.000 ohmios)
    • 2 pulsadores
    • Un cable de conexión GPIO con placa de adaptación (“Breakout board”) a la placa de prototipado.

Os dejo el vídeo:

Como notas importantes:

  1. Este artículo no está patrocinado ni es publicidad, ni una recomendación. No tengo nada que ver con Vilros, la tienda que “ensambla” y comercializa este kit más allá de que haya visto el kit y su contenido y me haya gustado a mí personalmente: para mí y para este blog. Tomadlo simplemente como una guía de “qué os haría falta para empezar a hacer los prototipos que haga en el blog”, no como una recomendación.
  2. Como medida orientativa, este kit en concreto me costó algo menos (céntimos) de 80 € en Amazon España, un precio muy atractivo para los componentes que incluye.
  3. A través de este enlace podrás comprarlo y contribuir a PItando sin esfuerzo por tu parte, ya que es un enlace de afiliado de Amazon: Raspberry Pi 2 Ultimate Starter Kit.
  4. No entendáis como “proyectos de electrónica” aquellos que incluyan PyGlow o PiCamera; esos dos módulos son cosas aparte y lo que se puede hacer sobre ellos es fundamentalmente programarlos. Me refiero a montajes electrónicos propios o propiamente dichos. Tampoco descartéis que algún experimento combine un prototipo con alguno de estos dos módulos.
]]>
http://pitando.net/2015/07/30/kit-de-inicio-de-raspberry-pi-2-orientado-a-electronica/feed/ 3 573
Sonic Pi http://pitando.net/2015/07/24/sonic-pi/ http://pitando.net/2015/07/24/sonic-pi/#comments Fri, 24 Jul 2015 18:36:36 +0000 http://pitando.net/?p=558 Sigue leyendo Sonic Pi ]]> logo_sonicPiEn este artículo vamos a cambiar la tónica reciente del blog, muy centrada hasta el momento en Python, para introducir Sonic Pi.

Sonic Pi es una herramienta diseñada y creada por Sam Aaron y el equipo de Sonic Pi. Durante el proceso se estuvo muy en contacto con el profesorado de música y ciencias de la computación, y el resultado es una herramienta de educación que abarca esos dos campos a la vez. Es decir, es una herramienta especialmente atractiva para aquellos niños (o no tan niños) interesados en la música y que quieran profundizar en ella, relacionándola con las matemáticas y la tecnología al mismo tiempo.

Permite componer piezas musicales que son reproducidas por el sintetizador de nuestra tarjeta de sonido, ya sea de la Raspberry Pi, o de cualquier ordenador con Windows, Linux o con Mac OS X. Para ello proporciona un lenguaje de programación sencillo que recoge tanto estructuras de control como sentencias que permiten reproducir notas y controlar distintos parámetros de cada sonido (ataque, tempo,…). Componemos las pistas y voces de nuestra pieza musical usando bucles, generadores de números aleatorios, de pausas, temporizadores, condiciones,… y en el mismo momento podremos reproducirlas.

Además, es posible modificar la pieza mientras suena, por lo que Sonic Pi es especialmente divertido para enseñar música en público, modificando la pieza en directo y viendo el efecto mientras se reproduce. También es bastante espectacular ver una sesión en directo combinando Sonic Pi con instrumentos musicales. Os voy a poner una pequeña demostración que, tras muchos intentos esta semana, he conseguido hacer yo mismo (!) y que espero que encontréis familiar los aficionados al cine de ciencia ficción de finales de los años 70.

Podréis imaginaros que mi formación musical es nula… y acertaréis. También lo es mi oído musical para localizar las notas de las sentencias play (C, D, E, F, G, A, B: notación anglosajona para Do, Re, Mi, Fa, Sol, La, Si) y sus octavas (el número que va a la derecha de la nota).

En sucesivos artículos del blog seréis testigos de mi aprendizaje musical, porque iré compartiendo con vosotros sentencias y estructuras de Sonic Pi a modo de ejemplo, y mis propias composiciones. No prometo una periodicidad, pero sí compartir con vosotros esa especie de… viaje 🙂

En la propia página Web del proyecto y en la ayuda del propio Sonic Pi podéis encontrar la referencia de su lenguaje aunque, lamentablemente, solo en inglés, ya que es de Inglaterra de donde proviene esta iniciativa. Espero que a medida que este tipo de aplicaciones se difundan en el panorama español y en su sistema educativo, poco a poco empiecen a aparecer traducciones. Mientras tanto, espero poder ir poniendo mi granito de arena.

Para terminar el artículo, veremos dónde obtener Sonic Pi y su (sencillísimo) proceso de instalación.

Instalar Sonic Pi en Windows

Id a la sección de descargas para Windows de Sonic Pi y haced click en el botón Download:

Captura 20150724 - 1Se descargará un archivo de extensión msi. Al ejecutarlo con doble click tendremos que seguir un asistente como el que reproduzco a continuación, y que consiste en autoizar cambios en el disco duro y pulsar el botón Next (Siguiente) y Finish (Finalizar), ya que ni siquiera nos ofrece la posibilidad de decidir el directorio de instalación. Se instalará en C:Program Files (x86)Sonic Pi, que en cualquier caso sería la carpeta por defecto en caso de que nos ofreciera la opción de cambiarla.

Haz click para ver el pase de diapositivas.

Instalar Sonic Pi en Mac

En este caso es incluso más sencillo, si cabe. Al visitar la sección de descargas de Mac Os X (Lion o superior), lo descargamos con el botón Download:

Captura 20150724 - 2

Se descargará un archivo de extensión dmg el cual, al ejecutarlo con doble click, nos ofrecerá el familiar asistente que consiste en arrastrar el logotipo de Sonic Pi a la carpeta de aplicaciones:

Captura de pantalla 2015-07-19 11.50.24

Tras hacerlo ya podremos cerrar la ventana y ejecutarlo. Recordad que hay que darle permisos de ejecución a través del panel de control de Mac OS X al haberlo descargado desde fuera de la tienda de aplicaciones de Apple.

]]>
http://pitando.net/2015/07/24/sonic-pi/feed/ 3 558
Soluciones a los ejercicios de funciones y módulos de Python http://pitando.net/2015/07/23/soluciones-a-los-ejercicios-de-funciones-y-modulos-de-python/ http://pitando.net/2015/07/23/soluciones-a-los-ejercicios-de-funciones-y-modulos-de-python/#comments Thu, 23 Jul 2015 07:06:50 +0000 http://pitando.net/?p=550 Sigue leyendo Soluciones a los ejercicios de funciones y módulos de Python ]]> La semana pasada os dejaba dos ejercicios como parte del artículo de funciones y módulos de Python. Se trataba de hacer una calculadora de la siguiente forma: Programa una calculadora que funcione por línea de comandos para las operaciones de suma, resta, multiplicación y división, y para un máximo de 2 números enteros (sumar, restar, multiplicar y dividir dos números enteros). Debe reconocer los símbolos usuales de estas operaciones: “+”, “-“, “*” y “/”. Debes usar funciones, una para cada operación. Captura de pantalla 2015-07-11 19.23.00 Ten en cuenta, eso sí, dos cosas que no tienes por qué saber aún:

  • Los valores numéricos llegarán a tu programa a través de la línea de comandos como cadenas, siempre. Es decir, cuando pases como parámetro un 2, tu programa Python entenderá “2” y no 2. Para que tus operaciones aritméticas funcionen bien, fuerza una conversión a números con la función int(), a la que le pasarás como parámetro cada argumento que quieras que se interprete como un número entero.
  • Ésta ya la habrás podido notar en la imagen superior 🙂 En Linux y en Mac OS X, el asterisco “*” es un caracter especial para el Terminal y lo interpretará antes de pasárselo a tu programa Python. Para que llegue un asterisco a tu programa, cuando lo pruebes tendrás que escapar el asterisco usando una barra invertida, : ./calculadora.py 2 * 3. Esto no ocurrirá en Windows.

Posteriormente, había un segundo ejercicio que trataba de mejorar la calculadora anterior para que usase un módulo. Vamos con las soluciones Lo primero que tenemos que hacer es preparar un programa ejecutable capaz de procesar la línea de comandos, de forma que podamos pasarle la información necesaria: dos factores y una operación enmedio. Para eso, vamos a crear el siguiente esqueleto y grabarlo con un nombre significativo, como calculadora.py:

#!/usr/bin/python3 
import sys 
# Programa de cálculo básico con enteros 
exit(0)

 Para dejarlo todo listo, podemos ya otorgarle permisos de ejecución con chmod +x calculadora.py usando el Terminal en el directorio en el que lo hemos guardado. Recuerda que si estás usando Windows puedes omitir la línea #!/usr/bin/python3 y tampoco tienes por qué conceder permisos de ejecución al programa. Si estás en Mac OS X puedes usar la orden which python3 para localizar la ruta que poner en la línea que comienza por #/.

A continuación vamos a pasar a definir las cuatro funciones de cálculo básico a continuación del comentario y antes de la sentencia exit (0):

def suma (sumando1, sumando2): 
    "Devuelve la suma de dos números" 
    return sumando1 + sumando2 

def resta (minuendo, sustraendo): 
    "Devuelve la resta de dos números" 
    return minuendo - sustraendo 
    
def producto (multiplicando, multiplicador): 
    "Devuelve el producto de dos números" 
    return multiplicando * multiplicador 
    
def cociente (dividendo, divisor): 
    "Devuelve el cociente de dos números" 
    return dividendo / divisor 

En estas definiciones me he preocupado en ir al grano y no procesar cada argumento como cadena, tal y como avisaba en el enunciado, sino asumir que quien llame a la función lo hará con las debidas precauciones convirtiendo previamente los argumentos del programa a números. Esto no es arbitrario, y es importante explicar bien por qué. Si programamos funciones y módulos es porque queremos que éstos sean lo más reutilizables posibles: que se puedan usar en muchas situaciones y contextos distintos. Si metemos dentro de las funciones la conversión de cadena a entero estaremos rompiendo ese sencillo principio porque sólo servirán para aquellos programas y contextos en los que se manejen los datos como cadenas de texto. Por ejemplo y sin ir más lejos, ya no serían de utilidad desde idle. Es mucho mejor hacer que las funciones se preocupen de los problemas técnicos más cercanos a lo que quieren ejecutar, y nada más: en este caso, aritmética. Tras la definición de las funciones de cálculo podremos poner ya la condición principal, que detecta la operación e invoca la función correspondiente:

if sys.argv[2] == "+": 
    print (suma (int(sys.argv[1]), int(sys.argv[3]))) 
elif sys.argv[2] == "-": 
    print (resta (int(sys.argv[1]), int(sys.argv[3]))) 
elif sys.argv[2] == "*": 
    print (producto (int(sys.argv[1]), int(sys.argv[3]))) 
elif sys.argv[2]== "/": 
    print (cociente (int(sys.argv[1]), int(sys.argv[3]))) 
else: 
    print ("No se ha reconocido la operación "" + sys.argv[2] + "".") 
    exit (1)

Si os fijáis, hemos hecho siempre conversiones a enteros, pero podríamos haberla hecho a números naturales con otras funciones como float(). Probadlo si queréis. También podéis ver que cuando el usuario introduce una operación no reconocible, el programa devuelve un mensaje explicando que ha ocurrido un error y sale con un código distinto de 0: 1. Recordad que, para el sistema operativo, un 0 como código de salida indica que el programa ha tenido éxito en su misión, así que si algo no ha salido como queríamos deberemos devolver un código diferente a 0. Podemos asignar cualquier número, y debemos usar distintos números para errores de distinta naturaleza, y siempre deberíamos documentar nuestros códigos si nuestro programa va a ser usado como parte de un sistema mayor. El código completo quedaría de esta forma:

#!/usr/bin/python3
 
import sys
 
# Programa de cáculo básico con enteros.
 
def suma (sumando1, sumando2):
        "Devuelve la suma de dos números"
        return sumando1 + sumando2
 
def resta (minuendo, sustraendo):
        "Devuelve la resta de dos números"
        return minuendo - sustraendo
 
def producto (multiplicando, multiplicador):
        "Devuelve el producto de dos números"
        return multiplicando * multiplicador
 
def cociente (dividendo, divisor):
        "Devuelve el cociente de dos números"
        return dividendo / divisor
 
if sys.argv[2] == "+":
    print (suma (int(sys.argv[1]), int(sys.argv[3])))
elif sys.argv[2] == "-":
    print (resta (int(sys.argv[1]), int(sys.argv[3])))
elif sys.argv[2] == "*":
    print (producto (int(sys.argv[1]), int(sys.argv[3])))
elif sys.argv[2]== "/":
    print (cociente (int(sys.argv[1]), int(sys.argv[3])))
else:
    print ("No se ha reconocido la operación "" + sys.argv[2] + "".")
    exit (1)
 
exit (0)

Vamos ahora a mover las funciones de aritmética a un módulo llamado artimetica_basica.py. Recordad que tenemos que guardarlo en el directorio principal del usuario si estamos en Linux, pero si estamos en Mac OS X en el directorio de documentos; en el directorio de instalación de Python si estamos trabajando en Windows. La localización de los módulos en Python está determinada por la librería sys, en una variable que se llama sys.path, sobre la que trabajaremos en otro artículo. El módulo aritmetica.py debe quedar así:

def suma (sumando1, sumando2):
        "Devuelve la suma de dos números"
        return sumando1 + sumando2
 
def resta (minuendo, sustraendo):
        "Devuelve la resta de dos números"
        return minuendo - sustraendo
 
def producto (multiplicando, multiplicador):
        "Devuelve el producto de dos números"
        return multiplicando * multiplicador
 
def cociente (dividendo, divisor):
        "Devuelve el cociente de dos números"
        return dividendo / divisor

A continuación, editamos el programa calculadora.py para que use dicho módulo:

#!/usr/bin/python3
 
import sys
import aritmetica_basica
 
# Programa de cáculo básico con enteros.
 
if sys.argv[2] == "+":
    print (aritmetica_basica.suma (int(sys.argv[1]), int(sys.argv[3])))
elif sys.argv[2] == "-":
    print (aritmetica_basica.resta (int(sys.argv[1]), int(sys.argv[3])))
elif sys.argv[2] == "*":
    print (aritmetica_basica.producto (int(sys.argv[1]), int(sys.argv[3])))
elif sys.argv[2]== "/":
    print (aritmetica_basica.cociente (int(sys.argv[1]), int(sys.argv[3])))
else:
    print ("No se ha reconocido la operación "" + sys.argv[2] + "".")
    exit (1)
 
exit (0)

Y esto es todo. Recordad que podéis dejar cualquier comentario o duda en los comentarios de esta entrada o a través del formulario de contacto del blog.

]]>
http://pitando.net/2015/07/23/soluciones-a-los-ejercicios-de-funciones-y-modulos-de-python/feed/ 1 550
Funciones y módulos en Python http://pitando.net/2015/07/16/funciones-y-modulos-en-python/ http://pitando.net/2015/07/16/funciones-y-modulos-en-python/#comments Thu, 16 Jul 2015 09:00:54 +0000 http://pitando.net/?p=532 Sigue leyendo Funciones y módulos en Python ]]> Este artículo es el último de la primera serie de Python que quiero hacer en PItando, y cierra el círculo de las técnicas más básicas necesarias para empezar ya a ponernos a hacer experimentos. En este artículo explicaré cómo se programan funciones en Python, y cómo agrupando funciones en archivos especiales conseguimos librerías propias que poder incluir en cualquiera de nuestros programas. Por último os propondré como ejercicio una calculadora sencilla que acepte sus parámetros por la línea de comandos.

¿Apetece? Pues al lío

Cómo definir funciones

Una función en Python se define de la siguiente forma:

def nombre_funcion ( argumentos ) :
    ["Cadena de documentación"]
    sentencias
    [return valor]

La definición de funciones siempre es igual y de la siguiente forma:

  • Empieza con la palabra clave def
  • Separado por un espacio, el nombre para la función. Los nombres de funciones pueden tener los siguientes caracteres:
    • Alfabéticos: “a, b,… z, A, B,… Z”. Incluyendo las letras ñ, Ñ y las vocales acentuadas, pero no es recomendable por si quieres ejecutar tu programa en un ordenador que no los soporte .
    • Guiones bajos, “_”
    • Numéricos: “0, 1,… 9”
  • Entre paréntesis, los argumentos: variables que aceptará la función. Si no acepta parámetros, no los ponemos, pero los paréntesis siempre tienen que ir.
  • Dos puntos.

Tras la definición de la función viene el cuerpo, o la implementación: la colección de sentencias que se ejecutarán al llamar a la función. Tiene la siguiente estructura:

  • Una cadena de texto entre comillas con la que explicaremos en palabras sencillas qué hace la función. Es opcional, pero muy conveniente como veremos.
  • Una serie de sentencias en Python.
  • Opcionalmente, una sentencia return que devolverá un resultado.

Vamos a probarlo, y a investigar un poco en las consecuencias, posibilidades y algunas cosas que conviene entender. Abre IDLE para Python 3 y escribe lo siguiente:

def suma ( sumando1, sumando2 ):
    "Devuelve la suma de 2 números"
    resultado = sumando1 + sumando2
    return resultado

Cuando acabes, pulsa 2 veces intro para volver al modo interactivo. Ahora escribe lo siguiente:

suma (2, 3)

Verás que al tiempo que lo escribes, IDLE te proporciona información acerca de la función y gracias a la cadena que hemos escrito:
Captura de pantalla 2015-07-11 18.23.00

Al terminar de escribirlo y pulsar intro, IDLE nos responde con el resultado:
Captura de pantalla 2015-07-11 18.24.00

Ahora, echa un vistazo de nuevo a la función. En su cuerpo hemos definido una nueva variable, resultado, que no está entre los parámetros de la función y que, evidentemente, sirve para alojar el resultado. Intenta acceder a ella desde fuera de la función escribiendo resultado en IDLE: verás que obtienes un error.

Captura de pantalla 2015-07-11 18.28.00

Traduciéndolo más o menos libremente, lo que dice ese letrero en rojo es lo siguiente:

Trazado (la llamada más reciente, de última):
    Fichero "", línea 1, en ;
        resultado
ErrorNombre: nombre 'resultado' no definido

Nos interesa la última línea; el resto sería de interés si estuviéramos escribiendo un programa en un fichero. Este error se produce porque la variable resultado no está definida en el ámbito donde hemos querido usarla, sino dentro de la función suma. Fuera de la función suma, resultado no existe. Y, es más, podríamos haberla definido y haberle asignado un valor, y seguiría teniéndolo. Prueba lo siguiente, escribiendo todas las líneas que ves en el cuadro:

resultado = 3
suma (3, 5)
resultado

Esto será lo que habrá pasado. Como ves, ahora resultado sí existe y, a pesar de que la suma vale 8 y, por lo tanto, “el resultado de dentro de la función suma” valdría 8, el que hemos definido fuera sigue valiendo 3:

Captura de pantalla 2015-07-11 18.35.00

Esto obedece a lo que se conoce como el ámbito de las variables: cuando se define una variable dentro de una función, se crea y se destruye dentro de la misma, sin afectar a las variables de fuera, se llamen como se llamen.

Las funciones aceptan como argumentos muchas cosas, entre ellas otras variables (no sólo valores literales). Otra prueba que podemos hacer es la siguiente. Escribe en IDLE la siguiente función:

def prueba_parametros ( parametro1, parametro2 ):
    parametro2 = parametro1.upper()

Ahora prueba lo siguiente:

nombre = "Gabriel"
cadena = "Valor anterior"
prueba_parametros (nombre, cadena)

¿Qué crees que ocurrirá?, ¿qué valor tendrá cadena después de la llamada a la función prueba_parametros? Vamos a verlo:

Captura de pantalla 2015-07-11 18.47.00

Como ves, para los casos sencillos como los números, cadenas, etc., los parámetros de una función son inmutables, aunque se pasen como variables. Dentro de la función sí habría cambiado el valor parametro2, pero ese cambio se perdería al salir de la función. Puedes interpretarlo como que Python crea una copia de las variables que se pasan por parámetro. Esto, en “argot” se llama pasar los parámetros por valor. ¿Existe algún caso en el que una función pueda cambiar los parámetros? Sí, y en ese caso hablaríamos de paso de parámetros por referencia. Se produce en el caso de muchas variables complejas como podrían ser las listas, pero lo veremos en su momento en otra entrada cuando profundicemos en los tipos de datos compuestos (conjuntos, listas, objetos,…).

Ejercicio de funciones

Programa una calculadora que funcione por línea de comandos para las operaciones de suma, resta, multiplicación y división, y para un máximo de 2 números enteros (sumar, restar, multiplicar y dividir dos números enteros). Debe reconocer los símbolos usuales de estas operaciones: “+”, “-“, “*” y “/”. Debes usar funciones, una para cada operación.

Captura de pantalla 2015-07-11 19.23.00

Ten en cuenta, eso sí, dos cosas que no tienes por qué saber aún:

  1. Los valores numéricos llegarán a tu programa a través de la línea de comandos como cadenas, siempre. Es decir, cuando pases como parámetro un 2, tu programa Python entenderá `”2″` y no `2`. Para que tus operaciones aritméticas funcionen bien, fuerza una conversión a números con la función `int()`, a la que le pasarás como parámetro cada argumento que quieras que se interprete como un número entero.
  2. Ésta ya la habrás podido notar en la imagen superior :). En Linux y en Mac OS X, el asterisco “*” es un caracter especial para el Terminal y lo interpretará antes de pasárselo a tu programa Python. Para que llegue un asterisco a tu programa, cuando lo pruebes tendrás que escapar el asterisco usando una barra invertida, : ./calculadora.py 2 * 3. Esto no ocurrirá en Windows.

Módulos

Un módulo no es más que un fichero que contiene definiciones de funciones, una tras otra, y que se puede importar con la palabra clave import en un programa en Python. No tiene ningún requisito especial; simplemente, hay que escribir una serie de funciones todas juntas en un fichero.

Por ejemplo, escribamos el siguiente código en el editor de texto de IDLE, abriendo una nueva ventana de edición de texto:

def saluda_esp (nombre):
    return "Hola, " + nombre

def saluda_ing (name):
    return "Hello, " + name

def saluda (nombre, idioma):
    if str(idioma).lower() == "esp":
        return saluda_esp(nombre)
    elif str(idioma).lower() == "ing":
        return saluda_ing(nombre)
    else:
        return "Idioma no soportado"

Grábalo, por ejemplo, con el nombre idiomas.py en el directorio del usuario pi, en tu directorio principal de Documentos en Mac OS X (/Users/tu_usuario/Documents). En Windows, guárdalo en el directorio de instalación de Python (en mi caso c:\progPython34). Ahora, en IDLE, podrás importar el módulo y usar sus funciones anteponiendo a su nombre el del módulo, de la siguiente forma (pruébalo):

import idiomas

idiomas.saluda("PItando", "ing")
idiomas.saluda_esp("PItando")

Este es el resultado:

Captura de pantalla 2015-07-11 19.53.00

En un programa deberás hacer exactamente igual: usar las funciones anteponiendo el nombre del Módulo. Esto sucede porque las librerías que programemos nosotros no son estándares de Python.

Ejercicio de módulos

Mejora el programa del ejercicio anterior para que use una librería llamada “aritmetica_basica” en lugar de declarar las funciones en el propio programa.

Y esto es todo por este artículo. Espero que os parezca útil esto, y preparaos: ¡en breve vamos a empezar a usar periféricos de la Raspberry Pi que podremos programar con Python!

]]>
http://pitando.net/2015/07/16/funciones-y-modulos-en-python/feed/ 4 532
Solución al ejercicio de programas ejecutables y línea de comandos http://pitando.net/2015/07/16/solucion-al-ejercicio-de-programas-ejecutables-y-linea-de-comandos/ Thu, 16 Jul 2015 07:45:33 +0000 http://pitando.net/?p=493 Sigue leyendo Solución al ejercicio de programas ejecutables y línea de comandos ]]> La semana pasada planteaba un problema partiendo del siguiente programa:

#!/usr/bin/python3
import sys

# usuario = "lector de PItando"
usuario = sys.argv[1]
print("Hola, ", usuario)

exit(0)

El ejercicio decía así:

Modifica el programa para que funcione de la siguiente forma:

  1. Si su primer argumento es “?” imprima un mensaje para ayudarnos a usarlo, en el que salga el nombre del programa ejecutable (para lo que tendrás que usar la variable sys.argv).
  2. Si no se especifica ningún argumento, debe saludar al lector (“Hola, lector“).Para detectar la longitud de una lista, usa la función len(lista).
  3. También haz que junte todos los argumentos en el mensaje de saludo. Para eso, ten en cuenta que en Python podemos juntar varias cadenas en una sola “sumándolas”:
cadena = "Hola" + ", Mundo"
# Cadena tendrá como valor "Hola, Mundo"

Vamos con la solución.

Lo mejor que podemos hacer es partir el problema en varios. Vamos a intentar detectar cuándo una lista está vacía, siguiendo el consejo del enunciado. Abre IDLE y prueba la función len(lista) con una lista que crearemos de una forma muy similar al conjunto, pero con corchetes (“[“, “]”) en lugar de llaves (“{“, “}”):

lista = [1, 2, 3]
len (lista)

El resultado que deberemos obtener es un 3: la lista tiene 3 elementos. Si creamos una lista vacía y examinamos su longitud, veremos que obviamente dará 0:

listaVacia = []
len (listaVacia)

Lo que ocurre es que, si os acordáis, sys.argv[0] siempre viene relleno con el nombre del programa ejecutable, por lo que la comparación no debe ser con 0, sino con 1. El programa se invocará sin parámetros si el tamaño de sys.argv es igual a 1. Así pues, nuestro programa tendrá que actuar en consecuencia:

...
if len (sys.argv) == 1:
    print ("Hola, lector.")
else:
...

El caso en el que el primer argumento sea “?” es muy fácil, simplemente hay que insertar un caso elif (posiblemente te tengas que desplazar a la derecha para ver todo el código):

...
if len (sys.argv) == 1:
    print ("Hola, lector")
elif sys.argv[1] == "?":
    print ("Uso de", sys.argv[0],":")
    print (" ", sys.argv[0], "nombre para saludar (y apellidos si se quiere).")
else:
...

Como ya vimos, la lista se accede poniendo el índice que deseamos leer entre corchetes. Prueba el siguiente código, que sirve para recorrer una lista:

indice = 0
while indice < len (lista):
    print("lista[", indice,"]:", lista[indice])
    indice = indice + 1

Como verás, la lista tiene 3 elementos, cuyos índices son 0, 1 y 2. EL último elemento de una lista siempre tiene como índice su longitud menos 1, ya que el primer elemento de la lista siempre tiene como índice el 0.

Ya sólo queda generar el saludo con todos los argumentos, si existen, y si el primero de ellos no es "?". Se hace con un while muy parecido al de arriba, así que directamente pondré el código definitivo del programa (posiblemente te tengas que desplazar a la derecha para ver todo el código):

 

#!/usr/bin/python3
import sys

if len (sys.argv) == 1:
    print ("Hola, lector.")
elif sys.argv[1] == "?":
    print ("Uso de", sys.argv[0], ": ")
    print (" ", sys.argv[0],"nombre para saludar (y apellidos si se quiere).")
else:
    cadenaSaludo = ""
    indice = 1
    while indice < len (sys.argv):
        cadenaSaludo = cadenaSaludo + sys.argv[indice] + " "
        indice = indice + 1
    print("Hola,", cadenaSaludo)
exit(0)

 

Recuerda que, si lo guardas en otro fichero diferente al que usaste la semana pasada, deberás darle permisos de ejecución si lo estás haciendo con la Raspberry Pi, otro ordenador con Linux, o con un Mac.

Y esto es todo. Con este ejercicio hemos repasado todo lo que conocemos: línea de comandos, programas ejecutables, bucles y condiciones. Recuerda que si tienes alguna duda puedes dejarla en los comentarios de esta entrada o a través del formulario de contacto.

]]>
493
Episodio 1 – (Casi) Un mes de PItando. Qué está por venir http://pitando.net/2015/07/09/episodio-1-casi-un-mes-de-pitando-que-esta-por-venir/ Thu, 09 Jul 2015 18:35:25 +0000 http://pitando.net/?p=519 PItando podcast, logoEn este episodio del podcast repaso este (casi) un mes en PItando, el primero, en lo que se refiere sobre todo a Python, y explico por qué me he centrado tanto en este lenguaje de programación.
 
Básicamente, de lo que hablo en esta entrada es de la proyección que tiene Python en la Raspberry Pi y en la industria por sí mismo, y por qué es conveniente para el aprendizaje en la programación más allá de su orientación a la sencillez y al aprendizaje.
 
Para terminar, doy unas pistas sobre lo que está por venir en PItando, como proyecto.
 
Disponible en iTunes y en iVoox.
 
Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.
]]>
519
Soluciones a los ejercicios de bucles y condiciones http://pitando.net/2015/07/09/soluciones-a-los-ejercicios-de-bucles-y-condiciones/ Thu, 09 Jul 2015 10:00:38 +0000 http://pitando.net/?p=472 Sigue leyendo Soluciones a los ejercicios de bucles y condiciones ]]> La semana pasada teníamos 2 ejercicios que resolver. Aquí van las soluciones.

1) Escribe un programa en IDLE que escriba aquellos números entre 1 y 100 que:

  • Sean pares
  • No sean múltiplos de 4
  • Tampoco sean múltiplos de 3.

Escríbelo dos veces: una con un bucle while y otra usando un bucle for.

Voy a empezar explicando la condición que indica si un número se debe escribir, o no. Si la leemos en castellano, deberíamos decir:

Si el resto de dividir el número entre 2 es 0, pero no se cumple que el resto de dividir el número entre 3 sea 0, ni entre 4, imprimirlo.

Dicho en “términos lógicos” usando los operadores de la semana pasada, diríamos que la condición a evaluar es:

numero % 2 == 0 and numero % 3 != 0 and numero % 4 != 0

También podemos escribirla de una forma alternativa si pensamos que debemos imprimirlo si no se cumple que numero % 3 == 0 ó numero % 4 == 0:

numero % 2 == 0 and not (numero % 3 == 0 or numero % 4 ==0)

Usa la que veas más clara; a medida que practiques cogerás soltura con estas alternativas y esta forma de escribir las condiciones lógica. Yo usaré la segunda.

Bucle while (es posible que te tengas que desplazar a la derecha para verlo todo):

numero = 1
while numero < 100:
    if numero % 2 == 0 and not (numero % 3 == 0 or numero % 4 == 0):
        print(numero)
    numero = numero + 1

Bucle for (es posible que te tengas que desplazar a la derecha para verlo todo):

del1al100 = range (1, 101)
for numero in del1al100:
    if numero % 2 == 0 and not (numero % 3 == 0 or numero % 4 == 0):
        print(numero)

En ambos casos la respuesta debe ser la siguiente:

Captura 20150628 - 5

2) Usando for, haz que Python escriba todas las tablas de multiplicar, del 1 al 10:

La solución a este problema consiste en anidar bucles: hacer un bucle dentro de otro. Así (es posible que te tengas que desplazar a la derecha para verlo todo):

for multiplicador in range (1, 11):
    print("Tabla de multiplicar del", multiplicador)
    for multiplicando in range (1, 11):
        print(multiplicando, "*", multiplicador, "=", multiplicando*multiplicador)
    print("==========================")
    print() # línea en blanco.

En este caso he incluido tres de sentencias para formatear un poco el resultado y que quede más legible, y he usado directamente la llamada a range(...) en las condiciones de los bucles.

También habría podido usar un caracter especial,\n, al final del print("=========================="), así: print("==========================\n"). Ese caracter hace que se imprima una línea en blanco, haciendo innecesaria la sentencia print(). Cualquiera de las dos opciones acabaría dejando la salida así (no la reproduzco toda):

Captura de pantalla 2015-07-05 17.14.04

Y esto es todo. Si hay algo que no entiendas, no dudes en dejar un comentario o contactar a través del formulario exponiendo cualquier duda.

]]>
472
Escribir programas ejecutables en Python. La línea de comandos. http://pitando.net/2015/07/09/escribir-programas-ejecutables-en-python-la-linea-de-comandos/ http://pitando.net/2015/07/09/escribir-programas-ejecutables-en-python-la-linea-de-comandos/#comments Thu, 09 Jul 2015 07:37:00 +0000 http://pitando.net/?p=421 Sigue leyendo Escribir programas ejecutables en Python. La línea de comandos. ]]> Hasta ahora hemos hecho todos nuestros experimentos usando directamente IDLE, el entorno de programación propio de Python. Hemos visto una serie de conceptos básicos para comenzar a entender el lenguaje, y ya estaríamos en condición de escribir nuestros propios programas, es decir: más allá de probar sentencias en IDLE, escribir los programas en un fichero para ejecutarlos directamente desde el sistema operativo.

En esta entrada vamos a ver dos cosas: cómo se escribe un programa en Python de una forma que luego sea posible usarla, y por otro lado, una de las muchas formas en las que nuestros programas pueden recibir datos con los que trabajar y con la que realmente ya estamos familiarizados gracias al artículo sobre el terminal: la línea de comandos. De paso, entraremos en contacto con la inclusión de funcionalidad ya existente en nuestros programas: las librerías y cómo incluirlas.

Lo primero que vamos a hacer es crear un directorio dentro de la carpeta del usuario pi, si trabajas en la Raspberry, o en el lugar de tu elección si trabajas en Windows, en otro ordenador con Linux, o en el Mac, para guardar los ejercicios que hagamos a partir de ahora. Recuerda que si lo haces desde el terminal, la orden que debes usar para crear el directorio, por ejemplo, pitando, es la siguiente:

mkdir pitando

Escribir programas en Python ejecutables desde el sistema

Para este apartado nos sobra y nos basta con una variante mejorada del programa original con el que empezamos a movernos en Python: 

print("Hola, mundo")

Lo que vamos a hacer con él es que acepte variables: haremos que use dentro del saludo un nombre de persona que leerá de una variable. Así:

print("Hola, ", usuario)

Puedes probarlo en IDLE para ver que el resultado es el esperado: si asignas un valor a la variable usuario y lo ejecutas, deberías ver lo siguiente:

Captura 20150702 - 1

Es hora de guardarlo en el disco duro, hacerlo ejecutable y probarlo, primero en la Raspberry Pi, es decir, en Linux, y en el Mac (el proceso es exactamente igual para estas 3 plataformas). Luego haremos lo mismo en Windows, ya que el proceso es ligeramente distinto.

Abre el editor de texto de IDLE yendo al menú “File”, y haciendo click en la opción “New File”. Se abrirá una ventana en blanco en la que podremos escribir un programa completo disfrutando del coloreado del lenguaje y de los tabuladores automáticos igual que en la propia ventana interactiva de IDLE:

Captura de pantalla 2015-07-05 20.49.34

También puedes abrir esta misma ventana de editor con Control + N en tu Raspberry Pi, en Linux y en Windows, o con Comando + N en Mac. Comando es la tecla en la que pone cmd y el símbolo .

Escribe el siguiente contenido en el editor de textos que acabas de abrir:

#!/usr/bin/python3

usuario = "lector de PItando"
print("Hola, ", usuario)

exit(0)

En este listado hay 2 líneas que no conocemos, veámoslas una por una:

#!/usr/bin/python3

Esta línea no es Python, es una línea estándar del Terminal de Linux. Indica que el terminal tendrá que invocar al intérprete de Python para poder interpretar todo lo que sigue. En detalle:

  • # es un caracter que marca el comienzo de un comentario en programas de Terminal. Se llama “marca de comentario”. Casualmente, también en Python, pero insisto que esta línea pertenece y va a ser interpretada por el mismo terminal que vimos en su momento.
  • ! es un símbolo que, si es el estrictamente siguiente a la marca de comentario en el Terminal (sin espacios de por medio), y está en la primera línea del fichero, indica al intérprete del Terminal que debe delegar la interpretación del resto del fichero a un comando que se especifica justo a continuación.
  • /usr/bin/python3 es la ruta en donde está el intérprete de Python 3 en la Raspberry Pi. En el Mac está en una ubicación totalmente diferente, que es /Library/Frameworks/Python.framework/Versions/3.4/bin/python3

A partir de esa línea, y hasta el final del fichero, ahora sí todo es Python: el mismo código, además, que hemos estado probando en la ventana interactiva de IDLE.

Un pequeño truco antes de seguir: en cualquier Unix puedes obtener la ruta donde está instalado un programa con la orden which:

which python3

En Mac OS X daría como resultado /Library/Frameworks/Python.framework/Versions/3.4/bin/python3, mientras que en Linux (por ejemplo, en la Raspberry Pi) daría /usr/bin/python3.

La última línea también merece una mención especial:

exit(0)

Esta línea es una llamada a una función de Python, como print(), pero de un tipo especial: es una de las conocidas como “llamadas al sistema”. Las llamadas al sistema son una clase de funciones que se ocupan de comunicar tu código, en cualquier lenguaje, con el sistema operativo, tanto para pedir cosas como para proporcionarlas. En este caso, esta función realiza dos cosas:

  • Termina con la ejecución del programa. Cualquier línea de código Python tras ella, jamás será ejecutada.
  • Comunica que ha terminado con éxito, mediante el paso del número 0. El 0 aquí es un código que indica que no ha habido error.

¿Para qué sirve comunicar el resultado de la ejecución al sistema, si nuestra tarea ya ha terminado? Porque una de las gracias que tiene esto de programar es que un programa puede formar parte de otro más grande, y así sucesivamente, todo lo que queramos. Si no comunicamos un resultado, el programa que nos llame no podrá reaccionar ante lo que hayamos podido hacer.

Una vez visto todo esto, toca guardar el programa y ejecutarlo: entra en el menú “File”, opción “Save As…”. Aparecerá una ventana titulada “Guardar como” (sí, en español: este es el encanto que tienen las traducciones parciales), que espera que indiques la carpeta donde quieres guardar el programa, y el nombre que quieres darle. Las ventanas que esperan una respuesta por tu parte para continuar con un proceso puntual, como éste que consiste en guardar el fichero con un nombre, se llaman “cuadros de diálogo” (porque es un diálogo entre el programa y tú). En principio aparece en la carpeta principal del usuario pi:

Captura de pantalla 2015-07-05 20.55.02

Entraremos en la carpeta creada al principio del artículo (pitando), y daremos un nombre al fichero, como podría ser ejemplo.py. Pulsa intro para terminar, o haz click en el botón “Guardar”.

Ahora abre un Terminal y, usando tus conocimientos del artículo de uso del terminal, sitúate en el directorio recién creado, donde has guardado el programa que hemos escrito. Una vez allí, comprueba que existe el programa recién guardado y, después, escribe lo siguiente:

chmod +x ejemplo.py

Si ahora ejecutas el comando ls para examinar de nuevo el contenido del directorio, verás que el programa sale en color verde.

Captura 20150702 - 6

¿Qué ha ocurrido aquí? En Linux (y en cualquier Unix, como el Mac OS X), para poder ejecutar un programa hay que darle primero permisos de ejecución. Por seguridad, nada que escribas podrá ejecutarse hasta que se los des. Con chmod podremos actuar sobre permisos de lectura, escritura y ejecución. En el ejemplo hemos puesto +x: el signo + otorga permisos, y la letra x especifica el tipo “ejecución”. Si no especificamos nada más, los permisos otorgados o retirados afectarán al usuario actual. Consulta su página de manual como hemos aprendido la semana pasada para ver en detalle sus opciones. Recuerda que si no lo has hecho, ejecutar el comando siguiente en la consola descarga de internet la mayor cantidad posible de traducciones al español de las páginas del manual:

sudo apt-get install manpages-es

Ya sólo queda ejecutar el programa recién programado. Escribe lo siguiente:

./ejemplo.py

Verás que el programa ha funcionado porque nos saluda a través del Terminal, ya que en este caso es la salida estándar. La salida estándar viene a significar “el mismo medio por donde nos han llegado las órdenes”, ¿recuerdas? El resultado que deberías ver es el siguiente:

Captura 20150702 - 7

Sólo nos queda explicar por qué hay que ejecutar el programa poniendo delante ./. Linux, y todos los Unix (como el Mac OS X) sólo ejecutan programas de una serie de carpetas muy bien definidas, y ~/pitando no es una de ellas. Si queremos que el intérprete del terminal busque un programa en la carpeta donde estamos y no en otra, debemos especificarle la carpeta presente, que es justamente ./ como ya vimos la semana pasada. Si no, se iría a buscarlas a otras carpetas. ¿A cuáles? Escribe en el Terminal la siguiente orden:

echo $PATH

$PATH es una variable del itérprete del Terminal, más conocida como “variable de entorno”, que tiene una lista de directorios separados por dos puntos, “:”. echo es una orden del intérprete del Terminal que imprime valores de una forma muy parecida a print() en Python. Consulta su página de manual para saber más.

La línea de comandos

Es posible pasarle una serie de valores a un programa a través de la línea de comandos. Es decir, a través de la línea que escribimos en el terminal para poder hacer la llamada. Para eso hay que preparar el programa para recibir argumentos enviados desde el terminal, y procesarlos.

Primero, tenemos que importar una librería que nos ayude a recibir y procesar la línea de comandos, en este caso la librería sys. Abre el fichero ejemplo.py y, tras la línea #!/usr/bin/python3 escribe import sys. Quedará así:

#!/usr/bin/python3
import sys

Ahora comenta la línea usuario = "lector de PItando" añadiendo una almohadilla al principio, y añade usuario = sys.argv[1]. Debe quedar así:

#!/usr/bin/python3
import sys

# usuario = "lector de PItando"
usuario = sys.argv[1]
print("Hola, ", usuario)
 
exit(0)

Guárdalo con la opción “Save” del menú “File” y vuelve a ejecutarlo en la consola escribiendo esta vez lo siguiente:

./ejemplo.py Mortadelo

Verás que ahora el programa saluda a Mortadelo.

Captura 20150702 - 8

¡Estupendo!, ya sabemos enviar información a un programa desde el terminal, a la hora de invocarlo, igual que hacemos con todas las órdenes del sistema que vimos la semana pasada: id, mkdir, ls,… Esto da muchísima flexibilidad porque ya tenemos una pieza más para hacer programas realmente útiles; una pieza fundamental: la que nos permite enviar la información que queremos en cada caso.

Ya sólo nos queda explicar la línea usuario = sys.argv[1]. Al importar la librería sys hemos conseguido ciertas funcionalidades que nos permiten acceder a la línea de comando que hemos usado para llamar a nuestro programa. En la variable sys.argv, que es una lista, tenemos lo siguiente:

  1. En la primera posición, el nombre de nuestro programa; en este caso, ./ejemplo.py.
  2. En la segunda posición, tenemos el primer argumento.
  3. En la tercera, el segundo.

Y así sucesivamente. Las listas en Python empiezan a contar sus elementos desde 0, por eso para obtener el primer argumento (Mortadelo) hemos recogido el elemento número 1.

Ejercicio

Para reflexionar y aplicar los conceptos aprendidos hasta ahora, modifica el programa para que funcione de la siguiente forma:

  1. Si su primer argumento es “?” imprima un mensaje para ayudarnos a usarlo, en el que salga el nombre del programa ejecutable (para lo que tendrás que usar la variable sys.argv).
  2. Si no se especifica ningún argumento, debe saludar al lector (“Hola, lector“). Para detectar la longitud de una lista, usa la función len(lista).
  3. También haz que junte todos los argumentos en el mensaje de saludo. Para eso, ten en cuenta que en Python podemos juntar varias cadenas en una sola “sumándolas”:
cadena = "Hola" + ", Mundo"
# Cadena tendrá como valor "Hola, Mundo"

La semana que viene haré un artículo comentando la solución.

Vamos, por último, a ver cómo adaptar este programa a Windows

En Windows

Windows es algo completamente diferente a Unix, que es el sistema en el que se basa tanto el Linux de la Raspberry Pi (o cualquier otro) o muchos aspectos del Mac OS X. No queda otro remedio que dedicarle un tratamiento aparte siempre que nos queramos comunicar con el sistema operativo desde nuestros programas.

En este caso, ni la línea #!/usr/bin/python3 ni nada parecido funcionará en Windows. Podemos dejar dicha línea porque, al ser un comentario (# es la marca de comentario de Python), no molesta, pero en cualquier caso para ejecutar el programa deberemos primero escribir la ruta completa al ejecutable de Python. Entonces:

  • Guarda el código en un directorio de tu elección. Yo por ejemplo he creado una carpeta pitando dentro de la librería de Documentos, y he guardado el programa como ejemplo.py. Le he quitado la primera línea, como discutíamos antes.
  • Abre una consola de Windows, “ventana de MS-DOS” o “Ventana de línea de comandos” pulsando la tecla de Windows + R y escribiendo cmd:

Captura de pantalla 2015-07-05 15.54.09

  • Sitúate en la carpeta donde has guardado el programa. Si lo has guardado como yo bajo la librería de documentos de tu cuenta de usuario, escribe cd %userprofile%documentspitando; si es otra carpeta cualquiera, escribe cd y la ruta correspondiente (por ejemplo, si es c:progpitando, cd c:progpitando).
  • Ahora, recuerda dónde has instalado Python y localiza el programa python.exe, que es el intérprete de Python para Windows. Por ejemplo, yo lo he instalado en c:progPython34, y ahí es donde está el programa python.exe:

Captura de pantalla 2015-07-05 16.00.32

  • Vuelve a la ventana de línea de comandos y escribe la ruta completa hasta el programa python.exe y el nombre del programa (ejemplo.py), más el parámetro que sería el nombre a saludar. Pulsa intro:

Captura de pantalla 2015-07-05 16.02.09

No hace falta que me lo digáis: es un proceso muy farragoso, y por ejemplo no tenemos que darle permisos de ejecución al programa porque lo ejecutamos a través del propio intérprete.

Podemos hacerlo un poquito más cómodo incluyendo el directorio de instalación de Python en la variable de entorno PATH. Esta variable de entorno funciona igual que en Unix: tiene una lista de directorios donde el sistema operativo buscará programas ejecutables. Para hacerlo, haz click con el botón derecho en Mi PC (o “Este equipo”, dependiendo de tu versión de Windows) y selecciona “Propiedades”. Ve a “Configuración avanzada del sistema” y, en la pestaña “Opciones avanzadas” haz click en el botón “Variables de entorno”:

Captura de pantalla 2015-07-05 16.07.48

Captura de pantalla 2015-07-05 16.08.00

Pulsa en el botón “Nueva…” dentro de la sección “Variables de usuario para Gabriel” (en vez de Gabriel pondrá tu nombre) y, dentro del cuadro de diálogo que se te abrirá, escribe como nombre de la variable PATH (sin comillas) y como valor el directorio donde has instalado Python, un punto y coma, y después %PATH%.

Así:

Captura de pantalla 2015-07-05 16.08.28

Ahora pulsa en Aceptar en todas las ventanas que has abierto de las propiedades del PC (Mi PC / Este Equipo) y haz lo siguiente:

  • Cierra la ventana de línea de comandos / terminal de Windows / ventana de MS-DOS, y vuelve a abrirla (Windows + R, escribir cmd y pulsar intro).
  • Sitúate en el directorio donde has guardado el programa en Python (ejemplo.py)
  • Ejecútalo simplemente con python ejemplo.py Filemón

Captura de pantalla 2015-07-05 16.09.19

No es mucho, pero algo hemos ganado.

]]>
http://pitando.net/2015/07/09/escribir-programas-ejecutables-en-python-la-linea-de-comandos/feed/ 5 421
Uso del terminal (o la consola) en la Raspberry Pi http://pitando.net/2015/07/02/uso-de-terminal-o-la-consola-en-la-raspberry-pi/ http://pitando.net/2015/07/02/uso-de-terminal-o-la-consola-en-la-raspberry-pi/#comments Thu, 02 Jul 2015 15:00:00 +0000 http://pitando.net/?p=175 Sigue leyendo Uso del terminal (o la consola) en la Raspberry Pi ]]> El terminal, o “la consola” es una aplicación de uso muy frecuente cuando se trata de sistemas Linux como es la Raspberry Pi, y en otros sistemas tipo Unix como puede ser, por ejemplo, Mac OS X. Sobre todo, cuando pensamos en un usuario que hace tareas técnicas como es, por ejemplo, programar. El terminal es un intérprete de órdenes basado en texto que permite manejar la totalidad de un sistema operativo. De hecho es, en sí, un intérprete parecido al de Python que vimos en la entrada correspondiente, y por eso las órdenes que ejecutamos pueden llegar a formar parte de un programa con el que automatizar un montón de procesos, en su propio lenguaje particular.

Lo que vamos a tratar en esta entrada es una serie de órdenes útiles para los primeros días, y cómo funcionan. Os adelanto que saber inglés es, a partir de este momento, conveniente. No es imprescindible pero hay que entender que facilita las cosas; en tecnología el inglés es el lenguaje universal nos guste o no. Sin embargo haré todo lo posible para que no sea una barrera.

Arranca el terminal haciendo click en el botón que hay en la barra de menú, en la parte superior de la pantalla. Este es el resultado que verás:

Captura de pantalla 2015-06-12 18.32.23

Me centraré en la parte negra de la ventana… realmente los menús no son importantes. Fijaos en la información que tenemos: pi@raspberrypi ~ $.

  • pi es el usuario que está conectado, es el usuario de trabajo. Lo recordáis de la entrada de puesta en marcha.
  • significa “en”.
  • raspberrypi es el nombre de la Raspberry Pi, útil para identificarlo cuando se está en red. Es el nombre del ordenador.
  • ~ es un símbolo importantísimo que representa la carpeta de inicio del usuario actual, que en este caso es pi, y es /home/pi.
    • Es decir, vale /home/pi.
    • / es el símbolo que, en la consola, indica que la carpeta pi está dentro de la carpeta home, y se llama separador de carpetaseparador de directorio.
    • Cuando encontramos / solamente, estaríamos ante la carpeta que contiene a todas las demás, y se llama carpeta raíz o directorio raíz.
  • es el indicador de que el intérprete de comandos del terminal espera que escribas algo. En inglés se llama prompt, que significa invitación.
  • El cuadrado gris que sale después es simplemente un cursor que parpadea, que indica que a partir de ahí será donde escribiremos.

Así pues, sin hacer nada, el terminal nos dice que estamos trabajando como el usuario pi, en la máquina raspberry, en el directorio de inicio de pi, y que está en disposición de recibir órdenes. ¿Por qué tanta información? Porque podríamos estar trabajando en la consola como otro usuario, como root (ya hablaremos de ese usuario), el directorio donde estamos siempre viene bien saberlo, pero la máquina también, porque podríamos estar conectados a otro ordenador, trabajando en remoto.

Ya que estamos entendiendo esta información, vamos a ver la estructura general de las órdenes en Linux usando una orden concreta que sirve para averiguar el usuario que está conectado. Escribe lo siguiente:

id -un

Esto hará que la consola nos responda con “pi”. ¿Qué significa lo que hemos escrito?, ¿qué estructura, o qué forma de escribir órdenes es ésa?

En general, una orden o un comando en el terminal se compone de las siguientes partes:

  • Nombre del comando. En este caso, id
  • Opciones o modificadores. En general, detrás de un guión. En este caso, –un. Son opcionales: podríamos haber escrito id solamente… pero el resultado habría sido muy diferente, como veremos.
  • Argumentos: los datos de trabajo de la orden. En este caso son opcionales; después veremos algún ejemplo.

El efecto de las opciones es, en ocasiones, muy profundo. Prueba a escribir ahora id, sin opciones. Como puedes ver, el resultado es totalmente distinto:

Así, en frío, es un galimatías en el que reconocemos al pobrecillo pi por ahí enmedio, repetido varias veces, pero también vemos cosas como sudo, audio, dialout, users… son grupos a los que el usuario pi pertenece. Los números son sinónimos, es decir, son códigos que el sistema operativo usa internamente porque le resulta más conveniente.

Escribid ahora lo siguiente:

man id

Veremos el (maravilloso) Manual mostrando la página de manual de id:

El manual es un programa de terminal que se invoca mediante su nombre, man y cuyo argumento es el nombre de la orden de la que queremos saber más. Para desplazarnos por el manual usaremos de momento las flechas ↑ y ↓. Si quisiéramos salir, usaríamos la tecla q.

No todas las páginas están en inglés, y además podemos instalar la mayor cantidad disponible de traducciones al español ejecutando la orden siguiente:

sudo apt-get install manpages-es

Para este caso concreto el proceso es automático; al necesitar descargar cosas puede que tarde un poco.

En su momento haré un monográfico del sistema de instalación de programas de Linux.

Siempre, en todas las versiones y distribuciones de Linux y de cualquiera de sus primos de la familia Unix, el manual está disponible para todas las órdenes disponibles por defecto, y tiene la siguiente estructura:

  • NAME / NOMBRE: nos explica el nombre de la orden y lo que hace, en una línea. En este caso, id – imprime los IDs reales y efectivos de usuario y grupo.
  • SYNOPSIS / SINOPSIS: de forma muy breve, cómo se usa. Y además en un formato que tiene un significado muy cuidado:
    • El nombre de la órden: id.
    • Dónde van las opciones, si las acepta. Si acepta varias o una sola, y si son opcionales u obligatorias. Veamos:
      • Si existe OPTION es que tiene opciones. A veces, en vez de OPTION, podemos encontrarlas explícitas (–u–n,…)
      • Si va entre corchetes, [ y ], es que son opcionales.
      • Si tras OPTION u [OPTION] hay puntos suspensivos, es que admite más de uno.
      • Si vemos opciones separadas por barras verticales, | es que, en ese ejemplo debemos escoger o una o la otra. Por ejemplo: [–a | –b].
    • Lo mismo para los argumentos:
      • A diferencia de las órdenes, los argumentos siempre se suelen nombrar de forma explicita (por ejemplo USERNAME, que significa NOMBRE DE USUARIO).
      • En este caso, [USERNAME] indica que el argumento es el nombre del usuario que queremos investigar, que es opcional pero que acepta sólo uno.
  • DESCRIPTION / DESCRIPCIÓN: explica las opciones sobre la orden. Una cosa que os puede llamar la atención es que muchas veces las opciones tienen variantes cortas (–u) y largas con dos guiones (– –user). Si la examináis podréis comprobar por qué he escogido las que he escogido:
    • –uimprime sólo el identificador de usuario efectivo. Esto deja el resto de información de grupos y seudónimos fuera.
    • –n: imprime un nombre en lugar de un número. En vez de 1000, escribirá pi.
  • Otras opciones menos útiles pero con información de interés: el autor, cómo informar acerca de defectos en su comportamiento, información sobre la licencia y otros documentos de interés.

Importante: así como las opciones aceptan cualquier orden a no ser que nos digan lo contrario, los argumentos no. Podemos juntar en una sóla opción las versiones cortas, pero las largas no. Para comprenderlo totalmente practica un poco, escribiendo lo siguiente:

id --name -u
id --user -n
id -nu
id -un root
id -u -n
id --nameuser
id -un root pi

Esto es lo que verás:

Sal del Manual usando la tecla q.

Órdenes para listar los contenidos de una carpeta

La orden ls, que viene de LiStar contenido del directorio, sirve para examinar los contenidos de las carpetas donde estemos (sin argumentos) o de la que queramos (con argumentos). Vamos a verlo con unas pruebas.

Estas cuatro órdenes ofrecen el mismo resultado:

ls
ls .
ls ~
ls /home/pi

La explicación es la siguiente: ls, sin argumentos, ofrece los contenidos de la carpeta donde estamos. Con argumentos, ofrece los contenidos de la carpeta que le digamos. . es un símbolo especial que significa “la carpeta donde estamos”. Estamos en la carpeta ~, que es el directorio de inicio del usuario pi, que es /home/pi.

Os recuerdo que / es el símbolo que, en la consola, indica que la carpeta pi está dentro de la carpeta home, y se llama separador de carpeta o separador de directorio. Cuando encontramos / solamente, estaríamos ante la carpeta que contiene a todas las demás, y se llama carpeta raíz o directorio raíz.

Vamos a hacer otras pruebas, ya con opciones:

ls -a
ls -l

-a muestra todos (viene de all) los ficheros, estén ocultos o no. En los sistemas de tipo Unix, como todos los Linux, ocultar un fichero es simplemente hacer que su nombre comience por .. En su resultado podemos ver, además, que los ficheros se representan en gris y las carpetas en azul, y que se representan dos carpetas especiales: ., la carpeta presente, y .., que como veremos más adelante es la carpeta superior a la presente (/home).

-l cambia el formato de salida de la orden ls, dando información adicional para cada carpeta y fichero, como por ejemplo el tamaño, la fecha y los permisos, temas que trataremos más adelante.

Combina los dos ahora escribiendo ls -la para obtener la totalidad de información de la carpeta donde estás.

-a y -l son dos de los argumentos más útiles de la orden ls.

Consulta más opciones en la página de manual de ls, escribiendo man ls. Prueba también a obtener las listas de contenidos de otros directorios tratando de entender cada opción y cada argumento, escribiendo por ejemplo:

ls ..
ls /home
ls python_games
ls ~/..

Rutas relativas frente a rutas absolutas

Os recuerdo de nuevo (es que es muy importante) que / es el símbolo que, en la consola, indica que la carpeta pi está dentro de la carpeta home, y se llama separador de carpeta o separador de directorio. Cuando encontramos / solamente, estaríamos ante la carpeta que contiene a todas las demás, y se llama carpeta raíz o directorio raíz.

Ruta relativa

Una ruta relativa es la sucesión de carpetas que nos llevan a un punto determinado de la tarjeta SD, pero desde donde estemos. Es decir, en este ejemplo, desde ~, o /home/pi.

Por ejemplo, si estamos en el directorio de inicio del usuario pi, ../../usr es una carpeta que contiene muchos de los programas instalados, y ../../usr es su ruta relativa al directorio de inicio de pi. Indicamos dos veces la carpeta superior (../..) y allí la encontramos.

Sabremos que una ruta es relativa porque no comienza con la carpeta raíz, que es /Documents/ será una ruta relativa a ~.

Ruta absoluta

Una ruta absoluta es la que indica como punto de partida el directorio raíz, /, es decir, toda aquella ruta que comience por /. En el ejemplo anterior, la ruta absoluta correspondiente a ../../usr es /usr.

Órdenes para movernos entre carpetas

pwd, que significa ruta al directorio de trabajo (en inglés path to working directory), muestra la ruta del directorio donde nos encontramos:

pwd

Es útil para comprobar, después de una sesión de trabajo donde nos hayamos podido mover mucho, dónde nos hemos quedado.

cd, de cambiar directorio, es la orden con la que conseguiremos movernos entre las carpetas de la Raspberry Pi usando el terminal. Podemos indicarle rutas absolutas y rutas relativas, y por supuesto podemos indicarle sucesivamente el directorio superior (..) y el alias del directorio de inicio de pi~.Con los conceptos que ya sabemos podemos directamente pasar a hacer pruebas. Prueba lo siguiente, intercalando ls o pwd para saber dónde te encuentras:

cd /usr
pwd
cd ~/python_games
pwd
cd /
ls
cd dev
ls
cd ~
pwd
cd /dev
pwd

… y todo aquello que se te pueda ocurrir. Consulta las páginas de manual de cd y de pwd para saber más.

Crear y borrar archivos y directorios usando órdenes de terminal. El editor “nano”

Vuelve a tu directorio de inicio (el de pi) y ejecuta la orden mkdir pruebas. Luego comprueba el contenido de tu directorio de inicio:

cd ~
mkdir pruebas
ls -l

Verás que has creado con la orden mkdir pruebas un directorio llamado pruebas:

Entra en él y comprueba que, como era esperable, está vacío. Escribe touch hola.txt:

cd pruebas
ls -l
touch hola.txt
ls -l

Con la orden touch lo que hacemos es crear un fichero vacío. Es como esas órdenes tipo “Nuevo…” en el explorador de ficheros, o en otros sistemas operativos aunque con alguna diferencia ya que en Linux no es necesario especificar el tipo de documento.

Podemos editarlo y escribir algo dentro. Escribe nano hola.txt. Verás lo siguiente:

nano es uno de los muchos editores de texto de terminal de Linux, y en mi caso es mi favorito porque es muy fácil de usar con un teclado normal. Funcionan las teclas de los cursores (←, ↑, →, ↓) y todas las demás teclas de desplazamiento de una forma razonablemente parecida a un editor más visual como el que podrás haber usado en alguna otra ocasión.

Escribe una frase, o un párrafo cualquiera, como por ejemplo Hola PItando, este es mi primer documento de texto y graba el fichero pulsando la tecla de Control y la letra o al mismo tiempo. Pedirá una confirmación.

Pulsa la tecla intro (↵) para confirmar. Ahora pulsa Control y la letra x al mismo tiempo para salir al terminal de nuevo. Si ahora consultas el contenido del directorio, verás que cambia el tamaño del fichero. Prueba ahora la orden cat hola.txt:

cat es una utilidad que permite sacar por el terminal el contenido de un fichero (o varios) de texto.

Vamos a borrarlo todo. Para borrar un fichero usa la orden rm con el fichero que quieres borrar como argumento:

rm hola.txt

Si ahora compruebas el contenido del directorio con ls, verás que está vacío. Ojo, y mucho cuidado, porque la papelera también. Lo que borremos en el terminal no pasa por la papelera y por lo tanto es irrecuperable.

Sube al directorio superior con cd .. y borra el directorio prueba ahora. Verás que no puedes con rm:

rm, tal cual, no sirve para borrar directorios. Para borrar el directorio deberías usar rmdir, pero esta utilidad tiene otro problema y es que no te permitirá borrar directorios que contengan cosas.

Para abreviar, pero siempre pensándolo dos veces porque es irreversible, puedes usar una opción de rm, -r:

Esto último borraría tanto el directorio que indicamos como todo su contenido, ficheros, directorios,…

Conclusiones

En esta entrada hemos visto tanto unas órdenes sencillas como la estructura general de las órdenes de la consola, el manual para documentarnos acerca de su uso, y hemos experimentado bastante con ello.

Este artículo, aunque es muy largo, es una pieza fundamental para muchos que están por venir. Es muy frecuente, cuando estemos haciendo experimentos, tener que editar y mover ficheros entre directorios, crear y borrar estructuras de carpetas y ficheros, y muchas otras cosas que serían muy lentas de hacer con el ratón y el entorno de ventanas.

En otros artículos de la serie de uso de Linux que se centren en el terminal veremos otras utilidades y conceptos como los permisos, la creación de programas o guiones de consola (llamados scripts en inglés), cómo comprimir directorios para hacer copias de seguridad… y un montón de cosas muy útiles.

]]>
http://pitando.net/2015/07/02/uso-de-terminal-o-la-consola-en-la-raspberry-pi/feed/ 4 175
Condiciones y bucles en Python http://pitando.net/2015/07/02/condiciones-y-bucles-en-python/ http://pitando.net/2015/07/02/condiciones-y-bucles-en-python/#comments Thu, 02 Jul 2015 10:11:00 +0000 http://pitando.net/?p=329 Sigue leyendo Condiciones y bucles en Python ]]> Captura 20150628 - 3El otro día empezaba con Python trabajando sobre una orden directa (print("Hola, mundo") y alguna variante), por lo que, aunque muy poco, ya tienes una noción de cómo se le pide a un ordenador que haga cosas por nosotros. Antes de empezar a contar cosas exhaustivas sobre el lenguaje y su tratamiento de los tipos de datos, y demás, que sería muy árido, prefiero escribir un par de artículos sobre lógica de control.

La lógica de control de un programa es aquello que hace que la ejecución de un programa vaya por un camino o por otro, en función de alguna comprobación. Permite establecer repeticiones y condiciones sobre los datos de entrada para producir algún resultado, y por lo tanto es una pieza fundamental para construir un algoritmo con cualquier lenguaje de programación. Aporta mucho más valor hablar de esto a estas alturas, porque permite empezar a decirle a un programa cómo interpretar datos y tomar decisiones sobre ellos.

Dentro de la lógica de control, en este artículo voy a hablar de bucles y condiciones, así que manos al teclado.

Abre IDLE en la Raspberry con el icono de Python 3, o en Windows o Mac como explicamos en su día.

Expresiones lógicas

Una expresión lógica es algo verdadero o falso. Por ejemplo, sabemos que 3 es mayor que 2, por lo tanto es verdadero. Si pensamos en “2 es mayor que 3”, es falso. Este tipo de expresiones están plenamente soportadas en cualquier lenguaje de programación, y en Python tenemos las de la siguiente tabla:

Expresión Resultado Operador  Ejemplo
Verdadero Verdadero siempre True True
Falso Falso siempre False False
Distinto Verdadero (True) si los dos operandos
son diferentes; falso (False) si no
!= 3 != 2
es True
Igualdad Verdadero (True) si los dos operandos
son iguales; falso (False) si no
== 3 == 3
es True
Mayor que Verdadero (True) si el miembro izquierdo
es mayor; falso (False) si no
> 3 > 3
es False
Mayor o igual que Verdadero (True) si el miembro izquierdo
es mayor o igual; falso (False) si no
>= 3 >= 3
es True
Menor que Verdadero (True) si el miembro izquierdo
es menor; falso (False) si no
< 3 < 3
es False
Menor o igual que Verdadero (True) si el miembro izquierdo
es menor o igual; falso (False) si no
<= 3 <= 3
es True
Negación Verdadero (True) si la expresión a su
derecha es falsa (False); falso (False) si
no
not not 3 < 2
es True
Y lógico Verdadero (True) si la expresión a su
izquierda es verdadera y también la
expresióna su derecha. Falso (False) en
cualquier otro caso
and 3 <= 3 and 3 > 2
es True.
O lógico Verdadero (True) si la expresión a su
izquierda es verdadera o lo es la
expresión a su derecha. Falso (False)
sólo en caso de que las dos sean falsas.
or 4 <= 3 or 3 > 2
es True.

Lógicamente, comparar literales como en la tabla no tiene mucha utilidad. Pero en la entrada anterior de Python hemos aprendido a usar variables, y en ese caso sí tiene sentido.

También podemos combinar condiciones con paréntesis y los operadores de negación, “Y” y “O” que vimos en la tabla superior. Por ejemplo, podemos escribir en Python:

not ( 2 > 3 and 7 > 2 ) or 5 == 6

Es una expresión que evalúa a True y que puedes probar a copiarla del blog y pegarla en IDLE para evaluarla:

Captura de pantalla 2015-06-28 13.37.42

Merece la pena que dediques un tiempo a experimentar con el entorno, escribiendo expresiones para comprobar cómo son evaluadas. Sobre todo, para familiarizarte con la forma de expresarlas y con cosas como, por ejemplo, que puedes combinarlas con aritmética y variables de una forma totalmente intuitiva. Por ejemplo, prueba los ejemplos de la tabla (3 != 2,…) y las siguientes variaciones:

numero = 2
numero >= 2
numero == 3
numero != numero * numero
numero = 1
numero != numero * numero
...

Fíjate también en la diferencia entre el operador de asignación (=) y el comparador de igualdad (==).

Condiciones

Echa un vistazo a la siguiente expresión:

if 2 > 3:
    print("Python no funciona: cree que 2 es mayor que 3.")
elif 2 == 3:
    print("Tampoco: ahora cree que 2 es igual a 3.")
else:
    print("Efectivamente, 2 es menor que 3. Y así debe ser.")

Si sabes inglés y con los conocimientos que ya tienes de Python, habrás reconocido claramente un discurso parecido a este:

Si 2 es mayor que 3, imprime “Python no funciona: cree que 2 es mayor que 3.”. Si, por otro lado, 2 es igual a 3, imprime “Tampoco: ahora cree que 2 es igual a 3.”. En otro caso, imprime “Efectivamente, 2 es menor que 3. Y así debe ser.”

Si no sabes inglés, simplemente te faltaba por saber que if significa “si”, sin tilde, condicional; else significa “en otro caso”, o “si no”; elif es simplemente una contracción de else e if. La parte de elif se podría haber omitido (es opcional) o repetido muchas veces, para evaluar toda una retahíla de condiciones alternativas. El final de la construcción, else, también es opcional pero sólo puede escribirse una vez.

Es obvio que el resultado de la sentencia de ejemplo va a ser la frase “Efectivamente, 2 es menor que 3. Y así debe ser.”. Puedes copiarla entera y pegarla en IDLE, pulsando la tecla intro dos veces para evaluarla:

Captura 20150628

Así, queda clara la estructura de las condiciones en Python: se escriben usando la sentencia compuesta if… elif… else. Nos proporciona una forma de especificar una condición principal que, si se cumple, ejecuta un bloque de código. Para los casos en los que esa condición no se cumpla, podremos opcionalmente especificar una serie arbitrariamente larga de condiciones alternativas que, si se cumpliesen, harían que el intérprete de Python ejecutase otros bloques de código. Por último podremos especificar opcionalmente un último miembro que se ejecutará si todo lo demás es falso.

Las condiciones de esta sentencia compuesta son excluyentes, tal y como marcan las conjunciones inglesas “else”, que podemos entender como “por el contrario”. if… elif… else especifica lo que se conoce como un árbol de reglas: en el momento en el que se cumple una de sus condiciones, el código toma una dirección y el resto del árbol no se evalúa. Es algo que quiero enfatizar de forma muy clara con un ejemplo:

i

if 3 > 2:
    print ("3 es mayor que 2.")
elif 3 < 5:
    print ("3 es menor que 3.")
elif 4 < 9:
    print ("4 es menor que 9.")
else:
    print ("Si lees esto, no sé qué está pasando.")

En el bloque superior, todas las condiciones son ciertas. Sin embargo (puedes comprobarlo) sólo se va a imprimir la primera cadena, “3 es mayor que 2.”. En cuanto Python encuentra una rama del árbol que se cumple, ejecuta su bloque y termina. Ni siquiera se ha molestado en evaluar el resto de las reglas.

Captura 20150628 - 2

Si quisiéramos evaluar estas reglas en cascada en lugar de en forma de árbol, es decir, una detrás de otra, deberíamos haber escrito lo siguiente (si lo intentas copiar todo en un bloque no funcionará, copia de una en una):

if 3 > 2:
    print ("3 es mayor que 2.")
if 3 < 5:
    print ("3 es menor que 5.")
if 4 < 9:
    print ("4 es menor que 9.")
if not (3 > 2 and 3 < 5 and 4 < 9):
    print ("Si lees esto, no sé qué está pasando.")

Si las transcribes todas, por cierto, verás que la última no se ejecuta. Esa condición compuesta es equivalente a un else con respecto a todas las anteriores.

Con esto vamos a dejar aquí el tema de las condiciones, consciente de que ha sido largo. Programar es como hacer deporte: se gana maestría con la práctica. Plantéate pruebas que hacer y, si encuentras alguna duda que no eres capaz de resolver, deja un comentario o escribe a través del formulario de contacto.

Bucles

Un bucle especifica una serie de condiciones que determinarán si se ejecuta un bloque de código una y otra vez. Cada vez que se cumplen las condiciones adecuadas, se ejecuta una pasada por el bucle, o una iteración. El código que se ejecuta dentro del bucle puede ser cualquier cosa: condiciones, otros bucles, o cualquier otro tipo de sentencias.

Vamos a ver los dos bucles de Python while y for, para aclarar esos conceptos.

Bucle while… else

while es una palabra que, en inglés, significa “mientras”. Si, con esto en mente, vemos el siguiente código, entenderemos de forma sencilla lo que hará:

numero = 0
while numero < 3:
    print (numero, "es menor que 3.")
    numero = numero + 1
else:
    print ("numero vale ", numero, ": se sale del bucle.")

Esa construcción significa que, mientras el valor de la variable numero valga menos que 3, el programa imprimirá este hecho e incrementará su valor en una unidad. Por lo tanto, deberíamos ver 3 impresiones por la pantalla: la correspondiente a cuando numero vale 0 (primera iteración), cuando vale 1 (segunda iteración) y cuando vale 2 (tercera iteración).

Cuando numero vale 3 la condición no se cumple, y por lo tanto se abandona el bucle a través de un viejo conocido, else. En los bucles de Python, una palabra else permite especificar qué se ejecuta cuando la condición del bucle deja de cumplirse. Al igual que en las condiciones if… elif… else, la parte else es opcional.

Si vas a copiar y pegar el código de arriba, fallará si lo haces en un sólo intento. Primero copia la sentencia numero = 0 y luego la estructura del bucle while… else. Después del while… else deberás pulsar 2 veces la tecla intro.

El resultado es el siguiente:

Captura 20150628 - 3 + else

Los bucles pueden alojar cualquier tipo de código en su interior, incluyendo, por supuesto, condiciones como las que conocemos. Para explicar esto y además introducir dos palabras especiales de Python relacionadas con el control de los bucles, vamos a ver otro ejemplo. Para ello, antes, voy a introducir el operador aritmético que calcula el resto de una división, que es el “%“. Por ejemplo, “27 % 3” daría 0, puesto que el resto de dividir 27 entre 3 es cero. “28 % 3“, por su parte, tendría como resultado 1. Puedes probarlo en IDLE.

Veamos ya el ejemplo siguiente para entender las palabras break, que significa “romper” y continue, que significa “continuar”:

numero = 0
while numero < 100:
    if numero == 14:
        break
    if numero % 2 == 0:
        print ("El número ", numero, "es par.")
    else:
        print ("El número ", numero, "es impar.")
    numero = numero +1
    continue
    print ("Esto nunca se ejecuta")
else:
    print("Se sale del bucle...")

Si ejecutas ese código en IDLE obtendrás como resultado el siguiente:

Captura 20150628 - 4

Probablemente estéis algo despistados a la primera, pero si reflexionáis las dudas se aclaran por sí solas. Vamos a ver por qué el bucle sólo se ejecuta 13 veces y por qué nunca se escribe la frase “Esto nunca se ejecuta”, ni “Se sale del bucle…”, tratando de leer en voz alta y en español el código de arriba:

Asignamos a la variable numero el valor 0.
Mientras el valor de la variable numero sea menor que 100:

  • Si el valor de numero es 14, romper.
  • Si el resto de dividir el valor de numero entre 2 es cero, imprimir que representa un número par. En otro caso, imprimir que el número que representa es impar.
  • Asignar a numero el resultado de sumarle 1 a su valor actual.
  • Continuar.
  • Imprimir un mensaje que dice que “Esto nunca se ejecuta”.

Cuando el valor de la variable numero sea 100, escribir “Se sale del bucle…”

Si reflexionamos sobre la traducción que acabo de escribir y lo que ha pasado, las conclusiones sólo pueden ser 2:

  • En el contexto de un bucle, break significa romper el bucle, y su efecto es dejar de ejecutarlo, para no ejecutar más iteraciones aunque se cumpla la condición de continuidad. Tampoco se va a ejecutar el bloque else, para mayor confusión.
  • En el contexto de un bucle, continue significa continuar con la siguiente iteración del bucle, y su efecto es dejar de ejecutar el bloque de código de la iteración actual, para pasar a evaluar la condición de continuidad y, si se debe hacer, ejecutar la siguiente iteración.

Mucha gente, entre la que me cuento, está en contra del uso de continue y break, porque en programas grandes hace que el programa sea más difícil de entender, y que cueste más trabajo evolucionarlo o corregir algún error. Representan recursos para un programador vago, que debe modificar el comportamiento de un bucle y decide hacerlo sin mejorar la condición. Metiendo la tijera y cortando por lo sano, dejando código muerto, es decir, sentencias que nunca se ejecutarán, y muchas otras medidas que sólo pueden ser definidas como… baratas.

Sin embargo, eso no quita que son dos conceptos que se deben explicar para que seáis vosotros quienes, desde el conocimiento, decidáis cómo programar y busquéis vuestro estilo. Para los que, como yo, consideramos continue y break algo no demasiado elegante, el bucle debería haber sido escrito de otra forma que, en este ejemplo, está clarísima:

numero = 0
while numero < 14:
    if numero % 2 == 0:
        print ("El número ", numero, "es par.")
    else:
        print ("El número ", numero, "es impar.")
    numero = numero +1
else:
    print ("Se sale del bucle, pues numero = ", numero, ".")

Pruébalo para hacerte una mejor composición de lugar.

Bucle for… else

El bucle for es algo más sofisticado que el while. Mientras que el bucle while está pensado de forma genérica, para aplicarlo a cualquier propósito, el bucle for está pensado para recorrer listas o conjuntos.

Un conjunto en Python es, como en la vida misma, una colección de valores. Se expresa de la siguiente forma: {valor_1, valor_2, valor_3,...}: los valores se separan por comas y el conjunto se delimita mediante llaves.

Por ejemplo, escribe en IDLE lo siguiente, sucesivamente:

{1, 2, 3}
numeros = {1, 2, 3}
numeros

El resultado el que sigue, y como verás, se puede asignar un conjunto a una variable de forma totalmente idéntica a hacer otras asignaciones “individuales”.

Captura 20150628 - 6

Ahora fíjate en el siguiente ejemplo:

colores = {"rojo", "verde", "azul"}
for color in colores:
    print ("El ", color, " es un color.")
else:
    print ("La lista se ha agotado")

Su resultado es el siguiente:

Captura 20150628 - 7 + else

Lo que ha ocurrido ahí es lo siguiente:

  • Hemos definido un conjunto con tres colores
  • Para (todos) los colores en el conjunto, imprimimos un mensaje

Las palabras clave aquí son for e in y en inglés significan “para” y “en”, respectivamente. En Python, for (para) tienen más matices que simplemente los del inglés, corresponde directamente con el texto que arriba dejé marcado en negrita: para todos: el bucle for recorre todos los elementos del conjunto o de la lista si usamos una (son cosas diferentes, como ya veremos), y no permite saltarse ninguno.

Al bucle for aplican exactamente las mismas consideraciones que al bucle while, por lo que ha llegado ya el momento de dejar de teorizar y hacer algún ejercicio.

EJERCICIOS

1) Escribe un bucle que imprima los números pares comprendidos entre el 1 y el 100, pero no aquellos que sean múltiplos de 3 ni múltiplos de 4, y sin usar ni break ni continue. Hazlo dos veces: con while y con for.

Deberás obtener estos resultados:

Captura 20150628 - 5

Pista: para el bucle for puedes generar todos los números del 1 al 100 de la siguiente forma:

del1al100 = range(1, 101)

2) Usando for, haz que Python escriba todas las tablas de multiplicar, del 1 al 10, obteniendo algo como esto:

Captura 20150628 - 8

Algunas notas más

En la documentación oficial de Python (en inglés), todas las sentencias de Python se describen de una forma estandarizada y esquemática que merece la pena examinar ahora con if… elif… else, para cuando queráis consultar alguna sentencia:

"if" expresión ":" suite
( "elif" expresión ":" suite )*
["else" ":" suite]

¿Cómo se interpreta eso?

  • Lo que va entre comillas es literal, es decir, donde pone “if” lo que quiere decir es que hay que escribir eso mismo, “if”. Lo mismo con los dos puntos, el “elif” y el “else”.
  • Lo que no va entre comillas quiere decir que ahí va código en Python.
    • En el caso de la expresión, se trata de una expresión del lenguaje que vale verdadero o falso, como veíamos antes
    • En el caso de suite, lo que quiere decir es que ahí va un bloque de sentencias en Python: instrucciones que hacen cosas al respecto si la expresión vale verdadero. Esto incluye cualquier cosa, incluyendo bucles y otras sentencias if… elif… else, caso en el que tendríamos condiciones anidadas.
  • Lo que va entre paréntesis y con un asterisco (*) al cierre puede repetirse ninguna o muchas veces (arbitrariamente). En el caso de `( “elif” expresión “:” suite)*`, tenemos lo siguiente:
    • El paréntesis simplemente agrupa.
    • El asterisco (*) significa “0 ó varias veces”.
    • Si fuese un signo más (+), significaría “1 ó varias veces”.
  • Lo que va entre corchetes ([, ]) es un grupo que puede aparecer ninguna o una vez, es decir, un “opcional de toda la vida”: `[“else” “:” suite]` podría usarse, o no.
]]>
http://pitando.net/2015/07/02/condiciones-y-bucles-en-python/feed/ 4 329
Podcast – episodio 0: ¿Por qué?, ¿cómo?, y, ¿qué? http://pitando.net/2015/06/25/podcast-episodio-0-por-que-como-y-que/ Thu, 25 Jun 2015 20:19:39 +0000 http://pitando.net/?p=311 Este es el primer episodio del podcast de PItando: el sitio de los que queremos acercar la tecnología a los niños y no tan niños. En este episodio doy respuestas a las preguntas sobre la motivación (¿por qué?), la forma de materializarla (¿cómo?) y, en resumen, de qué trata PItando en el fondo (¿qué?).
 
Enlaces de interés:

Muy relacionado:

Este podcast comienza, y termina, con una sintonía compuesta por Eric Skiff, “We’re the Resistors“.

]]>
311
Un primer programa en Python http://pitando.net/2015/06/25/un-primer-programa-en-python/ http://pitando.net/2015/06/25/un-primer-programa-en-python/#comments Thu, 25 Jun 2015 13:00:06 +0000 http://pitando.net/?p=135 Sigue leyendo Un primer programa en Python ]]> Python es el lenguaje de programación “oficial” de la Raspberry Pi. Es un lenguaje que nació a finales de la década de los 80 de la mano de Guido Van Rossum (Haarlem, Países Bajos, 1956).

A lo largo de sus muchos años de historia ha ido evolucionando con una idea fundamental que es la sencillez a la hora de aprender a usarlo. Es decir, el lenguaje es muy completo y permite hacer muchísimas cosas, pero la idea general es que “debería haber sólo una forma, obvia, de hacer las cosas”.

Al final esto significa que, en general, Python no llama a engaños y trata de conseguir que fallemos pocas veces cuando intentamos hacer algo. Vamos a probarlo y a poner las manos en unos primeros conceptos muy sencillos para empezar a pensar en programar.

Abre el menú y, dentro de “Programación” haz click en “Python 3”. Verás lo siguiente:

Lo que ves ahí es un entorno de desarrollo de Python llamado IDLE, que permite programar de forma interactiva. Es decir, se introducen instrucciones y en el mismo momento el entorno nos contesta con el resultado. Vamos a probarlo escribiendo lo siguiente sin pulsar la tecla intro (↵) todavía:

print("Hola, mundo")

Mientras lo haces, verás que el programa empieza a colorear lo que escribes y superpone cierta información de referencia. No te preocupes ahora de lo que significa, simplemente fíjate que escribe “print” y ciertos mensajes acerca de la misma:

Cuando termines pulsa la tecla de Intro (↵). Verás que el resultado es el texto que iba entre comillas y entre paréntesis, de esta forma:

¿Qué es lo que ha ocurrido ahí? En una sola línea de texto hemos pedido a Python que escriba por la salida estándar una cadena de caracteres "Hola, Mundo", usando para ello la función print. Esto tan sencillo introduce un montón de conceptos.

Función

Una función es una secuencia de instrucciones que se puede llamar, o invocar, dentro de un programa utilizando su nombre, en este caso print (en inglés “imprimir”) y unos argumentos entre paréntesis (en este caso la cadena "Hola, mundo").

Para quien la usa, que somos nosotros en el ejemplo, el conjunto de instrucciones la forman está oculto. Este conjunto de instrucciones se llama implementación. Más adelante en el blog veremos cómo definir nuestras propias funciones. Así, una función consta de:

  • Un nombre: print  
  • Un conjunto de argumentos que pueden ser obligatorios u opcionales y que van entre paréntesis. En este caso es una cadena de caracteres: "Hola, Mundo"
  • Un resultado, que en el ejemplo no entra en juego (es opcional). En otros lenguajes de programación, a las funciones que no devuelven un resultado les llaman procedimientos.
  • Una implementación que está oculta para el programador que llama, o invoca, la función.

Caracteres y cadenas de caracteres

Una cadena de caracteres: es una secuencia de caracteres ordenados que se representa entrecomillado. Es decir, las comillas de "Hola, Mundo" no son parte de la cadena de caracteres, sino su delimitador. Es decir, la cadena de caracteres propiamente dicha es Hola, Mundo.

Un caracter es, en un ordenador, un símbolo que se puede obtener pulsando una o varias teclas, y que representa información. Aunque es intuitiva, esta definición no es del todo correcta porque existen caracteres no imprimibles. Es decir, invisibles. ¿Qué sentido tiene esto? Algunos aparatos como las pantallas y los teclados los usan para comunicar cosas como, por ejemplo, el avance de línea. Cuando pulsas la tecla intro (↵) no ves un caracter, ves su efecto. Es un caracter no imprimible que hace que, en la pantalla, el cursor avance una línea y se sitúe al principio de la siguiente (esto último se llama retorno de carro y viene de las máquinas de escribir). Sí, se obtiene pulsando una tecla. Sí, representa información (avance de línea y retorno de carro). Pero, ¿es un símbolo?

Yendo un poquito más allá: variables y constantes

Para terminar el artículo vamos a complicar un poquito el programa para introducir el concepto de variable. Hemos visto que la línea anterior es poco flexible, puesto que tal y como está escrita sólo va a poder escribir "Hola, Mundo" y nada más.

print("Hola, Mundo")

Lo que vamos a hacer es definir una variable a la que asociaremos una cadena de texto y haremos unos pocos experimentos más. Escribe lo siguiente:

saludo = "Hola,"
nombre = "PItando"
print(saludo, nombre)

Si no has cometido ninguna errata, deberías ver lo siguiente:

Lo que acabas de hacer es definir dos elementos especiales, llamados variables. Como su nombre indica, estos elementos pueden variar. Así, cuando escribes "Hola, Mundo" estás escribiendo una cadena de caracteres constante, mientras que con una variable puedes pensar que estás indicando a la función una caja que  en el ejemplo contiene una cadena de caracteres, y que tiene una etiqueta con un nombre (saludo).

Prueba ahora a escribir lo siguiente, a continuación:

nombre = "Pepito de los palotes"
print (saludo, nombre)

Lo que verás ahora será que, mientras la llamada a la función es la misma print(saludo, nombre) que antes, el resultado ha variado ya que el valor de una de las variables es distinto.

Las variables son muy importantes, juegan un papel fundamental en la programación. Veremos muchas más cosas pronto, en otros artículos.

Conclusiones

En esta entrada hemos aprendido bastantes cosas a través de un programa en Python sencillísimo: qué son las funciones, sus argumentos, las cadenas de caracteres, y las variables y las constantes. Tenemos ya una serie de conocimientos que nos permitirán experimentar un poquito y abordar entradas más completas, tanto centradas en Python como en otros lenguajes ya que todos éstos son conceptos comunes dentro de la programación.

Para investigar tú

Haz las siguientes pruebas y trata de entender cada una:

print(nombre.upper())
nombre
print("¡", nombre, nombre.upper(), saludo, nombre, "!")

En dos o tres días actualizaré esta entrada con una explicación.

Actualizado

Más adelante entraré en la teoría que hay detrás, hoy voy a entrar en qué hemos hecho con cada línea de texto.

Con nombre.upper() hemos obtenido una versión en mayúsculas del contenido de la variable nombre, pero sin modificarla. En este sentido, upper es una abreviatura de uppercase, que significa “mayúsculas”. Al introducirlo en una llamada al ya conocido print, lo imprimimos por la pantalla. Como hemos obtenido una copia en mayúsculas, al escribir a continuación nombre, su contenido sigue en minúsculas.

Con la sentencia print siguiente, lo que estamos haciendo es imprimir varias variables y cadenas constantes, una detrás de otra. Esto explica aquél cuadro amarillo que se superponía al escribir en IDLE:

Es una ayuda de la forma en la que pdemos invocar dicha función. Veamos:

  • value indica cualquier valor, ya sea con una constante o una variable. El hecho de usar la palabra inglesa valor indica que si es una variable se escribirá su valor, no su nombre.
  • …, quiere decir que podemos escribir tantos valores como queramos.
  • sep=’ ‘ indica que cada variable se separará por un espacio, y ese es el valor por defecto. Ahora probamos a cambiarlo, ya verás.
  • end=’n’ indica el terminador de la cadena. n es un caracter especial que significa “retorno de carro y avance de línea”, y es equivalente a presionar intro (↵).
  • file=sys.stdout indica por dónde se imprime la cadenasys.stdout es la salida estándar y viene a significar “el mismo medio por donde nos han llegado las órdenes”. En este caso, IDLE. De momento lo vamos a dejar ahí, cuando empecemos a escribir ficheros usaremos ese parámetro.

Fijaos ahora:

print (apellidos, nombre, sep=',', end='*')

Esto tiene como efecto el cambio del separador entre valores desde el espacio a la coma, y un delimitador de fin que será visible: el asterisco.

Captura 20150628 - 9

La utilidad que tiene esto reside en el volcado de datos a ficheros para guardar el estado de un programa, por ejemplo, de forma que podamos localizar valores y segmentos usando los separadores de valor y los indicadores de fin.

Lo usaremos mucho, seguro 😉

]]>
http://pitando.net/2015/06/25/un-primer-programa-en-python/feed/ 4 135
¿Qué es programar? http://pitando.net/2015/06/25/que-es-programar/ Thu, 25 Jun 2015 11:12:00 +0000 http://pitando.net/?p=64 Sigue leyendo ¿Qué es programar? ]]> Una de las cosas que puedes hacer con la Raspberry Pi tan pronto como la pones en marcha es programarla.

Programar es decirle a un aparato electrónico qué pasos debe seguir para hacer algo útil. Por ejemplo, dibujar una forma en la pantalla, reaccionar cuando pulsas teclas en un teclado o un botón, emitir sonidos por un altavoz, encender y apagar una luz,… cualquier cosa que veas hacer a un aparato electrónico tiene detrás un programa. Es como un guión.

La Raspberry Pi es un ordenador, y los ordenadores son aparatos electrónicos fabricados sin un propósito concreto, es decir: de propósito general. Esto quiere decir que, con más o menos esfuerzo, pueden hacer cualquier cosa. Por ejemplo, un vídeo sólo se puede programar para grabar un programa a una cierta hora: su propósito es concreto. Un ordenador se puede programar para que haga lo mismo que un vídeo, o cálculos complicados, para jugar (un videojuego es un programa), para que reproduzca música, o para que vigile la temperatura de una habitación, o para que saque fotos a la puerta si se abre…

Programar es una técnica que proviene de las matemáticas (sí, las matemáticas; si no te gustaban y no lo sabías, espero que a partir de ahora las veas de otro modo) y aunque lleva muchos siglos usándose de una u otra forma gracias a personas como Charles Babbage y Ada Lovelace entre otras muchísimas, ha servido para construir el mundo que conocemos desde que en la década de 1940 aparecieron los primeros ordenadores modernos.

Grace Murray Hopper fue una mujer importantísima en la historia de la computación y la programación moderna. Pulsa en la foto para saber más de ella.

En la foto puedes ver el primer ordenador comercial de la historia. Ni siquiera tenía pantalla y era muy rudimentario de programar, necesitaba muchísima electricidad para funcionar y los resultados eran complicados de interpretar. Entre esa foto y la Raspberry pasaron más o menos 50 años, y ahora es mucho más fácil. Con una pantalla, unos altavoces y un teclado, la Raspberry puede hacer cosas increíbles fácilmente si sabes decirle cómo, pero sólo verás los resultados por la pantalla y los altavoces. Pero si le conectas más cosas (como un robot o una cámara) podrá hacer todavía más.

En siguientes artículos vamos a ir paso a paso aprendiendo a programar con un lenguaje llamado Python (que se dice más o menos “payton”). Es fácil de entender y muy potente, y la Raspberry lo incluye “de serie”. Pero como lo importante es entender un problema para saber qué hay que programar, en cuanto sepamos lo básico haremos siempre ejercicios prácticos; escribir el programa en el lenguaje Python, o en cualquier otro, es mucho menos importante y mucho menos divertido que aprender a “pensar” como lo haría un ordenador.

]]>
64
Instalar Python en Windows y en Mac OS X http://pitando.net/2015/06/21/instalar-python-en-windows-y-en-mac-os-x/ http://pitando.net/2015/06/21/instalar-python-en-windows-y-en-mac-os-x/#comments Sun, 21 Jun 2015 10:19:00 +0000 http://pitando.net/?p=254 Sigue leyendo Instalar Python en Windows y en Mac OS X ]]> Lo que viene inmediatamente después de anunciar algo es cumplirlo: hace unas horas anunciaba que iba a escribir lo necesario para que pudieseis seguir la mayoría de los artículos de PItando desde un PC o un Mac, y ahora toca hacerlo.

Así que, sin más preámbulos…

¿Qué versión?

Actualmente existen dos versiones de Python: Python 2 y Python 3. La primera de ellas tiene todavía mantenimiento para dar servicio a todos los programas y sistemas existentes que aún lo usan, pero ha dejado de evolucionar significativamente. La segunda de ellas, Python 3, es la que continúa con el desarrollo del lenguaje. Sobre este punto volveremos a incidir en el primer artículo sobre programación.

Por lo tanto, vamos a instalar Python 3.

Instalar Python 3 en Windows

Solamente hay que navegar a la página http://www.python.org/download (en inglés), que ya detecta la versión del sistema operativo, y presionar el botón Download Python 3.4.3 (a día de hoy esa es la versión, pero irá variando en el tiempo):

Una vez descargado el fichero, lo ejecutamos como cualquier otro instalador. Examina la galería siguiente para ver el proceso y sus pasos (en el texto sobreimpreso verás que las imágenes están numeradas del 1 al 5 para facilitar el seguimiento):

Haz click para ver el pase de diapositivas.

 

Por defecto este programa se instalará en el directorio c:\Python34, pero podemos escoger cualquier otro directorio (en mi caso, c:\prog\Python34). Quitando esta decisión, el resto de la instalación será directa, aceptando todos los pasos con los valores que proponen pulsando el botón “Next” y “Finish” en el último paso.

Una vez instalado, comprueba que todo está bien:

  • Abre la carpeta donde lo has instalado
  • Entra en la carpeta Lib y, dentro en idlelib
  • Ejecuta con doble click el archivo idle.bat

Deberías ver lo siguiente:

Escribe dentro de esa ventana

quit()

y pulsa la tecla intro (↵). El programa nos preguntará si realmente queremos cerrarlo (traducción un tanto libre), a lo que responderemos afirmativamente pulsando el botón “Aceptar“:

Haz un acceso directo al fichero idle.bat en el escritorio, porque a partir de él será como empezaremos a trabajar en las próximas entradas.

Instalar Python 3 en Mac OS X

Los Mac tienen Python instalado por defecto, pero es una versión de Python 2 seleccionada por Apple… con la cual no podríamos seguir al pie de la letra las entradas de PItando. Deberemos descargar e instalar Python 3.

De nuevo, al ir a la web http://www.python.org/download desde el Mac, la web detectará que estamos usando Mac OS X y nos propondrá una descarga de Python adecuada para el sistema operativo. Habremos de pulsar el botón “Download Python 3.4.3” (o la versión vigente):

Una vez descargado, ejecutamos el fichero como cualquier otro instalador para el Mac. A diferencia de otros, no basta con “arrastrar el contenido del paquete a la carpeta de aplicaciones”, sino que es un proceso de tipo asistente que recojo en la siguiente galería (en el texto sobreimpreso verás que las imágenes están numeradas del 1 al 8 para facilitar el seguimiento):

Haz click para ver el pase de diapositivas.

A diferencia de Windows, debemos descargar más cosas. Iremos a http://www.activestate.com/activetcl/downloads y descargaremos la versión 8.5.18 (o la más reciente de la 8.5) de Active TCL (la 8.6 no vale):

Captura de pantalla 2015-06-21 02.31.55

Al pulsar el enlace de descarga de “Mac Disk Image (DMG)” se descargará un fichero .dmg que, al ejecutarlo, dará paso al siguiente proceso de instalación:

Haz click para ver el pase de diapositivas.

Una vez instalado Active TCL, podemos entrar en el Launchpad y buscar IDLE:

Captura de pantalla 2015-06-21 02.41.43

Al ejecutarlo, veremos la siguiente ventana.

Captura de pantalla 2015-06-21 02.43.18

Podemos cerrarla escribiendo:

quit()

y pulsando la tecla intro (↵). Tras ello nos pedirá confirmación que, en este caso, daremos pulsando el botón Ok:

Captura de pantalla 2015-06-21 02.45.42

Y esto es todo

Siguiendo este artículo hemos instalado Python en los sistemas operativos Windows (7, 8, 8.1) y Mac OS X 10.6+. Esto nos permitirá seguir los artículos basados en Python en estas plataformas alternativamente, si no tenemos una Raspberry Pi.

]]>
http://pitando.net/2015/06/21/instalar-python-en-windows-y-en-mac-os-x/feed/ 4 254
PItando para todos: también Windows y Mac OS http://pitando.net/2015/06/20/pitando-para-todos-tambien-windows-y-mac-os/ http://pitando.net/2015/06/20/pitando-para-todos-tambien-windows-y-mac-os/#comments Sat, 20 Jun 2015 15:32:19 +0000 http://pitando.net/?p=247 Sigue leyendo PItando para todos: también Windows y Mac OS ]]> He tenido varias conversaciones acerca del blog y de la realidad de que muchos de los artículos que veremos aquí pueden ponerse en práctica sobre un ordenador cualquiera. A raíz de ellas he decidido “abarcar” más plataformas que la Raspberry Pi, aunque este blog seguirá girando alrededor de ella principalmente.

Python, Sonic Pi y la mayoría de herramientas de programación que incorpora la Raspberry están (lógicamente) disponibles para más plataformas que Linux, por lo que con muy poco esfuerzo este blog puede extenderse a todo aquél que quiera aprender a programar con el blog. En las próximas semanas escribiré una serie muy cortita sobre la búsqueda, instalación y configuración de estas herramientas en un PC con Windows y en un Mac, para que todo aquél que quiera pueda engancharse a las series de Python, en un principio, y a otras herramientas como Scratch o Sonic Pi más adelante.

Teniendo en cuenta que el sistema operativo de la Raspberry es un Linux derivado de la distribución Debian, y que además OS X es muy similar en muchos temas a un sistema Linux, creo que con esos artículos es fácil hacer que PItando cubra todas las posibilidades.

PItando para todos. ¡Espero que os guste esta decisión!

]]>
http://pitando.net/2015/06/20/pitando-para-todos-tambien-windows-y-mac-os/feed/ 1 247
El escritorio de Linux en la Raspberry Pi http://pitando.net/2015/06/18/el-escritorio-de-linux-en-la-raspberry-pi/ Thu, 18 Jun 2015 16:01:00 +0000 http://pitando.net/?p=96