Rubén Grande Baquero

Anuncio
INTELIGENCIA EN REDES DE COMUNICACIONES
El juego de la
Escoba
Rubén Grande Baquero
1- Introducción y objetivos
El objetivo de esta práctica es afianzar los conocimientos presentados a lo largo
del temario de la asignatura “Inteligencia en redes de comunicaciones”, para llegar a
comprobar si somos capaces de diseñar un sistema informático dotado de inteligencia.
En este caso, he optado por diseñar y desarrollar un sistema experto que sea
capaz de jugar a la escoba. La intención no es implementar el juego de la escoba para
jugar en el PC (como otros juegos como el Solitario o el Buscaminas), sino realizar un
sistema experto que determine las jugadas a seguir ante las situaciones que se le
presentarían a un jugador humano, de forma que llegue a pensar como tal. Es decir, el
usuario es el que debe indicar al PC cuáles son las cartas que recibe, así como las que el
contrincante emplea en cada jugada. De esta forma, el PC recibe la misma información
que tendría un jugador humano.
2- Reglas básicas del juego
Para entender el funcionamiento del sistema, antes de todo tenemos que tener
claro cuáles son las reglas del juego.
Este juego requiere la utilización de la baraja española de 40 cartas. Es decir, del
1 al 10 de cada uno de los cuatro palos (oros, copas, espadas y bastos).
El funcionamiento del juego es el siguiente: Al comenzar, se colocan 4 cartas
sobre la mesa y se reparten 3 cartas a cada uno de los dos jugadores. El jugador mano
comienza a jugar. En cada turno, el jugador echa una carta de las que tiene en la mano.
El jugador puede llevarse la carta que ha echado más una o varias de las cartas que hay
sobre la mesa siempre que la suma de los valores de las cartas que se lleva sea 15. Si el
jugador se lleva todas las cartas que hay sobre la mesa, se dice que ha hecho una
escoba.
Cuando ambos jugadores se hayan quedado sin cartas, se volverán a repartir 3
cartas a cada uno de los dos, y la partida continuará. La partida terminará cuando ambos
jugadores se hayan quedado sin cartas y ya no queden cartas que repartir. Cuando la
partida termina, el último jugador que haya conseguido llevarse cartas, se lleva también
las cartas que queden sobre la mesa.
La puntuación se realiza de la siguiente manera: Cada escoba realizada cuenta
como un punto. El jugador que se haya llevado el 7 de oros gana un punto. El jugador
que se haya llevado más sietes gana un punto. El jugador que se haya llevado más oros
gana un punto. El jugador que se haya llevado más cartas gana un punto. En estos tres
últimos casos, si ambos jugadores han conseguido el mismo número de sietes, oros o
cartas, ninguno de los dos consigue el correspondiente punto. Gana la partida el jugador
que más puntos haya conseguido.
Herramienta de desarrollo
La herramienta que se va a utilizar para desarrollar el sistema experto que juega
a la escoba será Jess. En concreto, se utilizará la versión de Jess 6.0a5, que es
compatible con Java 2.
La aplicación correrá en el sistema operativo Windows 2000 en un Pentium 3 a
866 MHz. Como se verá más adelante, en este equipo el tiempo de proceso es
inapreciable.
Características de la implementación
Las funciones y reglas que se han utilizado se encuentran comentadas en el
código de la aplicación. Aparte de lo comentado es necesario destacar los siguientes
puntos:
Una carta se almacena en la aplicación en forma de multicampo con dos
símbolos. El primero de ellos es un número del 1 al 10 que representa el valor de la
carta. El segundo representa el palo, y puede tomar uno de los siguientes valores: ‘oros’,
‘copas’, ‘espadas’ o ‘bastos’.
La entrada por el teclado de una carta se realiza de la misma forma. Es decir, el
usuario debe introducir un número del 1 al 10, un espacio en blanco, y el palo (‘oros’,
‘copas’, ‘espadas’ o ‘bastos’). No he creído necesario introducir tratamiento de errores
ante lo que pueda introducir el usuario, dado que la aplicación está pensada para ser
integrada en el futuro en una interfaz gráfica que sea mucho más amigable que la simple
interfaz textual. Si el usuario introduce erróneamente estos datos, la aplicación no
funcionará correctamente.
Un conjunto de cartas se representa como un multicampo formado por la
concatenación de los multicampos que representan cada una de las cartas que lo forman.
Por lo tanto, los índices impares (1,3,5,...) indican los valores de las cartas, y los índices
pares (2,4,6,...) indican los palos.
Al usuario nunca se le pedirá que introduzca un conjunto de cartas como tal.
Siempre que sea necesario, la aplicación solicitará al usuario las cartas una por una. De
esta forma, durante el juego, la única información que recibe el programa a través del
teclado es, o una carta en concreto, o respuestas del tipo sí/no.
Las cartas que quedan en el mazo, así como las cartas que hay sobre la mesa y
las cartas que tiene el PC en la mano, se almacenarán en forma de hechos. De esta
forma, su estado podrá disparar una determinada regla. Para eliminar una carta de uno
de estos tres conjuntos se incluirá un hecho en el que se indica la carta que hay que
eliminar. Este hecho disparará una regla cuyo resultado es la supresión de la carta.
La inteligencia del sistema se encuentra en las funciones jugada (en el caso de
que el PC se lleve alguna carta) y mejor-opcion-cartas-mesa (donde se decide entre dos
opciones qué conjunto de cartas que quedarían en la mesa podría venirle peor al
contrario).
El comportamiento de cada una de las funciones y reglas viene comentado en el
código de la aplicación.
Comportamiento del programa
A continuación se presentará el comportamiento del sistema en todos sus
aspectos. Para ello se incluyen capturas de pantalla de cada momento significativo de la
aplicación.
Comienzo
Lo primero que hace la aplicación es preguntar al usuario si desea ser mano, o
por el contrario, que lo sea el PC.
Inicio de la partida
Al comenzar la partida, se deben colocar 4 cartas sobre la mesa. Estas cuatro
cartas las debe introducir el usuario por teclado.
Repartir
Para repartir, el usuario debe indicar al PC cuáles son las 3 cartas que coge. No
se deben indicar las cartas que coge el jugador, ya que el PC no tiene por qué
conocerlas.
Turno del PC
Cuando el turno corresponde al PC, éste elige una de entre las cartas que tiene
para echar.
Lo primero que responde es cuál es la carta que elige de entre las que tiene en la
mano para jugar. Si consigue hacer jugada (sumar 15), a continuación muestras cuáles
son las cartas que se lleva. Lo siguiente que muestra por pantalla es la razón por la que
hace esa jugada y no otra. Es decir, por qué ha elegido esta jugada en lugar de otra
jugada que también podría hacer. Por último, muestra cuáles son las cartas que quedan
sobre la mesa.
Turno del jugador
Cuando el turno corresponde al jugador, el PC se queda a la espera de que el
usuario introduzca la carta con la que va a jugar.
El usuario introduce la carta con la que juega. A continuación, se le pregunta si
con la carta que ha echado se va a llevar alguna de la mesa.
El jugador se lleva cartas
Si el jugador responde afirmativamente a la pregunta anterior, se le pedirá que
introduzca una a una las cartas que se lleva.
Cuando la suma llega a 15, significa que el usuario ya ha terminado de
introducir las cartas que se lleva. Al final, muestra las cartas que quedan sobre la mesa.
El jugador no se lleva cartas
Si el usuario indica que no se lleva ninguna carta, la carta utilizada para jugar se
añade a las que hay sobre la mesa.
Escoba del PC
Si el PC consigue hacer una escoba, se muestra el mensaje “ESCOBA!!!!” por
pantalla. Además, se aumenta la cuenta de escobas del PC en uno.
También se indica que sobre la mesa no quedan cartas.
Escoba del jugador
El jugador no debe indicar explícitamente que hace escoba. Es el sistema el que
comprueba si el jugador hace una escoba si no deja cartas sobre la mesa.
El sistema muestra el mensaje “Eso es una ESCOBA!!” y aumenta la cuenta de
escobas del jugador.
Final de la partida
Cuando concluye la partida, lo primero que se averigua es cuál de los dos
jugadores se lleva las cartas que quedan sobrantes sobre la mesa, que será el último en
hacer jugada (el último que se ha llevado alguna carta).
A continuación se efectúa un recuento de las escobas conseguidas (llevarse el 7
de oros cuenta como una escoba) y quién ha ganado en número de cartas, oros y sietes.
Se muestra cuál de los dos jugadores ha ganado la partida (o si queda en empate) y se
pregunta si el usuario quiere comenzar una nueva partida. En caso afirmativo, el sistema
vuelve a comenzar su ejecución. Si el usuario no quiere jugar otra partida, la ejecución
termina.
Otros aspectos importantes sobre cómo utilizar el programa
Debe quedar claro que no se trata de un juego para ordenador en el que se puede
jugar a la escoba contra él, sino que es necesario disponer de una baraja. Al PC se le
debe introducir la información que recibiría un jugador (las cartas que tiene, las que el
otro echa, etc.) para que se comporte como lo haría una persona.
Por eso, para comprobar el funcionamiento, se debe utilizar una baraja y simular
una partida con ella. De esta forma nos aseguramos de que no se repiten cartas, y que la
partida termina sólo cuando se agota el mazo, ya que el sistema no da la partida por
concluida hasta que no han aparecido las 40 cartas.
Código de la aplicación
;;;;;;;;;;;;;;;;;;;;;;;;
;; JUEGO DE LA ESCOBA ;;
;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Rubén Grande Baquero
;;
;; Inteligencia en Redes de Comunicaciones
;; 5º Ing. de Telecomunicación
;; Curso 2003/2004
;;
;;;;;;;;;;;;;;;
;; Funciones ;;
;;;;;;;;;;;;;;;
; Esta funcion se ejecuta al comenzar la partida.
(deffunction inicializacion()
(printout t "Deseas ser mano? (si/no) ")
(if (eq si (read)) then (assert (turno-de jugador))
else (assert (turno-de PC))
)
; Al comenzar la partida, hay que colocar cuatro cartas sobre la mesa
(printout t "Introduzca las cuatro cartas que hay sobre la mesa" crlf)
(printout t "Primera carta: ")
(bind $?carta1 (explode$ (readline)))
(printout t "Segunda carta: ")
(bind $?carta2 (explode$ (readline)))
(printout t "Tercera carta: ")
(bind $?carta3 (explode$ (readline)))
(printout t "Cuarta carta: ")
(bind $?carta4 (explode$ (readline)))
(assert (cartas-en-mesa $?carta1 $?carta2 $?carta3 $?carta4))
(assert (eliminar-de-mazo $?carta1))
(assert (eliminar-de-mazo $?carta2))
(assert (eliminar-de-mazo $?carta3))
(assert (eliminar-de-mazo $?carta4))
(run-until-halt)
)
; Esta funcion determinara la estrategia del PC
; ?cartas-str es una cadena de caracteres que contiene las cartas que hay en la mano del
PC
; $?cartas-mesa contiene las cartas que hay sobre la mesa
(deffunction jugada(?cartas-str $?cartas-mesa)
; Recuperamos en un multicampo las cartas que puede echar el PC
(bind $?cartas (explode$ ?cartas-str))
; En principio, todavía no hemos hecho jugada
(bind ?hay-posibilidad FALSE)
(if (= 0 (length$ $?cartas-mesa)) then
;No hay cartas en la mesa. Hay que echar una
(assert (usar-carta (menos-mala (implode$ $?cartas) $?cartas-mesa)))
(assert (echar-sin-llevarse))
(return)
)
; Ahora se mira las posibilidades carta por carta
(while (<> 0 (length$ $?cartas))
(bind ?valor-actual (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(bind ?palo-actual (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(if (= 15 (+ ?valor-actual (sumar-puntos $?cartas-mesa))) then
;; HAY ESCOBA
; Si tenemos la carta de oros echamos esa. Si no, la primera.
(if (str-index (implode$ (create$ ?valor-actual oros)) (implode$ $?cartas)) then
(assert (usar-carta ?valor-actual oros))
else
(assert (usar-carta ?valor-actual ?palo-actual))
)
; El PC se lleva todas las cartas que hay sobre la mesa
(assert (echar-llevandose $?cartas-mesa))
; Añadimos uno a la cuenta de escobas
(bind ?*escobas-PC* (+ 1 ?*escobas-PC*))
(printout t "ESCOBA!!!!" crlf)
(bind ?*razon* "hago escoba")
(return)
)
(if (< 15 (+ ?valor-actual (sumar-puntos $?cartas-mesa))) then
;; Puede que haya alguna posibilidad de llevarse algo
; El numero de combinaciones de cartas que nos podemos llevar es 2^N - 1, siendo N
el numero de cartas
; que hay sobre la mesa. Cada carta ocupa dos campos (valor y palo) del multicampo
(bind ?n-posibles-combinaciones (- (** 2 (/ (length$ $?cartas-mesa) 2)) 1))
; Buscaremos combinacion por combinacion.
(bind ?indice 1)
(while (< ?indice ?n-posibles-combinaciones) ;La ultima combinacion ya se ha
tenido en cuenta (escoba)
(bind $?cartas-a-llevarse (combinacion ?indice $?cartas-mesa))
(if (= 15 (+ ?valor-actual (sumar-puntos $?cartas-a-llevarse))) then
; Nos podemos llevar cartas
(if ?hay-posibilidad then
; Hay que comparar las dos opciones:
; Opcion 1: La jugada que se habia decidido hasta ahora
; Opcion 2: La nueva jugada posible
(bind $?opcion1 (create$ ?carta-decidida $?jugada-decidida))
(bind $?opcion2 (create$ ?valor-actual ?palo-actual $?cartas-a-llevarse))
(bind ?*razon* "me llevo el 7 oros")
(if (and (str-index "7 oros" (implode$ $?opcion2)) (not (str-index "7 oros"
(implode$ $?opcion1)))) then
; Si con la nueva jugada nos llevamos el 7 de oros, que no nos llevabamos
con la anterior, sera la nueva la elegida
(bind $?carta-decidida (create$ ?valor-actual ?palo-actual))
(bind $?jugada-decidida $?cartas-a-llevarse)
else ;en caso contrario...
(if (not (and (str-index "7 oros" (implode$ $?opcion1)) (not (str-index "7
oros" (implode$ $?opcion2))))) then
; Si la jugada nueva no implica perder el 7 de oros...
(bind ?*razon* "me llevo mas sietes")
(if (and (not ?*num-sietes-decidido*) (> (sumar-sietes $?opcion2) (sumarsietes $?opcion1))) then
; El criterio en este caso es el numero de sietes, siempre que ya no este
decidido
(bind $?carta-decidida (create$ ?valor-actual ?palo-actual))
(bind $?jugada-decidida $?cartas-a-llevarse)
else
(if (or ?*num-sietes-decidido* (= (sumar-sietes $?opcion2) (sumar-sietes
$?opcion1))) then
(bind ?*razon* "me llevo mas oros")
(if (and (not ?*num-oros-decidido*) (> (sumar-oros $?opcion2) (sumar-oros
$?opcion1))) then
; Ahora el criterio es el numero de oros, siempre que ya no este decidido
(bind $?carta-decidida (create$ ?valor-actual ?palo-actual))
(bind $?jugada-decidida $?cartas-a-llevarse)
else
(if
(or
?*num-oros-decidido*
(=
(sumar-oros
$?opcion2)
(sumar-oros
$?opcion1))) then
(bind ?*razon* "me llevo mas cartas")
(if (and (not ?*num-cartas-decidido*) (> (length$ $?opcion2)
$?opcion1))) then
; Ahora el criterio es el numero de cartas que nos llevamos
(bind $?carta-decidida (create$ ?valor-actual ?palo-actual))
(bind $?jugada-decidida $?cartas-a-llevarse)
else
(length$
(if
(or
?*num-cartas-decidido*
(=
(length$
$?opcion2)
(length$
$?opcion1))) then
; Entonces hay que procurar dejar las peores cartas para el contrario
(bind $?cartas-sobrantes1 (quitar-cartas (implode$ $?jugada-decidida)
$?cartas-mesa))
(bind $?cartas-sobrantes2 (quitar-cartas (implode$ $?cartas-a-llevarse)
$?cartas-mesa))
(if
(=
2
(mejor-opcion-cartas-mesa
(implode$
$?cartas-sobrantes1)
$?cartas-sobrantes2)) then
(bind $?carta-decidida (create$ ?valor-actual ?palo-actual))
(bind $?jugada-decidida $?cartas-a-llevarse)
)
)
)
)
)
)
)
)
)
else
; Si es la primera jugada posible que encontramos...
(bind ?hay-posibilidad TRUE)
(bind $?carta-decidida (create$ ?valor-actual ?palo-actual))
(bind $?jugada-decidida $?cartas-a-llevarse)
(bind ?*razon* "no puedo hacer otra cosa")
)
)
; Terminada la busqueda de esta posibilidad, continuamos por la siguiente
(bind ?indice (+ 1 ?indice))
)
)
)
; En este punto ya se han estudiado todas las posibilidades
(if ?hay-posibilidad then
; Si podemos hacer jugada, ya tenemos decidida la carta a echar y la jugada
(assert (usar-carta $?carta-decidida))
(assert (echar-llevandose $?jugada-decidida))
else
; Si no podemos hacer jugada, simplemente procuramos dejar en la mesa las peores
cartas para el contrario
(assert (usar-carta (menos-mala ?cartas-str $?cartas-mesa)))
(assert (echar-sin-llevarse))
)
)
; Esta funcion elige una de entre las cartas que se tiene
; para echarla cuando el PC no se puede llevar ninguna
; ?cartas-str es una cadena de caracteres que contiene las cartas que hay en la mano del
PC
; $?cartas-mesa contiene las cartas que hay sobre la mesa
(deffunction menos-mala(?cartas-str $?cartas-mesa)
(bind $?cartas (explode$ ?cartas-str))
(bind ?valor-decidida (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(bind ?palo-decidida (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(bind $?carta-decidida (create$ ?valor-decidida ?palo-decidida))
; Por ahora la unica razon para echar esta carta es que no hay otra
(bind ?*razon* "no puedo echar otra")
(while (<> 0 (length$ $?cartas))
(bind
(bind
(bind
(bind
(bind
?valor-aestudiar (nth$ 1 $?cartas))
$?cartas (rest$ $?cartas))
?palo-aestudiar (nth$ 1 $?cartas))
$?cartas (rest$ $?cartas))
$?carta-aestudiar (create$ ?valor-aestudiar ?palo-aestudiar))
; Comparamos las dos opciones
(bind $?opcion1 (create$ $?carta-decidida $?cartas-mesa))
(bind $?opcion2 (create$ $?carta-aestudiar $?cartas-mesa))
(bind ?eleccion (mejor-opcion-cartas-mesa (implode$ $?opcion1) $?opcion2))
; Si es mejor la segunda opcion, sera la decidida por ahora
(if (= 2 ?eleccion) then (bind $?carta-decidida $?carta-aestudiar))
)
; Una vez estudiadas todas las opciones, devolvemos la mejor
(return $?carta-decidida)
)
; Esta funcion elige una de las dos opciones, que son las cartas
; que quedaran en la mesa despues de la jugada
; ?opcion1-str es una cadena de caracteres que contiene las cartas que quedarian
;
en la mesa de escoger la opcion 1
; $?cartas-mesa contiene las cartas que quedarian en la mesa de escoger la opcion 2
(deffunction mejor-opcion-cartas-mesa(?opcion1-str $?opcion2)
(bind $?opcion1 (explode$ ?opcion1-str))
;; Caracteristicas:
; Puntos sobre la mesa:
(bind ?puntos1 (sumar-puntos $?opcion1))
(bind ?puntos2 (sumar-puntos $?opcion2))
; Numero de oros;
(bind ?oros1 (sumar-oros $?opcion1))
(bind ?oros2 (sumar-oros $?opcion2))
; Numero de sietes:
(bind ?sietes1 (sumar-sietes $?opcion1))
(bind ?sietes2 (sumar-sietes $?opcion2))
; El criterio seguira el siguiente orden:
(if (and (not str-index "7 oros" (implode$ $?opcion1)) (str-index "7 oros" (implode$
$?opcion2))) then
(bind ?*razon* "no quiero echar el 7 de oros")
(return 1)
)
(if (and (not str-index "7 oros" (implode$ $?opcion2)) (str-index "7 oros" (implode$
$?opcion1))) then
(bind ?*razon* "no quiero echar el 7 de oros")
(return 2)
)
(if (and (or (< ?puntos1 5) (> ?puntos1 15)) (>=
(bind ?*razon* "el contrincante no puede hacer
(return 1)
)
(if (and (or (< ?puntos2 5) (> ?puntos2 15)) (>=
(bind ?*razon* "el contrincante no puede hacer
(return 2)
)
?puntos2 5) (<= ?puntos2 15)) then
escoba")
?puntos1 5) (<= ?puntos1 15)) then
escoba")
(if (> 5 ?puntos1 ?puntos2) then
(bind ?*razon* "es mas probable que despues podamos hacer escoba")
(return 1)
)
(if (> 5 ?puntos2 ?puntos1) then
(bind ?*razon* "es mas probable que despues podamos hacer escoba")
(return 2)
)
(if (and (not ?*num-sietes-decidido*) (< ?sietes1 ?sietes2)) then
(bind ?*razon* "dejo menos sietes en la mesa")
(return 1)
)
(if (and (not ?*num-sietes-decidido*) (< ?sietes2 ?sietes1)) then
(bind ?*razon* "dejo menos sietes en la mesa")
(return 2)
)
(if (and (not ?*num-oros-decidido*) (< ?oros1 ?oros2)) then
(bind ?*razon* "dejo menos oros en la mesa")
(return 1)
)
(if (and (not ?*num-oros-decidido*) (< ?oros2 ?oros1)) then
(bind ?*razon* "dejo menos oros en la mesa")
(return 2)
)
; Hay que constatar que si ya hay una figura, el hecho de echar otra figura
; del mismo valor no aumenta las posibilidades de jugada por parte del contrario.
; En caso contrario, las posibilidades siempre pueden aumentar.
(if (> (contar-veces 10 $?opcion1) (contar-veces 10 $?opcion2) 0) then
)
(bind ?*razon* "ya hay un 10 en la mesa")
(return 1)
(if (> (contar-veces 10 $?opcion2) (contar-veces 10 $?opcion1) 0) then
(bind ?*razon* "ya hay un 10 en la mesa")
(return 2)
)
(if (> (contar-veces 9 $?opcion1) (contar-veces 9 $?opcion2) 0) then
(bind ?*razon* "ya hay un 9 en la mesa")
(return 1)
)
(if (> (contar-veces 9 $?opcion2) (contar-veces 9 $?opcion1) 0) then
(bind ?*razon* "ya hay un 9 en la mesa")
(return 2)
)
(if (> (contar-veces 8 $?opcion1) (contar-veces 8 $?opcion2) 0) then
(bind ?*razon* "ya hay un 8 en la mesa")
(return 1)
)
(if (> (contar-veces 8 $?opcion2) (contar-veces 8 $?opcion1) 0) then
(bind ?*razon* "ya hay un 8 en la mesa")
(return 2)
)
; Llegados a este punto, no hay criterio para elegir una opcion u otra
; En posteriores versiones se podrian aumentar los criterios aumentando
; de esta forma la inteligencia del programa
(bind ?*razon* "me es indiferente")
; Opcion por defecto
(return 1)
)
; Esta funcion auxiliar devuelve los puntos que suman un conjunto de cartas
(deffunction sumar-puntos($?cartas)
(bind ?total 0)
(while (<> 0 (length$ $?cartas))
(bind ?total (+ ?total (nth$ 1 $?cartas)))
(bind $?cartas (rest$ $?cartas))
(bind $?cartas (rest$ $?cartas))
)
?total
)
; Esta funcion auxiliar devuelve la cantidad de oros que hay en un conjunto de cartas
(deffunction sumar-oros($?cartas)
(bind ?total 0)
(bind ?indice (member$ oros $?cartas))
(while ?indice
(bind ?total (+ 1 ?total))
(bind $?cartas (delete$ $?cartas ?indice ?indice))
(bind ?indice (member$ oros $?cartas))
)
?total
)
; Esta funcion auxiliar devuelve la cantidad de sietes que hay en un conjunto de cartas
(deffunction sumar-sietes($?cartas)
(bind ?total 0)
(bind ?indice (member$ 7 $?cartas))
(while ?indice
(bind ?total (+ 1 ?total))
(bind $?cartas (delete$ $?cartas ?indice ?indice))
(bind ?indice (member$ 7 $?cartas))
)
?total
)
; Esta funcion auxiliar devuelve la cantidad de veces que aparece un valor
; determinado en un conjunto de cartas
(deffunction contar-veces(?valor $?cartas)
(bind ?total 0)
(bind ?indice (member$ ?valor $?cartas))
(while ?indice
(bind ?total (+ 1 ?total))
(bind $?cartas (delete$ $?cartas ?indice ?indice))
(bind ?indice (member$ ?valor $?cartas))
)
?total
)
; Esta funcion auxiliar devuelve el conjunto de cartas resultante de eliminar
; unas cartas determinadas de un conjunto dado
; ?cartas-a-quitar-str es una cadena de caracteres que contiene el conjunto de cartas
que hay que eliminar
; $?cartas es el conjunto de cartas del que hay que eliminar las indicadas
(deffunction quitar-cartas(?cartas-a-quitar-str $?cartas)
(bind $?cartas-a-quitar (explode$ ?cartas-a-quitar-str))
; Vamos a quitar las cartas una a una
(while (<> 0 (length$ $?cartas-a-quitar))
; En este multicampo se iran añadiendo las que no hay que quitar
(bind $?cartas-aux (create$))
(bind ?valor-a-quitar (nth$ 1 $?cartas-a-quitar))
(bind $?cartas-a-quitar (rest$ $?cartas-a-quitar))
(bind ?palo-a-quitar (nth$ 1 $?cartas-a-quitar))
(bind $?cartas-a-quitar (rest$ $?cartas-a-quitar))
; Vamos recorriendo las cartas que tenemos, buscando la que hay que quitar
(while (<> 0 (length$ $?cartas))
(bind ?valor (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(bind ?palo (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(if (or (<> ?valor-a-quitar ?valor) (neq ?palo-a-quitar ?palo)) then
; Si no hay que eliminar esta carta, la añadimos a $?cartas-aux
(bind $?cartas-aux (create$ $?cartas-aux ?valor ?palo))
)
)
; Las cartas recogidas en $?cartas-aux son las que nos quedan
(bind $?cartas $?cartas-aux)
)
(return $?cartas)
)
; Esta funcion auxiliar devuelve una combinacion determinada de cartas a partir de un
conjunto dado
; Para ello, el numero de combinacion realiza una especie de seleccion binaria de las
cartas.
; Por ejemplo, si tenemos 4 cartas y el indice es 9 (= 1001b) se devolveran las cartas
primera y ultima.
; ?indice es el numero que determina la combinacion escogida
; $?cartas es el conjunto de cartas de las que hay que seleccionar las indicadas
(deffunction combinacion(?indice $?cartas)
; En este multicampo se iran añadiendo las cartas seleccionadas
(bind $?escogidas (create$))
(while (<> 0 (length$ $?cartas))
(bind ?valor-a-anadir (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(bind ?palo-a-anadir (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(if (= 1 (mod ?indice 2)) then
(bind $?escogidas (create$ $?escogidas ?valor-a-anadir ?palo-a-anadir))
)
(bind ?indice (div ?indice 2)) ; Division entera (desplazamiento en binario)
)
(return $?escogidas)
)
; Esta funcion añade a la cuenta de cartas, oros y sietes del PC los obtenidos de una
jugada.
(deffunction contar-puntos-PC($?cartas)
; Se añade le numero de cartas conseguidas
(bind ?*monton-PC* (+ ?*monton-PC* (/ (length$ $?cartas) 2)))
; Se añade el numero de oros
(bind ?*oros-PC* (+ ?*oros-PC* (sumar-oros $?cartas)))
; Se añade el numero de sietes
(bind ?*sietes-PC* (+ ?*sietes-PC* (sumar-sietes $?cartas)))
; Si hemos conseguido el 7 de oros, esto cuenta como una escoba
(if (str-index "7 oros" (implode$ $?cartas)) then
(bind ?*escobas-PC* (+ 1 ?*escobas-PC*))
)
; Tambien comprobamos si algun objetivo del juego ya se ha conseguido
(if (> ?*monton-PC* 20) then
(bind ?*num-cartas-decidido* TRUE)
)
(if (> ?*oros-PC* 5) then
(bind ?*num-oros-decidido* TRUE)
)
(if (> ?*sietes-PC* 2) then
(bind ?*num-sietes-decidido* TRUE)
)
)
; Esta funcion es analoga a la anterior, ahora en el caso del jugador humano
(deffunction contar-puntos-jugador($?cartas)
(bind ?*monton-jugador* (+ ?*monton-jugador* (/ (length$ $?cartas) 2)))
(bind ?*oros-jugador* (+ ?*oros-jugador* (sumar-oros $?cartas)))
(bind ?*sietes-jugador* (+ ?*sietes-jugador* (sumar-sietes $?cartas)))
(if (str-index "7 oros" (implode$ $?cartas)) then
(bind ?*escobas-jugador* (+ 1 ?*escobas-jugador*))
)
(if (> ?*monton-jugador* 20) then
(bind ?*num-cartas-decidido* TRUE)
)
(if (> ?*oros-jugador* 5) then
(bind ?*num-oros-decidido* TRUE)
)
(if (> ?*sietes-jugador* 2) then
(bind ?*num-sietes-decidido* TRUE)
)
)
;;;;;;;;;;;;
;; Reglas ;;
;;;;;;;;;;;;
; Esta regla se activa cada vez que aparece una carta nueva, para eliminarla del mazo
(defrule eliminar-de-mazo (declare (salience 10))
?regla-eliminar <- (eliminar-de-mazo ?valor ?palo)
?regla-mazo <- (cartas-en-mazo $?previas ?valor ?palo $?posteriores)
=>
(retract ?regla-eliminar)
(retract ?regla-mazo)
(assert (cartas-en-mazo $?previas $?posteriores))
)
; Esta regla se activa cada vez que se coge una carta de la mesa, para eliminarla
(defrule eliminar-de-mesa (declare (salience 10))
?regla-eliminar <- (eliminar-de-mesa ?valor ?palo)
?regla-mesa <- (cartas-en-mesa $?previas ?valor ?palo $?posteriores)
=>
(retract ?regla-eliminar)
(retract ?regla-mesa)
(assert (cartas-en-mesa $?previas $?posteriores))
)
; Esta regla se activa cada vez que el PC echa una carta
(defrule eliminar-de-mano (declare (salience 10))
?regla-eliminar <- (eliminar-de-mano ?valor ?palo)
?regla-mano <- (cartas-de-PC $?previas ?valor ?palo $?posteriores)
=>
(retract ?regla-eliminar)
(retract ?regla-mano)
(assert (cartas-de-PC $?previas $?posteriores))
)
; Esta regla se activa cuando el PC va a echar una carta sin llevarse ninguna
(defrule echar-sin-llevarse
?regla-echar <- (echar-sin-llevarse)
?regla-turno <- (turno-de PC)
?regla-mesa <- (cartas-en-mesa $?cartas-mesa)
?regla-carta <- (usar-carta $?carta) ; Indica la carta que va a echar
=>
; Se eliminan los hechos obsoletos
(retract ?regla-echar)
(retract ?regla-turno)
(retract ?regla-mesa)
(retract ?regla-carta)
; La carta ya no la tiene el PC en la mano
(assert (eliminar-de-mano $?carta))
; El turno siguiente sera del jugador
(assert (turno-de jugador))
; Se añade la carta a la mesa
(assert (cartas-en-mesa $?cartas-mesa $?carta))
; Se indica todo por pantalla
(printout t "Echo el " (implode$ $?carta) " porque " ?*razon* crlf)
(printout t "Cartas sobre la mesa:" crlf)
(printout t (create$ $?cartas-mesa $?carta) crlf)
)
; Esta regla se activa cuando el PC va a hacer jugada
(defrule echar-llevandose
?regla-echar <- (echar-llevandose $?jugada) ; Indica las cartas que se lleva
?regla-turno <- (turno-de PC)
?regla-mesa <- (cartas-en-mesa $?cartas-mesa)
?regla-carta <- (usar-carta $?carta) ; Indica la carta que va a echar
?regla-ganador <- (ultimo-ganador ?)
=>
; Se eliminan los hechos obsoletos
(retract ?regla-echar)
(retract ?regla-turno)
(retract ?regla-mesa)
(retract ?regla-carta)
(retract ?regla-ganador)
; La carta ya no la tiene el PC en la mano
(assert (eliminar-de-mano $?carta))
; El turno siguiente sera del jugador
(assert (turno-de jugador))
; Se indica todo por pantalla
(printout t "Echo el " (implode$ $?carta) " ." crlf)
(printout t "Me llevo: " $?jugada crlf)
(printout t "Porque " ?*razon* crlf)
(printout t "Cartas sobre la mesa:" crlf)
(bind $?cartas-que-quedan (quitar-cartas (implode$ $?jugada) $?cartas-mesa))
(printout t $?cartas-que-quedan crlf)
; Se actualiza la puntuacion obtenida por el PC
(contar-puntos-PC $?carta $?jugada)
; Se indica que el ultimo que se ha llevado cartas es el PC, necesario para saber
; quien se lleva las cartas sobrantes al final de la partida
(assert (ultimo-ganador PC))
; Se eliminan de la mesa las cartas que se ha llevado el PC
(assert (cartas-en-mesa $?cartas-que-quedan))
)
; Esta regla se activa cada vez que el turno corresponde al PC.
; El comportamiento se define en la funcion 'jugada'.
(defrule turnoPC
(turno-de PC)
(cartas-de-PC ? $?) ;Si le quedan cartas
(cartas-de-PC $?cartas)
(cartas-en-mesa $?cartas-mesa)
=>
(jugada (implode$ $?cartas) $?cartas-mesa)
)
; Esta regla se activa cada vez que el turno corresponde al jugador.
(defrule turnojugador (declare (salience -10))
?regla-turno <- (turno-de jugador)
?regla-cartas <- (cartas-en-mesa $?cartas-mesa)
?regla-ganador <- (ultimo-ganador ?)
=>
; Las cartas en la mesa no seran las mismas al terminar la jugada
(retract ?regla-cartas)
; Se pide al jugador que indique la carta que echa
(printout t "Introduzca la carta que echa: ")
(bind $?carta (explode$ (readline)))
; La carta introducida ya no estara en el mazo
(assert (eliminar-de-mazo $?carta))
(bind ?*n-cartas-jugador* (- ?*n-cartas-jugador* 1))
; En esta variable booleana se indicara si el texto introducido por el usuario es
erroneo
(bind ?respuesta-erronea TRUE)
(while ?respuesta-erronea
; Se pregunta al jugador si con la carta que echa se lleva alguna
(printout t "Se lleva alguna carta? (si/no) ")
(bind ?respuesta (read))
(if (eq ?respuesta no) then
; Si no se lleva ninguna carta, simplemente se añade a la mesa
(bind ?respuesta-erronea FALSE)
(assert (cartas-en-mesa $?cartas-mesa $?carta))
(printout t "Cartas sobre la mesa:" crlf)
(printout t (create$ $?cartas-mesa $?carta) crlf)
)
(if (eq ?respuesta si) then
; Si consigue hacer jugada, se le pregunta que cartas son las que se lleva
(bind ?respuesta-erronea FALSE)
(printout t "Introduzca las cartas que se lleva con el " (implode$ $?carta) " :"
crlf)
; En este multicampo se iran guardando las cartas que se lleva el jugador
(bind $?jugada (create$))
; Aqui se van sumando los puntos de la jugada para comprobar si son 15
(bind ?total (nth$ 1 $?carta))
; Contador del numero de carta
(bind ?i 1)
; Esta variable booleana toma el valor TRUE si aun no tenemos 15 puntos
(bind ?otra-mas TRUE)
(while ?otra-mas
; Se va preguntando al jugador las cartas que se lleva una por una
(printout t ?i "a carta: ")
(bind $?carta-cogida (explode$ (readline)))
(bind ?total (+ ?total (nth$ 1 $?carta-cogida)))
(if (= 15 ?total) then
; Si ya tiene 15 puntos, no se tiene que llevar ninguna mas
(bind ?otra-mas FALSE)
)
(if (< 15 ?total) then
; Si supera los 15 puntos, significa que la jugada no es valida, porque tiene
que ser 15 puntos exactos
(printout t "Jugada no valida." crlf)
; Se le vuelve a preguntar al jugador por la jugada correcta
(printout t "Introduzca las cartas que se lleva con el " (implode$ $?carta) "
:" crlf)
(bind ?i 0)
(bind ?total (nth$ 1 $?carta))
(bind $?jugada (create$))
)
; Contador++
(bind ?i (+ 1 ?i))
; Se añade la carta indicada a la jugada
(bind $?jugada (create$ $?jugada $?carta-cogida))
)
(if (eq (quitar-cartas (implode$ $?jugada) $?cartas-mesa) (create$)) then
; Si no uedan cartas en la mesa, significa que el jugador ha conseguido una
escoba
(printout t "Eso es una ESCOBA!!" crlf)
(bind ?*escobas-jugador* (+ 1 ?*escobas-jugador*))
)
; Se actualiza la puntuacion del jugador
(contar-puntos-jugador $?carta $?jugada)
; Se indica que el ultimo en hacer jugada es el jugador humano
(retract ?regla-ganador)
(assert (ultimo-ganador jugador))
; Se actualizan las cartas que hay sobre la mesa
(assert (cartas-en-mesa (quitar-cartas (implode$ $?jugada) $?cartas-mesa)))
(printout t "Cartas sobre la mesa:" crlf)
(printout t (quitar-cartas (implode$ $?jugada) $?cartas-mesa) crlf)
)
)
; El siguiente turno corresponde al PC
(retract ?regla-turno)
(assert (turno-de PC))
)
; Esta regla se activa cada vez que tenemos que repartir 3 cartas a cada uno
(defrule repartir
(cartas-en-mazo ? $?) ;si todavia quedan cartas
?hecho-PC <- (cartas-de-PC)
(test (= ?*n-cartas-jugador* 0)) ;y los jugadores se han quedado sin cartas
=>
(retract ?hecho-PC)
; Pedimos al usuario que introduzca las 3 cartas que coge el PC
(printout t "Introduzca las cartas que coge el PC" crlf)
(printout t "Primera carta: ")
(bind $?carta1 (explode$ (readline)))
(printout t "Segunda carta: ")
(bind $?carta2 (explode$ (readline)))
(printout t "Tercera carta: ")
(bind $?carta3 (explode$ (readline)))
(assert (cartas-de-PC $?carta1 $?carta2 $?carta3))
; Las cartas indicadas ya no estaran en el mazo
(assert (eliminar-de-mazo $?carta1))
(assert (eliminar-de-mazo $?carta2))
(assert (eliminar-de-mazo $?carta3))
; El jugador ahora tendra 3 cartas en la mano
(bind ?*n-cartas-jugador* 3)
)
; Esta regla solo se activa cuando ha terminado la partida
(defrule final (declare (salience -10))
; Si ya no quedan cartas ni en el mazo ni en ninguna mano, la partida ha terminado
(cartas-en-mazo)
(cartas-de-PC)
(test (= 0 ?*n-cartas-jugador*))
(ultimo-ganador ?ganador)
(cartas-en-mesa $?restantes)
=>
(printout t crlf)
; El ultimo en hacer jugada se lleva las cartas restantes en la mesa
(if (eq ?ganador PC) then
(printout t "El PC se lleva las cartas que quedan" crlf)
(contar-puntos-PC $?restantes)
)
(if (eq ?ganador jugador) then
(printout t "Te llevas las cartas que quedan" crlf)
(contar-puntos-jugador $?restantes)
)
; Se hace el recuento final
(bind ?puntuacion-PC ?*escobas-PC*)
(printout t "El PC ha conseguido " ?*escobas-PC* " escobas." crlf)
(if (> ?*monton-PC* 20) then
(printout t "El PC ha ganado en numero de cartas, ya que tiene " (integer ?*montonPC*) "." crlf)
(bind ?puntuacion-PC (+ 1 ?puntuacion-PC))
)
(if (> ?*oros-PC* 5) then
(printout t "El PC ha ganado en numero de oros, ya que tiene " ?*oros-PC* "." crlf)
(bind ?puntuacion-PC (+ 1 ?puntuacion-PC))
)
(if (> ?*sietes-PC* 2) then
(printout t "El PC ha ganado en numero de sietes, ya que tiene " ?*sietes-PC* "."
crlf)
(bind ?puntuacion-PC (+ 1 ?puntuacion-PC))
)
(printout t "En total, el PC ha conseguido " ?puntuacion-PC " puntos." crlf)
(bind ?puntuacion-jugador ?*escobas-jugador*)
(printout t "Has conseguido " ?*escobas-jugador* " escobas." crlf)
(if (> ?*monton-jugador* 20) then
(printout t "Has ganado en numero de cartas, ya que tienes " (integer ?*montonjugador*) "." crlf)
(bind ?puntuacion-jugador (+ 1 ?puntuacion-jugador))
)
(if (> ?*oros-jugador* 5) then
(printout t "Has ganado en numero de oros, ya que tienes " ?*oros-jugador* "." crlf)
(bind ?puntuacion-jugador (+ 1 ?puntuacion-jugador))
)
(if (> ?*sietes-jugador* 2) then
(printout t "Has ganado en numero de sietes, ya que tienes " ?*sietes-jugador* "."
crlf)
(bind ?puntuacion-jugador (+ 1 ?puntuacion-jugador))
)
(printout t "En total, has conseguido " ?puntuacion-jugador " puntos." crlf)
)
; Se indica quien ha ganado, o en su caso, si la partida ha terminado en empate
(if (> ?puntuacion-PC ?puntuacion-jugador) then
(printout t "EL PC HA GANADO LA PARTIDA" crlf)
)
(if (< ?puntuacion-PC ?puntuacion-jugador) then
(printout t "HAS GANADO LA PARTIDA" crlf)
)
(if (= ?puntuacion-PC ?puntuacion-jugador) then
(printout t "LA PARTIDA HA ACABADO EN EMPATE" crlf)
)
; Se pregunta si el usuario quiere jugar otra partida
(printout t "Deseas jugar otra vez? (si/no)" crlf)
(bind ?seguir (read))
(if (eq si ?seguir) then
; Si el usuario quiere jugar otra vez, comenzamos una nueva partida
(reset)
(inicializacion)
else
; En caso contrario, salimos de la aplicacion
(exit)
)
;;;;;;;;;;;;
;; Hechos ;;
;;;;;;;;;;;;
(deffacts hechos-iniciales
; Mazo inicial: Las 40 cartas de la baraja
(cartas-en-mazo
1 oros
2 oros
3 oros
4 oros
5 oros
6 oros
7 oros
8 oros
9 oros
10 oros
1 copas
2 copas
3 copas
4 copas
5 copas
6 copas
7 copas
8 copas
9 copas
10 copas
1 espadas
2 espadas
3 espadas
4 espadas
5 espadas
6 espadas
7 espadas
8 espadas
9 espadas
10 espadas
1 bastos
2 bastos
3 bastos
4 bastos
5 bastos
6 bastos
7 bastos
8 bastos
9 bastos
10 bastos)
; Tanto el PC como el jugador carecen de cartas al principio
(cartas-de-PC)
; "?*n-cartas-jugador*=0" en defglobal
; En este hecho se guarda el ultimo que se ha llevado cartas,
; para saber quien se lleva las cartas que sobran al final.
(ultimo-ganador PC)
)
;;;;;;;;;;;;;;;;;;;;;;;;
;; Variables globales ;;
;;;;;;;;;;;;;;;;;;;;;;;;
(defglobal
?*n-cartas-jugador* = 0 ;Numero de cartas que el jugador tiene en la mano
?*escobas-PC* = 0 ;Numero de escobas que ha conseguido el PC
?*escobas-jugador* = 0 ;Numero de escobas que ha conseguido el jugador
?*monton-PC* = 0 ;Numero de cartas que ha conseguido el PC
?*monton-jugador* = 0 ;Numero de cartas que ha conseguido el jugador
?*oros-PC* = 0 ;Numero de oros que ha conseguido el PC
?*oros-jugador* = 0 ;Numero de oros que ha conseguido el jugador
?*sietes-PC* = 0 ;Numero de sietes que ha conseguido el PC
?*sietes-jugador* = 0 ;Numero de sietes que ha conseguido el jugador
?*num-cartas-decidido* = FALSE ;Si ya se sabe quien gana en numero de cartas
?*num-oros-decidido* = FALSE ;Si ya se sabe quien gana en numero de oros
?*num-sietes-decidido* = FALSE ;Si ya se sabe quien gana en numero de sietes
?*razon* = "" ;Indica la razon por la que el PC echa una carta y no otra
)
(reset)
(inicializacion)
Descargar