Los 4 cuatros

articulos

"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:

  • 4444=0
  • 4444=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:

<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 markdown='1'>
   <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">&#8730;</span>, 
    factorial: <span class="span">!</span>
   </p>
   </ul> 
  </div>
  <div class="div" markdown='1'><p>Target: <span  id="target"></span></div>
  <div id="controls" markdown='1'></div>
  <div markdown='1'>
   <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" markdown='1'><p id="proposal"></p></div>
  <div markdown='1'>
   <p>When you are ready you can evaluate your expression or reset the target with the blue buttons below:</p>
  </div>
  <div id="evaluate" markdown='1'></div>
  <div id="success" class="message" markdown='1'></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': '&#8730;',
    '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:

<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:

<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:

<H1>4 fours</H1>
  <div markdown='1'>
   <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">&#8730;</span>, 
    factorial: <span class="span">!</span>
   </p>
   </ul> 
  </div>
  <div class="div" markdown='1'><p>Target: <span  id="target"></span></div>
  <div id="controls" markdown='1'></div>
  <div markdown='1'>
   <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" markdown='1'><p id="proposal"></p></div>
  <div markdown='1'>
   <p>When you are ready you can evaluate your expression or reset the target with the blue buttons below:</p>
  </div>
  <div id="evaluate" markdown='1'></div>
  <div id="success" class="message" markdown='1'></div>

Y, a partir de aquí, viene el código Python. Primero importamos unas pocas bibliotecas.

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.

# define the target value
#########################
doc['target'].append(randint(1, 10))

En el código HTML de más arriba habíamos definido

<div class="div" markdown='1'><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.

# define the controls
#####################
possibilities = {
    'open_parenthesis': '(',
    'four': '4',
    'addition': '+',
    'subtraction': '-',
    'multiplication': '*',
    'division': '/',
    'decimal_point': '.',
    'square_root': '&#8730;',
    '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:

    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.

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:

# 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.

# 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.