Juego Pong: segunda parte

Recordando un poco la primera parte, el objetivo es desarrollar el juego Pong en Java que, mediante una red neuronal tipo Backpropagation, se le presente los datos de entrada (muchos de ellos puede que hayan sido los de entrenamiento) y averigüe dónde va a caer la pelota, desplazando la raqueta hasta la posición deseada.

Para ello primero la red debe pasar una etapa de entrenamiento en la que se irán a recogiendo los movimientos que hace el usuario, es decir, que la red va a intentar aprender de lo que hace el jugador en este proceso de entrenamiento. Una vez entrenado, el juego debe enfrentarse contra lo que ha aprendido.

El juego está implementado en el lenguaje de programación Java y se hace uso de un framework de software libre neuroph. El juego se divide en tres etapas:
  • Etapa de entrenamiento: El juego almacena lo que hace el usuario. En este punto, se recogen las acciones del usuario para poder pasárselas en una etapa posterior a la red neuronal.
  • Etapa de aprendizaje: Se entrena la propia red neuronal, pasándole los valores entrenados con el valor deseado. Es por tanto una red con aprendizaje supervisado. Se emplea una red de tipo BackPropagation con Momentum para agilizar el proceso de aprendizaje.
  • Etapa de juego: El juego se enfrenta con lo que ha aprendido. Se deduce que mientras más juegue el usuario en la etapa de aprendizaje, mejor entrenada estará la red y mejor será jugando en esta última etapa.

Interfaz gráfica

La interfaz gráfica consta de dos partes principales:
  • Un panel de dibujo donde estará la pelota y la raqueta de juego.
  • Una zona de control, donde estarán varios botones para el entrenamiento y el testeo del juego.
El panel de dibujo, corresponde con la clase PanelDibujo. Esta clase implementa, entre otras, a la interfaz Runnable, y por tanto, es un Thread. El motivo por el que es un Thread es para mantener en un bucle infinito la animación de la pelota y de la raqueta y al mismo tiempo realizando la captura de datos, además de poder dar diferentes velocidades durmiendo menos tiempo el dicho Thread.
La clase ControlPanel corresponde con la creación de los botones y etiquetas con información. La clase MarcoPong es la encargada de componer todos los elementos de la ventana y de registrar los eventos de los botones como oyentes de la clase PanelDibujo. Es por ello, que la clase PanelDibujo también implementa las interfaz ActionListener.
Por último, la clase Test, instancia la clase MarcoPong y registra el exit para salir.

Clase Test

1 import java.awt.event.*;
2 import javax.swing.*;
3
4 public class Test {
5
6    public static void main( String args[]) {
7      MarcoPong frame = new MarcoPong("Pong Game");
8      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
9      frame.setVisible(true);
10  }
11 }

Clase MarcoPong

1 import java.awt.*;
2 import java.awt.event.*;
3 import javax.swing.*;
4
5 public class MarcoPong extends JFrame {
6
7     public static final int WIDTH = 400;
8     public static final int HEIGHT = 300;
9
10  public MarcoPong(String t) {
11   super(t);
12         setSize(WIDTH, HEIGHT);
13         JPanel panel = new PanelContenedor();
14      Container contentPanel = this.getContentPane();
15      contentPanel.add(panel);
16   }
17 }
18
19 class PanelContenedor extends JPanel {
20     PanelDibujo pd;
21     ControlPanel cp;
22
23     public PanelContenedor() {
24         pd = new PanelDibujo();
25         cp = new ControlPanel();
26         setLayout(new BorderLayout());
27         add(BorderLayout.CENTER, pd);
28         add(BorderLayout.EAST, cp);
29         cp.bTraining.addActionListener(pd);
30         cp.bStop.addActionListener(pd);
31         cp.bPlay.addActionListener(pd);
32   Thread t = new Thread(pd);
33   t.start();
34    }
35 }

Clase ControlPanel

1 import java.awt.*;
2 import java.awt.event.*;
3 import javax.swing.*;
4
5 public class ControlPanel extends JPanel {
6
7     public JButton bTraining;
8     public JButton bPlay;
9     public JButton bStop;
10     public static JLabel labelT;
11     public static JLabel labelG;
12     public static JLabel labelF;
13
14     public ControlPanel() {
15         // setLayout (new BorderLayout());
16         setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
17         setLayout(new GridLayout(8, 1, 2, 3));
18         bTraining = new JButton("Training");
19         bPlay = new JButton("Play");
20   bStop = new JButton("Stop");
21         labelT = new JLabel("Total: 0");
22         labelG = new JLabel("Goods: 0");
23         labelF = new JLabel("Failure: 0");
24         add(bTraining);
25         add(bStop);
26         add(bPlay);
27         add(labelT);
28         add(labelG);
29         add(labelF);
30     }
31 }
32


Entrada de datos

Al pulsar el botón Training comenzará la fase de recogida de datos que serán entregados a la red neuronal. Los datos a recoger son:
  • La posición según eje x de la pelota.
  • La dirección a la que va la pelota: 0 para la izquierda y 1 para la derecha.
El único dato de salida es:
  • La posición x a la que tiene que ir la pelota.
Por tanto, a la entrada de datos, para cada par (x, dirección) se le asocia una salida que es la posición x de la raqueta.
Para representar los datos de entrada, se ha hecho la clase NetData que cuenta con los siguientes atributos:
  • int dir : dirección de la pelota.
  • double xracket: valor entre 0 y 1, pues la red neuronal trabaja con estos valores. Entonces se tiene que hacer es escalado a la entrada y salida de este dato.

¿Cómo se asocian los datos?

Se almacena el par (x, dir) cuando la pelota está en y = 10. Esto es una línea en la parte superior de la zona de juego tal y como se ve en la figura (la línea azul). A este par (x, dir) se le asocia como salida la x de la raqueta normalizada, esto es, dividiendo la x de la raqueta entre MAXX (constante en el programa que indica la dimensión de las x del panel de dibujo).

 

Entrenamiento MomentumBackpropagation  

Se ha usado una topología de red multicapa fullconection con entretamiento tipo Backpropagation y función de activación sigmoide. El número de capas son 3: la capa de entrada a a red, la capa oculta que cuenta con 4 neuronas y la capa de salida con una neurona.


Creación de la red

78 multiLaterPerceptron = new MultiLayerPerceptron
(TransferFunctionType.SIGMOID, 2, 4, 1);

Entrenamiento

357         mb = new MomentumBackpropagation(multiLaterPerceptron);
358         mb.setMomentum(0.9);
359         mb.setMaxError(0.1E-90);
360         mb.setMaxIterations(3000);
361         long t1, t2;
362         t1 = System.currentTimeMillis();
363         mb.learn(trainingSet);
364         t2 = System.currentTimeMillis();

Salida de datos

Accedemos a esta parte pulsando el botón PLAY (previamente de tiene que haber entrenado). A partir de este momento, la salida x de la raqueta se obtiene tras haber introducido como entrada a la red la x y la dirección por la que va la pelota cuando y = 10. Es decir, cuando la pelota esté en y = 10, se obtiene la dirección de la pelota y su valor en el eje x. Esto se le pasa como entrada a la red neuronal y devuelve un valor de salida. éste, es un valor normalizado que habrá que multiplicar por MAXX para obtener la posición X de la raqueta.


Referencias:
Código fuente completo:


Para compilar es necesario el compilador de java-sun. Además es necesario incluir el directorio org del framework neuroph en el directorio donde estén los fuentes de juego.

0 comentarios:

Publicar un comentario en la entrada