Download 3 en raya en modo gráfico
Document related concepts
no text concepts found
Transcript
MIDDLEWARE Pensando en Python (II): 3 en raya en modo gráfico DIEGO LZ. DE IPIÑA GZ. DE ARTAZA (profesor del departamento de Ingeniería del Software de la facultad de Ingeniería (ESIDE) de la Universidad de Deusto) En esta segunda entrega de la serie sobre Python aprenderemos a realizar aplicaciones gráficas y a guardar de manera persistente datos que han de subsistir a la ejecución de un programa. Extenderemos la aplicación de tres en raya de la anterior entrega, con una interfaz gráfica y la capacidad de guardar en ficheros los detalles de los jugadores y sus estadísticas. Introducción La clave del éxito de toda aplicación profesional es proveer una interfaz gráfica amigable que facilite la interacción con el usuario y la explotación de su funcionalidad. Además, la mayoría de los programas que usamos a diario deben guardar (o recordar) datos o preferencias utilizados en anteriores ejecuciones de los mismos. Es decir, a menudo las aplicaciones requieren de un mecanismo para persistir datos a disco, siendo los mecanismos más convencionalmente utilizados los ficheros o bases de datos. En este artículo aprenderemos a usar Python en el desarrollo de interfaces gráficas y la serialización de datos en ficheros. Pospondremos la programación de bases de datos desde Python hasta la tercera entrega de esta serie. Programación gráfica en Python Material complementario El material complementario de este artículo está disponible en http://digital.revistasprofesionales.com SOLO PROGRAMADORES nº 119 26 Python ofrece varias librerías de clases (o toolkits) alternativas para la implementación de una aplicación gráfica. Entre otras, las más conocidas son: Tkinter, PyGTK, PyQt, Pythonwin y wxPython. A continuación las analizamos y elegimos una de ellas, wxPython. Tkinter es la toolkit para el desarrollo de interfaces gráficas (GUIs) que acompaña a la distribución de Python. La documentación sobre esta API es parte de la Python Library Referente (http://www. python.org/doc/current/lib/module-Tkinter.html). Tkinter es una adaptación en Python de la API gráfica tk ofrecida por el lenguaje de programación Tcl. Se distingue por ser multiplataforma y venir integrada con la distribución estándar de Python, no tendremos que instalar nada adicional. Sin embargo, no presenta una apariencia nativa. Es similar a Java Swing que independientemente de la plataforma (Windows, UNIX o Mac) ofrece, por defecto, la misma apariencia. Su uso es muy sencillo, pero es lenta y ofrece un número limitado de controles gráficos. Carece de controles avanzados como los paneles multipestaña, los árboles de directorios o incluso controles más sencillos, como el combobox. Pmw (Python meta widgets) disponible en http://pmw.sourceforge.net, define encima de Tkinter una serie de componentes más sofisticados. Aunque resuelve las limitaciones de controles de Tkinter, hereda su lentitud y su apariencia no nativa. Existen otras toolkits que definen finas capas de Python encima de toolkits gráficas más conocidas que tk tales como GTK y Qt en Linux y la Windows API. Estos módulos Python son llamados PyGTK (http://www.riverbankcomputing.co.uk/pyqt/), PyQt (http://www.pygtk.org/) y Pythonwin (http://www. python.org/windows/pythonwin/), respectivamente. Su principal desventaja es que están orientados a una plataforma específica (UNIX o Windows), aunque las interfaces que producen son muy rápidas dado que interactúan directamente con APIs nativas de la plataforma. Su uso, es más complicado que en el caso de Tkinter. La toolkit wxPython (http://www.wxpython.org/) es multiplataforma, presenta apariencia nativa y ofrece un alto rendimiento gráfico. Está basada en la API en C++ wxWidgets (http://www.wxwidgets.org/). wxWidgets ha venido representado desde hace unos años una alternativa muy capaz y sencilla tanto a las APIs de Windows: MFC (Microsoft Foundation Classes) e incluso .NET Windows Forms, como a las toolkits gráficas de Linux: GTK y Qt. El combo wxWidgets/wxPython nos permite realizar interfaces gráficas muy sofisticadas de una manera tan sencilla como Tkinter. A juicio del autor wxPython es la mejor toolkit para el desarrollo de interfaces gráficas con Python. Combina eficiencia, con sencillez de uso y nos permite escribir código fuente una sola vez MIDDLEWARE Pensando en Python (II): 3 en raya en modo gráfico independientemente de la plataforma de explotación, adoptando tu aplicación la apariencia y forma más apropiada a tu plataforma. En la siguiente sección estudiamos esta toolkit en más profundidad. wxPython wxWidgets fue creada para proveer una manera barata y flexible de maximizar la inversión realizada en el desarrollo de aplicaciones con interfaz gráfica. A pesar de que otras librerías de clases ya existían para desarrollo multiplataforma, ninguna cumplía los siguientes criterios: Bajo precio Código abierto (Open Source) Facilidad de programación Soporte de un amplio rango de compiladores Hasta Febrero del 2004, wxWidgets era conocida como wxWindows. Sin embargo, dadas las repercusiones legales que el uso del término “Windows”, marca registrada por Microsoft, podría tener, los creadores de wxWindows y wxPython, Julian Smart y Robin Dunn, respectivamente, decidieron, previa sugerencia de Microsoft, renombrar el proyecto como wxWidgets. wxPython añade una simple capa de Python encima de wxWidgets, aislando la complejidad de la programación en C++ y todavía ofreciendo la misma funcionalidad de esta completísima API a los programadores de Python. Instalando wxPython En Windows, wxPython tan sólo requiere que hayas instalado previamente Python en tu sistema. De la dirección de Internet http://www.wxpython.org/ download.php se puede bajar un ejecutable con un instalador sobre el que sólo hay que hacer doble clic. Para Linux, la instalación es un poco más complicada. wxPython depende de las librerías glib y gtk+, que normalmente ya están instaladas en todos los sistemas Linux. Además, requiere el uso de la librería compartida wxGTK. Para detalles sobre cómo conseguir su instalación, mirar tanto la URL http://www.wxpython.org/ download.php como http://www.wxwidgets.org, sección de Downloads. Programando con wxPython En wxPython todas las clases están definidas dentro del módulo wx. Por tanto, en toda aplicación debemos importar ese módulo. Para inicializar una aplicación wxPython hay que crear una clase que derive de la clase wx.App y sobreescriba su método App.OnInit. Toda aplicación debe tener al menos un marco (Frame) o diálogo (Dialog) como nivel superior. Cada marco o diálogo contendrá, a su vez, opcionalmente, una o más instancias de clases como Panel, SplitterWindow u otras ventanas, que son a su vez contenedoras de controles. Toda ventana puede tener una barra de menús y herramientas (MenuBar y ToolBar), una línea de estado (StatusBar) y un icono para cuando ésta se minimice. Los marcos, diálogos y paneles se usan para colocar controles que sirven para interactuar con una aplicación. Algunos de los controles más comunes son: Button, CheckBox, Choice, ListBox, RadioBox y Slider. wxWidgets/wxPython proporciona cualquiera de los controles que te puedes encontrar en tus aplicaciones gráficas en Linux o Windows. Estos controles derivan de la clase base Control, la cual deriva a su vez de la clase base Window. Para facilitar la creación de aplicaciones gráficas, wxPython provee un conjunto de clases de diálogo comunes ya preparadas tales como MessageDialog o FileDialog. Aplicación “Hola Solop” Pongamos en práctica estas nociones sobre wxPython con el más simple de los ejemplos. Vamos a desarrollar una simple ventana que muestre el logo de Sólo Programadores (véase la figura 1). El listado 1 muestra el código de esta aplicación. En primer lugar, creamos una clase MiAplic que deriva de wx.App, requerimiento que debe cumplir toda aplicación wxWidgets. Para evitar que la aplicación termine una vez creada la instancia de MiAplic, invocamos su método MainLoop, el cuál hará que el programa empiece a escuchar eventos de ratón y teclado sobre la aplicación gráfica. La clase MiAplic sobrescribe el método OnInit de wx.App, donde se crean los elementos gráficos de la aplicación. En este método llamamos en primer lugar a la función wx.InitAllImageHandlers() para inicializar los gestores de imágenes (bmp, jpg, png, etc.), dado que vamos a usar un fichero .jpg con el logo de Sólo Programadores. Luego, creamos una instancia de una imagen, pasando como parámetros el nombre de la imagen (solop.jpg) y el tipo de la imagen (wx.BITMAP_TYPE_JPEG). A continuación, creamos una instancia de la clase MiMarco. Finalmente, visualizamos la ventana MiMarco y nos aseguramos que sea la ventana principal de la aplicación al invocar el método SetTopWindow. La clase MiMarco convierte la imagen a un bitmap, obtiene el tamaño de la imagen creada e inicializa la clase padre de MiMarco, que es un wx.Frame. Finalmente, inicializa el atributo self.bmp con una instancia de un control wx.StaticBitmap, que contiene el bitmap creado con anterioridad. No siempre es necesario (o conveniente) crear de manera programática las interfaces en wxPython. Hay herramientas que nos ayudarán a generar el código wxPython Figura 1. Aplicación “Hola Solop” en wxPython. 27 SOLO PROGRAMADORES nº 119 MIDDLEWARE LISTADO 1 “Hola Solop” en wxPython #!/usr/bin/env python import wx Gestores de posición: sizers class MiMarco(wx.Frame): “””Clase frame que visualiza una imagen.””” def __init__(self, imagen, padre=None, id=-1, pos=wx.DefaultPosition, titulo=’Hola solo programadores’): “””Crea una instancia de Frame y visualiza una imagen.””” temp = imagen.ConvertToBitmap() tamano = temp.GetWidth(), temp.GetHeight() wx.Frame.__init__(self, padre, id, titulo, pos, tamano) self.bmp = wx.StaticBitmap(self, -1, temp) class MiAplic(wx.App): “””La clase aplicacion.””” def OnInit(self): wx.InitAllImageHandlers() imagen = wx.Image(‘solop.jpg’, wx.BITMAP_TYPE_JPEG) self.miMarco = MiMarco(imagen) self.miMarco.Show() self.SetTopWindow(self.miMarco) return True correspondiente. wxGlade (http://wxglade.sourceforge.net/) es un buen ejemplo. wxGlade es un diseñador de interfaces que asiste en la creación de interfaces gráficas con wxPython y wxWidgets. Genera código fuente tanto en Python como en C++. Manejo de eventos Toda ventana, y, por extensión, todo control (dado que los controles derivan de la clase wx.Window), definen una serie de eventos que se lanzan cuando el usuario interactúa sobre ellos. wxPython define por cada control una tabla de eventos que asocia un tipo de evento con un gestor del mismo. Por ejemplo, como se ilustra debajo, para el evento EVT_BUTTON que ocurre cuando se pulsa un botón (botonNuevoUsuario), se podría asociar el manejador OnNuevoUsuario. Obsérvese que el segundo parámetro de este método evt es de tipo wx.Event. Este objeto contiene información sobre el evento: su tipo, identificador del control que lo generó, cuándo ocurrió el evento, una referencia al objeto que lo generó, etc.: ... wx.EVT_BUTTON(self, self.botonNuevoUsuario. GetId(), self.OnNuevoUsuario) Figura 2. Ventana de login a la aplicación tres en raya. ... def OnNuevoUsuario(self, evt): # código para crear nuevo usuario Para poder conocer qué eventos puede generar cada control en wxPython remitimos al lector a la documentación de wxWidgets, que acompaña a la distribución de wxPython. Por 28 Los gestores de posición, representados por la clase wx.Sizer y sus descendientes en la jerarquía de clases de wxPython, se han convertido en el método elegido para definir el posicionamiento de controles en diálogos, dada su habilidad para crear diálogos atractivos independientes de la plataforma, que tengan en consideración las diferencias en tamaño y estilo de los controles individuales. El cuadro “Gestores de posición más comúnmente utilizados” muestra algunos de los sizers disponibles en wxPython. Gestores de posición más comúnmente utilizados Sizer GridSizer if __name__ == ‘__main__’: miAplic = MiAplic() miAplic.MainLoop() SOLO PROGRAMADORES nº 119 cada control se detallan los eventos que éste puede generar. BoxSizer Clase base abstracta Un gestor de posición que dispone los controles en una matriz donde las celdas tienen el mismo tamaño. Gestor para disponer controles en una fila o columna. Dibujando gráficos En wxPython no se puede dibujar directamente en una ventana. Es preciso utilizar un contexto de dispositivo (device context o DC). DC es la clase base para ClientDC, PaintDC, MemoryDC, PostScriptDC, MemoryDC, MetafileDC and PrinterDC. La virtud de los DC es que se puede utilizar exactamente el mismo código para dibujar encima de cualquiera de estos dispositivos. Algunos ejemplos de las funciones disponibles para dibujar son DC.DrawLine, DC.DrawCircle o DC.DrawText. Para optimizar el pintado de figuras geométricas, es recomendable incluir las primitivas de dibujo entre las llamadas DC.BeginDrawing y DC.EndDrawing. El color del fondo de cada figura dibujada se puede controlar con el concepto de brocha (Brush) y el grosor y color de los bordes de cada figura con el concepto de bolígrafo (Pen). Añadiendo interfaz gráfica a la aplicación de tres en raya Recordemos que en la anterior entrega definimos una aplicación de tres en raya en modo texto que requería el login de cada jugador y mantenía en memoria las estadísticas sobre los resultados de sus partidas. Usando wxPython, añadiremos ahora las siguientes ventanas gráficas a la aplicación tres en raya: 1- Ventana de login (véase la figura 2) a través de la cual el usuario puede entrar en la aplicación, crear nuevos detalles de login de un usuario o simplemente salir de la aplicación. MIDDLEWARE Pensando en Python (II): 3 en raya en modo gráfico 2- Ventana de nuevo usuario (véase la figura 3), donde se puede introducir el nombre de usuario y contraseña de un nuevo usuario 3- Ventana principal de la aplicación (véase la figura 4), a través de la cual el usuario puede iniciar una partida, observar sus estadísticas de partidas jugadas o regresar a la ventana de login. 4- Ventana de partida de tres en raya (véase la figura 4), en la cual el usuario verá un tablero de tres en raya sobre el cual podrá jugar. 5- Diálogo de resultado de partida (véase la figura 4), en el que se indicará a la conclusión de una partida el ganador de la misma o si ha habido empate. 6- Ventana de estadísticas (véase la figura 5) que muestre los resultados obtenidos por un jugador. Ventana de login El listado 2 muestra un extracto del constructor de la clase LoginGUI, responsable de generar la ventana mostrada en figura 2. La clase LoginGUI hereda de wx.Frame. Su constructor recibe como parámetro obligatorio una referencia al registro de jugadores y estadísticas de partidas. Esta clase podría instanciarse con la llamada: ventanaLogin = LoginGUI(registro) en el constructor de LoginGUI, después de invocar el constructor de la clase padre, hacemos que el fondo de la pantalla tenga color blanco. A continuación, guardamos una referencia al registro y creamos una barra de menú a la que añadimos la cabecera de menú TresEnRaya, que contiene como una única opción de selección Salir. Asociamos al evento de selección de esta opción, el método de la clase self.OnSalir, mediante la sentencia: wx.EVT_MENU(self, 101, self.OnSalir) El contenido de este método es mostrado en el listado 3. Simplemente destruye las ventanas que hayan sido creadas a partir de ella, ventanaUsuario (véase la figura 4) si existe, y luego se destruye a si misma para acabar la aplicación. Después de crear la barra de menú el constructor (véase el listado 2) crea los campos de entrada de datos “nombre de usuario” y “contraseña”. Lo hace usando los controles wx.StaticText y wx.TextCtrl. Nótese que el botón “Login” en la figura 2 sólo se activará si hay datos en los dos campos de entrada self.nombreUsuarioTxtCtrl y self.passwordTxtCtrl. Esto se consigue al manejar el evento de introducción de texto en uno de esos controles, mediante llamadas en la forma: wx.EVT_TEXT(self, self.nombreUsuarioTxtCtrl. GetId(), self.EvtText) Tras crear los campos de entrada, el constructor instancia los botones de la ventana de login: self.botonNuevoUsuario, self.botonLogin y self.botonSalir. Mediante las llamadas en la forma: wx.EVT_BUTTON(self, <identificadorBotónPulsado>, <métodoAInvocar>) Asocia métodos que serán invocados cada vez que se pulse uno de estos botones. Por ejemplo: wx.EVT_BUTTON(self, self.botonSalir.GetId(), self.OnSalir) La implementación de los manejadores de eventos para estos botones: OnLogin, OnNuevoUsuario y OnSalir pueden verse en el listado 3. El manejador OnNuevoUsuario creará una ventana NuevoUsuarioGUI LISTADO 2 Figura 3. Ventana de nuevo usuario. Constructor de clase LoginGUI class LoginGUI(wx.Frame): def __init__(self, registro, parent=None, id=-1, title=’Juego tres en raya: login’): wx.Frame.__init__(self, parent, id, title) self.SetBackgroundColour(wx.WHITE) self.registro = registro # Preparamos la barra de menu menuBar = wx.MenuBar() menu1 = wx.Menu() menu1.Append(101, “&Salir”, “Haz clic para salir”) menuBar.Append(menu1, “&TresEnRaya”) self.SetMenuBar(menuBar) wx.EVT_MENU(self, 101, self.OnSalir) nombreUsuarioLabel = wx.StaticText(self, -1, “Nombre usuario:”) self.nombreUsuarioTxtCtrl= wx.TextCtrl(self, -1, “”, size=(125, -1)) passwordLabel = wx.StaticText(self, -1, “Password:”) self.passwordTxtCtrl = wx.TextCtrl(self, -1, “”, size=(125, -1), style=wx.TE_PASSWORD) ... self.botonNuevoUsuario = wx.Button(self, -1, “Nuevo usuario”) wx.EVT_BUTTON(self, self.botonNuevoUsuario.GetId(), self.OnNuevoUsuario) self.botonLogin = wx.Button(self, -1, “Login”) self.botonLogin.Enable(False) wx.EVT_BUTTON(self, self.botonLogin.GetId(), self.OnLogin) self.botonSalir = wx.Button(self, -1, “Salir”) wx.EVT_BUTTON(self, self.botonSalir.GetId(), self.OnSalir) bsizer = wx.BoxSizer(wx.VERTICAL) topSizer = wx.BoxSizer(wx.HORIZONTAL) topSizer.Add(nombreUsuarioLabel, 0, wx.GROW|wx.ALL, 4) topSizer.Add(self.nombreUsuarioTxtCtrl, 0, wx.GROW|wx.ALL|wx.ALIGN_RIGHT , 4) bsizer.Add(topSizer, 0, wx.GROW|wx.ALL, 4) ... botonAccionSizer = wx.BoxSizer(wx.HORIZONTAL) botonAccionSizer.Add(self.botonNuevoUsuario, 0, wx.GROW|wx.ALL, 4) ... bsizer.Add(botonAccionSizer, 0, wx.GROW|wx.ALL, 4) bsizer.Fit(self) self.SetAutoLayout(True) self.SetSizer(bsizer) wx.EVT_CLOSE(self, self.OnSalir) 29 SOLO PROGRAMADORES nº 119 MIDDLEWARE LISTADO 3 Manejadores de eventos de la clase LoginGUI def OnSalir(self, evt): self.__cerrarAplicacion() def OnLogin(self, evt): try: self.registro.login(self.nombreUsuarioTxtCtrl.GetValue(), self.passwordTxtCtrl.GetValue()) except: mostrarDialogo(self, ‘No hay ningun usuario registrado con el nombre de usuario y password especificados’, ‘Login incorrecto’) return try: self.ventanaUsuario = LoggedInGUI(self.nombreUsuarioTxtCtrl.GetValue(), self.passwordTxtCtrl.GetValue(), parent=self) self.ventanaUsuario.Show() self.Hide() except Exception, e: mostrarDialogo(self, “Problemas haciendo login para usuario “ + self.nombreUsuarioTxtCtrl.GetValue() + “: “ + str(e.args), “Error al hacer login”) def OnNuevoUsuario(self, evt): ventanaNuevoUsuario = NuevoUsuarioGUI(self) ventanaNuevoUsuario.Show() self.Hide() def __cerrarAplicacion(self): if self.__dict__.has_key(‘ventanaUsuario’): if self.ventanaUsuario: self.ventanaUsuario.Destroy() self.Destroy() def EvtText(self, event): if len(self.nombreUsuarioTxtCtrl.GetValue().strip()) > 0 and len(self.passwordTxtCtrl.GetValue().strip()) > 0: self.botonLogin.Enable(True) else: self.botonLogin.Enable(False) Figura 4. Ventana principal de la aplicación tres en raya, con ventana de partida y diálogo de resultado. (véase la figura 3) donde se podrán introducir los detalles de un nuevo usuario y esconderá la ventana de login. El manejador OnLogin, comprobará que el usuario ha sido registrado y si es así, esconderá la ventana de login y creará una ventana principal de la aplicación (véase la figura 4). El constructor (líneas finales del listado 2) usa gestores de posicionamiento de tipo BoxSizer para colocar tanto en vertical como en horizontal los controles antes mencionados. Cuando creamos un BoxSizer indicamos la dirección en la que colocaremos los controles, y luego simplemente usamos el método Add del BoxSizer para añadir controles al mismo. Como última línea del constructor definimos un manejador para el evento cerrar ventana. Ventana de partida (figura 4) El código de la ventana de partida (JuegoTresEnRayaGUI, véase el cuadro “Material complementario”), presenta como una única novedad el uso del manejador GridSizer. El siguiente método ilustra cómo se inicializa el tablero de tres en raya con bitmaps que indican casillas no seleccionadas. Básicamente, se define una matriz de 3x3 en la que se colocan los botones sobre los que el usuario puede hacer clic. Cuando el usuario hace clic se invoca el método self.OnClick que cambiará el icono para mostrar que esa casilla ha sido seleccionada. La clase JuegoTresEnRayaGUI hereda de la clase JuegoTresEnRaya vista en la anterior entrega: def __initTablero(self): self.gridsizer = wx.GridSizer(3,3,0,0) self.celdaTablero = [] self.bitmaps = [] for i in range(9): self.bitmaps.append(wx.Bitmap (“images/blank.png”, wx.BITMAP_TYPE_PNG)) self.celdaTablero.append(wx.BitmapButton (self, i, self.bitmaps[i])) wx.EVT_BUTTON(self, i , self.OnClick) self.gridsizer.Add(self.celdaTableroa[i]) self.gridsizer.Fit(self) self.SetAutoLayout(True) self.SetSizer(self.gridsizer) Diálogo de resultado de partida (figura 4) Todos los resultados, o mensajes que hay que dar al usuario se visualizan a través de una función de ayuda, que simplemente instancia un wx.MessageDialog: def mostrarDialogo(ventanaPadre, mensaje, titulo, modo=wx.OK): dialog = wx.MessageDialog(ventanaPadre, mensaje, titulo, modo); dialog.ShowModal() Ventana de estadísticas (figura 5) El listado 4 muestra la implementación de la clase (EstadisticasGUI) que hereda de SOLO PROGRAMADORES nº 119 30 MIDDLEWARE Pensando en Python (II): 3 en raya en modo gráfico LISTADO 4 Implementación de la ventana de estadísticas class EstadisticasGUI(wx.Frame): def __init__(self, ventanaUsuario, nombreUsuario, resultadoEstadisticas): ... self.anchura = 640 self.altura = 480 self.SetSize((self.anchura, self.altura)) # asociar tamaño a ventana self.SetBackgroundColour(wx.WHITE) # cambiar color fondo wx.EVT_PAINT(self, self.OnPaint) def OnPaint(self, evt): dc = wx.PaintDC(self) # inicializar un contexto de dispositvo dc.Clear() Figura 5. Ventana de estadísticas. wx.Frame. En el constructor se le asigna un tamaño fijo de 640x480 pixels y se define un manejador OnPaint para el redibujado de la ventana. Este evento será invocado cada vez que se mueva, maximice o cambie el tamaño de la ventana de estadísticas. En OnPaint, se crea una instancia de DC, se limpia el área de dibujado del marco y se inicia el redibujado con la sentencia dc.BeginDrawing(). A continuación, se inicializa el formato de los trazos a dibujar con un objeto Pen de color negro: dc.SetPen(wx.Pen(“BLACK”,1)). Similarmente, se inicializa una brocha de color rojo para el relleno de la primera barra de resultados a dibujar: dc.SetBrush(wx.Brush(“RED”)). Luego se dibujan las barras que muestran el porcentaje de victorias, empates y partidas perdidas por un jugador. Se calculan las alturas de cada una de las barras y a continuación se procede a su dibujado, por medio de las primitivas dc.DrawRectangle. Es importante indicar que el origen de coordenadas de dibujado se encuentra en la parte superior izquierda de la ventana. Esa es la razón por la que se utilizan valores negativos para indicar tamaños en vertical. Por ejemplo: dc.DrawRectangle(100, 350, 120, -alturaBarraGanadas) dibuja un rectángulo que empieza en las coordenadas (100, 350), tiene 120 pixels de anchura y se extiende hasta la coordenada 350-alturaBarraGanadas de altura. Serialización de datos en Python La mayoría de los datos en los sistemas de información se guardan en ficheros. Python ofrece unas interfaces muy sencillas para guardar y recuperar datos de ficheros. Para leer o escribir un fichero en Python usamos la función predefinida open, que tiene la siguiente firma: open(<nombre-fichero>, <modo: r (read), w(write), a(append), b(binario)> dc.BeginDrawing() # indicar que se va a empezar a dibujar dc.SetPen( wx.Pen(“BLACK”,1) ) dc.SetBrush( wx.Brush(“RED”) ) partidasJugadas = self.resultadoEstadisticas[0] + self.resultadoEstadisticas[1] + self.resultadoEstadisticas[2] + 0.0 if partidasJugadas > 0: alturaBarraGanadas = int(250*(self.resultadoEstadisticas[0]/partidasJugadas)) alturaBarraEmpatadas = int(250*(self.resultadoEstadisticas[1]/partidasJugadas)) alturaBarraPerdidas = int(250*(self.resultadoEstadisticas[2]/partidasJugadas)) if alturaBarraGanadas < 1: alturaBarraGanadas = 1 if alturaBarraEmpatadas < 1: alturaBarraEmpatadas = 1 if alturaBarraPerdidas < 1: alturaBarraPerdidas = 1 else: alturaBarraGanadas = 1 alturaBarraEmpatadas = 1 alturaBarraPerdidas = 1 cabecera = “Total partidas jugadas: “ + str(int(partidasJugadas)) (w, h) = dc.GetTextExtent(cabecera) dc.DrawText(cabecera, 320-(w/2), 70-h) # dibujar cabecera gráficos (w, h) = dc.GetTextExtent(“Ganadas”) # cabecera barra Ganadas dc.DrawText(“Ganadas”, 160-(w/2), 390-h) dc.DrawRectangle(100, 350, 120, -alturaBarraGanadas) # dibujar barra de ganadas (w, h) = dc.GetTextExtent(`self.resultadoEstadisticas[0]`) dc.DrawText(`self.resultadoEstadisticas[0]`, 160-(w/2), 350alturaBarraGanadas-20) dc.SetBrush( wx.Brush(“GREEN”) ) # código similar a barra Ganadas para barra verde de empates ... dc.SetBrush( wx.Brush(“BLUE”) ) # código similar a barra Ganadas para barra azul de empates ... dc.EndDrawing() Esta función devuelve un objeto de tipo fichero que define entre otros los métodos read, para leer uno o un buffer de caracteres, write, para escribir uno o un grupo de caracteres, readlines para leer todas las líneas de un fichero y close para cerrar un fichero. A continuación se muestra el código para leer un fichero de texto línea a línea. Como siempre, remitimos al lector a la Python Library Referente (http://docs.python.org/lib/lib.html) para más detalles: # leerfichero.py 31 SOLO PROGRAMADORES nº 119 MIDDLEWARE LISTADO 5 Haciendo persistente el registro de jugadores class RegistroJugadoresPersistente(RegistroJugadores): def __init__(self): RegistroJugadores.__init__(self) self._RegistroJugadores__jugadores = shelve.open(“jugadores”) if not self._RegistroJugadores__jugadores.has_key(‘solop’): self._RegistroJugadores__jugadores[‘solop’] = ‘solop’ self._RegistroJugadores__estadisticas = shelve.open(“estadisticas”) if not self._RegistroJugadores__estadisticas.has_key(‘solop’): # jugador -> [ganados, empatados, perdidos] self._RegistroJugadores__estadisticas[‘solop’] = [0, 0, 0] def __del__(self): self._RegistroJugadores__jugadores.close() self._RegistroJugadores__estadisticas.close() fh = open(“holamundo.py”) # open crea un objeto de tipo fichero for line in fh.readlines() : # lee todas las líneas en un fichero print line, fh.close() $ python leerfichero.py #!/usr/bin/python print “Hola mundo” El siguiente código muestra cómo escribir datos a un fichero de texto: # escribirfichero.py fh = open(“out.txt”, “w”) fh.write (“estamos escribiendo ...\n”) fh.close() $ python escribirfichero.py $ cat out.txt estamos escribiendo ... Serialización de objetos: Pickle y shelves Al igual que otros lenguajes modernos como Java y C#, Python ofrece mecanismos para serializar y deserializar objetos desde disco o a través de la red. El módulo pickle implementa un algoritmo para la serialización y deserialización de objetos en Python. Para serializar una jerarquía de objetos, deberemos crear un Pickler, y luego llamar al método dump(), pasándole como argumento el objeto a serializar. Para deserializar crearemos un Unpickler e invocaremos su método load(). El módulo shelve, por su parte, define diccionarios persistentes. Las claves tienen que ser cadenas de caracteres mientras que los valores pueden ser cualquier objeto que se puede serializar con pickle. A continuación mostramos un ejemplo de cómo implementar un diccionario o mapa persistente: import shelve d = shelve.open(filename) # abre un fichero SOLO PROGRAMADORES nº 119 32 d[key] = data # guarda un valor bajo key data = d[key] # lo recupera del d[key] # lo borra d.close() # cierra el diccionario persistente Añadiendo persistencia de datos a la aplicación tres en raya Una vez añadida una interfaz gráfica a nuestra aplicación de tres en raya, quizás el único aspecto que le resta para darle un carácter más profesional, es añadirle la capacidad de recordar los usuarios que se registran con la misma, así como las estadísticas de los resultados obtenidos por esos usuarios. En la primera entrega de esta serie definimos una clase llamada RegistroJugadores que mantenía en dos diccionarios, en memoria, información sobre los jugadores registrados (__jugadores) y sobre los resultados obtenidos por los mismos (__estadisticas). Para hacer que estos datos se guarden de manera persistente, sólo debemos definir una nueva clase que herede de RegistroJugadores a la que llamaremos RegistroJugadoresPersistente. El listado 5 muestra el código de esta clase. En el constructor de esta nueva clase nos aseguramos que los miembros de RegistroJugadores __jugadores y __estadisticas pasen de ser simples mapas a shelves, es decir, mapas de datos persistentes. Es preciso recordar que en Python se usa la técnica de name mangling, para hacer que todos los campos y funciones privadas o protected, no sean visibles desde fuera. Por esa razón, tenemos que usar la notación _nombreClase__nombreCampoPrivado, por ejemplo, self._RegistroJugadores__estadisticas, para tener acceso a estos campos. Finalmente, para asegurarnos que el mapa persistente es correctamente cerrado, añadimos en el destructor de la clase (__del__) llamadas a los metodos close() de las baldas de datos (shelves). Conclusiones En esta entrega hemos incrementado nuestros conocimientos del lenguaje de programación Python, aprendiendo cómo se pueden crear ventanas gráficas con la toolkit wxPython y cómo se pueden serializar datos a través de ficheros, y los módulos pickle y shelve. Con ésto, hemos conseguido darle una apariencia y un funcionamiento mucho más profesional a la aplicación de tres en raya que comenzamos en la anterior entrega. En el próximo artículo aprenderemos a crear una interfaz web al juego de tres en raya y a guardar los datos de jugadores y estadísticas en una base de datos.