Professional Documents
Culture Documents
FACULTAD DE INGENIERÍA
INSTITUTO DE COMPUTACIÓN
ORIENTACIÓN A
OBJETOS EN PROLOG
PROGRAMACIÓN LÓGICA
DANIEL PEROVICH
GUILLERMO MONCECCHI
JULIO, 2002
Orientación a Objetos en Prolog
PROGRAMACIÓN LÓGICA
INTRODUCCIÓN ________________________________________________ 3
Sintaxis _____________________________________________________________________________ 4
Declaración de Objetos _______________________________________________________________ 4
Declaración de Métodos ______________________________________________________________ 5
Atributos __________________________________________________________________________ 6
Ejemplo de Declaración ______________________________________________________________ 6
CONCLUSIONES _______________________________________________ 34
I N TRO D U C C I Ó N
O R I E N TA C I Ó N A O B J E TO S E N S I C S T U S P RO L O G
PROLOG OBJECTS
Los mecanismos básicos para compartir datos en Prolog Objects son la herencia y la delegación. La
herencia permite definir un objeto tomando como base las características de otro ya existente, y para
su implementación se utiliza el mecanismo de importación del sistema de módulos de SICStus. La
delegación permite a un objeto enviar un mensaje a otro para invocar a un método definido por el
receptor pero interpretado en el contexto del objeto que lo envía.
En Prolog Objects, los objetos son, conceptualmente, una colección de predicados Prolog
(métodos), a los que se les asigna un nombre. Además pueden tener atributos modificables a través
de métodos predefinidos. Por ser Prolog, los métodos pueden ser definidos no determinísticamente y
puede haber respuestas alternativas a través del mecanismo de backtracking.
Los objetos pueden definirse estáticamente en un archivo, o ser creados durante la ejecución.
Vale notar que Prolog Objects maneja orientación a objetos – y no a clases, como por ejemplo lo
hacen Java, C++, u otras versiones de Prolog, como Trinc Prolog. Esto es, un objeto puede heredar
de otro, pero no existe el concepto de “clase”, como una definición, aunque (como se verá más
adelante), las instancias podrían verse como objetos de una clase dada por el objeto a partir del cual
se construyen.
Existe un proto-objeto inicial object, desde el cual otros objetos pueden crearse directa o
indirectamente.
Prolog Objects es una biblioteca más de SICStus, por lo que, para que esté disponible, debe
importarse el módulo:
?- use_module(library(objects)).
SINTAXIS
DECLARACIÓN DE OBJETOS
obj_ident :: {
sentence_1 &
sentence_2 &
...
sentence_n
}.
Donde obj_ident es un átomo o un término f(v1,…vn), donde v1,…,vn son diferentes variables.
El cuerpo del objeto consiste en un conjunto de sentencias (posiblemente vacío), las cuales pueden
ser:
Una cláusula de método, que tiene una sintaxis similar a las cláusulas comunes de Prolog,
pero que, en lugar de llamadas a predicados, tiene llamadas a métodos. Un método es un
conjunto de cláusulas de método, con el mismo functor. Tambien es posible llamar a
objetivos Prolog comunes, prefijándolos con ‘:’
Ejemplo:
func_lista :: {
length([], 0) &
length([_|L], N) :-
:: length(L, N1),
:(N is N1 + 1)
}.
Declara un objeto func_lista, el cual contiene el método length, y que utiliza las funciones
append y member de la biblioteca de listas.
DECLARACIÓN DE MÉTODOS
Los cláusulas de método se declaran de forma similar a las cláusulas Prolog. Por lo tanto pueden
ser hechos o cláusulas con cuerpo no vacío. Los objetivos en el cuerpo conservan las estructuras de
control normales en Prolog (conjunción, disjunción, cut, negación, unificación).
Los átomos en el cuerpo de una cláusula de método pueden ser alguno de los siguientes:
:: goal → envía el mensaje goal a un método que puede estar definido localmente
o heredado
<: goal → delega el mensaje goal a un método que puede estar definido
localmente o heredado
ATRIBUTOS
Existe en Prolog Object una forma estándar de definir atributos de un objeto. Para esto se utiliza
el predicado attributes. Los atributos definidos pueden ser leídos y modificados a través de los
métodos get/1 y set/1.
EJEMPLO DE DECLARACIÓN
A continuación se muestra como ejemplo la definición de un objeto lista, el cual tiene como
único atributo una lista Prolog, y como único método a sin_ultimo/1 y sin_ultimo/0, que devuelve la
lista sin el último elemento. Accesoriamente, se define el objeto list_lib, que es una biblioteca de
funciones genéricas sobre listas, que será utilizada por el objeto lista. En este ejemplo puede verse la
sobrecarga de métodos y el uso de los métodos get y set para trabajar con los atributos.
:- use_module(library(lists),[append/3,member/2]).
:- use_module(library(objects)).
list_lib :: {
%% sin_ultimo(+L, ?S)
sin_ultimo([X|R], [X|Y]) :-
:: sin_ultimo(R, Y)
}.
list :: {
%% Hereda de 'object'
super(object) &
attributes([content([])]) &
%% sin_ultimo(-S).
%% último elemento.
sin_ultimo(S) :-
<: get(content(L)),
list_lib :: sin_ultimo(L, S)
&
sin_ultimo :-
<: get(content(L)),
<: set(content(S))
}.
Método de la biblioteca:
X = [1,2,3] ? ;
no
| ?- list :: sin_ultimo.
no
| ?- list :: set(content([a,b,c])).
yes
| ?- list :: get(content(X)).
X = [a,b,c] ? ;
no
| ?- list :: sin_ultimo(X).
X = [a,b] ? ;
no
| ?- list :: sin_ultimo.
yes
| ?- list :: get(content(X)).
X = [a,b] ? ;
Por medio del método super/2 es posible declarar los super-objetos de un objeto dado. Los
objetos declarados por super/2 son los objetos inmediatos desde los cuales se hereda un método si
éste no está definido dentro del objeto (se utiliza un mecanismo de herencia por overriding). La
sintaxis es super(Super, NotInherited). El primer argumento es el objeto a declarar como super-objeto, y
el segundo es una lista con los métodos no heredados. Todas las llamadas a super/1 se transforman a
super(Super, []).
Un objeto puede heredar de más de un objeto (herencia múltiple), y esto se logra utilizando
varias cláusulas super en el objeto. En caso de conflictos (esto es, hay métodos definidos en más de un
super-objeto) predominan los métodos del objeto definido primero como super-objeto.
obj1 :: {
super(object) &
m(1) &
p(2)
}.
obj2 :: {
super(object) &
m(2)&
q(3)
}.
obj3 :: {
super(obj2) &
super(obj1)
}.
Y veamos como se heredan los métodos y como se definen las prioridades cuando existe más de
un super-objeto.
?- obj3 :: p(X).
X=2;
no
?- obj3 :: q(X).
X=3;
no
?- obj3 :: m(X).
X=2;
no
Existen tres métodos definidos en el objeto inicial object (del cual, por ello mismo, todo objeto
debiera heredar), que retornan los padres, los hijos y el propio objeto. Estos métodos son,
respectivamente super/1, sub/1 y self/0.
OBJETOS GENÉRICOS
Existen dos objetos genéricos en Prolog Objects: object (que provee métodos básicos que están
disponibles a todos los objetos por medio de la delegación, tales como super/1,sub/1,object/1) y util
(que provee métodos adicionales como subs/1,supers/1,o ancestors/2). El detalle de las funcionalidades
provistas, puede verse en el punto “Biblioteca de Soporte a Objetos” de este documento.
ENVIO DE MENSAJES
En Prolog Objects cada método se ejecuta en el contexto de un objeto. Este objeto puede o no
ser el objeto estático donde se declara el método. Qué atributos acceder y cuáles métodos utilizar, se
determina dinámicamente según el contexto (dynamic binding). Cuando se envía un mensaje a un
objeto, el método se ejecuta en el contexto del objeto destino. En cambio, si el método se delega,
éste se ejecuta en el contexto del objeto que lo envía.
object :: message → envía el mensaje a object, poniendo como Self del receptor al receptor. Si se
omite object, el receptor es el objeto donde aparece textualmente el objetivo.
object <: message → envía el mensaje a object, seteando Self del receptor a Self del que envía. Si se
omite object, el receptor es el objeto en el cual aparece textualmente el objetivo.
:- use_module(library(objects)).
obj :: {
attributes([valor(1)]) &
}.
% el metodo new.
v :: {
attributes([valor(2)]) &
super(object) &
p :-
obj :: m,
:: m,
obj <: m,
<: m
&
}.
| ?- v :: new(v2).
yes
| ?- v2 :: set(valor(3)).
yes
| ?- v2 :: p.
Metodo m en v, valor 2
Metodo m en v, valor 3
yes
En el primer caso (obj :: m), se envía el mensaje m al objeto obj, utilizando al receptor (el propio
obj) como contexto. En el segundo caso v2 envía el mensaje a su superobjeto v (donde está definido el
método), utilizando además su contexto. En los casos 3 y 4, los mensajes se siguen enviando a obj y v,
pero (como se utiliza delegación) el contexto es v2, y es por ello que el valor del atributo valor es 3.
Se presenta a continuación un ejemplo que indica como el utilizar envío de mensaje en lugar de
delegación puede provocar resultados no esperados. Considere para este ejemplo las definiciones en
el ejemplo de lista.
list2 :: { super(list) }.
| ?- list :: sin_ultimo(X).
no
| ?- list :: set(content([1,2,3])).
yes
| ?- list2 :: set(content([a,b,c])).
yes
| ?- list :: sin_ultimo(X).
X = [1,2] ? ;
no
| ?- list2 :: sin_ultimo(X).
X = [a,b] ? ;
no
sin_ultimo(S) :-
:: get(content(L)),
list_lib :: sin_ultimo(L, S)
&
el resultado es diferente:
| ?- list :: sin_ultimo(X).
no
| ?- list :: set(content([1,2,3])).
yes
| ?- list2 :: set(content([a,b,c])).
yes
| ?- list :: sin_ultimo(X).
X = [1,2] ? ;
no
| ?- list2 :: sin_ultimo(X).
X = [1,2] ? ;
no
O B J E TO S E S T Á T I C O S Y D I N Á M I C O S
OBJETOS ESTÁTICOS
Los objetos vistos hasta el momento son estáticos: son declarados y compilados y no pueden
cambiarse durante la ejecución.
OBJETOS DINÁMICOS
Los objetos pueden declararse como dinámicos, y de esta forma es posible modificar sus
métodos dinámicamente. Para declarar un objeto como dinámico, se utiliza la palabra clave dynamic.
obj_din :: {
dynamic &
...
}.
MÉTODOS DINÁMICOS
También es posible declarar como dinámicos sólo a algunos métodos dentro de un objeto:
objeto::{
dynamic F/N&
...
Luego de definidos como dinámicos, la forma de modificarlos es enviándoles los mensajes assert
y retract, los cuales se utilizan de la misma forma que las correspondientes cláusulas Prolog. El
comportamiento dinámico de un objeto se conserva con la herencia, esto es, los métodos declarados
como dinámicos en el padre serán copiados en el objeto y su comportamiento dinámico se
preservará.
Ejemplo:
grupo :: {
super(object)&
dynamic integrante/1&
integrante(juan)
}.
| ?- grupo :: asserta(integrante(pedro)).
yes
| ?- grupo :: asserta(integrante(jose)).
yes
| ?- grupo :: integrante(X).
X = jose ? ;
X = pedro ? ;
X = juan ? ;
no
Con el operador new, es posible declarar dinámicamente nuevos objetos basados en otro. El
nuevo objeto tendrá como super objeto a aquel que se le haya invocado new. A su vez, los objetos así
creados pueden servir como super objeto a otros objetos. Si se crea un objeto dinámicamente a partir
de otro, no se heredan los atributos del objeto original.
Ejemplo:
list :: new(list3).
list3 :: new(list4).
Las instancias son objetos con capacidades restringidas. Se crean a partir de un objeto, obtienen
una copia de los atributos y no pueden ser utilizados como super objeto para otros objetos., aunque
sí pueden crearse nuevas instancias a partir de ellas. La utilización de este mecanismo se asemeja a la
instanciación a partir de una clase en los lenguajes orientados a objetos tradicionales. Se crean nuevas
instancias con el predicado instante la cual recibe como parámetro el nombre de la instancia.
Ejemplo:
| ?- list :: instance(lista5).
yes
| ?- lista5 :: instance(lista6).
yes
| ?- lista6 :: new(lista7).
! Existence error in user:(::)/2
! object lista6 does not exist
! goal: lista6 :: new(lista7)
| ?- lista6 :: instance(lista7).
yes
| ?- lista7 :: get(content(X)).
X = [1,2,3] ? ;
no
B I B L I O TE C A D E S O P O RT E O O E N P RO L O G ( S I C S TU S )
MÉTODOS UNIVERSALES
Estos métodos están definidos localmente para todo objeto de Prolog Objects:
super(?Object, ?NotInheritList)
attributes(+Attributes)
Attributes es una lista de términos compuestos que especifican los atributos locales de Self y sus
valores iniciales.
METODOS INLINED
Estos métodos se compilan inline (las llamadas se reemplazan por definiciones). Esto implica que
tienen semántica fija y no pueden ser redefinidos.
self(?Self)
get(+Attribute)
Obtiene los valores de los atributos definidos con el functor de Attribute. Los valores son
unificados con los argumentos de Attribute.
set(+Attribute)
Modifica los valores de los atributos definidos con el functor de Attribute. Los valores son
tomados de los argumentos de Attribute.
PROTOTIPO: OBJECT
El objeto object provee métodos disponibles para todos los otros objetos por delegación.
Algunos de ellos son1:
super(?Object)
Object es padre de Self.
sub(?Object)
Object es padre de Self.
self(?Self)
Unifica Self con “self”.
object(?Object)
Uno de los objetos definidos en el sistema es Object
static
Self es un objeto estático
dynamic
Self es un objeto dinámico
dynamic ?Nombre/?Aridad
?Nombre/?Aridad es un método dinámico de Self.
static ?Nombre/?Aridad
?Nombre/?Aridad es un método estático de Self.
new(?Object)
Crea un nuevo objeto dinámico Object del cual Self será el prototipo. Object puede ser un
término compuesto, un átomo o una variable. En el último caso, el método genera un nombre
único para Object.
instance(?Instance)
Crea una nueva instancia Instance, de la cual Self será clase. Instance puede ser un término
compuesto, un átomo o una variable. En el último caso, el método genera un nombre único para
Instance.
has_instance(?Instance)
Self tiene la instancia Instante
assert(+Fact)
Agrega un nuevo Fact en Self. Si Self es estático, el nombre y la aridad de Fact deben ser
declarados como dinámicos.
PROTOTIPO: UTILITY
subs(?Objects)
Devuelve todo los hijos de Self
supers(?Objects)
Devuelve todo los padres de Self
objects(?Objects)
Devuelve una lista de todos los objetos
methods(?Methods)
Devuelve una lista de todos los métodos de Self
ancestor(?Object)
Uno de los ancestros de Self es Object
and_cast(+Objects,?Message)
Envía el mismo mensaje a todos los objetos en la lista Objects
or_cast(+Objects,?Message)
Envía el mismo mensaje a uno de los objetos de Objects, haciendo backtracking entre los
diferentes objetos.
A P É N D I C E 1 : L I S TA S , C O N J U N TO S , Á R B O L E S B I NA R I O S
El esquema general es el siguiente: se define un objeto list_lib, que contiene operaciones sobre
listas, para luego definir los objetos (esto es: lista, conjunto, arbol_bin). Aquí sólo se muestra como
ejemplo la definición de la biblioteca, el objeto lista y el método sin_último, que devuelve la lista sin
su último elemento. En el archivo adjunto pca.pl, se incluye una implementación completa de los
objetos, los cuales siguen los mismos criterios que en el ejemplo presentado.
Puede observarse que, en el objeto list, el método sin último se comporta diferente según el
número y tipo de sus parámetros (sobrecarga). Si recibe como parámetro una instancia existente,
entonces compara el contenido de la instancia con la lista contenida en el objeto, sin el último
elemento. Sino, crea una nueva instancia con la lista sin el último elemento como atributo.
Finalmente, si lo que recibe es una variable Prolog, entonces devuelve la lista del objeto sin el último
elemento. En caso de ser llamado sin parámetros, modifica el objeto, sustituyendo la lista implícita
actual por ella misma sin el último elemento.
:- use_module(library(objects)).
list_lib :: {
%% sin_ultimo(+L, ?S)
sin_ultimo([X|R], [X|Y]) :-
:: sin_ultimo(R, Y)
}.
list :: {
super(object) &
attributes([content([])]) &
%%% sin_ultimo(+S)
sin_ultimo(S) :-
: atom(S) ->
:: has_instance(S) ->
<: get(content(L)),
S :: get(content(LS)),
\+ object(S) ->
<: get(content(L)),
<: get(content(L)),
list_lib :: sin_ultimo(L, S)
&
sin_ultimo :-
<: get(content(L)),
<: set(content(S))
}.
APÉNDICE 2: OOTUTLOG
ESQUEMA DE TRABAJO
1. Análisis de requerimientos.
ANÁLISIS DE REQUERIMIENTOS
Se detalla a continuación la descripción general del problema. Una versión más detallada se encuentra
en la letra del laboratorio.
“Se desea simular una partida de “Tutlog” el cuál es, básicamente, una versión modificada del popular “Tute
Cabrero”. Se juega con un mazo de cartas españolas, quitándole los ochos y nueves. En cada partida hay un palo
llamado triunfo, que gana por sobre todos los otros. Cada carta tiene su puntaje propio. Inicialmente se elige el
palo del triunfo al azar. Luego se reparten las cartas y el jugador que es mano tira primero, jugando el resto de
izquierda a derecha. El jugador que es mano puede jugar cualquier carta. En cambio los demás jugadores deben
tirar según lo indicado por las reglas del juego. Una vez que hayan jugado todos los jugadores, el ganador recoge la
baza y pasa a ser mano. Esto querrá decir que ahora será él el que empezará a jugar, y los demás jugarán tras él,
según el orden antes explicado. Cuando los jugadores se queden sin cartas acabará la partida y se procederá a
contar los puntos de cada jugador. El jugador que gane la última baza sumará diez puntos adicionales. Los
ganadores de la partida son: el jugador que ha hecho más puntos y el que ha hecho menos. El primer jugador que
logre ganar una cierta cantidad de partidas (establecidas de antemano), será el ganador del juego.”
El objetivo del análisis orientado a objetos es encontrar y describir los objetos (o conceptos) en
el dominio de la aplicación. Estos conceptos son una primera aproximación a la solución al
problema.
Carta 10 1 4 1
Palo Mazo
numero
nombre : string
/puntos
0..4
1 triunfo
0..13
conformada
0..*
Baza
enMano 0..*
0..13 10,13
gana
0..1 1
0,1
1
Juego
{ordered}
En él se muestra el concepto mazo, compuesto de 4 palos, que a su vez estan conformados por
10 cartas (recordar que no hay ni 8 ni 9). El juego consta de partidas. En cada partida se juegan 10 o
13 bazas, dependiendo la cantidad de jugadores. Cada baza esta conformada por cartas. Cada jugador
registra las bazas que ha ganado. Además conoce las cartas que tiene en la mano.
Respecto a los jugadores, interesa reflejar que hay distintos tipos de ellos. En particular, un
jugador puede ser un usuario o la propia computadora. Para el caso de esta última, puede tener tres
estrategias diferentes de juego, a saber, Primera, Azar e Inteligente. Primera elige tira siempre la
primer carta dentro de las posibles. Azar elige una carta de las posibles en forma aleatoria. Inteligente
tiene una estrategia de juego más complicada, en función de las cartas ya tiradas, de las cartas en la
mesa, y de las que restan ser tiradas. Conceptualmente, los jugadores pueden modelarse de la
siguiente forma:
Jugador
Usuario Computadora
El objetivo de esta etapa es definir objetos lógicos (de software) y la forma de comunicación
entre ellos para una posterior programación. En base a los “conceptos candidatos” encontrados
durante el análisis y por medio de ciertos principios y técnicas, se debe decidir cuáles de éstos serán
los objetos que participarán en la solución y cómo se hablan entre ellos para obtener el resultado
deseado. El concepto clave aquí radica en la asignación de responsabilidades. En la transición del
análisis al diseño, puede encontrarse conceptos que no participen de la solución, puede ser necesario
reflotar conceptos que inicialmente fueron dejados de lado, y puede ser necesario crear ayudantes
(también objetos) para que los objetos puedan llevar a cabo su tarea.
Dos herramientas principales a utilizar aquí son los diagramas de secuencia y los diagramas de
colaboración de UML. En ellos se muestra la forma en que los objetos interactúan para llegar al
resultado.
Se presenta en primera instacia un diagrama de secuencia donde tiene lugar un juego entero. Se
agregó el objeto visualizador encargado de desplegar los mensajes del juego en pantalla. El juego
comienza mediante el mensaje iniciarJuego que recibe los jugadores que participarán (en el orden
deseado) y la cantidad de partidas a jugar. El juego elige un palo triunfo al azar e indica el mismo a los
jugadores y al visualizador mediante el mensaje indicarPaloTriunfo. Luego se barajan las cartas y se
reparten a cada jugador (mediante el mensaje indicarCartas). En este momento el jugador recibe las
cartas y es cuando decide si va a más o a menos. Luego se inicia una nueva baza y se avisa al
visualizador (nuevaBaza). Se consulta a cada jugador que carta desea tirar. En la primer partida
comienza el primer jugador, mientras que en las siguientes comienza el de la derecha del que había
comenzado la partida anterior. Cada jugador conoce las reglas del juego, por lo que determina de las
cartas que tiene en la mano cuales son las que puede tirar, eligiendo una de ellas. Esto último no esta
mostrado en el diagrama, ya que cada jugador realiza la elección en forma distinta. Una vez que cada
jugador tiró una carta, se avisa quien es el ganador de la baza (mensaje ganadorBaza al visualizador).
Luego se avisa a cada jugador que la baza finalizó. El jugador puede tomar nota de que cartas fueron
tiradas. Una vez que se acaben las cartas en la mano de los jugadores se procede a que cada uno
calcule los puntos ganados. A partir de estos puntos se calcula los ganadores de la partida. Se repite
este proceso hasta que algun(os) jugadores alcancen la cantidad de partidas a jugar en el juego. Por
último se indica en el visualizador los ganadores del juego (ganadoresJuego).
Se los considera
:Visualizador j1:Jugador ordenados de :Juego j2:Jugador
izquierda a derecha Se elige un palo
triunfo al azar
iniciarJuego(jugadores:Collection, cantPartidas:int)
indicarPaloTriunfo(palo:Palo) indicarPaloTriunfo(palo:Palo)
indicarPaloTriunfo(palo:Palo)
Cuando recibe las
cartas decide si va
a más o a menos
indicarCartas(cartas:Collection) indicarCartas(cartas:Collection)
cartaTirada(baza:Baza)
c:=tirarCarta(baza:Baza)
cartaTirada(baza:Baza)
ganaBaza(baza:Baza)
Se repite hasta que se acaben las
cartas en la mano de cada jugador
ganadorBaza(baza:Baza)
finalizaBaza(baza:Baza) finalizaBaza(baza:Baza)
puntos:=darPuntosGanados() puntos:=darPuntosGanados()
ganadorJuego(j:Jugador)
Una vez decidido a grandes rasgos como procederá el juego, se diseña como hace cada objeto
para llevar a cabo las responsabilidades que le fueron asignadas. A partir de esto vamos a detectar
responsabilidades en otros objetos. Para cada uno de los mensajes que “llegan” a visualizador se
realiza un diagrama de colaboración. A modo de ejemplo, se presentan los diagramas de colaboración
para algunas operaciones:
indicarPaloTriunfo(palo) 1: n:=getNombre()
1: palo:=getPalo()
cartaTirada(baza) 2: carta:=getNumero()
: Visualizador c : Juego::Carta
ganadorBaza(baza) 1: j:=darGanador()
j : Juego::Jugador
El objeto con mayor responsabilidad es juego. El diseño de sus operaciones dio lugar a nuevos
objetos que eran necesarios para realizar la tarea asignada. Este es el caso de tanteador. Se muestra a
continuación los diagramas de colaboración para juego.
1: g:=jugarJuego(jugadores, cantPartidas)
: Juego : Visualizador::Visualizador
: Visualizador::Visualizador
1: paloTriunfo:=sortearPaloTriunfo(jugadores)
2: repartirCartas(jugadores)
4* [1..40/cantJugadores]: mano:=jugarBaza(jugadores, mano, paloTriunfo)
3: cantJugadores:=count()
ganadores:=jugarPartida(jugadores) 6* [foreach]: j:=next()
: Juego jugadores:Jugador
7*:
pun
tos
:=d
ar Pun
t osG
9: add(jmenos)
8: add(jmas)
a
5: cre
nad
os (
)
ate()
j : Jugador
: Juego jugadores:Jugador
2*: in
dica
rP aloT
riunf
3: in
o(pa
lo)
dica
rPa
j : Jugador
riunfo
(pa
lo)
: Visualizador::Visualizador
: Juego jugadores:Jugador
2*: in
dica
rC ar
tas(c
a r ta
s)
f o)
riun
p a loT r, c) : Baza
, o
ano (jugad or()
e(m a d
c reat aTirad rGana
2: t
car o:=da
5 *: a n
7: m
3* [foreach]: j:=next()
jugarBaza:=jugarBaza(jugadores, mano, paloTriunfo) 9* [foreach]: next:=next()
: Juego jugadores:Jugador
4*:
c
10* :=tirar
ba a)
az (b ()
: fin C
rB da za
a( az
)
aliz arta(b
za
do ira a
aB a a
na taT vaB
za( za)
ga ar ue baz
a)
8: *: c : n
6 1
j : Jugador
: Visualizador::Visualizador
La etapa de diseño puede involucrar un diagrama de colaboración para cada una de las
operaciones de cada clase. Sin embargo, algunas de ellas pueden no hacerse debido la sencillez de la
operación, a que involucran excesiva algoritma, o que se deja al implementador que tome las
decisiones.
IMPLEMENTACIÓN UTILIZANDO C#
Las principales clases implementadas fueron Juego, Jugador, Mazo, Palo, Carta, Baza y
Tanteador. Se implementó además un jugador usuario (JugadorConsola) y un jugador computadora que
elige la primera carta (JugadorComputadoraPrimera).
Los predicados se agruparon de acuerdo a los módulos identificados en el estudio del problema:
juego, partida, turno, vuelta y estrategia.
Estos predicados modelan el transcurso del juego. Incluyen inicializar la base de conocimiento,
inicializar el mazo y lanzar el juego según los jugadores y estrategias definidos por el usuario. La base
de conocimiento está modelada por los siguientes predicados:
PREDICADOS DE LA PARTIDA
Definen también las estrategias de las computadoras. Para ello, determinan si se está jugando al
azar o inteligente (según la base de conocimiento), y para cada jugador de tipo computadora, eligen si
ir a más o menos según las cartas que tiene.
Una vuelta se considera al juego de una carta por jugador. Estos predicados modelan una vuelta
de juego, con un cierto palo como triunfo. Controlan el lanzamiento de la vuelta, y, cuando finaliza
determinan el ganador de la baza según las reglas del juego.
PREDICADOS DE UN TURNO
Un turno es el juego de una carta por uno de los jugadores. Estos predicados son esencialmente
los que eligen una carta, ya sea solicitándola a un humano o jugando según los criterios de la
computadora. Son quienes regulan que el juego se realice de acuerdo a las reglas validando que las
cartas jugadas sean las correctas.
ESTRATEGIA
La computadora tiene dos formas de jugar: al azar o inteligentemente. En el segundo caso, tiene
algunas reglas para jugar, las que se detallan a continuación.
• Determina si ir a más o menos. Para esto , suma los puntos que tiene en la mano, asignando
doble valor a los triunfos con puntos, y sumando dos puntos por cada triunfo con valor 7 o
menor. Si la suma le da mayor a 45, juega a más, sino juega a menos. Este valor se obtuvo
realizando muchas corridas y viendo que valore obtenía cada jugador. El predicado que
implementa la decisión es elegirMasmenos(+Cartas,-MasMenos).
• Si la mesa está vacía y va a más, determina (a partir de las cartas jugadas) si existe alguna carta
“inmatable”, esto es, que nadie la puede superar. Si tiene alguna la juega, sino elige una al azar.
• Si la mesa está vacía y va a menoss, determina (a partir de las cartas jugadas) si existe alguna carta
“matable”, esto es, que alguien puede superarla. De las que tiene, prefiere siempre para jugar
triunfos y cartas altas.
• Si la mesa no está vacía y va a más, mata con la más alta posible y pierde con la más baja posible.
• Si la mesa no está vacía y va a menos, mata con la más baja posible y pierde con la más alta
posible.
Es en este módulo donde se implementan los predicados que toman estas decisiones.
GENERALIDADES
Clase App
Objeto que contiene el método main, que inicia el juego.
Clase Carta
Modela a las cartas del juego. Tiene como atributos un número y una referencia a un objeto
Palo. En esta clase está el método puedeMatar que indica si un objeto Carta puede matar a otro
objeto Carta, de acuerdo a un palo de triunfo dado, según las reglas del juego.
Clase Palo
Modela a un Palo del juego. Incluye como atributos el nombre y las cartas que pertenecen al
palo. Puede observarse que existe una doble navegabilidad entre las clases Carta y Palo (lo que nos
permite fácilmente saber cuáles son las cartas de un Palo, y a qué Palo corresponde una cierta Carta).
En el constructor de la clase son creadas las cartas del Palo, agregando las instancias creadas en una
lista en el atributo cartas.
Clase Mazo.
Modela el Mazo. Sus atributos son los 4 palos de la baraja. Cuando se crea el objeto Mazo, se
crean los cuatro palos que lo componen. con lo que se logra de esta forma crear las 40 cartas del
mazo. Esta clase es la encargada de armar el mazo, barajar y repartir las cartas a los jugadores, a través
del método repartirCartas.
Clase Jugador
Modela a un jugador. Esta clase contiene los atributos genéricos de los jugadores, y tiene como
sub-clases a los jugadores humanos (JugadorConsola) o computadoras (JugadorComputadora). Es la
responsable de que el Jugador tire una carta (a través del predicado tirarCarta). Este método envía el
mensaje tirarCarta(Baza,Carta) a la biblioteca de funciones, quien obtiene las cartas del jugador, calcula
las cartas pasibles de ser jugadas por el mismo, y envía el mensaje tirarCarta(Baza,CartasPosibles,Carta)
nuevamente al jugador. Este mensaje será interpretado dinámicamente por la subclase
correspondiente, quien actuará de acuerdo a sus características para elegir la carta. Lo mismo sucede
con decidirEstrategia(+Mazo), que, dadas las cartas recibidas, determina si se va a ir a más o menos.
Clase JugadorConsola
Modela a un jugador humano. Como se mencionó, tiene como superclase a jugador. Esta clase
implementa en particular al requerimiento elegirCartaH, a través de su redefinición de
tirarCarta(+Baza, +CartasPosibles, -Carta). Esto es, es la responsable de solicitar al jugador que juegue
su carta, dejando la validación a la clase jugador de la que hereda.
Clase JugadorComputadora
Modela a un jugador computadora. Hereda de jugador, y su única función es servir como
superclase a jugadorComputadoraAzar, jugadorComputadoraInteligente, jugadorComputadoraPrimera.
Clase JugadorComputadoraPrimera
Modela a una computadora que juega tontamente. Su forma de elegir la carta es simplemente
eligiendo la primer carta de las que puede tirar.
Clase JugadorComputadoraAzar
Modela a una computadora que juega al azar. Su forma de elegir la carta es simplemente
sorteando un número y tirando la carta indizada por el número.
Clase JugadorComputadoraInteligente
Modela a una computadora que juega inteligentemente. Para detalles de la estrategia, véase el
punto Estrategia de la implementación en Prolog tradicional, ya que el funcionamiento es idéntico.
En este caso, la estrategia seleccionada y las cartas del oponente se incluyeron como atributos
propios del objeto. Sólo en esta subclase el comportamiento del método decidirEstrategia(+Mazo) no
es trivial, ya que en este caso es donde se cuentan puntos según el criterio definido. Lo mismo sucede
con la redefinición tirarCarta(+Baza,+CartasPosible,-Carta), donde se codifican las elecciones realizadas
para elegir la mejor carta.
Clase Baza
Esta clase modela las cartas jugadas en la mesa y es la responsable de mantener el estado de la
misma. Incluye, por ejemplo, métodos como darGanador(?Jugador) que devuelve el ganador de una
baza o cartaTirada(+Jugador,+Carta) que registra que un jugador jugo una carta a la mesa. Incluye
como atributos al palo del triunfo, al mano, y la lista de jugadores que jugaron y las cartas en la mesa.
Interfaz Visualizador
Implementada por el objeto JugarConsolaUI. Esta clase es la responsable de dar los mensajes a la
consola.
Clase Tanteador
Lleva el tanteador del juego. Incluye como atributos a los jugadores y los puntos y partidas
ganadas por cada uno. Es responsable de registrar cuando un jugador gana una partida, y de
determinar si hay un ganador de acuerdo a las partidas ganadas y las partidas a las que se jugaba.
Clase juego
Modela la dinámica del juego en general. Incluye como atributos un mazo, un tanteador, y un
visualizador para los resultados. Es responsable de conducir el juego (predicado JugarJuego) , de lanzar
el juego de las partidas, de las bazas y del reparto de cartas.
MODO DE USO
El usuario puede crear nuevos jugadores instanciando directamente algunas de las clases de los
jugadores. El siguiente ejemplo muestra como hacer competir a la computadora consigo misma,
donde hay un jugador de cada tipo.
CONCLUSIONES
La extensión Prolog Object de SICStus presenta las características necesarias para utilizar
conceptos del paradigma de orientación a objetos en la programación lógica. Permite orientación a
objetos “pura” y además simular la orientación a clases provistas por lenguajes de programación
como Java y C#.
En lo que respecta a las implementaciones en Prolog “puro” y Prolog Objects se destaca: mayor
facilidad de encontrar la información, mayor legibilidad en el código. La organización de la base de
conocimiento en Prolog “puro”, principalmente cuando la base es alterada dinámicamente (mediante
el uso de assert y retract), dificulta identificar el estado del sistema. Respecto a la legibilidad, el uso de
módulos puede ayudar notoriamente en la programación en Prolog “puro”. El uso de Prolog Objects
extiende el sistema de módulos con las características de OO mencionadas, lo cual facilita la lectura y
el mantenimiento del código generado.