Dado para Android hecho con Python

Disponible en Google Play


Creación de un Dado para Android utilizando Apache Cordova y Brython

Vamos a crear una aplicación sencilla para Android utilizando Python. Crearemos un Dado:

App Dado Screenshot

Antes de empezar pongo a tu disposición los archivos que crearemos en el proyecto y el apk final:


CREAR EL PROYECTO DADO

Lo primero que tenemos que hacer es crear un Proyecto Cordova. Si no tienes instalado el Framework Cordova o no sabes como crear un proyecto puedes visitar la página principal de esta sección.

A continuación crearé el proyecto Cordova, agregaré la plataforma Android al proyecto, eliminaré los archivos innecesarios generados por defecto e instalaré Brython:

cordova create dado com.tecnobillo.dado Dado
cd dado
cordova platform add android
cd www
rm index.html css/index.css js/index.js
rm -r img
cd js
python3 -m brython --install
rm *.txt *.html

Ya tenemos preparado el proyecto dado. Crearemos la aplicación mediante 4 archivos en el proyecto calculadora:

Además en la carpeta www debe haber un directorio assets que contiene las imágenes que representan las caras del dado.

Cuando tengamos todo listo compilaremos nuestro archivo APK mediante:

cordova build android

CONFIG.XML

Archivo dado/config.xml

Lo primero que debemos hacer es editar el archivo config.xml para especificar el nombre de la aplicación, icono, etc. Yo lo dejaré del siguiente modo:

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.tecnobillo.dado" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>Dado</name>
    <description>
        Dado creado con Apache Cordova y Brython
    </description>
    <author email="[email protected]" href="https://tecnobillo.com">
        José María Sánchez Ruiz (tecnobillo)
    </author>
    <content src="index.html" />
    <icon src="dado.png" />
    <preference name="Orientation" value="portrait" />
</widget>

Si utilizas este config.xml debes copiar un archivo dado.png en el directorio raíz del proyecto (el mismo donde está config.xml), ya que lo he especificado en el atributo src de la etiqueta icon.

He utilizado el siguiente icono:

icono-dado

Quiero que la aplicación tenga orientación portrait, es decir, que siempre se vea en vertical aunque el dispositivo se gire, para ello he añadido:

<preference name="Orientation" value="portrait" />

He configurado la aplicación con orientación portrait porque el archivo styles.css que crearemos mas adelante solamente está pensado para esta orientación, pero si quieres puedes modificarlo para que la aplicación también se vea bien en landscape.

Cambia mis datos por los tuyos (autor, email, sitio web, etc), y fíjate en el atributo src de la etiqueta content: Indica que el contenido que debe cargarse al ejecutarse la aplicación es el del archivo index.html.


INDEX.HTML

Archivo dado/www/index.html

Una vez editado el archivo config.xml, el siguiente paso es crear un archivo index.html en el directorio www. Copia lo siguiente en dicho archivo:

<!DOCTYPE html>

<html>

<head>

    <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'">

    <meta name="format-detection" content="telephone=no">
    <meta name="msapplication-tap-highlight" content="no">
    <meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">

    <script src="js/brython.js"></script>
    <script src="js/brython_stdlib.js"></script>

    <script type="text/javascript">

    function startBrython(){
        document.addEventListener('deviceready', brython, false);
    }

    </script>

    <link rel="stylesheet" href="css/styles.css"/>

</head>

<body onload='startBrython();'>

    <script type="text/javascript" src="cordova.js"></script>

    <img id="vista-dado" src="assets/1.png"/>

    <button id="lanzar-dado">LANZAR<br/>DADO</button>

    <script type="text/python">

    from browser import ajax, run_script

    ajax.get('app.py', oncomplete=lambda req:run_script(req.text, '__main__'))

    </script>

</body>

</html>

Si quieres conocer los detalles de este archivo puedes ver una descripción completa en la página principal de esta sección.

Fíjate en los siguientes puntos:


STYLES.CSS

Archivo dado/www/css/styles.css

Crea un archivo styles.css en el directorio www/css y copia lo siguiente:

*{
    margin:0px;
    padding:0px;
    box-sizing:border-box;
}

body {
    width:100vw;
    height:100vh;
    background:gray;

}

#vista-dado{
    width:95vw;
    max-width:95vw;
    max-height:50vh;
    position:fixed;
    top:0px;
    left:0px;
    margin:2vw;
    border:0.5em solid black;
    border-radius:100px;
}

#lanzar-dado{
    width:100vw;
    max-width:100vw;
    height:40vh;
    max-height:40vh;
    font-size:3em;
    position:fixed;
    bottom:0px;
    left:0px;
}

No estamos aquí para aprender CSS así que esta parte no voy a explicarla, simplemene copia el código o adáptalo a tu gusto. Este código css está pensado para una aplicación portrait.

Sinceramente no me gusta demasiado CSS y se me da fatal. Por favor no me juzges si mi css te parece ridículo


APP.PY

Archivo dado/www/app.py

Crea un archivo app.py en el directiorio www.

¡MANOS A LA OBRA!

Lo primero que debes hacer es importar los módulos que necesitaremos en nuestro programa:

from browser import document, window
from browser.timer import set_interval, clear_interval
import random

A continuación crea una clase DadoApp:

class DadoApp:
    def __init__(self, vista, boton):
    	self.vista = vista
    	self.boton = boton

Continuaremos definiendo la clase DadoApp, pero antes añade lo siguiente al script:

if __name__ == '__main__':

    imgVista = document['vista-dado']
    btnLanzar = document['lanzar-dado']

    DadoApp(imgVista, btnLanzar)

Aquí lo que hemos hecho es obtener la img y el button que habíamos definido en index.html. Guardamos la imagen en la variable imgVista y el botón en la variable btnLanzar.

A continuación hemos instanciado una DadoApp pasándole como argumentos la imágen y el botón:

DadoApp(imgVista, btnLanzar)

Volvamos ahora a la clase DadoApp:

class DadoApp:
    def __init__(self, vista, boton):
    	self.vista = vista
    	self.boton = boton

self.vista es la imagen del dado, y self.boton es el botón para lanzar el dado.

Vamos a almacenar las direcciones de las imágenes del dado en un diccionario al que identificamos como self.states. Las imágenes del dado se encuentran en la carpeta assets, y se llaman 1.png, 2.png, 3.png, 4.png, 5.png y 6.png (6 imágenes para representar las 6 caras del dado). Añade lo siguiente al método __init__ de DadoApp:

for i in range(1, 7):
    self.states[i] = f'assets/{i}.png'

De este modo hemos creado un diccionario en el que las claves son los números enteros del 1 al 6, y su valor es el path de la imagen correspondiente formateada con el valor de la clase.

Ahora vamos a definir un método show_random para mostrar una cara aleatoria del dado:

def show_random(self):
    n = random.randrange(1, 7)
    self.vista.src = self.states[n]

show_random obtiene un número aleatorio comprendido entre 1 y 6, y lo almacena en la variable local n, que es utilizada para obtener el path de una de las imágenes del dado a través de self.states[n], cuyo valor se asigna a self.vista.src, es decir, al atributo src de la imágen en el documento html.

Una vez definido el método show_random, lo invocamos dentro del método __init__ para mostrar una imágen aleatoria del dado al abrir la aplicación. La clase DadoApp de momento se ve así:

class DadoApp:
    def __init__(self, vista, boton):
    	self.vista = vista
    	self.boton = boton

    	for i in range(1, 7):
    		self.states[i] = f'assets/{i}.png'

    	self.show_random()

    def show_random(self):
    	n = random.randrange(1, 7)
    	self.vista.src = self.states[n]

En este punto podrías finalizar la aplicación añadiendo al final de __init__ lo siguiente:

self.boton.bind('click', lambda e:self.show_random())

Así, cada vez que se pulse self.boton se ejecutará el método self.show_random y se mostrará una cara aleatoria del dado.

Pero vamos a complicar un poco la cosa para crear la sensacióm de que el dado ha sido realmente tirado: Define un método lanzar así:

def lanzar(self, e):
    pass

Al final de __init__ enlaza este método con el evento click sobre el botón:

self.boton.bind('click', self.lanzar)

Ya sólo nos queda definir el método lanzar, pero antes hay que añadir una serie de atributos de clase que utilizaremos en este método:

states = {}
interval = None
times = 0

La clase DadoApp ahora mismo se ve así:

class DadoApp:

    states = {}
    interval = None
    times = 0

    def __init__(self, vista, boton):

    	self.vista = vista
    	self.boton = boton

    	for i in range(1, 7):
    		self.states[i] = f'assets/{i}.png'

    	self.show_random()

    	self.boton.bind('click', self.lanzar)

    def show_random(self):
    	n = random.randrange(1, 7)
    	self.vista.src = self.states[n]

    def lanzar(self, e):
    	pass

Tenemos que ejecutar self.show_random varias veces seguidas para dar la sensación de que el dado está rodando. Define una función anidada dentro de self.lanzar y ejecútala a intervalos de 100ms:

def lanzar(self, e):

    def show_timer():
    	self.show_random()

    self.boton.disabled = True
    self.interval = set_interval(show_timer, 100)

Observa que mediante self.boton.disabled = True desactivo el botón cuando se hace click en él. Solamente al finalizar la tarea de show_timer volveremos a activar el botón.

Hay que añadirle un poco mas de funcionalidad a show_timer:

def show_timer():

    self.show_random()

    if self.times < 20:
    	self.times += 1

    else:
    	clear_interval(self.interval)
    	window.navigator.vibrate(100)
    	self.boton.disabled = False
    	self.times = 0

Lo que hemos hecho en show_timer es ejecutar self.show_random() y a continuación comprobar self.times < 20. En ese caso incrementamos en 1 el valor de self.times.

Si self.times >= 20, se reinicia la variable mediante self.times = 0 y se detiene el self.interval (que hace referencia al intervalo que está ejecutando periódicamente a show_timer).

Además, cuando detenemos a self.interval reactivamos el botón de lanzar el dado: self.boton.disabled = False.

He añadido window.navigator.vibrate(100) para que el dispositivo vibre cuando el dado se haya detenido. Esta sentencia solamente funcionará si previamente has instalado el plugin vibrate en el proyecto cordova. Puedes hacerlo mediante:

cordova plugin add cordova-plugin-vibration

EL archivo app.py finalmente debe verse así:

from browser import document, window
from browser.timer import set_interval, clear_interval
import random


class DadoApp:

    states = {}
    interval = None
    times = 0

    def __init__(self, vista, boton):

    	self.vista = vista
    	self.boton = boton

    	for i in range(1, 7):
    		self.states[i] = f'assets/{i}.png'

    	self.show_random()

    	self.boton.bind('click', self.lanzar)

    def show_random(self):

    	n = random.randrange(1, 7)
    	self.vista.src = self.states[n]

    def lanzar(self, e):

    	def show_timer():

    		self.show_random()

    		if self.times < 20:
    			self.times += 1
    		else:
    			clear_interval(self.interval)
    			window.navigator.vibrate(100)
    			self.boton.disabled = False
    			self.times = 0


    	self.boton.disabled = True
    	self.interval = set_interval(show_timer, 100)




if __name__ == '__main__':

    imgVista = document['vista-dado']
    btnLanzar = document['lanzar-dado']

    DadoApp(imgVista, btnLanzar)

__Ya puedes crear el *archivo APK* e instalarlo en tu dispositivo:__
cordova build android

El APK se genera en el directorio dado/platforms/android/app/build/outputs/apk/debug y se llama app-debug.apk.

Si quieres crear una versión de lanzamiento de la aplicación (app-release.apk) puedes ver como hacerlo en Cordova-Brython.




Animación Aplicación Dado