Análisis del juego
Pero volvamos al papel. Vamos a diseñar un programa orientado a objetos. Lo primero es, por supuesto, identificar los objetos.
Algunos de ellos son obvios: el naipe, la baraja, el tapete (donde colocaremos las cartas). En principio, para hacer solitarios no necesitamos mucho más.
Podemos subdividir el tapete en varios subobjetos: el mazo de las cartas que aún no hemos usado, el montón donde acumulemos las cartas que sacamos del mazo y no podemos colocar, las cuatro pilas de cartas de trabajo, y las cuatro pilas de salida. Evidentemente, cada una de las pilas de trabajo son objetos de la misma clase, igual que las cuatro pilas de salida.
Otros objetos útiles para crear el programa pueden ser, por ejemplo, los gráficos, que se encarguen de todas las tareas de visualización. También necesitaremos una pila, para almacenar los movimientos de la partida, de modo que podamos implementar opciones de deshacer. Por supuesto, objetos 'movimiento', que almacenaremos en esa pila.
Para empezar, tanto el mazo, como el montón, las cuatro pilas de trabajo y las de salida, son pilas. Es decir, su funcionamiento es el de una lista LIFO. Por lo tanto, los implementaremos como pilas. Por supuesto, el jugador no tendrá posibilidad de realizar operaciones de apilado y desapilado en todas las pilas, en algunas sólo podrá desapilar, y en otras sólo apilar. Sin embargo, deberemos implementar las dos operaciones, ya que el juego sí podrá llevar a cabo ambas acciones, ya sea al hacer o deshacer jugadas, o como parte de operaciones automáticas del juego.
Esto significa que todas las zonas del tapete serán objetos de clases derivadas de pila. La primera acción de cada partida será apilar la baraja completa en el mazo, y desapilar una carta del mazo para cada pila de trabajo, y una más para la pila del montón.
Veamos que tareas debe realizar cada objeto:
Naipe
Para cada naipe también crearemos una clase, ya que es evidente que un naipe es un objeto. Los atributos de cada naipe serán:
- palo:
- Tréboles, diamantes, corazones o picas (0, 1, 2 y 3, respectivamente).
- numero:
- 1 al 10. Los números de 8 a 10 corresponden a las figuras.
- visible:
- Indica si el valor de la carta es visible o no, es decir, si está boca arriba.
Habrá algunos métodos que nos serán de utilidad en el juego:
- Sucesor:
- Naipe siguiente en el orden de salida a un naipe especificado. El sucesor de un rey es un valor nulo.
- Predecesor:
- Naipe anterior, según el mismo orden, a un naipe especificado. El predecesor de un as es un valor nulo.
- Palo:
- Devolverá el valor del palo.
- Número:
- Devolverá el valor del número.
- Orden:
- Número calculado correspondiente al gráfico dentro de la DLL de cards.dll.
- Visible:
- Devuelve un valor verdadero si el naipe es visible.
- Volver:
- Si estaba boca abajo, pone la carta boca arriba, y viceversa.
- Levantar:
- Pone la carta boca arriba, es decir, asigna un valor verdadero a visible.
- Ocultar:
- Pone la carta boca abajo, es decir, asigna un valor falso a visible.
- Comparación:
- Sobrecargaremos el operador de comparación "==" para comparar naipes.
Baraja
El único dato que contiene esta clase será un array de 40 objetos de clase Naipe.
En el constructor deberemos insertar en un array las cuarenta cartas de nuestra baraja. Para cada palo las cartas del 1 al 7 y las tres figuras.
En cuanto a los procedimientos:
- Mezclar:
- Mezclará las cartas. Podemos implementar un algoritmo de mezcla, que intercambie de forma aleatoria las posiciones de las cartas dentro del array.
- Carta:
- Obtiene una referencia al Naipe dentro de la baraja indicado por el parámetro dado.
Pila
Usaremos dos tipos de pilas en este juego. La mayoría son pilas de naipes, salvo una que es una pila de movimientos, que nos servirá para implemetar la opción de deshacer. Por lo tanto, es recomendable usar una plantilla genérica para manejar todas ellas.
Pero hay que tener en cuenta un pequeño detalle. A pesar de que las pilas funcionan como tales (sólo es posible apilar y desapilar naipes o movimientos en ellas), para algunas tareas que tiene que realizar el juego será necesario recorrerlas de forma secuencial. Por ejemplo, cuando necesitemos redibujar todo el tapete. En esos casos será necesario mostrar todas las cartas de algunas pilas, concretamente, las de trabajo. Las pilas de salida sólo muestran la carta de la cima, al igual que el montón. El mazo siempre muestra el revés de la carta de la cima, salvo que esté vacío.
Por lo tanto, tendremos que almacenar algunos valores:
- ultimo:
- Puntero al último nodo de la pila.
- primero:
- Puntero al primer nodo de la pila.
- actual:
- Puntero al nodo actual en los recorridos secuenciales de la pila.
No nos bastará una estructura de pila corriente, ya que necesitamos algunos métodos más para recorrer la pila sin destruirla.
- Push:
- Apilar un naipe.
- Pop:
- Desapilar un naipe.
- Vaciar:
- Vacía la pila completamente. Esta función la necesitaremos cuando haya que empezar una nueva partida.
- Vacia:
- Devuelve un valor verdadero si la pila está vacía.
- Primero:
- Inicia el recorrido secuencial de la pila, desde el fondo a la cima.
- Ultimo:
- Inicia el recorrido de la pila, pero directamente desde la cima.
- Siguiente:
- Avanza al siguiente elemento de la pila, con dirección a la cima.
- Actual:
- Obtiene una referencia al elemento actual de la pila.
- Cuenta:
- Devolverá el número de elementos almacenados en la pila.
Debido a esto, la pila es en realidad una cola doblemente enlazada, a la que hemos añadido los métodos Push y Pop para añadir y eliminar elementos, respectivamente.