Diseño de las Tres En Raya

El juego de las Tres en Raya consta de tres proyectos:

  • JuegosClienteTresEnRaya
  • JuegosServidorTresEnRaya
  • JuegosComunTresEnRaya

Dentro de cada proyecto, las clases se encuentran dentro del paquete IStation.Juegos.TresEnRaya.Tipo, donde Tipo será Cliente, Servidor o Compartido respectivamente para cada proyecto.

Parte Común (JuegosComunTresEnRaya)

En este proyecto se encuentran las clases utilizadas tanto por la parte del cliente del juego como por la parte del servidor. Son dos:

  • DatosPartidaTresEnRaya: hereda de la clase DatosPartida; contiene todos los datos concernientes a la partida, como máximo y mínimo número de jugadores, si se puede entrar a mitad de una partida, si es observable, el número de segundos por turno, etc. De todos ellos, el creador de la partida tendrá que elegir un valor para el último. Al crear una partida se le mostrará un diálogo para poder configurarlo. Esta clase será la encargada de recoger esos datos para que el servidor tenga constancia de ellos. En cuanto a los métodos que posee, además de una constructora por defecto y otra que recibe como parámetro el número de segundos por turno, están el método getNombreJuego(), redefinido de la superclase DatosPartida, que devuelve un String con el nombre del juego y una accesora para el número de segundos por turno dameSegundosTurno(), que será utilizada en la parte del servidor.

public String getNombreJuego()
public int dameSegundosTurno()

  • MensajeTresEnRaya: esta clase se utiliza para la comunicación mediante mensajes entre el servidor y el cliente. Cada vez que uno de ellos quiera mandarle cierta información al otro, incluirá en el mensaje un objeto MensajeTresEnRaya con dicha información. Esta incluirá las posiciones X e Y de una ficha dentro del tablero, el resultado de la elección de una casilla al querer mover una ficha, el número (1 ó 2) del cliente del jugador que manda el mensaje y, por último, el número de ficha (numeradas de 0 a 2) sobre la que se quiere realizar la una acción. La clase consta de tres constructoras: la por defecto y dos que incluyen como parámetros las posiciones X e Y y el resultado de la acción o el número del jugador respectivamente. Todos los atributos disponen de accesoras y tan sólo el atributo del número de la ficha posee además de una mutadora.

public void ponNumFicha(int numFicha)
public int dameNumFicha()
public int damePosX()
public int damePosY()
public boolean dameResultado()
public int dameNumJugador()

Este es el diagrama UML del proyecto.

Parte Servidor (JuegosServidorTresEnRaya)

Este proyecto engloba las clases que representan la parte del servidor del juego. Contiene en concreto dos clases:

  • InicioTresEnRaya: implementa la interfaz IInicioPartida y se utiliza para crear instancias de la partida. Contiene el método iniciarPartida, el cual se encarga de crear una nueva partida de las Tres en Raya. Dicha partida necesita los datos con los que se ha creado, el módulo de juegos para poder realizar el paso de mensajes y el identificador de la partida.

public Partida iniciarPartida(DatosPartida datos, IModuloJuegos mJuegos, int idPartida)

  • PartidaTresEnRaya: extiende la clase Partida, definiendo los métodos abstractos de ésta o redefiniendo los que sean necesarios. Consta de dos atributos: un array de enteros dos dimensiones que representa al tablero de juego y un entero para controlar la generación de la cuenta atrás por turno. La matriz que representa al tablero albergará el valor 0 para indicar que no hay puesta ninguna ficha en una posición, o un entero de dos cifras. La primera cifra corresponderá al número de jugador al que pertenece la ficha y la segunda al número de ficha de entre las 3 que puede tener (númeradas de 0 a 2). Por ejemplo, el valor 12 en una casilla indica que en esa posición el jugador 1 tiene puesta la última de sus fichas. La constructora de la partida es llamada por la clase InicioTresEnRaya y recibe como parámetros el módulo de juegos, el identificador de partida y los datos de configuración. Todos ellos se pasarán a la superclase. En cuanto a los métodos abstractos que hereda, se encuentran procesarDatos, al cual se llama cada vez que el cliente manda un mensaje al servidor (después se explicarán estos mensajes), accionesNuevoTurno, que reúne todas las acciones que se deban ejecutar en el servidor cada vez que hay un cambio de turno (en este caso, comprobar si hay algún ganador), accionesInicioPartida, que como su nombre indica son todas las acciones que se realizarán al iniciar la partida, las cuales serán inicializar el tablero en la posición central con la primera ficha (círculo) y por último accionesFinPartida, que difundirá un mensaje a todos los jugadores comunicando el fin. Los métodos jugadorAnadido y jugadorAbandona se incluyen entre los métodos pero no se implementan. También se implementan ciertas funciones de carácter privado que se utilizan de manera auxiliar. Estas son huecoVacio, que dadas dos coordenadas enteras, comprueba si en dichas coordenadas de la matriz del tablero hay un hueco (es decir, hay un 0) para mover una ficha; fichaMia se asegura de que en una coordenada expresada mediante dos enteros haya una ficha perteneciente al jugador pasado por parámetro; comprobarFila y comprobarColumna son utilizados por comprobarGanador para intentar averiguar si hay una línea de fichas perteneciente al mismo jugador. comprobarGanador se asegura también de que en las dos diagonales haya ganador.

public void procesarDatos(Jugador jugador, String evento, Object datos)
private boolean huecoVacio(int x, int y)
private boolean fichaMia(int x, int y, int numJugador)
private boolean comprobarFila(int numFila)
private boolean comprobarColumna(int numColumna)
private boolean comprobarGanador()
protected void jugadorAnadido(Jugador arg0)
protected void jugadorAbandona(Jugador arg0)
protected void accionesNuevoTurno(Jugador jugadorAnt, Jugador jugadorNue)
protected void accionesInicioPartida()
protected void accionesFinPartida()

Este es el diagrama UML para el proyecto.

Parte Cliente (JuegosClienteTresEnRaya)

El proyecto del cliente es el más complicado, ya que implica la parte gráfica del juego además de la comunicación con el servidor de las acciones que va realizando el usuario en la partida. Las clases que contiene son:

  • ConfiguracionTresEnRaya: extiende de la clase DialogoConfiguracion. Es el diálogo mediante el cual el usuario que crea la partida podrá cambiar los parámetros configurables del juego. En el caso de las Tres en Raya, el único parámetro que se puede modificar es el del tiempo por turno, disponiendo desde un mínimo de 10 segundos hasta un máximo de 60. Para poder seleccionar la cantidad de segundos se hará uso de un JSlider. Esta clase contiene como atributo un objeto de la clase DatosPartidaTresEnRaya, descrito anteriormente en la parte común, en el cual se introducirá el valor leído del JSlider. Asimismo, tiene una accesora para dicho atributo dameConfiguración(), el cual será llamado por la clase JuegoTresEnRaya.

protected DatosPartida dameConfiguracion()

  • Inicio: es una clase que hereda de AppletBase. No contiene ningún método ni constructora.
  • JuegoTresEnRaya: hereda de la clase JuegoAbstracto. Controla la parte de cliente de la partida de tres en raya. Su única constructora recibe un módulo de comunicaciones para el paso de mensajes con el servidor y dos String que se corresponden con el nombre del jugador actual y la ruta base en el servidor para el juego.Los atributos que posee esta clase son el nombre corto del juego, un entero utilizado para la precarga de las imágenes, el panel de juego, el diálogo de configuración, un entero que indica la ficha que se modificará en un próximo movimiento, un String con la ficha seleccionada por el usuario, un entero que representa el tiempo restante del turno del jugador y un objeto de la clase Timer para poder implementar el temporizador para el turno. Los métodos que hereda de JuegoAbstracto y que debe implementar son dameNombreJuegoCorto (devuelve el nombre corto del juego, en este caso "tresenraya"), ponFichaSeleccionada (mutadora para el atributo _fichaSeleccionada), damePanelJuego (devuelve el panel del juego; en el caso de que no exista, lo crea y lo asigna al atributo _panel), damePanelConfiguracion (devuelve el panel de configuración; al igual que con el panel del juego, si no existe lo crea y se lo asigna al atributo _config), partidaTerminada (son las acciones que se realizarán en el cliente cuando la partida finalice), procesaMensaje (se llamará a este método cada vez que el cliente reciba un mensaje del servidor), procesaEvento (este método será invocado cada vez que se produzca un evento en el cliente; en este caso se tratarán sólo los eventos MouseClick referidos al botón para realizar acciones), jugadorEntra (acciones a realizar en el cliente cuando entra un nuevo jugador; se llamará al método nuevoJugador del panel), jugadorSale (acciones a realizar cuando abandona la partida un jugador; se llamará al método saleJugador del panel de juego), pasaTurno (acciones que se producirán cada vez que haya un cambio de turno; se llama al método cambiaturno del panel de juego para que lo refleje en la interfaz) y partidaIniciada (se llama al método inicia del panel para que muestre en la parte gráfica que la partida comienza). Además, esta clase dispone de dos métodos privados para realizar tareas auxiliares. Estos métodos son huecoCorrecto, que comprueba que el hueco elegido sea alguno de los paneles del juego, y correspondencia, que dado un hueco correcto, se encarga de construir un objeto de tipo MensajeTresEnRaya (ver parte común anteriormente descrita) para mandarlo al servidor. Por último, esta clase dispone de una clase privada en su declaración llamada Temporizador, la cual extiende de TimerTask del paquete java.util. Este temporizador ejecuta su método run cada cierto tiempo medido en milisegundos y programable mediante el método scheduleAtFixedRate. En este caso, el método run se encargará de disminuir el atributo _tiempoRestante de la clase JuegoTresEnRaya y lo hará cada segundo.

public void ponFichaSeleccionada(String fichaSeleccionada)
public PanelBase damePanelJuego()
public DialogoConfiguracion damePanelConfiguracion(Frame arg0)
public void partidaTerminada()
public void procesaMensaje(String comando, Object dato)
public void procesaEvento(Object origen, String evento, AWTEvent datosEvento)
private MensajeTresEnRaya correspondencia(String hueco, boolean esJugador1)
private boolean huecoCorrecto(String huecoElegido)
public void jugadorEntra(String nombre)
public void jugadorSale(String nombre)
public void pasaTurno()
public void partidaIniciada()

  • PanelBaseTresEnRaya: hereda de la clase PanelBase. Se trata de la interfaz gráfica. El juego de las Tres en Raya se representa en su parte gráfica mediante 9 paneles (uno por cada posición posible para colocar una ficha). Los paneles se nombran de la siguiente manera: si los paneles se encuentran en la primera fila se les nombrará mediante el prefijo Sup; si se encuentran en la última, mediante el prefijo Inf; a los paneles de la primera columna se les nombrará además con el sufijo Izq, a los de la segunda columna con el sufijo Med y a los de la tercera con el sufijo Izq. A los paneles de la fila del centro se les nombrará Izq, Medio y Der respectivamente. En la parte derecha de la interfaz habrá otro panel que contendrá información sobre la partida, como nombre de los dos jugadores, quién posee el turno en cada momento, las acciones que debe realizar el jugador, etc. Para que los paneles se "iluminen" y "apaguen" y para que queden "iluminados" cuando se hace clic sobre ellos, se utilizan los eventos MouseEntered, MouseExited y MouseClicked para cada panel excepto para el del centro, que según las reglas, queda fijo para toda la partida (ver reglas de las Tres en Raya). En cuanto a los atributos de la clase, contiene un ArrayList que representa la lista de los jugadores de la partida en cada momento, un objeto JuegoAbstracto, un array de PanelSprite para las imágenes de los círculos, otro para las imágenes de las cruces, dos enteros para contabilizar el número de cruces y de círculos puestos sobre el tablero, un booleano para controlar el fin de la partida, otro booleano para controlar la selección de paneles y dos paneles que representan los dos últimos paneles seleccionados y que se utilizan por motivos gráficos únicamente. Los métodos públicos que se implementan son cambiaTurno (realiza todos los cambios necesarios sobre los componenes para reflejar un cambio de turno), ponerEventoBoton (cambia el nombre del botón de acciones, así como el nombre del evento asociado a él), deseleccionarPanel (deselecciona los dos últimos paneles seleccionados), deseleccionarTodosLosPaneles (deselecciona todos los paneles de juego), ponFicha (dibuja una ficha en el tablero a partir de los datos que recibe en un objeto de la clase MensajeTresEnRaya), ganar (realiza las acciones oportunas sobre el panel base cuando un jugador, pasado por parámetro, gana la partida), nuevoJugador (acciones a realizar en el panel cuando entra un nuevo jugador), saleJugador (acciones a realizar en el panel cuando sale un jugador de la partida), inicia (acciones a realizar en el panel cuando se inicia la partida), mostrarTiempo (muestra el número de segundos que faltan para finalizar el turno). En cuanto a los métodos privados, excepto los eventos antes mencionados asociados a cada panel, todos sirven para encapsular operaciones repetitivas o para hacer más legible el código, por lo que no se mencionará aquí su uso. También se dispone de accesoras y mutadoras para los atributos.

public int dameNumCruces()
public int dameNumCirculos()
public String dameInfo()
public void ponInfo(String info)
public void ponHayFichaSeleccionada(boolean hayFichaSeleccionada)
public void ponFicha(String jugadorFicha, MensajeTresEnRaya mensaje)
private JPanel correspondencia(int x, int y)
private boolean comprobarTurno()
public void ganar(int numJugadorGanador)
public void nuevoJugador(String nombre)
public void saleJugador(String nombre)
public void inicia()
private void indicarTurno(String turnoDe)
public void mostrarTiempo(int segundos)
public void cambiaTurno(String nombreJugador, String miNombre)
public void ponerEventoBoton(String nombreBoton, String nombreEvento)
private void deseleccionarPanel(JPanel panel)
public void deseleccionarPanel()
public void deseleccionarTodosLosPaneles()
private boolean igualAlUltimo(JPanel panel)
Tan sólo se mencionarán los métodos de eventos para uno de los paneles, el resto son iguales
private void _panelDerMouseClicked(java.awt.event.MouseEvent evt)
private void _panelDerMouseEntered(java.awt.event.MouseEvent evt)
private void _panelDerMouseExited(java.awt.event.MouseEvent evt)

Este es el diagrama UML para el proyecto del cliente.

Paso de mensajes entre cliente y servidor

Mensajes del Cliente al Servidor

Nombre del mensaje Cuándo se envía Datos enviados
elección_hueco Una vez que el usuario ha elegido un hueco correcto al que mover una ficha Coordenadas 'x' e 'y' del panel donde se quiere hacer el movimiento, número del jugador (1 ó 2) que realiza el movimiento y número de la ficha (de 0 a 2) que se va a mover. Toda esta información irá encapsulada en un objeto de la clase MensajeTresEnRaya
elección_ficha Cuando el usuario tiene todas sus fichas sobre el tablero deberá elegir una de las que tiene y confirmar su elección, momento en el cual se envía el mensaje Coordenadas 'x' e 'y' del panel donde se quiere hacer el movimiento, número del jugador (1 ó 2) que realiza el movimiento y número de la ficha (de 0 a 2) que se va a mover. Toda esta información irá encapsulada en un objeto de la clase MensajeTresEnRaya
pasar Cada vez que el usuario mueva una ficha de las ya puestas sobre el tablero y se reciba la confirmación del servidor null

Mensajes del Servidor al Cliente

Nombre del mensaje Cuándo se envía Datos enviados
tiempo Cada vez que el servidor tenga que avisar al cliente de la inicialización de la cuent atrás para el turno Número de segundos del turno, extraídos de los datos de la partida
resultado_eleccion_hueco Cuando el servidor reciba por parte del cliente el mensaje eleccion_hueco se comprobará si efectivamente es un hueco válido y se le comunicará de esta manera Coordenadas 'x' e 'y' del panel donde se quiere hacer el movimiento, booleano confirmando o negando la existencia de un hueco en dichas coordenadas y número de la ficha (de 0 a 2) que se va a mover al hueco. Todo ello encapsulado en un objeto de la clase MensajeTresEnRaya
resultado_eleccion_ficha Cuando el servidor reciba del cliente el mensaje eleccion_ficha comprobará que la casilla seleccionada por el cliente contiene una ficha suya. A continuación se le manda el mensaje notificándole la comprobación Coordenadas 'x' e 'y' del panel donde está la ficha seleccionada, booleano indicando si la ficha seleccionada pertenece al cliente y número de la ficha (de 0 a 2) que se desea mover. Todo se encapsula en un objeto de la clase MensajeTresEnRaya
ganar Cuando algún jugador gana la partida Número del jugador (1 ó 2) que gana la partida

Por último hay que mencionar que el servidor se manda a sí mismo un mensaje temporizador mediante el método generarEco, el cual permite recibir cada cierto tiempo medido en milisegundos (pasado por parámetro al método) el mensaje mencionado, en el cual se pueden realizar acciones. En el caso de las Tres en Raya se utiliza para saber cuándo debe avisar el servidor al cliente de la inicialización de la cuenta atrás cada vez que se produzca un cambio de turno.