Herramientas Informaticas

Mes: agosto 2012 Página 16 de 22

15.8. Eliminación y reparto de los naipes

Otro metodo que podría ser útil para la clase Mazo es eliminaCarta, que toma un naipe como parámetro, lo elimina, y retorna verdadero (1) si el naipe estaba en el mazo, y falso (0) si no estaba:

   1: class Mazo:

   2:     ...

   3:     def eliminaCarta(self, carta):

   4:         if carta in self.cartas:

   5:             self.cartas.remove(carta)

   6:             return 1

   7:         else:

   8:             return 0

El operador in retorna verdadero si el primer operando esta en el segundo, el cual debe ser una lista o tupla. Si el primer operando es un objeto, Python usa el metodo __cmp__ del objeto para determinar la igualdad entre los elementos de la lista. Como el __cmp__ en la clase Carta verifica la igualdad en profundidad, el metodo eliminaCarta también verifica igualdad en profundidad.

Para repartir los naipes, queremos eliminar y devolver el naipe que ocupa la posición superior en el mazo. El metodo pop de las listas proporciona una manera conveniente de realizar esto:

   1: class Mazo:

   2:     ...

   3:     def darCarta(self):

   4:         return self.cartas.pop()

En realidad, pop elimina el ultimo naipe en la lista, así que en efecto estamos repartiendo desde el extremo inferior del mazo.
Otra operación mas que es muy probable necesitemos es la función booleana estaVacio, la cual devuelve verdadero si el mazo no contiene ningún naipe:

   1: class Deck:

   2:     ...

   3:     def estaVacio(self):

   4:         return (len(self.cartas) == 0)

 

 

 

15.9. Glosario

codificar: Representar un conjunto de valores utilizando otro conjunto de valores, entre los cuales se construye una correspondencia.

atributo de clase: Una variable que se define dentro de la definición de un clase pero fuera de cualquiera de sus métodos. Los atributos de clase son accesibles desde cualquier metodo de la clase y están compartidos por todas las instancias de la misma.

acumulador: Una variable que se usa en un bucle para acumular una serie de valores, por ejemplo concatenándolos dentro de una cadena de caracteres o adicionándolos a una suma.

Herencia

16.1. Herencia

La característica de un lenguaje que mas se asocia con la programación orientada a objetos es la herencia. La herencia es la capacidad de definir una nueva clase que es una versión modificada de otra ya existente.

La principal ventaja de esta característica es que se pueden agregar nuevos métodos a una clase sin modificar la clase existente. Se denomina “herencia” porque la nueva clase hereda todos los métodos de la clase existente. Si extendemos
esta metáfora, a la clase existente a veces se la denomina clase padre. La nueva clase puede denominarse clase hija, o también “subclase”.

La herencia es una característica poderosa. Ciertos programas que serían complicados sin herencia pueden escribirse de manera simple y concisa gracias a ella.

Además, la herencia puede facilitar la reutilización del código, pues se puede adaptar el comportamiento de la clase padre sin tener que modificarla. En algunos casos, la estructura de la herencia refleja la propia estructura del problema, Lo que hace que el programa sea mas fácil de comprender.

Por otro lado, la herencia pude hacer que los programas sean difíciles de leer.

Cuando se llama a un metodo, a veces no esta claro donde debe uno encontrar su definición. El código relevante puede estar diseminado por varios módulos.

Además, muchas de las cosas que se hacen mediante el uso de la herencia, se pueden lograr de forma igualmente (incluso mas) elegante sin ella. Si la estructura general del problema no nos gu³a hacia la herencia, dicho estilo de programación
puede hacer mas mal que bien.

16.2. Una mano de cartas

Para casi cualquier juego de naipes, necesitamos representar una mano de cartas.

Una mano es similar a un mazo, por supuesto. Ambos están compuestos de un conjunto de naipes, y ambos requieren de operaciones tales como agregar y eliminar una carta. Además, necesitaremos la capacidad de mezclar tanto un
mazo como una mano de cartas.

Una mano es diferente de un mazo en ciertos aspectos. Según el juego al que se este jugando, podemos querer realizar ciertas operaciones sobre una mano que no tienen sentido sobre un mazo. Por ejemplo, en el póker queremos clasificar una mano (straight (consecutiva), flush (de un solo palo), etc.) y compararla con otra. En bridge necesitaremos calcular el puntaje para la mano para as³ poder hacer la subasta.

Esta situación sugiere el uso de la herencia. Si Mano es una subclase de Mazo, entonces tendrá todos los métodos de Mazo y le podremos agregar otros métodos nuevos.

En la definición de clase, el nombre de la clase padre aparece entre paréntesis:

   1: class Mano(Mazo):

   2:     pass

Esta sentencia indica que la nueva clase Mano hereda de la clase existente Mazo.

El constructor de Mano inicializa los atributos para la mano, que son nombre y cartas. La cadena de caracteres nombre identifica a esta mano, probablemente mediante el nombre del jugador que la sostiene. El nombre es un parámetro opcional con un valor por omisión de cadena vacía. cartas es la lista de cartas de la mano, inicializada como lista vacía.

   1: class Mano(Mazo):

   2:     def __init__(self, nombre=""):

   3:         self.cartas = []

   4:         self.nombre = nombre

 

Casi para cualquier juego de naipes, es necesario agregar y quitar cartas del mazo. La eliminación de cartas ya ha sido resuelta, pues Mano hereda eliminaCarta de Mazo. Pero deberemos escribir agregaCarta:

   1: lass Mano(Mazo):

   2:     ...

   3:     def agregaCarta(self,carta) :

   4:     self.cartas.append(carta)

De nuevo, los puntos suspensivos indican que hemos omitido los otros métodos.

El metodo de lista append agrega la nueva carta al final de la lista de cartas.

16.3. El reparto de los naipes

Ahora que ya tenemos la clase Mano, queremos repartir las cartas del Mazo en manos. No es claramente obvio si este metodo debe ir en la clase Mano o en la clase Mazo, pero como opera sobre un mazo único y (posiblemente) sobre varias manos, es mas natural ponerlo en el Mazo. repartir debe ser bastante general, pues los diferentes juegos tienen distintos requerimientos.

Puede que necesitemos repartir todo el mazo de una vez, o que agreguemos una carta a cada mano.
Repartir toma dos parámetros, una lista (o tupla) de manos y la cantidad total de naipes a repartir. Si no hay suficientes cartas en el mazo, el metodo reparte todas las cartas y se detiene:

   1: class Mazo :

   2:     ...

   3:     def repartir(self, manos, nCartas=999):

   4:     nManos = len(manos)

   5:         for i in range(nCartas):

   6:         if self.estaVacio(): break # fin si se acaban las cartas

   7:         carta = self.darCarta() # da la carta superior

   8:         mano = manos[i % nManos] # a qui¶en le toca?

   9:         mano.agregaCarta(carta) # agrega la carta a la mano

 

El segundo parámetro, nCartas es opcional; el valor por omisión es un numero muy grande, lo cual es lo mismo que decir que se repartirán todos los naipes del mazo.

La variable de bucle i va desde 0 hasta nCartas-1. A cada paso a través del bucle, se elimina una carta del mazo mediante el metodo de lista pop, que quita y devuelve el ultimo elemento de la lista.

El operador modulo ( %) permite que podamos repartir las cartas de una en una (una carta cada vez para cada mano). Cuando i es igual a la cantidad de manos en la lista, la expresión i % nManos salta hacia el comienzo de la lista (el índice
es 0).

16.4. Mostremos la mano

Para mostrar el contenido de una mano, podemos sacar partido de la existencia de los métodos muestraMazo y __str__ que se heredan de Mazo. Por ejemplo:

   1: >>> mazo = Mazo()

   2: >>> mazo.mezclar()

   3: >>> mano = Mano("hugo")

   4: >>> mazo.repartir([mano], 5)

   5: >>> print mano

   6: La mano de hugo contiene

   7: 2 de Picas

   8: 3 de Picas

   9: 4 de Picas

  10: As de Corazones

  11: 9 de Treboles

No es una gran mano, pero tiene lo necesario como para disponer de una escalera de color.

Aunque es conveniente usar la herencia de los métodos existentes, existe información adicional en una Mano que desearíamos mostrar al imprimirla. Para ello, podemos proporcionar a la clase Mano un metodo str que reemplace al de
la clase Mazo:

   1: class Mano(Mazo)

   2:     ...

   3:     def __str__(self):

   4:         s = "La mano de " + self.nombre

   5:         if self.estaVacio():

   6:             s = s + " est¶a vac¶³an"

   7:         else:

   8:             s = s + " contienen"

   9:         return s + Mazo.__str__(self)

Al principio s es una cadena de caracteres que identifica a la mano. Si la mano esta vacía, el programa agrega las palabras esta vacía y devuelve s.

En caso contrario, el programa agrega la palabra contiene y la representación como cadena de caracteres del Mazo, que se obtiene llamando al metodo __str__ de la clase Mazo sobre la instancia self.

Puede parecer extra~no que enviemos a self, que se requiere a la Mano actual, como argumento de un metodo de la clase Mazo, hasta que nos damos cuenta de que una Mano es un tipo de Mazo. Los objetos Mano pueden hacer cualquier cosa que pueda hacer un objeto Mazo, y por ello es legal que pasemos una Mano a un metodo de Mazo.

En general, siempre es legal usar una instancia de una subclase en el lugar de una instancia de una clase padre.

16.5. La clase JuegoDeCartas

La clase JuegoDeCartas asume la responsabilidad sobre algunas obligaciones básicas comunes a todos los juegos, tales como la creación del mazo y la mezcla de los naipes:

   1: class JuegoDeCartas:

   2:     def __init__(self):

   3:         self.mazo = Mazo()

   4:         self.mazo.mezclar()

 

Esta es la primera vez que vemos que un metodo de inicialización realiza una actividad computacional significativa, mas allá de la inicialización de atributos.

Para implementar juegos específicos, debemos heredar de JuegoDeCartas y agregar las características del nuevo juego.

Como ejemplo, escribiremos una simulación para La Mona.

La meta de La Mona es desembarazarse de las cartas que uno tiene en la mano.

Uno se saca las cartas de encima emparejándolas por valor y color. Por ejemplo, el 4 de Tréboles se empareja con el 4 de

Picas porque ambos palos son negros.

La Sota de Corazones se empareja con la Sota de Diamantes porque ambos son rojos.

Para iniciar el juego, se elimina la Reina de Tréboles del mazo, de manera que la Reina de Picas no tiene con quien emparejarse. Las cincuenta y una cartas restantes se reparten entre los jugadores, de una en una. Luego del reparto, todos los jugadores emparejan y descartan tantas cartas como sea posible.

Cuando no se pueden realizar mas concordancias, el juego comienza. Por turnos, cada jugador toma una carta (sin mirarla) del vecino mas cercano de la izquierda que aun tiene cartas. Si la carta elegida concuerda con una de la mano del
jugador, se elimina dicho par. Si no, la carta se agrega a la mano del jugador.

Llega el momento en el que se realizan todas las concordancias posibles, con lo que queda solo la Reina de Picas en la mano del perdedor.

En nuestra simulación informática del juego, la computadora juega todas las

manos. Desafortunadamente, se pierden algunos de los matices del juego real.

En una partida real, el jugador que tiene la Mona realiza ciertos esfuerzos para que su vecino la tome, por ejemplo mostrándola prominentemente o al contrario, errando al intentar mostrarla abiertamente, o incluso puede fallar al tratar de errar en su intento de mostrarla prominentemente. La computadora simplemente toma una carta al azar de su vecino.

16.6. La clase ManoDeLaMona

Una mano para jugar a La Mona requiere ciertas capacidades que están mas allá de las que posee una Mano. Definiremos una nueva clase ManoDeLaMona, que hereda de Mano y nos proporciona un metodo adicional denominado eliminaCoincidencias:

   1: class ManoDeLaMona(Mano):

   2:     def eliminaCoincidencias(self):

   3:         cant = 0

   4:         cartasOriginales = self.cartas[:]

   5:         for carta in cartasOriginales:

   6:             empareja = Carta(3 - carta.palo, carta.valor)

   7:             if empareja in self.cartas:

   8:                 self.cartas.remove(carta)

   9:                 self.cartas.remove(empareja)

  10:                 print "Mano %s: %s con %s" % (self.nombre,carta,empareja)

  11:                 cant = cant + 1

  12:         return cant

 

 

Comenzamos por hacer una copia de la lista de las cartas, de tal manera que podamos recorrer la copia mientras vamos quitando cartas de la lista original.

Como self.cartas se modifica en el bucle, no vamos a querer usarla para controlar el recorrido. ¡Python puede quedar realmente confundido si se recorre una lista que esta cambiando!

Para cada carta de la mano, averiguamos cual es la carta que concordara con ella y la buscamos. La carta que concuerda tiene el mismo valor y el otro palo del mismo color. La expresión 3 – carta.palo transforma un Trébol (palo 0) en una Pica (palo 3) y un Diamante (palo 1) en un Corazón (palo 2). Verifique por su cuenta que las operaciones opuestas también funcionan. Si la carta que concuerda esta en la mano, ambas se eliminan.

El siguiente ejemplo demuestra el uso de eliminaCoincidencias:

   1: >>> print mano

   2: La mano de hugo contiene

   3: As de Picas

   4: 2 de Diamantes

   5: 7 de Picas

   6: 8 de Treboles

   7: 6 de Corazones

   8: 8 de Picas

   9: 7 de Treboles

  10: Raina de Treboles

  11: 7 de Diamantes

  12: 5 de Treboles

  13: Sota de Diamantes

  14: 10 de Diamantes

  15: 10 de Corazones

  16: >>> mano.eliminaCoincidencias()

  17: Mano hugo: 7 de Picas con 7 de Treboles

  18: Mano hugo: 8 de Picas con 8 de Treboles

  19: Mano hugo: 10 de Diamantes con 10 de Corazones

Debe usted notar que no existe un metodo __init__ para la clase ManoDeLaMona.

Lo heredamos de Mano.

16.7. La clase JuegoDeLaMona

hora podemos poner nuestra atención en el juego en sí mismo. JuegoDeLaMona es una subclase de JuegoDeCartas con un metodo nuevo denominado jugar que toma una lista de jugadores como parámetro.

Como el metodo __init__ se hereda de JuegoDeCartas, el nuevo objeto JuegoDeLaMona contiene un mazo recientemente mezclado:

   1: Aclass JuegoDeLaMona(JuegoDeCartas):

   2:     def jugar(self, nombres):

   3:         # quitamos la Reina de Treboles

   4:         self.mazo.eliminaCarta(Carta(0,12))

   5:         # construimos una mano para cada jugador

   6:         self.manos = []

   7:         for nombre in nombres :

   8:             self.manos.append(ManoDeLaMona(nombre))

   9:             # repartimos los naipes

  10:             self.mazo.repartir(self.manos)

  11:             print "----- Se han repartido las cartas."

  12:         self.muestraManos()

  13:         # eliminamos las coincidencias iniciales

  14:         emparejadas = self.eliminaTodasLasCoincidencias()

  15:         print "----- Coincidencias eliminadas, el juego comienza."

  16:         self.muestraManos()

  17:         # se juega hasta que se han descartado las 50 cartas

  18:         turno = 0

  19:         cantManos = len(self.manos)

  20:         while emparejadas < 25:

  21:             emparejadas = emparejadas + self.jugarUnTurno(turno)

  22:             turno = (turno + 1) % cantManos

  23:         print "----- El juego termino."

  24:         self.muestraManos()

Algunos de los pasos que componen el juego se han colocado en métodos separados. eliminaTodasLasCoincidencias recorre la lista de manos y llama a eliminaCoincidencias para cada una de ellas:

   1: class JuegoDeLaMona(JuegoDeCartas):

   2:     ...

   3:     def eliminaTodasLasCoincidencias(self):

   4:         cant = 0

   5:         for mano in self.manos:

   6:             cant = cant + mano.eliminaCoincidencias()

   7:         return cant

Como ejercicio, escriba muestraManos, el cual recorre self.manos y muestra cada mano.

 

cant es un acumulador que va sumando la cantidad de concordancias en cada mano y devuelve el total.

Cuando la cantidad total de coincidencias alcanza a las veinticinco significa que se han eliminado cincuenta cartas de las manos, lo que es lo mismo que decir que solo queda una carta y el juego ha terminado.

La variable turno recuerda el turno de cual jugador se esta jugando. Comienza en cero y se incrementa en uno cada vez; cuando alcanza el valor cantManos, el operador de modulo lo hace volver a cero.

El metodo jugarUnTurno toma un parámetro que indica de quien es el turno. El valor de retorno es la cantidad de concordancias que se han realizado durante ese turno:

   1: class JuegoDeLaMona(JuegoDeCartas):

   2: ...

   3:     def jugarUnTurno(self, i):

   4:         if self.manos[i].estaVacio():

   5:             return 0

   6:         vecino = self.encuentraVecino(i)

   7:         cartaElegida = self.manos[vecino].darCarta()

   8:         self.manos[i].agregaCarta(cartaElegida)

   9:         print "Mano", self.manos[i].nombre, "eligi¶o", cartaElegida

  10:         cant = self.manos[i].eliminaCoincidencias()

  11:         self.manos[i].mezclar()

  12:         return cant

Si la mano de un jugador esta vacía, el jugador salió del juego, así que no hace nada y devuelve 0.

Si no, un turno consiste en encontrar el primer jugador a la izquierda que aun tiene cartas, tomar una carta de las que posee, y controlar si hay concordancias.

Antes de volver se mezclan las cartas de la mano, de tal manera que la selección del siguiente jugador sea al azar.

El metodo encuentraVecino comienza con el jugador que esta inmediatamente a la izquierda y continua alrededor del círculo hasta que encuentra un jugador que aun tiene cartas.

   1: class JuegoDeLaMona(JuegoDeCartas):

   2:     ...

   3:     def encuentraVecino(self, i):

   4:         cantManos = len(self.manos)

   5:         for proximo in range(1,cantManos):

   6:             vecino = (i + proximo) % cantManos

   7:             if not self.manos[vecino].estaVacio():

   8:                 return vecino

Si por cualquier motivo encuentraVecino llegara a dar la vuelta completa al círculo sin encontrar cartas, devolvería None y eso causaría un error en alguna otra parte del programa.

Afortunadamente podemos probar que eso no va a suceder nunca (siempre y cuando se detecte correctamente el final del juego).

Hemos omitido el metodo muestraManos. Ese puede escribirlo usted mismo.

La siguiente salida proviene de una forma reducida del juego, en la cual solamente se reparten las quince cartas mas altas(desde los dieces hacia arriba) a tres jugadores. Con este mazo mas pequeño, el juego termina tras siete coincidencias, en lugar de veinticinco.

   1: >>> import cartas

   2: >>> juego = cartas.JuegoDeLaMona()

   3: >>> juego.jugar(["Allen","Jeff","Chris"])

   4: ----- Se han repartido las cartas.

   5: Mano Allen contiene

   6: Rey de Corazones

   7: Sota de Treboles

   8: Reina de Picas

   9: Rey de Picas

  10: 10 de Diamantes

  11: Mano Jeff contiene

  12: Reina de Corazones

  13: Sota de Picas

  14: Sota de Corazones

  15: Rey de Diamantes

  16: Reina de Diamantes

  17: Mano Chris contiene

  18: Sota de Diamantes

  19: Rey de Treboles

  20: 10 de Picas

  21: 10 de Corazones

  22: 10 de Treboles

  23: Mano Jeff: Reina de Corazones con Reina de Diamantes

  24: Mano Chris: 10 de Picas con 10 de Treboles

  25: ----- Se eliminaron las coincidencias, el juego comienza.

  26: Mano Allen contiene

  27: Rey de Corazones

  28: Sota de Treboles

  29: Reina de Picas

  30: Rey de Picas

  31: 10 de Diamantes

  32: Mano Jeff contiene

  33: Sota de Picas

  34: Sota de Corazones

  35: Rey de Diamantes

  36: Mano Chris contiene

  37: Sota de Diamantes

  38: Rey de Treboles

  39: 10 de Corazones

  40: Mano Allen: eligio Rey de Diamantes

  41: Mano Allen: Rey de Corazones con Rey de Diamantes

  42: Mano Jeff: eligio 10 de Corazones

  43: Mano Chris: eligio Sota de Treboles

  44: Mano Allen: eligio Sota de Corazones

  45: Mano Jeff: eligio Sota de Diamantes

  46: Mano Chris: eligio Reina de Picas

  47: Mano Allen: eligio Sota de Diamantes

  48: Mano Allen: Sota de Corazones con Sota de Diamantes

  49: Mano Jeff: eligio Rey de Treboles

  50: Mano Chris: eligio Rey de Picas

  51: Mano Allen: eligio 10 de Corazones

  52: Mano Allen: 10 de Diamantes con 10 de Corazones

  53: Mano Jeff: eligio Reina de Picas

  54: Mano Chris: eligio Sota de Picas

  55: Mano Chris: Sota de Treboles con Sota de Picas

  56: Mano Jeff: eligio Rey de Picas

  57: Mano Jeff: Rey de Treboles con Rey de Picas

  58: ----- El juego termino.

  59: La mano de Allen esta vac³a.

  60: La mano de Jeff contiene

  61: Reina de Picas

  62: La mano de Chris esta vac³a.

  63: As³ que Je® es quien perdio.

Página 16 de 22

Creado con WordPress & Tema de Anders Norén