I1M2012: Simulación de un juego de cartas en Haskell
En la clase de hoy de Informática de 1º del Grado en Matemáticas hemos comentado las soluciones de los ejercicios de la relación 21. En esta relación se estudia la modelización de un juego de cartas como aplicación de los tipos de datos algebraicos. Además, se definen los generadores correspondientes para comprobar las propiedades con QuickCheck.
Las soluciones de los ejercicios de la relación son
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
-- ---------------------------------------------------------------------------- -- Importación de librerías auxiliares -- -- ---------------------------------------------------------------------------- import Test.QuickCheck import Data.Char import Data.List -- --------------------------------------------------------------------- -- Ejercicio resuelto. Definir el tipo de datos Palo para representar -- los cuatro palos de la baraja: picas, corazones, diamantes y -- tréboles. Hacer que Palo sea instancia de Eq y Show. -- --------------------------------------------------------------------- -- La definición es data Palo = Picas | Corazones | Diamantes | Treboles deriving (Eq, Show) -- --------------------------------------------------------------------- -- Nota: Para que QuickCheck pueda generar elementos del tipo Palo se -- usa la siguiente función. -- --------------------------------------------------------------------- instance Arbitrary Palo where arbitrary = elements [Picas, Corazones, Diamantes, Treboles] -- --------------------------------------------------------------------- -- Ejercicio resuelto. Definir el tipo de dato Color para representar los -- colores de las cartas: rojo y negro. Hacer que Color sea instancia de -- Show. -- --------------------------------------------------------------------- data Color = Rojo | Negro deriving Show -- --------------------------------------------------------------------- -- Ejercicio 1. Definir la función -- color :: Palo -> Color -- tal que (color p) es el color del palo p. Por ejemplo, -- color Corazones ==> Rojo -- Nota: Los corazones y los diamantes son rojos. Las picas y los -- tréboles son negros. -- --------------------------------------------------------------------- color :: Palo -> Color color Picas = Negro color Corazones = Rojo color Diamantes = Rojo color Treboles = Negro -- --------------------------------------------------------------------- -- Ejercicio resuelto. Los valores de las cartas se dividen en los -- numéricos (del 2 al 10) y las figuras (sota, reina, rey y -- as). Definir el tipo -- de datos Valor para representar los valores -- de las cartas. Hacer que Valor sea instancia de Eq y Show. -- Main> :type Sota -- Sota :: Valor -- Main> :type Reina -- Reina :: Valor -- Main> :type Rey -- Rey :: Valor -- Main> :type As -- As :: Valor -- Main> :type Numerico 3 -- Numerico 3 :: Valor -- --------------------------------------------------------------------- data Valor = Numerico Int | Sota | Reina | Rey | As deriving (Eq, Show) -- --------------------------------------------------------------------- -- Nota: Para que QuickCheck pueda generar elementos del tipo Valor se -- usa la siguiente función. -- --------------------------------------------------------------------- instance Arbitrary Valor where arbitrary = oneof $ [ do return c | c <- [Sota,Reina,Rey,As] ] ++ [ do n <- choose (2,10) return (Numerico n) ] -- --------------------------------------------------------------------- -- Ejercicio 2. El orden de valor de las cartas (de mayor a menor) es -- as, rey, reina, sota y las numéricas según su valor. Definir la -- función -- mayor :: Valor -> Valor -> Bool -- tal que (mayor x y) se verifica si la carta x es de mayor valor que -- la carta y. Por ejemplo, -- mayor Sota (Numerico 7) ==> True -- mayor (Numerico 10) Reina ==> False -- --------------------------------------------------------------------- mayor :: Valor -> Valor -> Bool mayor _ As = False mayor As _ = True mayor _ Rey = False mayor Rey _ = True mayor _ Reina = False mayor Reina _ = True mayor _ Sota = False mayor Sota _ = True mayor (Numerico m) (Numerico n) = m > n -- --------------------------------------------------------------------- -- Ejercicio 3. Comprobar con QuickCheck si dadas dos cartas, una -- siempre tiene mayor valor que la otra. En caso de que no se verifique, -- añadir la menor precondición para que lo haga. -- --------------------------------------------------------------------- -- La propiedad es prop_MayorValor1 a b = mayor a b || mayor b a -- La comprobación es -- Main> quickCheck prop_MayorValor1 -- Falsifiable, after 2 tests: -- Sota -- Sota -- que indica que la propiedad es falsa porque la sota no tiene mayor -- valor que la sota. -- La precondición es que las cartas sean distintas: prop_MayorValor a b = a /= b ==> mayor a b || mayor b a -- La comprobación es -- Main> quickCheck prop_MayorValor -- OK, passed 100 tests. -- --------------------------------------------------------------------- -- Ejercicio resuelto. Definir el tipo de datos Carta para representar -- las cartas mediante un valor y un palo. Hacer que Carta sea instancia -- de Eq y Show. Por ejemplo, -- Main> :type Carta Rey Corazones -- Carta Rey Corazones :: Carta -- Main> :type Carta (Numerico 4) Corazones -- Carta (Numerico 4) Corazones :: Carta -- --------------------------------------------------------------------- data Carta = Carta Valor Palo deriving (Eq, Show) -- --------------------------------------------------------------------- -- Ejercicio 4. Definir la función -- valor :: Carta -> Valor -- tal que (valor c) es el valor de la carta c. Por ejemplo, -- valor (Carta Rey Corazones) ==> Rey -- --------------------------------------------------------------------- valor :: Carta -> Valor valor (Carta v p) = v -- --------------------------------------------------------------------- -- Ejercicio 5. Definir la función -- palo :: Carta -> Valor -- tal que (palo c) es el palo de la carta c. Por ejemplo, -- palo (Carta Rey Corazones) ==> Corazones -- --------------------------------------------------------------------- palo :: Carta -> Palo palo (Carta v p) = p -- --------------------------------------------------------------------- -- Nota: Para que QuickCheck pueda generar elementos del tipo Carta se -- usa la siguiente función. -- --------------------------------------------------------------------- instance Arbitrary Carta where arbitrary = do v <- arbitrary p <- arbitrary return (Carta v p) -- --------------------------------------------------------------------- -- Ejercicio 6. Definir la función -- ganaCarta :: Palo -> Carta -> Carta -> Bool -- tal que (ganaCarta p c1 c2) se verifica si la carta c1 le gana a la -- carta c2 cuando el palo de triunfo es p (es decir, las cartas son del -- mismo palo y el valor de c1 es mayor que el de c2 o c1 es del palo de -- triunfo). Por ejemplo, -- ganaCarta Corazones (Carta Sota Picas) (Carta (Numerico 5) Picas) -- ==> True -- ganaCarta Corazones (Carta (Numerico 3) Picas) (Carta Sota Picas) -- ==> False -- ganaCarta Corazones (Carta (Numerico 3) Corazones) (Carta Sota Picas) -- ==> True -- ganaCarta Treboles (Carta (Numerico 3) Corazones) (Carta Sota Picas) -- ==> False -- --------------------------------------------------------------------- ganaCarta :: Palo -> Carta -> Carta -> Bool ganaCarta triunfo c c' | palo c == palo c' = mayor (valor c) (valor c') | palo c == triunfo = True | otherwise = False -- --------------------------------------------------------------------- -- Ejercicio 7. Comprobar con QuickCheck si dadas dos cartas, una -- siempre gana a la otra. -- --------------------------------------------------------------------- -- La propiedad es prop_GanaCarta t c1 c2 = ganaCarta t c1 c2 || ganaCarta t c2 c1 -- La comprobación es -- Main> quickCheck prop_GanaCarta -- Falsifiable, after 0 tests: -- Diamantes -- Carta Rey Corazones -- Carta As Treboles -- que indica que la propiedad no se verifica ya que cuando el triunfo -- es diamantes, ni el rey de corazones le gana al as de tréboles ni el -- as de tréboles le gana al rey de corazones. -- --------------------------------------------------------------------- -- Ejercicio resuelto. Definir el tipo de datos Mano para representar -- una mano en el juego de cartas. Una mano es vacía o se obtiene -- agregando una carta a una mano. Hacer Mano instancia de Eq y -- Show. Por ejemplo, -- Main> :type Agrega (Carta Rey Corazones) Vacia -- Agrega (Carta Rey Corazones) Vacia :: Mano -- --------------------------------------------------------------------- data Mano = Vacia | Agrega Carta Mano deriving (Eq, Show) -- --------------------------------------------------------------------- -- Nota: Para que QuickCheck pueda generar elementos del tipo Mano se -- usa la siguiente función. -- --------------------------------------------------------------------- instance Arbitrary Mano where arbitrary = do cs <- arbitrary let mano [] = Vacia mano (c:cs) = Agrega c (mano cs) return (mano cs) -- --------------------------------------------------------------------- -- Ejercicio 8. Una mano gana a una carta c si alguna carta de la mano -- le gana a c. Definir la función -- ganaMano :: Palo -> Mano -> Carta -> Bool -- tal que (gana t m c) se verifica si la mano m le gana a la carta c -- cuando el triunfo es t. Por ejemplo, -- ganaMano Picas (Agrega (Carta Sota Picas) Vacia) (Carta Rey Corazones) -- ==> True -- ganaMano Picas (Agrega (Carta Sota Picas) Vacia) (Carta Rey Picas) -- ==> False -- --------------------------------------------------------------------- ganaMano :: Palo -> Mano -> Carta -> Bool ganaMano triunfo Vacia c' = False ganaMano triunfo (Agrega c m) c' = ganaCarta triunfo c c' || ganaMano triunfo m c' -- --------------------------------------------------------------------- -- Ejercicio 9. Definir la función -- eligeCarta :: Palo -> Carta -> Mano -> Carta -- tal que (eligeCarta t c1 m) es la mejor carta de la mano m frente a -- la carta c cuando el triunfo es t. La estrategia para elegir la mejor -- carta es la siguiente: -- * Si la mano sólo tiene una carta, se elige dicha carta. -- * Si la primera carta de la mano es del palo de c1 y la mejor del -- resto no es del palo de c1, se elige la primera de la mano, -- * Si la primera carta de la mano no es del palo de c1 y la mejor -- del resto es del palo de c1, se elige la mejor del resto. -- * Si la primera carta de la mano le gana a c1 y la mejor del -- resto no le gana a c1, se elige la primera de la mano, -- * Si la mejor del resto le gana a c1 y la primera carta de la mano -- no le gana a c1, se elige la mejor del resto. -- * Si el valor de la primera carta es mayor que el de la mejor del -- resto, se elige la mejor del resto. -- * Si el valor de la primera carta no es mayor que el de la mejor -- del resto, se elige la primera carta. -- --------------------------------------------------------------------- eligeCarta :: Palo -> Carta -> Mano -> Carta eligeCarta triunfo c1 (Agrega c Vacia) = c -- 1 eligeCarta triunfo c1 (Agrega c resto) | palo c == palo c1 && palo c' /= palo c1 = c -- 2 | palo c /= palo c1 && palo c' == palo c1 = c' -- 3 | ganaCarta triunfo c c1 && not (ganaCarta triunfo c' c1) = c -- 4 | ganaCarta triunfo c' c1 && not (ganaCarta triunfo c c1) = c' -- 5 | mayor (valor c) (valor c') = c' -- 6 | otherwise = c -- 7 where c' = eligeCarta triunfo c1 resto -- --------------------------------------------------------------------- -- Ejercicio 10. Comprobar con QuickCheck que si una mano es ganadora, -- entonces la carta elegida es ganadora. -- --------------------------------------------------------------------- -- La propiedad es prop_eligeCartaGanaSiEsPosible triunfo c m = m /= Vacia ==> ganaMano triunfo m c == ganaCarta triunfo (eligeCarta triunfo c m) c -- La comprobación es -- Main> quickCheck prop_eligeCartaGanaSiEsPosible -- Falsifiable, after 12 tests: -- Corazones -- Carta Rey Treboles -- Agrega (Carta (Numerico 6) Diamantes) -- (Agrega (Carta Sota Picas) -- (Agrega (Carta Rey Corazones) -- (Agrega (Carta (Numerico 10) Treboles) -- Vacia))) -- La carta elegida es el 10 de tréboles (porque tiene que ser del mismo -- palo), aunque el mano hay una carta (el rey de corazones) que gana. |