You are on page 1of 18

INTELIGENCIA EN REDES DE COMUNICACIONES

El juego de la
Escoba

Rubn Grande Baquero


1- Introduccin y objetivos
El objetivo de esta prctica 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 disear un sistema informtico dotado de inteligencia.

En este caso, he optado por disear y desarrollar un sistema experto que sea
capaz de jugar a la escoba. La intencin 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
presentaran a un jugador humano, de forma que llegue a pensar como tal. Es decir, el
usuario es el que debe indicar al PC cules son las cartas que recibe, as como las que el
contrincante emplea en cada jugada. De esta forma, el PC recibe la misma informacin
que tendra un jugador humano.

2- Reglas bsicas del juego


Para entender el funcionamiento del sistema, antes de todo tenemos que tener
claro cules son las reglas del juego.

Este juego requiere la utilizacin de la baraja espaola 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 ms 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 volvern 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 tambin
las cartas que queden sobre la mesa.

La puntuacin 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 ms sietes gana un punto. El jugador que se haya llevado ms oros
gana un punto. El jugador que se haya llevado ms cartas gana un punto. En estos tres
ltimos casos, si ambos jugadores han conseguido el mismo nmero de sietes, oros o
cartas, ninguno de los dos consigue el correspondiente punto. Gana la partida el jugador
que ms 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 versin de Jess 6.0a5, que es
compatible con Java 2.

La aplicacin correr en el sistema operativo Windows 2000 en un Pentium 3 a


866 MHz. Como se ver ms adelante, en este equipo el tiempo de proceso es
inapreciable.

Caractersticas de la implementacin
Las funciones y reglas que se han utilizado se encuentran comentadas en el
cdigo de la aplicacin. Aparte de lo comentado es necesario destacar los siguientes
puntos:

Una carta se almacena en la aplicacin en forma de multicampo con dos


smbolos. El primero de ellos es un nmero 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 nmero del 1 al 10, un espacio en blanco, y el palo (oros,
copas, espadas o bastos). No he credo necesario introducir tratamiento de errores
ante lo que pueda introducir el usuario, dado que la aplicacin est pensada para ser
integrada en el futuro en una interfaz grfica que sea mucho ms amigable que la simple
interfaz textual. Si el usuario introduce errneamente estos datos, la aplicacin no
funcionar correctamente.

Un conjunto de cartas se representa como un multicampo formado por la


concatenacin 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 aplicacin solicitar al usuario las cartas una por una. De
esta forma, durante el juego, la nica informacin que recibe el programa a travs 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 almacenarn 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 supresin 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 quedaran en la mesa podra venirle peor al
contrario).
El comportamiento de cada una de las funciones y reglas viene comentado en el
cdigo de la aplicacin.

Comportamiento del programa


A continuacin se presentar el comportamiento del sistema en todos sus
aspectos. Para ello se incluyen capturas de pantalla de cada momento significativo de la
aplicacin.

Comienzo
Lo primero que hace la aplicacin 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 cules 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 cul es la carta que elige de entre las que tiene en la
mano para jugar. Si consigue hacer jugada (sumar 15), a continuacin muestras cules
son las cartas que se lleva. Lo siguiente que muestra por pantalla es la razn por la que
hace esa jugada y no otra. Es decir, por qu ha elegido esta jugada en lugar de otra
jugada que tambin podra hacer. Por ltimo, muestra cules 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 continuacin, 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
aade a las que hay sobre la mesa.

Escoba del PC
Si el PC consigue hacer una escoba, se muestra el mensaje ESCOBA!!!! por
pantalla. Adems, se aumenta la cuenta de escobas del PC en uno.
Tambin se indica que sobre la mesa no quedan cartas.

Escoba del jugador


El jugador no debe indicar explcitamente 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 cul 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 continuacin se efecta un recuento de las escobas conseguidas (llevarse el 7


de oros cuenta como una escoba) y quin ha ganado en nmero de cartas, oros y sietes.
Se muestra cul 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 ejecucin. Si el usuario no quiere jugar otra partida, la ejecucin
termina.

Otros aspectos importantes sobre cmo 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 informacin que recibira un jugador (las cartas que tiene, las que el
otro echa, etc.) para que se comporte como lo hara 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 slo cuando se agota el mazo, ya que el sistema no da la partida por
concluida hasta que no han aparecido las 40 cartas.

Cdigo de la aplicacin
;;;;;;;;;;;;;;;;;;;;;;;;
;; JUEGO DE LA ESCOBA ;;
;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Rubn Grande Baquero
;;
;; Inteligencia en Redes de Comunicaciones
;; 5 Ing. de Telecomunicacin
;; 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, todava 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))
; Aadimos 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) (sumar-
sietes $?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) (length$
$?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
(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 ?valor-aestudiar (nth$ 1 $?cartas))


(bind $?cartas (rest$ $?cartas))
(bind ?palo-aestudiar (nth$ 1 $?cartas))
(bind $?cartas (rest$ $?cartas))
(bind $?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)) (>= ?puntos2 5) (<= ?puntos2 15)) then
(bind ?*razon* "el contrincante no puede hacer escoba")
(return 1)
)
(if (and (or (< ?puntos2 5) (> ?puntos2 15)) (>= ?puntos1 5) (<= ?puntos1 15)) then
(bind ?*razon* "el contrincante no puede hacer escoba")
(return 2)
)

(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 aadiendo 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 aadimos 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 aadiendo 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 aade a la cuenta de cartas, oros y sietes del PC los obtenidos de una
jugada.
(deffunction contar-puntos-PC($?cartas)
; Se aade le numero de cartas conseguidas
(bind ?*monton-PC* (+ ?*monton-PC* (/ (length$ $?cartas) 2)))
; Se aade el numero de oros
(bind ?*oros-PC* (+ ?*oros-PC* (sumar-oros $?cartas)))
; Se aade 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 aade 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 aade 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 aade 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 ?*monton-
PC*) "." 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 ?*monton-
jugador*) "." 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)

You might also like