“En todo el universo, la Matemática es número y medida. La unidad, símbolo del Creador (*), es el principio de todas las cosas que no existen sino en virtud de las inmutables proporciones y relaciones numéricas. Todos los grandes enigmas de la vida pueden reducirse a simples combinaciones de elementos variables o constantes, conocidos o incógnitos que nos permiten resolverlos.”
El hombre que calculaba, capítulo XI. Malba Tahan.
(*) Monesvol.
Estos días estoy leyendo ‘El hombre que calculaba’. El libro tiene cosas curiosas y una de esas cosas curiosas la vamos a convertir en un juego matemático para el navegador.
Los 4 cuatros
El juego lo llamaremos ‘los 4 cuatros’. En realidad no me he inventado el nombre, incluso tiene página en la Wikipedia, o sea que no soy muy original.
Las reglas son muy básicas, tenemos que conseguir distintos números usando cuatro 4’s y varias operaciones matemáticas básicas. En la versión que vamos a programar las operaciones permitidas serán:
- Suma: +,
- Resta: -,
- Multiplicación: *,
- División: /,
- Abrir paréntesis: (,
- Cerrar paréntesis: ),
- Punto decimal: .,
- Raiz cuadrada: √,
- Factorial: !
Por ejemplo, para conseguir el valor 0 podríamos hacer:
- \(\frac{4}{4} – \frac{4}{4} = 0\)
- \(44 – 44 = 0\)
- …
El juego
Debajo tienes el juego. Puedes pulsar los distintos botones para jugar al juego:
El objetivo, el número a resolver, se encuentra en el área rosa. Los botones verdes nos permiten crear nuestra expresión. Los botones azules nos permiten evaluar si la expresión es correcta o si queremos resetear el valor porque no conseguimos hallar una solución. Debajo de los botones azules aparecerán mensajes que nos irán informando de determinadas cosas.
Así se hizo
Y ahora voy a explicar un poco cómo está el juego hecho. Pongo todo el código a continuación y os voy explicando poco a poco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
<html> <head> <script src="https://cdn.jsdelivr.net/npm/brython@3.9.0/brython.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/brython@3.9.0/brython_stdlib.js"></script> <style> body {background-color: #EADEDA;} .button { background-color: #4CAF50; /* Green */ border-radius: 10px; color: white; padding: 12px 15px; margin: 5px 5px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; } .btn-green {background-color: #4CAF50;} .btn-red {background-color: #f44336;} .btn-blue {background-color: #008cba;} .div { background-color: #D90368; color: white; padding: 5px 10px; margin: 5px 5px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; height: 40px; } .span { background-color: #000000; color: white; padding: 5px; } .message { font-size: 20px; font-weight: bold; } .div-yellow {background-color: #FFD400;} .padding {line-height: 200%;} </style> </head> <body onload="brython()"> <H1>4 fours</H1> <div> <p>With 4 fours and some operators you have to get the target number below (rose area):</p> <p class="padding"> addition: <span class="span">+</span>, subtraction: <span class="span">-</span>, multiplication: <span class="span">*</span>, division: <span class="span">/</span>, open parenthesis: <span class="span">(</span>, close parenthesis: <span class="span">)</span>, decimal point: <span class="span">.</span>, square root: <span class="span">√</span>, factorial: <span class="span">!</span> </p> </ul> </div> <div class="div"><p>Target: <span id="target"></span></div> <div id="controls"></div> <div> <p>Use the green buttons above to create your expression. Your expression will be shown in the yellow area below:</p> </div> <div class="div div-yellow"><p id="proposal"></p></div> <div> <p>When you are ready you can evaluate your expression or reset the target with the blue buttons below:</p> </div> <div id="evaluate"></div> <div id="success" class="message"></div> <script type="text/python"> from browser import document as doc from browser import html from random import randint from math import sqrt, factorial # define the target value ######################### doc['target'].append(randint(1, 10)) # define the controls ##################### possibilities = { 'open_parenthesis': '(', 'four': '4', 'addition': '+', 'subtraction': '-', 'multiplication': '*', 'division': '/', 'decimal_point': '.', 'square_root': '√', 'factorial': '!', 'close_parenthesis': ')', } for k, v in possibilities.items(): doc['controls'] <= html.BUTTON(v, id=k, Class="button btn-green") def write(what): proposal = doc['proposal'].text if what == '4' and proposal.count('4') >= 4: doc['success'].text = "" doc['success'].append("You already have used 4 fours...") return None doc['proposal'].append(what) doc['open_parenthesis'].bind('click', lambda ev: write('(')) doc['four'].bind('click', lambda ev: write('4')) doc['addition'].bind('click', lambda ev: write('+')) doc['subtraction'].bind('click', lambda ev: write('-')) doc['multiplication'].bind('click', lambda ev: write('*')) doc['division'].bind('click', lambda ev: write('/')) doc['decimal_point'].bind('click', lambda ev: write('.')) doc['square_root'].bind('click', lambda ev: write('sqrt(')) doc['factorial'].bind('click', lambda ev: write('factorial(')) doc['close_parenthesis'].bind('click', lambda ev: write(')')) doc['controls'] <= html.BUTTON('DELETE', id='delete', Class="button btn-red") def delete(ev): proposal = doc['proposal'].text doc['proposal'].text = '' doc['proposal'].append(proposal[:-1]) doc['delete'].bind('click', delete) # evaluate ########## def evaluate(ev): proposal = doc['proposal'].text doc['success'].text = "" if proposal.count('4') != 4: doc['success'].text = "You are not using 4 fours. " try: result = int(eval(proposal)) except: doc['success'].append( "Incorrect expression. " ) result = False target = int(doc['target'].text) if result == target: doc['success'].append("Well done!!!!!") else: doc['success'].append("Oooops, try again!!!!!") doc['evaluate'].append( html.BUTTON('Evaluate', id="evaluate_btn", Class="button btn-blue") ) doc['evaluate_btn'].bind('click', evaluate) # reset ########## def reset(ev): doc['target'].text = "" doc['target'].append(randint(0, 50)) doc['proposal'].text = "" doc['success'].text = "" doc['evaluate'].append( html.BUTTON('Reset', id="reset_btn", Class="button btn-blue") ) doc['reset_btn'].bind('click', reset) </script> </body> </html> |
En el código anterior está mezclado el HTML, el CSS y Python (en lugar de javascript) usando Brython.
El cabecero:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<html> <head> <script src="https://cdn.jsdelivr.net/npm/brython@3.9.0/brython.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/brython@3.9.0/brython_stdlib.js"></script> <style> body {background-color: #EADEDA;} .button { background-color: #4CAF50; /* Green */ border-radius: 10px; color: white; padding: 12px 15px; margin: 5px 5px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; } .btn-green {background-color: #4CAF50;} .btn-red {background-color: #f44336;} .btn-blue {background-color: #008cba;} .div { background-color: #D90368; color: white; padding: 5px 10px; margin: 5px 5px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; height: 40px; } .span { background-color: #000000; color: white; padding: 5px; } .message { font-size: 20px; font-weight: bold; } .div-yellow {background-color: #FFD400;} .padding {line-height: 200%;} </style> </head> |
Básicamente, tenemos:
- los scripts con la implementación de Python en javascript (Brython) y parte de la biblioteca estándar Python. Esto nos permite escribir código Python en lugar de javascript y ese código Python se traducirá a javascript al vuelo gracias a la magia de Brython.
- Por otro lado tenemos un poco de código CSS para demostrar mis nulas dotes de diseño. No voy a entrar en ello.
Lo siguiente es importante:
1 |
<body onload="brython()"> |
Abrimos el body
del documento HTML y usamos la función brython()
(javascript) cuando la página se cargue. Esta función es la que se encarga de buscar en el documento HTML todas las etiquetas script
con atributo type
que sea igual a text/python
. Es decir, busca código Python dentro del documento HTML y lo traduce a javascript para que el navegador lo entienda.
Lo siguiente es un poco de estructura del documento HTML y algo de contenido con las explicaciones oportunas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<H1>4 fours</H1> <div> <p>With 4 fours and some operators you have to get the target number below (rose area):</p> <p class="padding"> addition: <span class="span">+</span>, subtraction: <span class="span">-</span>, multiplication: <span class="span">*</span>, division: <span class="span">/</span>, open parenthesis: <span class="span">(</span>, close parenthesis: <span class="span">)</span>, decimal point: <span class="span">.</span>, square root: <span class="span">√</span>, factorial: <span class="span">!</span> </p> </ul> </div> <div class="div"><p>Target: <span id="target"></span></div> <div id="controls"></div> <div> <p>Use the green buttons above to create your expression. Your expression will be shown in the yellow area below:</p> </div> <div class="div div-yellow"><p id="proposal"></p></div> <div> <p>When you are ready you can evaluate your expression or reset the target with the blue buttons below:</p> </div> <div id="evaluate"></div> <div id="success" class="message"></div> |
Y, a partir de aquí, viene el código Python. Primero importamos unas pocas bibliotecas.
1 2 3 4 |
from browser import document as doc from browser import html from random import randint from math import sqrt, factorial |
Aquí tenemos la biblioteca browser
que es específica de Brython. Esta biblioteca nos permite tener acceso al DOM y añadir nuevas etiquetas al documento.
En lo siguiente añadimos un número aleatorio entre 1 y 10 a la zona rosa. Empezamos con un problema sencillo, por eso solo considero valores del 1 al 10, para que nadie se desanime a las primeras de cambio.
1 2 3 |
# define the target value ######################### doc['target'].append(randint(1, 10)) |
En el código HTML de más arriba habíamos definido
1 |
<div class="div"><p>Target: <span id="target"></span></div> |
Donde tenemos el elemento span
que tiene el atributo id="target"
. Usando doc['target']
accedemos a ese elemento. Es similar al siguiente código javascript: document.getElementById('target')
. Con Brython podemos añadir contenido a un elemento de varias formas. Una de ellas es usando el método append
.
En la siguiente parte vamos a definir una serie de botones. Normalmente, un botón tiene una funcionalidad cuando lo pulsamos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# define the controls ##################### possibilities = { 'open_parenthesis': '(', 'four': '4', 'addition': '+', 'subtraction': '-', 'multiplication': '*', 'division': '/', 'decimal_point': '.', 'square_root': '√', 'factorial': '!', 'close_parenthesis': ')', } for k, v in possibilities.items(): doc['controls'] <= html.BUTTON(v, id=k, Class="button btn-green") def write(what): proposal = doc['proposal'].text if what == '4' and proposal.count('4') >= 4: doc['success'].text = "" doc['success'].append("You already have used 4 fours...") return None doc['proposal'].append(what) doc['open_parenthesis'].bind('click', lambda ev: write('(')) doc['four'].bind('click', lambda ev: write('4')) doc['addition'].bind('click', lambda ev: write('+')) doc['subtraction'].bind('click', lambda ev: write('-')) doc['multiplication'].bind('click', lambda ev: write('*')) doc['division'].bind('click', lambda ev: write('/')) doc['decimal_point'].bind('click', lambda ev: write('.')) doc['square_root'].bind('click', lambda ev: write('sqrt(')) doc['factorial'].bind('click', lambda ev: write('factorial(')) doc['close_parenthesis'].bind('click', lambda ev: write(')')) doc['controls'] <= html.BUTTON('DELETE', id='delete', Class="button btn-red") def delete(ev): proposal = doc['proposal'].text doc['proposal'].text = '' doc['proposal'].append(proposal[:-1]) doc['delete'].bind('click', delete) |
En la primera parte tenemos un bucle donde iteramos sobre un diccionario. Con cada iteración añadimos un botón:
1 |
doc['controls'] <= html.BUTTON(v, id=k, Class="button btn-green") |
Los botones los añadimos a la etiqueta div
con id="controls"
. Antes hemos usado el método append
para añadir contenido. Ahora voy a usar un operador especial de Brython, <=
, que es como una flecha que indica que estamos metiendo el valor de la derecha (en este caso un botón) al valor de la izquierda, el div
. El operador <=
es similar al método append
. Con el bucle for
hemos añadido 10 botones.
La función write
es la que se encarga de hacer varias cosas. Por un lado, escribe contenido en la zona amarilla, que es la zona donde iremos escribiendo nuestra expresión a evaluar. También se encarga de comprobar si estamos usando más de cuatro 4’s. Para actualizar algunas cosas usamos, nuevamente, el método append
y el atributo text
. El equivalente a text
en javascript sería innerText
.
Las siguientes líneas nos permiten añadir funcionalidad a cada uno de los botones que acabamos de crear usando el método bind
.
1 |
doc['open_parenthesis'].bind('click', lambda ev: write('(')) |
doc['open_parenthesis']
accede a uno de los botones que acabamos de crear. Con el método bind
hacemos que al evento click
(pulsamos el botón) se responda llamando a la función write
y ejecutando ese código.
A continuación hacemos algo similar para el botón DELETE
pero este tiene una funcionalidad un poco distinta. No lo explico porque creo que ya deberías saber cómo funciona.
El siguiente código se encargará de evaluar la expresión que hayamos escrito y mostrar distintos mensajes en función de si acertamos o no:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# evaluate ########## def evaluate(ev): proposal = doc['proposal'].text doc['success'].text = "" if proposal.count('4') != 4: doc['success'].text = "You are not using 4 fours. " try: result = int(eval(proposal)) except: doc['success'].append( "Incorrect expression. " ) result = False target = int(doc['target'].text) if result == target: doc['success'].append("Well done!!!!!") else: doc['success'].append("Oooops, try again!!!!!") doc['evaluate'].append( html.BUTTON('Evaluate', id="evaluate_btn", Class="button btn-blue") ) doc['evaluate_btn'].bind('click', evaluate) |
Además, creamos el botón evaluate
y le añadimos funcionalidad usando un código similar al que hemos usado anteriormente.
Por último, hacemos algo similar para el botón reset
. Aquí añadimos funcionalidad para resetear el valor objetivo a calcular. Por si ya hemos acertado o, por si es muy difícil, poder pedir un nuevo valor.
1 2 3 4 5 6 7 8 9 10 11 12 |
# reset ########## def reset(ev): doc['target'].text = "" doc['target'].append(randint(0, 50)) doc['proposal'].text = "" doc['success'].text = "" doc['evaluate'].append( html.BUTTON('Reset', id="reset_btn", Class="button btn-blue") ) doc['reset_btn'].bind('click', reset) |
En este caso, el valor a calcular lo actualizamos con valores entre 0 y 50 para hacerlo más difícil.
Y, cerramos las etiquetas HTML que quedaban abiertas.
Y se acabó
Ha sido una explicación rápida.
Para las que quieran, podéis modificar el código para hacer una versión en español, para usar otro número que no sea el 4, para usar otros operadores,…
Si tenéis dudas o nuevas versiones puedes usar los comentarios más abajo.
Saludos.