TP2: Shape Shifter Chess

Para el segundo trabajo práctico, se pide implementar una versión en gamelib del juego Shape Shifter Chess.

Gameplay

Explicación del juego

Shape Shifter Chess es un juego diferente al ajedrez. Es de a un jugador y está basado en niveles. Cada nivel N tendrá N + 2 piezas. La idea del juego es poder "visitar" todas las piezas utilizando los movimientos de las mismas para moverse entre pieza y pieza. Habrá una pieza única pieza "activa" en todo momento y será la única que se puede mover.

Como restricciones, la pieza "activa" solo se puede mover a un casillero si:

  • Su movimiento normal se lo permite. Por ejemplo, el alfil solo se puede mover en casilleros diagonales.
  • Si al moverse toma la posición de una pieza.

Al finalizar el movimiento, se reemplaza la pieza activa actual por la que está en el casillero destino y se repiten movimientos hasta que quede una sola pieza en el tablero, donde en tal caso finaliza el nivel. Luego el juego generará el siguiente nivel.

Si en algún punto no quedan otros movimientos posibles, se deberá reiniciar el nivel y volver a intentar otras combinaciones.

A diferencia del juego implementado por Iramontes en itch.io y el ajedrez "normal", para facilitar nuestra implementación permitiremos que las piezas puedan "saltar" sobre otras piezas. Por ejemplo, una torre que tiene en la misma dirección a otras dos piezas, podrá tomar la posición de cualquiera de las dos piezas.

Requerimientos mínimos

Como mínimo se espera que el juego tenga las siguientes características:

  • Estar hecha con Gamelib, permitiendo seleccionar movimientos utilizando el mouse.
  • Manejar un archivo con el detalle de las piezas y sus movimientos, el cual debe ser procesado de manera que pueda ser modificado (modificar qué piezas existen y los movimientos de cada una).
  • Generar niveles en un tablero 8x8 de manera aleatoria. Cada vez que se gane un nivel de dificultad N, se debe generar uno nuevo de dificultad N+1.
  • Indicar visualmente cuál es la pieza actual y qué movimientos tiene disponible.
  • Guardar el nivel actual en un archivo.
  • Al comenzar el juego, permitir al usuario cargar el último nivel guardado o empezar desde un nivel 1. Sea si se empiece del nivel 1 o con el nivel guardado, si se gana ese nivel, el siguiente nivel a generar debe cumplir con las reglas descriptas anteriormente.
  • Reintentar un nivel al presionar una tecla o usando el mouse.
  • En caso de algún error al leer o escribir un archivo, se debe informar al usuario (utilizando por ejemplo gamelib.say).

El juego no se puede ganar, siempre que se gane un nivel, se generará uno nuevo de mayor dificultad. No es necesario chequear el caso en el que no se puedan colocar mas piezas en el tablero (por ejemplo, el nivel 63 de un tablero de 8x8) pero puede hacerse si se desean.

Archivo de movimientos

Se cuenta con el siguiente archivo movimientos.csv provisto por el curso. Así como está sirve para replicar el movimiento del alfil, la torre y el caballo del ajedrez tradicional:

caballo,1;2,false
caballo,2;1,false
caballo,-1;2,false
caballo,-2;1,false
caballo,-1;-2,false
caballo,-2;-1,false
caballo,1;-2,false
caballo,2;-1,false
torre,0;1,true
torre,1;0,true
torre,-1;0,true
torre,0;-1,true
alfil,1;1,true
alfil,-1;1,true
alfil,-1;-1,true
alfil,1;-1,true

Cada línea tiene el mismo formato: pieza,dir_x;dir_y,extensible. Se permite agregar entradas al archivo, pero no se permite cambiar su formato.

Las direcciones dir_x;dir_y indican las direcciones de cada movimiento. Por ejemplo observando la primer línea, un posible movimiento para el caballo es moverse un casillero a la derecha y dos hacia abajo. Y los movimientos del caballo estarán dados por todas las líneas que corresponden a su pieza.

El parámetro extensible toma valores false y true. En el caso que sea true, significa que dicho movimiento también puede repetirse hasta llegar a los bordes del tablero. Por ejemplo la primer línea que corresponde a torre indica que puede moverse un casillero abajo, o dos casilleros, o tres, así hasta ocho casilleros.

El archivo de movimientos permite agregar o modificar cualquier pieza del juego. Se puede asumir que viene sin errores.

Para facilitar la implementación, se recomienda leer el archivo al comienzo del programa y generar un diccionario de movimientos con la siguiente estructura:

{
  'caballo': [(1, 2), (2, 1), ...],
  'torre': [(0, 1), (0, 2), ..., (0, 8), (1, 0), (2, 0), ..., (8, 0), ...],
  ...
}

Donde la clave es la pieza y el valor una lista con todos los movimientos posibles que puede realizar dicha pieza. Es decir, un caballo que se encuentra en la posición (5, 4) tiene como movimientos válidos la posición (6, 6), (7, 5), y demás.

Entonces para realizar un movimiento deberá considerarse la posición actual de la pieza, el movimiento disponible, y si en el casillero destino se encuentra una pieza. Si todo esto se cumple, la pieza podrá moverse hacia dicho casillero final.

Generación de niveles

Debe garantizarse que el nivel generado N tenga solución. Para esto debe realizarse el mismo proceso que el juego normal tomando posiciones y piezas al azar:

  1. Se comienza con un tablero 8x8 vacío.
  2. Se elije una posición al azar.
  3. Se coloca una pieza aleatoria en esa posición. Esta pieza será la marcada como "activa" al comenzar el nivel.
  4. Se elije un movimiento al azar de la última pieza seleccionada hasta que en la posición destino se encuentre dentro de las dimensiones del tablero, y no haya ninguna pieza en la misma.
  5. Se coloca una pieza aleatoria en la posición obtenida previamente.
  6. Se repiten los pasos (4) y (5) hasta que el tablero tenga N+2 piezas.

Guardar y cargar niveles

Una vez generado el nivel, debe guardarse en un archivo de tal forma que el usuario tenga la opción de cargarlo la próxima vez que se inicie el juego (utilizando, por ejemplo un prompt que espera como respuesta Si/No usando gamelib.input).

Para que el programa sea simple, solo será necesario guardar el último nivel generado. El formato del archivo queda a criterio propio.

Se permite utilizar el mismo archivo del nivel guardado para la implementación de "reintentar" el nivel.

Esqueleto

Se sugiere arrancar el código utilizando el siguiente esqueleto, que puede ser adaptado según sea necesario. En particular, las llamadas a print están únicamente a modo ilustrativo:

import gamelib

def juego_nuevo(movimientos, n_nivel):
    '''inicializa el estado del juego para el numero de nivel dado'''
    return None

def juego_mostrar(juego):
    '''dibuja la interfaz de la aplicación en la ventana'''
    gamelib.draw_begin()
    gamelib.draw_text('Hola mundo', 100, 100)
    gamelib.draw_end()

def main():
    gamelib.title("Shape Shifter Chess")
    gamelib.resize(200, 200)

    juego = juego_nuevo()

    while gamelib.is_alive():
        juego_mostrar(juego)

        ev = gamelib.wait()
        if not ev:
            break

        if ev.type == gamelib.EventType.ButtonPress and ev.mouse_button == 1:
            print(f'se ha presionado el botón del mouse: {ev.x} {ev.y}')
        elif ev.type == gamelib.EventType.KeyPress:
            print(f'se ha presionado la tecla: {ev.key}')

gamelib.init(main)

Recursos

Restricciones y condiciones de entrega

En general, el uso de cualquier cosa que no haya sido explicada en clase requiere previa autorización de un docente. Particularmente:

  • El uso de los módulos gamelib y random está permitido.
  • No se permite el uso de variables globales (sí de constantes globales).
  • No se permite el uso de eval ni exec.
  • No se permite el uso de funcion(*argumentos).

Entrega

Se deberán entregar al formulario los archivos que se hayan utilizado. No es necesario subir gamelib.py ni las imágenes ya dadas por el enunciado. Si se utilizaron otros recursos que no se incluyeron en el enunciado, deberá incluirse en la entrega.