2. Piedra, Papel o Tijera

portada

El primer ejercicio que te proponemos es desarrollar el clásico piedra papel o tijera, un juego que seguro ha decidido tu destino en más de una ocasión. Es sencillo y te servirá para repasar conceptos básicos. Aunque seguramente ya sabes jugar, vamos a repasar las normas:

  1. Cada jugador podrá escoger uno de estos tres elementos:

    • Piedra

    • Papel

    • Tijera

  2. El ganador del juego se dedice segun la siguiente clasificación:

    • La piedra gana a la tijera

    • La tijera gana al papel

    • El papel gana a la piedra

See also

Puedes encontrar más información sobre este juego aquí.

2.1. Generación del menú de opciones

En el primer apartado construiremos una función que muestre las opciones. Esta función será de mucha utilidad para evitar repetir el mismo código cada vez que queramos echar una partida. Vamos a codificar la función menu() que no recibe ningún argumento de entrada ni devuelve ninguna variable de salida. Simplemente imprime el siguiente menú:

Welcome to rock, paper, scissors game:

[1] - Rock
[2] - Paper
[3] - Scissors

Solución:

def menu():
    menu = """
    Welcome to rock, paper, scissors game:

    [1] - Rock
    [2] - Paper
    [3] - Scissors

    """
    print(menu)

Note

En este caso hemos metido todo el texto dentro de una única variable STRING. Es una de las múltiples posibilidades que permite Python. Podrías haberlo resuelto utilizando una sentencia print para cada una de las líneas.

2.2. Petición de nombres y tiradas

Nuestro juego va a permitir que dos jugadores se disputen varias partidas. En este caso, el segundo jugador será el ordenador, pero podremos personalizarlo dándole un nombre. Para ello, vas a implementar la función request_data() que va a preguntar al usuario por los nombres de los dos juegadores y el número de partidas que quieren jugar. La función request_data() no recibe ningún argumento de entrada, pero sí que devuelve una variable data de tipo DICT que contiene los nombres de los dos jugadores y el numero de partidas.

Para limitar la duración del juego, la función request_data() sólo aceptará entre 1 y 10 partidas.

Solución:

def request_data():
    user = input("Enter your name: ")
    rival = input("Enter your rival name: ")
    attempts = int(input("How many battles to play?:"))

    while (attempts < 0 or attempts > 10):
        print("Error, the number of battles must be between 1 and 10")
        attempts = int(input("How many battles to play?:"))

    data = dict(user=user, rival=rival, attempts=attempts)
    return data

Note

Para asegurarnos de que el usuario sólo puede elegir entre 1 y 10 partidas, hemos utilizado un bucle WHILE:

while (attempts < 0 or attempts > 10):
        print("Error, the number of battles must be between 1 and 10")
        attempts = int(input("How many battles to play?:"))

Este bucle se repetirá indefinidamente hasta que el jugador introduzca un valor para la variable attemps que se encuentre entre 1 y 10.

Vamos a probar su ejecución dando los nombres de los dos jugadores y un número de partidas:

parameters=request_data()
parameters
Enter your name: Theo
Enter your rival name: Simon
How many battles to play?:5
{'user': 'Theo', 'rival': 'Simon', 'attempts': 5}

2.3. Elección de la jugada

Nuestro juego comienza pidiéndo al jugador que seleccione una de las tres posibles jugadas. Para evitar que el usuario tenga que escribir los nombres de las jugadas, vamos a simplificarlo asignando un número a cada una de ellas. Para ser coherentes con el mensaje impreso por la función menu, vamos a definir la siguiente correspondencia:

[1] - Rock
[2] - Paper
[3] - Scissors

La función select_option() no recibe ningún argumento de entrada pero sí debe devolver una variable de tipo STRING con el nombre del elemento elegido.

Solución

def select_option():

    option = int(input("Choose your weapon: \n"))

    while option < 1 or option > 3:
        print("""Option not allowed. Choose:
        [1] - Rock
        [2] - Paper
        [3] - Scissors
        """)
        option = int(input("Choose your weapon: \n"))
        
    if option == 1:
        user_choice = "rock"
    elif option == 2:
        user_choice = "paper"
    else:
        user_choice = "scissors"

    return user_choice

Note

De nuevo, para hacer el juego más resistente a fallos, utilizamos otro bucle WHILE para asegurar que sólo vamos a aceptar como jugadas válidas los números 1, 2 o 3.

    while option < 1 or option > 3:
        print("""Option not allowed. Choose:
        [1] - Rock
        [2] - Paper
        [3] - Scissors
        """)
        option = int(input("Choose your weapon: \n"))
select_option()
Choose your weapon: 
1
'rock'

2.4. Simulando al rival

Sería complicado implementar este juego para dos jugadores reales sin que uno de ellos viera la jugada del otro con antelación. Para transmitir la sensación de jugar contra un adversario real, vamos a permitir al ordenador elegir una jugada de manera aleatoria y completamente oculta al jugador.

Para ello vas a diseñar la función rival_simulation() que elegirá un elemento aleatorio de entre las opciones “papper”, “rock” y “scissors”. Esta funcionalidad se simplifica mucho utilizando la función choice de la librería random.

See also

Puedes encontrar la documentación de ese método aquí.

La función rival_simulation() tampoco recibe ningún argumento, y como única salida debe devolver la jugada del rival simulado en formato STRING.

Solución

import random
def rival_simulation():
    options = ["paper", "rock", "scissors"]
    rival_choice = random.choice(options)
    return rival_choice

See also

Puedes consultar la gran variedad de funcionalidades de la librería random aqui.

Para probar que la función genera valores aleatorios, prueba a ejecutar la siguiente celda varias veces y deberías obtener diferentes resultados:

rival_simulation()
'scissors'

2.5. Decidir el ganador

Antes de implementar nuestro programa principal, tenemos que definir una última función. En este caso será el método calculate_winner(user_choice, rival_choice, parameters) que es la encargada de definir, a partir de las jugadas del usuario y del rival, quién ha resultado ganador. Los argumentos de entrada son:

  • user_choice contiene la jugada del usuario en formato STRING

  • rival_choice contiene la jugada del rival en formato STRING

  • parameters el diccionario con los nombres de los jugadores y el número de partidas

La función debe devolver una única variable que contendrá el nombre del jugador vencedor en formato STRING o la palabra “tie” en caso de que se haya producido un empate.

Solución

def calculate_winner(user_choice, rival_choice, parameters):

    if  user_choice==rival_choice:
        print("We had a tie!")
        winner = "tie"
    elif (user_choice == "paper" and rival_choice == "scissors") or \
         (user_choice == "rock" and rival_choice == "paper") or     \
         (user_choice == "scissors" and rival_choice == "rock"):
        winner = parameters['rival']
        print("The winner is: ",winner)
    else:
        winner = parameters['user']
        print("The winner is: ",winner)

    return winner

Ahora podemos probar diferentes combinaciones para asegurarnos que calculate_winner funciona correctamente. Por ejemplo, si ambos jugadores eligen la misma acción:

winner=calculate_winner("rock", "rock", parameters)
We had a tie!

En caso de que el jugador (Theo) obtena una jugada ganadora sobre el ordenador (Simon):

winner=calculate_winner("rock", "scissors", parameters)
The winner is:  Theo

Y por último, el caso en el que el jugador (Theo) obtena una jugada perdedora frente al ordenador (Simon):

winner=calculate_winner("rock", "paper", parameters)
The winner is:  Simon

2.6. Juego completo

Con todas estas funciones ya podemos implementar nuestro programa principal en el que un usuario y el ordenador se disputen un número de partidas. El programa principal debe contabilizar las victorias de cada uno de ellos y presentar un resumen cuando se hayan completado todas las partidas.

Solución

user_wins = 0
computer_wins = 0
ties = 0

parameters=request_data()
for i in range(parameters["attempts"]):

    print("Match number {}".format(i))
    menu()
    user_choice = select_option()
    rival_choice = rival_simulation()

    print(f"{user_choice.upper()} against {rival_choice.upper()}!!!")
    winner = calculate_winner(user_choice,rival_choice,parameters) 

    if winner == parameters["user"]:
        user_wins += 1
    elif winner == parameters["rival"]:
        computer_wins += 1
    else:
        ties += 1
        
print(f"""  
    - {parameters["user"]} won {user_wins} matches\n
    - {parameters["rival"]} won {computer_wins} matches \n 
    - There were {ties} ties\n
""")

Note

Como ves, es un código muy sencillo. Un bucle FOR se encarga de repetir la partida tantas veces como se haya decidido en la función request_data. Tres contadores: user_wins, computer_wins y ties se encargan de ir sumando las victorias y los empates. Y una última instrucción print imprime un resumen por pantalla:

print(f"""  
    - {parameters["user"]} won {user_wins} matches\n
    - {parameters["rival"]} won {computer_wins} matches \n 
    - There were {ties} ties\n
""")

Seguro que a estas alturas ya te habrás dado cuenta que la función print es muy versatil y permite muchas maneras de presentar información por pantalla. En este ejemplo vemos una de las más populares en las que se puede “insertar” la variable dentro del mensaje del print utilizando las llaves “{}”.

Otra posibles alternativa habría sido:

print("- {} won {} matches\n- {} won {} matches \n- There were {} ties\n".
        format(parameters["user"],user_wins, parameters["rival"],computer_wins,ties))

O incluso:

print("- ",parameters["user"], "won ",user_wins," matches\n")
print("- ",parameters["rival"],"won ",computer_wins," matches\n")
print("- There were ",ties," ties\n")

2.7. Extensiones del juego

Facil, ¿verdad?. Puedes complicar el juego con nuevas posibilidades. Al final de cada capítulo te sugerimos unas ideas para mejorar el juego y de paso, practicar con nuevos recursos. Algunas propuestas son muy sencillas, pero el objetivo de este capítulo, y del libro en general, es favorecer la práctica de la programación de una manera divertida.

  • Puedes enriquecer los mensajes de salida utilizando iconos como estos: ✊, ✋ y ✌

  • Puedes intentar codificar variantes del juego añadiendo más elementos. Aquí tienes algunas sugerencias.

  • Al terminar el juego, puedes preguntar al usuario si quiere echarse otra partida contra el ordenador. Y en caso de responder afirmativamente, resetear los contadores y volver a reinicializar el juego