Algoritmos de Programación con Python

6.5. Nuestro primer juego

Con todo esto ya estamos en condiciones de escribir un programa para jugar con la computadora: el Mastermind. El Mastermind es un juego que consiste en deducir un código numérico de (por ejemplo) cuatro cifras.

1. Análisis (explicación del juego):

Cada vez que se empieza un partido, el programa debe "eligir" un número de cuatro cifras (sin cifras repetidas), que será el código que el jugador debe adivinar en la menor cantidad de intentos posibles. Cada intento consiste en una propuesta de un código posible que tipea el jugador, y una respuesta del programa. Las respuestas le darán pistas al jugador para que pueda deducir el código.

Estas pistas indican cuán cerca estuvo el número propuesto de la solución a través de dos valores: la cantidad de aciertos es la cantidad de dígitos que propuso el jugador que también están en el código en la misma posición. La cantidad de coincidencias es la cantidad de digitos que propuso el jugador que también están en el código pero en una posición distinta.

Por ejemplo, si el código que eligió el programa es el 2607, y el jugador propone el 1406, el programa le debe responder un acierto (el 0, que está en el código original en el mismo lugar, el tercero), y una coincidencia (el 6, que también está en el código original, pero en la segunda posición, no en el cuarto como fue propuesto). Si el jugador hubiera pro-puesto el 3591, habría obtenido como respuesta ningún acierto y ninguna coincidencia, ya que no hay números en común con el código original, y si se obtienen cuatro aciertos es porque el jugador adivinó el código y ganó el juego.

2. Especificación:

El programa, entonces, debe generar un número que el jugador no pueda predecir. A continuación, debe pedirle al usuario que introduzca un número de cuatro cifras distintas, y cuando éste lo ingresa, procesar la propuesta y evaluar el número de aciertos y de coincidencias que tiene de acuerdo al código elegido. Si es el código original, se termina el programa con un mensaje de felicitación. En caso contrario, se informa al jugador la cantidad de aciertos y la de coincidencias, y se le pide una nueva propuesta. Este proceso se repite hasta que el jugador adivine el código.

2. Diseño:

Lo primero que tenemos que hacer es indicarle al programa que tiene que "elegir" un número de cuatro cifras al azar. Esto lo hacemos a través del módulo random. Este módulo provee funciones para hacer elecciones aleatorias.

La función del módulo que vamos a usar se llama choice. Esta función devuelve un elemento al azar de una n-upla, y toma como parámetro la n-upla de la que tiene que elegir. Vamos a usarla entonces para elegir cifras. Para eso tenemos que construir una n-upla que tenga todas las cifras, lo hacemos de la misma manera que en la parte 3.6:

digitos = ('0','1','2','3','4','5','6','7','8','9')

Como están entre comillas, los dígitos son tratados como cadenas de caracteres de longitud uno. Sin las comillas, habrían sido considerados números enteros. En este caso elegi-mos verlos como cadenas de caracteres porque lo que nos interesa hacer con ellos no son cuentas sino comparaciones, concatenaciones, contar cuántas veces aparece o donde está en una cadena de mayor longitud, es decir, las operaciones que se aplican a cadenas de texto. Entonces que sean variables de tipo cadena de caracteres es lo que mejor se adapta a nuestro problema.

Ahora tenemos que generar el número al azar, asegurándonos de que no haya cifras repetidas. Esto lo podemos modelar así:

  • Tomar una cadena vacía
  • Repetir cuatro veces:
    1. Elegir un elemento al azar de la lista de dígitos
    2. Si el elemento no está en la cadena, agregarlo
    3. En caso contrario, volver al primer punto

Una vez elegido el número, hay que interactuar con el usuario y pedirle su primera propuesta. Si el número no coincide con el código, hay que buscar la cantidad de aciertos y de coincidencias y repetir el pedido de propuestas, hasta que el jugador adivine el código.

Para verificar la cantidad de aciertos se pueden recorrer las cuatro posiciones de la propuesta: si alguna coincide con los dígitos en el código en esa posición, se incrementa en uno la cantidad de aciertos. En caso contrario, se verifica si el dígito está en alguna otra posición del código, y en ese caso se incrementa la cantidad de coincidencias. En cualquier caso, hay que incrementar en uno también la cantidad de intentos que lleva el jugador.

Finalmente, cuando el jugador acierta el código elegido, hay que dejar de pedir propuestas, informar al usuario que ha ganado y terminar el programa.

4. Implementación: Entonces, de acuerdo a lo diseñado en 3, el programa quedaría más o menos así:

# ARCHIVO: [mastermind.py]

# modulo que va a permitir elegir numeros aleatoriamente
import random

# el conjunto de simbolos validos en el codigo
digitos = ('0','1','2','3','4','5','6','7','8','9')

# "elegimos" el codigo
codigo = ''
for i in range(4):
    candidato = random.choice(digitos)
    # vamos eligiendo digitos no repetidos*
    while candidato in codigo:
        candidato = random.choice(digitos)
    codigo = codigo + candidato

# iniciamos interaccion con el usuario*
print "Bienvenido/a al Mastermind!"
print "Tienes que adivinar un numero de", 4, "cifras distintas"
propuesta = raw_input("¿Que codigo propones?: ")

# procesamos las propuestas e indicamos aciertos y coincidencias
intentos = 1
while propuesta != codigo:
    intentos = intentos + 1
    aciertos = 0
    coincidencias = 0

    # recorremos la propuesta y verificamos en el codigo
    for i in range(4):
        if propuesta[i] == codigo[i]:
            aciertos = aciertos + 1
        elif propuesta[i] in codigo:
            coincidencias = coincidencias + 1
    print "Tu propuesta (", propuesta, ") tiene", aciertos, \
          "aciertos y ", coincidencias, "coincidencias."
    # pedimos siguiente propuesta*
    propuesta = raw_input("Propón otro codigo: ")

print "Felicitaciones! Adivinaste el codigo en", intentos, "intentos."

Truco Cuando lo que queremos escribir es demasiado largo como para una sola línea que entre cómodamente en el editor o en el campo visual, le indicamos al intérprete que queremos seguir en la siguiente línea por medio de la barra invertida (como al final anterior que empieza por "Tu propuesta ...).

5. Pruebas: La forma más directa de probar el programa es jugándolo, y verificando manualmente que las respuestas que da son correctas, por ejemplo:

jugador@casino:~$ python mastermind.py
Bienvenido/a al Mastermind!
Tienes que adivinar un numero de 4 cifras distintas
¿Que codigo propones?: 1234
Tu propuesta ( 1234 ) tiene 0 aciertos y  1 coincidencias.
Propón otro codigo: 5678
Tu propuesta ( 5678 ) tiene 0 aciertos y  1 coincidencias.
Propón otro codigo: 1590
Tu propuesta ( 1590 ) tiene 1 aciertos y  1 coincidencias.
Propón otro codigo: 2960
Tu propuesta ( 2960 ) tiene 2 aciertos y  1 coincidencias.
Propón otro codigo: 0963
Tu propuesta ( 0963 ) tiene 1 aciertos y  2 coincidencias.
Propón otro codigo: 9460
Tu propuesta ( 9460 ) tiene 1 aciertos y  3 coincidencias.
Propón otro codigo: 6940
Felicitaciones! Adivinaste el codigo en 7 intentos.

Podemos ver que para este caso el programa parece haberse comportado bien. ¿Pero cómo podemos saber que el código final era realmente el que eligió originalmente el programa? ¿O qué habría pasado si no encontrábamos la solución?

Para probar estas cosas recurrimos a la depuración del programa. Una forma de hacerlo es simplemente agregar algunas líneas en el código que nos informen lo que está sucediendo que no podemos ver. Por ejemplo, los números que va eligiendo al azar y el código que queda al final. Así podremos verificar si las respuestas son correctas a medida que las hacemos y podremos elegir mejor las propuestas enlas pruebas.

# "elegimos" el codigo
codigo = ''
for i in range(4):
    candidato = random.choice(digitos)
    # vamos eligiendo digitos no repetidos
    while candidato in codigo:
        print 'DEBUG: candidato =', candidato
        candidato = random.choice(digitos)
    codigo = codigo + candidato
    print 'DEBUG: el codigo va siendo =', codigo

De esta manera podemos monitorear cómo se va formando el código que hay que adivinar, y los candidatos que van apareciendo pero se rechazan por estar repetidos:

jugador@casino:~$ python master_debug.py
DEBUG: el codigo va siendo = 8
DEBUG: candidato = 8
DEBUG: el codigo va siendo = 81
DEBUG: candidato = 1
DEBUG: el codigo va siendo = 814
DEBUG: el codigo va siendo = 8145
Bienvenido/a al Mastermind!
Tienes que adivinar un numero de 4 cifras distintas
¿Que codigo propones?:

6. Mantenimiento: Supongamos que queremos jugar el mismo juego, pero en lugar de hacerlo con un número de cuatro cifras, adivinar uno de cinco. ¿Qué tendríamos que hacer para cambiarlo?

Para empezar, habría que reemplazar el 4 en la línea 11 del programa por un 5, indicando que hay que elegir 5 dígitos al azar. Pero además, el ciclo en la línea 31 también necesita cambiar la cantidad de veces que se va a ejecutar, 5 en lugar de 4. Y hay un lugar más, adentro del mensaje al usuario que indica las instrucciones del juego en la línea 20.

El problema de ir cambiando estos números de a uno es que si quisiéramos volver al programa de los 4 dígitos o quisiéramos cambiarlo por uno que juegue con 3, tenemos que volver a hacer los reemplazos en todos lados cada vez que lo queremos cambiar, y corremos el riesgo de olvidarnos de alguno e introducir errores en el código.

Una forma de evitar esto es fijar la cantidad de cifras en una variable y cambiarla sólo ahí:

# "elegimos" el codigo
cant_digitos = 5
codigo = ''
for i in range(cant_digitos):
    candidato = random.choice(digitos)
    # vamos eligiendo digitos no repetidos

El mensaje al usuario queda entonces:

# iniciamos interaccion con el usuario
print "Bienvenido/a al Mastermind!"
print "Tenes que adivinar un numero de", cant_digitos, 
      "cifras distintas"

Y el chequeo de aciertos y coincidencias:

# recorremos la propuesta y verificamos en el codigo
for i in range(cant_digitos):
    if propuesta[i] == codigo[i]:

Con 5 dígitos, el juego se pone más difícil. Nos damos cuenta que si el jugador no logra adivinar el código, el programa no termina: se queda preguntando códigos y respondiendo aciertos y coincidencias para siempre. Entonces queremos darle al usuario la posibilidad de rendirse y saber cuál era la respuesta y terminar el programa.

Para esto agregamos en el ciclo while principal una condición extra: para seguir pregun-tando, la propuesta tiene que ser distinta al código pero además tiene que ser distinta del texto "Me doy".

# procesamos las propuestas e indicamos aciertos y coincidencias
intentos = 1
while propuesta != codigo and propuesta != "Me doy":
    intentos = intentos + 1
    aciertos = 0

Entonces, ahora no sólamente sale del while si acierta el código, sino además si se rinde y quiere saber cuál era el código. Entonces afuera del while tenemos que separar las dos posibilidades, y dar distintos mensajes:

if propuesta == "Me doy":
    print "El codigo era", codigo
    print "Suerte la proxima vez!"
else:
    print "Felicitaciones! Adivinaste el codigo en", \
    intentos, "intentos."

El código de todo el programa queda entonces así:

# ARCHIVO: mastermind5.py

# modulo que va a permitir elegir numeros aleatoriamente
import random

# el conjunto de simbolos validos en el codigo*
digitos = ('0','1','2','3','4','5','6','7','8','9')

# "elegimos" el codigo
cant_digitos = 5
codigo = ''
for i in range(cant_digitos):
    candidato = random.choice(digitos)
    # vamos eligiendo digitos no repetidos
    while candidato in codigo:
        candidato = random.choice(digitos)
    codigo = codigo + candidato

# iniciamos interaccion con el usuario
print "Bienvenido/a al Mastermind!"
print "Tienes que adivinar un numero de", cant_digitos, 
      "cifras distintas"
propuesta = raw_input("¿Que codigo propones?: ")

# procesamos las propuestas e indicamos aciertos y coincidencias
intentos = 1
while propuesta != codigo and propuesta != "Me doy":
    intentos = intentos + 1
    aciertos = 0
    coincidencias = 0

    # recorremos la propuesta y verificamos en el codigo
    for i in range(cant_digitos):
        if propuesta[i] == codigo[i]:
            aciertos = aciertos + 1
        elif propuesta[i] in codigo:
            coincidencias = coincidencias + 1
    print "Tu propuesta (", propuesta, ") tiene", aciertos, \
          "aciertos y ", coincidencias, "coincidencias."
    # pedimos siguiente propuesta
    propuesta = raw_input("Propón otro codigo: ")

if propuesta == "Me doy":
    print "El codigo era", codigo
    print "Suerte la proxima vez!"
else:
    print "Felicitaciones! Adivinaste el codigo en", \
    intentos, "intentos."

Copyright (c) 2011-2014 Rosita Wachenchauzer, Margarita Manterola, Maximiliano Curia, Marcos Medrano, Nicolás Paez. La copia y redistribución de esta página se permite bajo los términos de la licencia Creative Commons Atribución - Compartir Obras Derivadas Igual 3.0 siempre que se conserve esta nota de copyright.