# Test de Majin
import unittest
#from majin import Ficha
#from majin import FichaAS
#from majin import Jugador
#from majin import JugadorHumano
#from majin import JugadorOrdenador
# Majin
"""Para un juego de dominó basado en el Mahjong, el Majín,
es necesario crear objetos Ficha con doble valor facial (iz izquierdo y dr derecho)
del 1 al 9.
Internamente se guardan los dos valores como una tupla,
accesible mediante las propiedades valor (devuelve la tupla entera),
iz (el primer valor) y dr (el segundo valor).
El método __str_() devuelve los valores con el formato “[<iz>|<dr>]”.
El método encaje_con(par: tuple[Ficha]) devuelve a) ENCAJE_NULO,
b) ENCAJE_PARCIAL o c) ENCAJE_TOTAL según la ficha
a) no contenga ninguno de los valores de las fichas en el par,
b) contenga uno de los valores de las fichas en el par,
o c) contenga valores de las dos fichas en el par.
Por ejemplo, dadas las fichas f0 = (1, 1), f1 = (4, 5), f2 = (9, 4), f3 = (5, 9)
y f4 = (4, 4), la ficha f0 no encaja con ninguna otra tomadas en pares,
la ficha f4 encaja parcialmente con f2 y f3,
y la ficha f1 encaja totalmente con f2 y f3.
"""
class Ficha:
ENCAJE_NULO = 1111
ENCAJE_PARCIAL = 1112
ENCAJE_TOTAL = 1113
def __init__(self, iz: int, dr: int):
if iz < 0 or iz > 9:
raise ValueError("iz fuera de rango (0-9): " + str(iz))
if dr < 0 or dr > 9:
raise ValueError("dr fuera de rango (0-9): " + str(dr))
self.__valor = (iz, dr)
@property
def valor(self):
return self.__valor
@property
def iz(self):
return self.valor[0]
@property
def dr(self):
return self.valor[1]
def encaje_con(self, par: tuple["Ficha"]) -> int:
toret = Ficha.ENCAJE_NULO
for ficha in par:
if (self.iz == ficha.iz
or self.dr == ficha.dr):
toret = Ficha.ENCAJE_PARCIAL if toret == Ficha.ENCAJE_NULO else Ficha.ENCAJE_TOTAL
return toret
def __str__(self):
return f"[{self.iz}|{self.dr}]"
"""La clase FichaAS representa a una ficha que puede encajar con cualquier otra,
cuya representación textual es “[*|*]”.
Internamente, los valores de esta ficha son (0, 0).
"""
class FichaAS(Ficha):
def __init__(self):
super().__init__(0, 0)
def encaje_con(self, par: tuple["Ficha"]) -> int:
return Ficha.ENCAJE_TOTAL
def __str__(self):
return "[*|*]"
"""Escriba (al menos), las clases JugadorHumano y JugadorOrdenador.
Ambas clases guardan un nombre
y una colección doble de fichas
(una lista y un diccionario sobre las mismas fichas),
que son las que el jugador posee.
Ambas tienen un método admite(fichas: lista[Ficha])
que añade las fichas dadas a las colecciones de fichas del jugador,
una propiedad de solo lectura para devolver nombre y un método __str_()
que devuelve “<nombre>: <ficha1>, <ficha2> [, … <fichan>]”.
Ambas tienen un método fichas_con(i: int) - > tuple[Ficha],
que simplifica el mostrar agrupadas al jugador las fichas
que tuvieran un determinado valor i en alguno de sus dos extremos.
El jugador ordenador siempre se llama Ralph^,
y si por ejemplo tuviese las fichas (1, 3), (4, 2) y (9, 6),
su método __str_() devolvería “Ralph^: [1|3] [4|2] [9|6]”.
Además, ambas clases tienen el método
haz_jugada(self, par: tuple[Ficha]) -> Ficha”.
El jugador humano simplemente devuelve la jugada almacenada mediante
la propiedad de lectura y escritura jugada, que acepta
y devuelve un objeto Ficha.
En la clase JugadorOrdenador, el método haz_jugada()
busca una ficha que haga encaje total,
en caso de no encontrarla devuelve una ficha que haga encaje parcial,
o devuelve None."""
class Jugador:
def __init__(self, nombre: str):
self.__nombre = nombre
self.__fichas = []
self.__dict_fichas = {0: [], 1: [], 2:[], 3:[], 4:[],
5:[], 6:[], 7:[], 8:[], 9:[]}
@property
def nombre(self):
return self.__nombre
@property
def fichas(self):
return list(self.__fichas)
def admite(self, fichas: list[Ficha]):
self.__fichas += fichas
for f in fichas:
self.__dict_fichas[f.iz].append(f)
self.__dict_fichas[f.dr].append(f)
def fichas_con(self, i: int) -> tuple[Ficha]:
return tuple(self.__dict_fichas[i])
def __str__(self):
return f"{self.nombre}: {str.join(' ', [str(x) for x in self.fichas])}"
"""Al jugador humano se le pasa la jugada."""
class JugadorHumano(Jugador):
def __init__(self, nombre: str):
super().__init__(nombre)
self.__jugada: Ficha| None = None
@property
def jugada(self) -> Ficha|None:
return self.__jugada
@jugada.setter
def jugada(self, f:Ficha):
self.__jugada = f
def haz_jugada(self, otro_par: tuple[Ficha]) -> Ficha|None:
return self.__jugada
"""El jugador ordenador deduce la jugada."""
class JugadorOrdenador(Jugador):
def __init__(self):
super().__init__("Ralph^")
def haz_jugada(self, otro_par: tuple[Ficha]) -> Ficha|None:
toret = None
for ficha in self.fichas:
if ficha.encaje_con(otro_par) == Ficha.ENCAJE_TOTAL:
toret = ficha
break
elif ficha.encaje_con(otro_par) == Ficha.ENCAJE_PARCIAL:
toret = ficha
return toret
class TestFicha(unittest.TestCase):
def test_crea_ficha(self):
f = Ficha(1, 5)
self.assertEqual((1, 5), f.valor)
self.assertEqual(1, f.iz)
self.assertEqual(5, f.dr)
self.assertEqual("[1|5]", str(f))
self.assertNotEqual(5, f.iz)
self.assertNotEqual(1, f.dr)
self.assertNotEqual("[5|1]", str(f))
#self.assertRaises(ValueError, lambda: Ficha(-1, 5))
#self.assertRaises(ValueError, lambda: Ficha(9, -5))
#self.assertRaises(ValueError, lambda: Ficha(9, 11))
#self.assertRaises(ValueError, lambda: Ficha(11, 5))
def test_encaje(self):
f1 = Ficha(1, 6)
f2 = Ficha(9, 9)
f3 = Ficha(5, 6)
f4 = Ficha(1, 6)
self.assertEqual(Ficha.ENCAJE_NULO, f2.encaje_con((f1, f3)))
self.assertEqual(Ficha.ENCAJE_PARCIAL, f1.encaje_con((f2, f3)))
self.assertEqual(Ficha.ENCAJE_TOTAL, f1.encaje_con((f4, f3)))
def test_crea_as(self):
f1 = Ficha(1, 5)
f2 = Ficha(9, 9)
f3 = Ficha(5, 6)
fas = FichaAS()
self.assertEqual("[*|*]", str(fas))
self.assertEqual(Ficha.ENCAJE_TOTAL, fas.encaje_con((f1, f2)))
self.assertEqual(Ficha.ENCAJE_TOTAL, fas.encaje_con((f3, f1)))
class TestJugador(unittest.TestCase):
def setUp(self):
self.__fichas = [Ficha(1, 2), Ficha(3, 4), Ficha(5, 6)]
def test_crea_jugador(self):
j = Jugador("abstract")
j.admite(self.__fichas)
self.assertEqual("abstract", j.nombre)
self.assertEqual(self.__fichas, j.fichas)
self.assertEqual("abstract: [1|2] [3|4] [5|6]", str(j))
def test_jugador_fichas_con(self):
j = Jugador("abstract")
j.admite(self.__fichas)
self.assertEqual("[1|2]", str(j.fichas_con(1)[0]))
self.assertEqual("[1|2]", str(j.fichas_con(2)[0]))
self.assertEqual("[3|4]", str(j.fichas_con(3)[0]))
self.assertEqual("[3|4]", str(j.fichas_con(4)[0]))
self.assertEqual("[5|6]", str(j.fichas_con(5)[0]))
self.assertEqual("[5|6]", str(j.fichas_con(6)[0]))
def test_jugada_jugador_humano(self):
jh1 = JugadorHumano("jh1")
jh1.admite(self.__fichas)
self.assertEqual("jh1", jh1.nombre)
self.assertEqual(self.__fichas, jh1.fichas)
self.assertEqual("jh1: [1|2] [3|4] [5|6]", str(jh1))
f = Ficha(9, 9)
jh1.jugada = f
self.assertEqual(f, jh1.jugada)
self.assertEqual(f, jh1.haz_jugada((None, None)))
def test_jugada_jugador_ordenador(self):
jo1 = JugadorOrdenador()
jo1.admite(self.__fichas)
self.assertEqual(self.__fichas, jo1.fichas)
self.assertEqual("Ralph^: [1|2] [3|4] [5|6]", str(jo1))
# No match
other_pieces = (Ficha(8, 9), Ficha(7, 8))
f = jo1.haz_jugada(other_pieces)
self.assertEqual(None, f)
# Partial match
other_pieces = (Ficha(3, 9), Ficha(5, 7))
f = jo1.haz_jugada(other_pieces)
self.assertNotEqual("[1|2]", str(f))
# Total match
other_pieces = (Ficha(3, 9), Ficha(4, 7))
f = jo1.haz_jugada(other_pieces)
self.assertEqual("[3|4]", str(f))
if __name__ == "__main__":
unittest.main()