Logo Studenta
¡Este material tiene más páginas!

Vista previa del material en texto

Capítulo 4.
Interacción y Movimientos
Clemente Rubio Manzano (clrubio@ubiobio.cl)
Departamento de Sistemas de Información
Universidad del Bío-Bío
Versión: 18 de Abril del 2022
Qué vamos a aprender en este capítulo
Las interacciones en el mundo virtual son muy importantes ya que son las
encargadas de determinar que ocurre cuando dos tipos de celdas diferentes
colisionan. Por ejemplo, si en una celda esta la entidad y en la otra hay una
pared, la entidad nunca podrá ocupar el espacio de la pared. Sin embargo si
en el lado derecho hay una recompensa entonces la entidad la puede ocurrir
y la recompensa desaparecerá (si es así como lo hemos decidido). Por otro,
que una o varias entidades se puedan mover permitirá dar vida a las mismas.
En este capítulo vamos a explicar cómo se puede tener diferentes tipos
de entidades en el escenario y cómo podemos crear cierta interacción en-
tre ellas para dotarlas de inteligencia, aunque ésta sea muy sencilla y solo
permitamos por ahora movimientos autónomos y aleatorios.
Añadiendo entidades sin movimiento y con inter-
acción al escenario
Una forma de situar nuevas entidades en el escenario es cambiar el pro-
tocolo pintado con el que estábamos trabajando habitualmente. Hasta ahora
el pintado de celdas consistía en dibujar un rectángulo sin fondo (celdas) y
una matriz de celdas (escenario). Ahora asociaremos diferentes tipos de cel-
das con diferente tipo de pintado. Por ejemplo, podremos tener celdas pared
que se pintaran de color negro, celdas de tipo adversario que se pintaran de
color rojos, etc. En realidad el tipo de celdas y sus colores es una decisión
de diseño y dependerá del tipo de mundo virtual que se quiera implementar.
Emplearemos una nueva variable que nos diga de que tipo será cada una
de ellas. Como el pintado del escenario depende de la clase celda podemos
1
2
extender su pintarCelda para que en función del tipo que queramos visua-
lizar se opte por esa opción. Por tanto el primer paso consiste en extender
dicha clase.
import java.awt.Color;
import java.awt.Graphics;
/**
*
* @author klemenzza
*/
public class Celda implements Constantes {
public int xPixel,yPixel;
//añado un nuevo atributo tipo
public char tipo;
//constructor
public Celda(int x,int y,char t) {
this.xPixel=x;
this.yPixel=y;
this.tipo=t;
}
//funcion de pintado en funcion del tipo
public void pintarCelda(Graphics g) {
switch(tipo) {
case CAMINO:
g.drawRect(xPixel,yPixel,PIXELS,PIXELS);
break;
case OBSTACULO:
g.fillRect(xPixel,yPixel,PIXELS,PIXELS);
break;
case AMOR:
g.setColor(Color.pink);
g.fillRect(xPixel,yPixel,PIXELS,PIXELS);
g.setColor(Color.black);
break;
}
}
}
Como se puede observar ahora contamos con un nuevo atributo del tipo
char que almacena información sobre el tipo de celda que se desea pintar
al instanciar un objeto de la clase Celda. Tendremos tres tipos: CAMINO,
OBSTACULO y AMOR. El tipo CAMINO dibuja una celda de la forma habitual
(un rectángulo simple sin fondo). El tipo OBSTACULO dibujará un rectángulo
con fondo negro y su funcionalidad será impedir el paso de la entidad o de
los adversarios (ver Figura 1). Por último el tipo AMOR pintará un rectángulo
rosa y su funcionalidad será la de modificar el estado de la entidad de alguna
forma (lo veremos más adelante).
Como hemos añadido nuevos elementos en el escenario necesitamos rea-
lizar la nueva programación del comportamiento del agente donde tendremos
3
Figura 1: Nuevas entidades sin movimiento en el escenario con alguna inter-
acción respecto de la entidad
en cuenta diferentes casuísticas de interacción. Para ello vamos a extender
los movimientos de la entidad teniendo considerando que, ahora, cuando la
entidad se mueve hacia alguna celda cercana podría toparse con alguno de
los nuevos elementos presentes en el escenario: una pared, una celda de
amor o un adversario. De esta forma, el protocolo de actuación consiste en
conocer el tipo de la celda a la que nos queremos mover y en función de dicho
tipo implementaremos un tipo de interacción u otra. Veamos primero como
queda la clase Entidad.
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JComponent;
/**
*
* @author klemenzza
*/
public class Entidad extends JComponent implements Constantes{
public int xMov;
public int yMov;
public String estado;
public Entidad(int x,int y) {
xMov=x;
yMov=y;
estado="VIVO";
}
public void moverArriba() {
yMov-=1;
}
public void moverAbajo() {
4
yMov+=1;
}
public void moverIzquierda() {
xMov-=1;
}
public void moverDerecha() {
xMov+=1;
}
//algoritmo de pintado de una entidad
@Override
public void paintComponent(Graphics g) {
g.drawRect(xMov*PIXELS+DESPLAZAMIENTO,
yMov*PIXELS+DESPLAZAMIENTO,PIXELS,PIXELS);
g.setColor(Color.green);//cambiamos color
g.fillRect(xMov*PIXELS+DESPLAZAMIENTO,
yMov*PIXELS+DESPLAZAMIENTO,PIXELS,PIXELS);
g.setColor(Color.BLACK);//volvemos a cambiar
g.setFont(FUENTE);
g.drawString("E",(xMov*PIXELS+DESPLAZAMIENTO)+5,
(yMov*PIXELS+DESPLAZAMIENTO)+28);
g.setColor(Color.white);//volvemos a cambiar
g.fillRect((xMov*PIXELS+DESPLAZAMIENTO),
(yMov*PIXELS+DESPLAZAMIENTO)-15,PIXELS,PIXELS/2);
g.setColor(Color.BLACK);//volvemos a cambiar
g.setFont(FUENTE2);
g.drawString(estado,
(xMov*PIXELS+DESPLAZAMIENTO)+1,
(yMov*PIXELS+DESPLAZAMIENTO)-5);
}
}
Junto a la E aparece el estado de la entidad: VIVO, si la entidad está
viva; AMOR, si la entidad paso por una celda del tipo AMOR; PARED, si la
entidad chocó con obstáculo negro; MUERTO, si la entidad fue capturada
por le adversario. Para completar la implementación añadiremos tres nuevas
constantes y un método especial que nos permite mostrar mensajes gráficos
mediante una ventana de diálogo.
import java.awt.Font;
public interface Constantes {
// el resto permanece igual
public final Font FUENTE2=new Font("Times New
Roman",Font.BOLD,10);
5
//para la interaccion
public final char OBSTACULO=’O’;
public final char CAMINO=’C’;
public final char AMOR=’A’;
default void lanzar_mensaje(String mensaje) {
JOptionPane.showMessageDialog(null,
mensaje,
"Mundo Virtual",
JOptionPane.PLAIN_MESSAGE);
}
}
Antes de pasar al MundoVirual para implementar la interacción veamos
como queda el constructor de Escenario y la diferencia con la definición
de las Celdas. Ahora cada Celda lleva el tipo CAMINO. Todas las celdas son
camino inicialmente, será en MundoVritual donde iremos definiendo cómo
será nuestro mundo.
public Escenario() {
celdas=new Celda[NUMERO_CELDAS_ANCHO][NUMERO_CELDAS_LARGO];
//inicializar el array de celdas
for(int i=0; i < NUMERO_CELDAS_ANCHO; i++) {
for ( int j=0 ; j < NUMERO_CELDAS_LARGO ; j++) {
celdas[i][j]=new Celda(i*PIXELS+DESPLAZAMIENTO,
j*PIXELS+DESPLAZAMIENTO,CAMINO);
}
}
}
Ahora extendemos la funcionalidad de movimientos en MundoVirtual. La
clase MundoVirtual quedaría de la siguiente forma. Primero mostramos los
atributos y la definición del MundoVirtual
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
/**
*
* @author klemenzza
*/
public class MundoVirtual extends Canvas implements Constantes{
6
//para pintar el lienzo
public Escenario escenario;
public Entidad ent;
public Adversario adv1;
//public Adversario adv2;
//public Adversario adv3;
//public ArrayList<Adversario> adversarios;
public MundoVirtual(){
escenario=new Escenario();
ent=new Entidad(1,1);
//se indican donde estarán los obstaculos
//celda (2,2) es obstaculo
escenario.celdas[2][2].tipo=OBSTACULO;
//celda (2,3) es obstaculo
escenario.celdas[2][3].tipo=OBSTACULO;
//escenario.celdas[8][8].tipo=OBSTACULO;
//escenario.celdas[9][8].tipo=OBSTACULO;
//celda (10,8) es amor
escenario.celdas[10][8].tipo=AMOR;
adv1=new Adversario("#1",5,5);
//adversarios
/*adversarios=new ArrayList<>();
adv1=new Adversario("#1",5,5);
adv2=new Adversario("#2",6,6);
adv3=new Adversario("#3",7,7);
adversarios.add(adv1);
adversarios.add(adv2);
adversarios.add(adv3);*/
//color de fondo
this.setBackground(Color.orange);
this.setFocusable(true); //queremosque escuches en este
elemento
//escuchador eventos de teclado
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent evt) {
mover(evt);
repaint();
}
});
}
@Override
7
public void paint(Graphics g) {
ent.paintComponent(g);
escenario.paintComponent(g);
adv1.pintarAdversario(g);
//adv2.paintComponent(g);
//adv3.paintComponent(g);
}
public void pre_movimiento( KeyEvent evento ) {
ent.estado="VIVO";
switch( evento.getKeyCode() ) {
case KeyEvent.VK_UP:
System.out.println("Mover arriba");
if ( ent.yMov > 0 ) {
if (
escenario.celdas[ent.xMov][ent.yMov-1].tipo!=OBSTACULO
) {
ent.moverArriba();
}else ent.estado="PARED";
}
break;
case KeyEvent.VK_DOWN:
System.out.println("Mover abajo");
if ( ent.yMov < NUMERO_CELDAS_LARGO-1 ) {
if (
escenario.celdas[ent.xMov][ent.yMov+1].tipo!=OBSTACULO)
{
ent.moverAbajo();
}else ent.estado="PARED";
}
break;
case KeyEvent.VK_LEFT:
System.out.println("Mover izquierda");
if ( ent.xMov!=0) {
if (
escenario.celdas[ent.xMov-1][ent.yMov].tipo!=OBSTACULO)
{
ent.moverIzquierda();
}else ent.estado="PARED";
}
break;
8
case KeyEvent.VK_RIGHT:
System.out.println("Mover derecha");
if ( ent.xMov < NUMERO_CELDAS_ANCHO-1 ) {
if (
escenario.celdas[ent.xMov+1][ent.yMov].tipo!=OBSTACULO)
ent.moverDerecha();
else ent.estado="PARED";
}
break;
}
}
//metodo llamada la primera vez que se pinta
public void mover( KeyEvent evento ) {
//mientras no está muerto
if ( !ent.estado.equals("MUERTO")) {
//arriba, abajo, izquierda o derecha?
pre_movimiento(evento);
//consulto si estoy en celda amor, si es así cambio etado
if ( amor2() ) ent.estado="AMOR";
else {
//consulto si no fui atrapado por adversario
if ( !chocarAdversario(adv1) ) {
if ( !ent.estado.equals("PARED")) //si no choqué con
pared
ent.estado="VIVO";
}else {
//cuando me atrapa el adversario
lanzar_mensaje("El adversario te atrapó");
ent.estado="MUERTO";
}
}
}
}
public boolean amor() {
return escenario.darCelda(ent.xMov,
ent.yMov).tipo==AMOR;
}
public boolean amor2() {
return ent.xMov==10 && ent.yMov==8;
}
public boolean chocarAdversario(Adversario adv) {
9
return (ent.xMov==adv.xMov && ent.yMov==adv.yMov);
}
/*public boolean chocarAdversarios() {
boolean resultado=false;
for(int i=0; i < adversarios.size();i++) {
resultado=chocarAdversario(adversarios.get(i));
}
return resultado;
}*/
}
Una vez que disponemos de la funcionalidad para pintar nuestro mundo
virtual vamos mostrar cómo quedaría la lógica de los movimientos. El com-
portamiento de nuestro mundo se implementa en varios métodos. El método
mover es el encargado de implementar los cambios de estado en función
de lo que haya ocurrido, en nuestro caso: solo podremos mover la entidad
mientras esté viva, si es así identificamos el movimiento y comprobamos si
la entidad pasa por la celda amor, si es así cambiamos su estado, si no es
así chequeamos si tocamos al adversario. Si fuera ese caso, cambiamos el
estado a MUERTO e informaremos al usuario y además ya no nos podremos
mover. Note que también se va a implementar la funcionalidad de cambiar el
estado a PARED cuando nos topamos con una de ellas.
public void mover( KeyEvent evento ) {
if ( !ent.estado.equals("MUERTO")) {
identificar_Tecla_Pulsada(evento);
if ( amor()) ent.estado="AMOR";
else {
if ( !adversario() ) {
if ( !ent.estado.equals("PARED"))
ent.estado="VIVO";
}
else {
lanzar_mensaje("El adversario te atrapó");
ent.estado="MUERTO";
}
}
}
}
10
public boolean amor() {
return escenario.darCelda(ent.xMov,
ent.yMov).tipo==AMOR;
}
public boolean adversario() {
return ent.xMov==adv1.xMov && ent.yMov==adv1.yMov;
}
Una vez explicado el método principal que implementa la funcionalidad
en la interacción de ciertas entidades vamos a completar la explicación con
el método identificar tecla pulsada que implementa los movimientos. Nos
vamos a centrar un movimiento, el resto se implementan de forma análoga
public void identificar_Tecla_Pulsada( KeyEvent evento ) {
ent.estado="VIVO";
switch( evento.getKeyCode() ) {
case KeyEvent.VK_UP:
System.out.println("Mover arriba");
if ( ent.yMov > 0 ) {
if (
escenario.celdas[ent.xMov][ent.yMov-1].tipo!=OBSTACULO
) {
ent.moverArriba();
}else ent.estado="PARED";
}
break;
//...resto de movimientos
}
}
En este caso si xMov es mayor que cero y la celda a la que queremos ir es
diferente de pared entonces nos moveremos. En caso contrario, la entidad se
quedará en el mismo sitio. La clave está en la instrucción:
if ( escenario.celdas[ent.xMov][ent.yMov-1].tipo!=OBSTACULO )
donde se comprueba si la celda a la que me quiero mover (en este caso
arriba) es distinto del tipo OBSTACULO.
Por último, se puede observar que existe un atributo nuevo denomina-
do Adversario adv1. Hemos decidido crear una clase Adversario que será
diferente a los otros elementos ya que tendrá método de movimientos (al fi-
nal un algoritmo guiará al adversario). El código del adversario se muestra a
continuación:
11
import java.awt.Color;
import java.awt.Graphics;
/**
*
* @author klemenzza
*/
public class Adversario implements Constantes{
public int xMov,yMov;
public String nombre;
public Adversario(String n,int x,int y) {
xMov=x;
yMov=y;
nombre=n;
}
public void moverArriba() { yMov-=1; }
public void moverAbajo() { yMov+=1; }
public void moverIzquierda() { xMov-=1; }
public void moverDerecha() { xMov+=1; }
//algoritmo de pintado de un adversario
public void pintarAdversario(Graphics g) {
g.drawRect(xMov*PIXELS+DESPLAZAMIENTO,
yMov*PIXELS+DESPLAZAMIENTO,PIXELS,PIXELS);
g.setColor(Color.RED);//cambiamos color
g.fillRect(xMov*PIXELS+DESPLAZAMIENTO,
yMov*PIXELS+DESPLAZAMIENTO,PIXELS,PIXELS);
g.setColor(Color.BLACK);//volvemos a cambiar
g.setFont(FUENTE);
g.drawString("A",(xMov*PIXELS+DESPLAZAMIENTO)+5,
(yMov*PIXELS+DESPLAZAMIENTO)+28);
g.setColor(Color.white);//volvemos a cambiar
g.fillRect((xMov*PIXELS+DESPLAZAMIENTO),
(yMov*PIXELS+DESPLAZAMIENTO)-15,PIXELS,PIXELS/2);
g.setColor(Color.BLACK);//volvemos a cambiar
g.setFont(FUENTE2);
g.drawString(nombre,
(xMov*PIXELS+DESPLAZAMIENTO)+1,
(yMov*PIXELS+DESPLAZAMIENTO)-5);
}
}
Finalmente el escenario con todos los elementos: entidad, obstáculos (ne-
gros y rosas) y adversario quedaría cómo se muestra en la Figura 2. Note
además que el tamaño de las celdas es mayor en esa visualización, basta con
ir a Constantes y poner la constante PIXELS a 48.
12
Figura 2: Nuevas entidades sin movimiento en el escenario con alguna inter-
acción respecto de la entidad
Movimientos: animación e inteligencia básica
El objetivo de esta sección es explicar cómo se pueden animar las entida-
des, esto es, proporcionales movimiento de forma automática. Para nosotros
animar significa indicarle a la entidad qué pasos debe realizar, por tanto, será
nuestra primera inteligencia, una inteligencia muy básica. Esta funcionali-
dad puede implementarse en Java con Hilos de Ejecución. Informalmente,
y en este contexto, un hilo de ejecución será una tarea (bloque de código) que
se ejecuta cada cierto tiempo de forma concurrente o paralela junto a otros
hilos de ejecución (el propio método main() se ejecuta como un hilo).
En Java existen varias formas de implementar un hilo de ejecución (here-
dando de la clase Thread o implementando la clase Runnable). También se
puede utilizar la funcional de los TimerTask. Un TimerTask puede lanzarse
cada X segundos por un lanzador de tareas. En cada lanzamiento se ejecuta
el código situado en el método run(). Una primera implemtación de la clase
Inteligencia podría ser la siguiente:
iimport java.awt.Color;
import java.awt.Graphics;
import java.util.TimerTask;
public class Entidad extends TimerTask implements Constantes{
public int xMov;
public int yMov;
public String estado;
public MundoVirtual mv;
public Entidad(int x,int y, MundoVirtual mv) {
xMov=x;
yMov=y;
estado="VIVO";
this.mv=mv;
}
13
...
@Override
public void run() {
realizar_Movimiento_Aleatorio();
actualizarMundoVirtual();
}
}
Si observamos la clase tiene un atributo MundoVirtual que será la infor-mación que la entidad tiene del mundo en el que se encuentra.
El método run() es donde se implementa la inteligencia de la Entidad.
En este caso es muy sencilla: se genera un número aleatorio entre 0 y 3. Si
es 0 la entidad se mueve arriba, si es 1 la entidad se mueve hacia abajo,
si es 2 se mueve a la derecha y si es 3 se mueve a la izquierda. En todos
lo casos imprimimos un mensaje que indica que la entidad se ha movido y
en qué dirección. Note que la inteligencia sustituye a la parte del código que
teníamos implementadas para los eventos de teclado. Ya que ahora no es el
humano quien dirigirá a la entidad, será la inteligencia artificial (su método
run) de cada entidad quien lo haga.
Por último una vez se ha definido la entidad y su inteligencia (hilo de
ejecución) necesitamos un objeto especial que lance o ejecute dichas tareas.
Para ello haremos uso de la funcionalidad Timer que permite lanzar o ejecu-
tar objetos del tipo TimerTask. La clase VentanaPrincipal sufre un pequeño
cambio:
import java.util.Timer;
import javax.swing.JFrame;
public class VentanaPrincipal extends JFrame implements Constantes{
//nuestra clase se compone de un lienzo de dibujo (herada de
canvas)
public MundoVirtual mundo_virtual;
public Timer lanzadorEntidadesAutonomas;
//constructor
public VentanaPrincipal() {
mundo_virtual=new MundoVirtual();
this.getContentPane().add(mundo_virtual);
//el tamaño de la venta es la del escenario y el incremento de
los bordes
this.setSize(ANCHURA_ESCENARIO,LARGO_ESCENARIO);
lanzadorEntidadesAutonomas=new Timer();
lanzadorEntidadesAutonomas.
scheduleAtFixedRate(mundo_virtual.ent,0,1000);
}
14
}
Las parte de código destacable es la llamada al método scheduleAtFixedRate
que permite lanzar un objeto TimerTask cada cierto tiempo, en nuestro caso,
cada segundo. De esta forma al ejecutar nuestra aplicación la entidad se irá
movimiento de celda en celda de forma aleatoria. A partir de aquí comen-
zaremos a ver algoritmos de inteligencia artificial para resolver problemas,
nuestra primera parada será los basados en búsqueda en un espacio de es-
tados de los que su mayor exponente es el algoritmo A estrella o también
conocido por A*.