[código] Implementación juego TicTacToe en red
Continuando con el tema de las redes en Java, veremos una vez más un ejemplo modificado del libro Cómo programar en Java de Deitel, en donde se implementa el juego del TicTacToe (gato, triqui, o como le digan en tu pais) usando conexiones de red. En el ejemplo hay una clase servidor que gestiona los jugadores, y los clientes son applets desde donde el jugador interactúa con el programa.
El resultado...

El código...
El servidor encargado de comunicar a los dos clientes:
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import javax.swing.*;
public class ServidorTresEnRaya extends JFrame {
private char[] tablero;
private JTextArea areaSalida;
private Jugador[] jugadores;
private ServerSocket servidor;
private int jugadorActual;
private final int JUGADOR_X = 0, JUGADOR_O = 1;
private final char MARCA_X = 'X', MARCA_O = 'O';
private char ganador = ' ';
// establecer servidor de tres en raya y GUI para mostrar mensajes
public ServidorTresEnRaya()
{
super( "Servidor de Tres en raya" );
tablero = new char[ 9 ];
jugadores = new Jugador[ 2 ];
jugadorActual = JUGADOR_X;
// establecer objeto ServerSocket
try {
servidor = new ServerSocket( 12345, 2 );
}
// procesar los problemas que pueden ocurrir al crear el objeto ServerSocket
catch( IOException excepcionES ) {
excepcionES.printStackTrace();
System.exit( 1 );
}
// establecer objeto JTextArea para mostrar mensajes durante la ejecución
areaSalida = new JTextArea();
getContentPane().add( areaSalida, BorderLayout.CENTER );
areaSalida.setText( "Servidor esperando conexiones\n" );
setSize( 300, 300 );
setVisible( true );
} // fin del constructor de ServidorTresEnRaya
// esperar dos conexiones para poder jugar
public void ejecutar()
{
// esperar a que se conecte cada cliente
for ( int i = 0; i < jugadores.length; i++ ) {
// esperar conexión, crear Jugador, iniciar subproceso
try {
jugadores[ i ] = new Jugador( servidor.accept(), i );
jugadores[ i ].start();
}
// procesar los problemas que pueden ocurrir al recibir la conexión del cliente
catch( IOException excepcionES ) {
excepcionES.printStackTrace();
System.exit( 1 );
}
}
// El Jugador X se suspende hasta que se conecte el Jugador O.
// Reactivar ahora al jugador X.
synchronized ( jugadores[ JUGADOR_X ] ) {
jugadores[ JUGADOR_X ].establecerSuspendido( false );
jugadores[ JUGADOR_X ].notify();
}
} // fin del método ejecutar
// método utilitario que es llamado desde otros subprocesos para manipular a
// areaSalida en el subproceso despachador de eventos
private void mostrarMensaje( final String mensajeAMostrar )
{
// mostrar mensaje del subproceso de ejecución despachador de eventos
SwingUtilities.invokeLater(
new Runnable() { // clase interna para asegurar que la GUI se actualice apropiadamente
public void run() // actualiza a areaSalida
{
areaSalida.append( mensajeAMostrar );
areaSalida.setCaretPosition(
areaSalida.getText().length() );
}
} // fin de la clase interna
); // fin de la llamada a SwingUtilities.invokeLater
}
// Determinar si un movimiento es válido. Este método es sincronizado porque
// sólo puede realizarse un movimiento a la vez.
public synchronized boolean validarYMover( int posicion, int jugador )
{
boolean movimientoRealizado = false;
// mientras no sea el jugador actual, debe esperar su turno
while ( jugador != jugadorActual ) {
// esperar su turno
try {
wait();
}
// atrapar interrupciones de wait
catch( InterruptedException excepcionInterrupcion ) {
excepcionInterrupcion.printStackTrace();
}
}
// si la posición no está ocupada, realizar movimiento
if ( !estaOcupada( posicion ) ) {
// establecer movimiento en arreglo del tablero
tablero[ posicion ] = jugadorActual == JUGADOR_X ? MARCA_X : MARCA_O;
// cambiar jugador actual
jugadorActual = ( jugadorActual + 1 ) % 2;
// hacer saber al nuevo jugador actual que ocurrió un movimiento
jugadores[ jugadorActual ].elOtroJugadorMovio( posicion );
notify(); // indicar al jugador en espera que continúe
// indicar al jugador que hizo el movimiento, que éste fue válido
return true;
}
// indicar al jugador que hizo el movimiento, que éste no fue válido
else
return false;
} // fin del método validarYMover
// determinar si la posición está ocupada
public boolean estaOcupada( int posicion )
{
if ( tablero[ posicion ] == MARCA_X || tablero [ posicion ] == MARCA_O )
return true;
else
return false;
}
// colocar código en este método para determinar si terminó el juego
public boolean terminoElJuego(DataOutputStream salida)
{
int lineas[][] = {{0,1,2}, {3,4,5}, {6,7,8}, {0,3,6}, {1,4,7}, {2,5,8}, {0,4,8}, {2,4,6}};
for(int i = 0; i < 8; i++){
if(tablero[lineas[i][0]] == tablero[lineas[i][1]]
&& tablero[lineas[i][0]] == tablero[lineas[i][2]]
&& (tablero[lineas[i][0]] == MARCA_X ||
tablero[lineas[i][0]] == MARCA_O) )
{
try{
salida.writeUTF("Gana el jugador "+tablero[lineas[i][0]]);
}
catch(IOException ioe){}
ganador = tablero[lineas[i][0]];
return true;
}
}
return false; // este se deja como un ejercicio
}
public static void main( String args[] )
{
ServidorTresEnRaya aplicacion = new ServidorTresEnRaya();
aplicacion.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
aplicacion.ejecutar();
}
// la clase interna privada Jugador administra a cada Jugador como un subproceso
private class Jugador extends Thread {
private Socket conexion;
private DataInputStream entrada;
private DataOutputStream salida;
private int numeroJugador;
private char marca;
protected boolean suspendido = true;
// establecer subproceso para Jugador
public Jugador( Socket socket, int numero )
{
numeroJugador = numero;
// especificar la marca del jugador
marca = ( numeroJugador == JUGADOR_X ? MARCA_X : MARCA_O );
conexion = socket;
// obtener flujos del objeto Socket
try {
entrada = new DataInputStream( conexion.getInputStream() );
salida = new DataOutputStream( conexion.getOutputStream() );
}
// procesar los problemas que pueden ocurrir al obtener los flujos
catch( IOException excepcionES ) {
excepcionES.printStackTrace();
System.exit( 1 );
}
} // fin del constructor de Jugador
// enviar mensaje indicando que el otro jugador hizo un movimiento
public void elOtroJugadorMovio( int posicion )
{
// enviar mensaje indicando el movimiento
try {
salida.writeUTF( "El oponente hizo un movimiento" );
salida.writeInt( posicion );
}
// procesar los problemas que pueden ocurrir al enviar un mensaje
catch ( IOException excepcionES ) {
excepcionES.printStackTrace();
}
}
// controlar la ejecución del subproceso
public void run()
{
// enviar mensaje al cliente indicando su marca (X o O),
// procesar mensajes del cliente
try {
mostrarMensaje( "Jugador " + ( numeroJugador ==
JUGADOR_X ? MARCA_X : MARCA_O ) + " conectado\n" );
salida.writeChar( marca ); // enviar marca del jugador
// enviar mensaje indicando que hay conexión
salida.writeUTF( "Jugador " + ( numeroJugador == JUGADOR_X ?
"X conectado\n" : "O conectado, por favor espere\n" ) );
// si es jugador X, esperar a que llegue el otro jugador
if ( marca == MARCA_X ) {
salida.writeUTF( "Esperando al otro jugador" );
// esperando al jugador O
try {
synchronized( this ) {
while ( suspendido )
wait();
}
}
// procesar interrupciones mientras está en espera
catch ( InterruptedException excepcion ) {
excepcion.printStackTrace();
}
// enviar mensaje indicando que el otro jugador se conectó y
// que el jugador X puede realizar un movimiento
salida.writeUTF( "El otro jugador se conectó. Le toca a usted mover." );
}
// mientras el juego no esté terminado
while ( ! terminoElJuego(salida) ) {
// obtener del cliente la posición del movimiento
int posicion = entrada.readInt();
// comprobar que sea un movimiento válido
if ( validarYMover( posicion, numeroJugador ) ) {
mostrarMensaje( "\nposición: " + posicion );
salida.writeUTF( "Movimiento válido." );
}
else
salida.writeUTF( "Movimiento inválido, intente otra vez" );
}
salida.writeUTF( "Gano el jugador " + ganador);
conexion.close(); // cerrar conexión con el cliente
} // fin del bloque try
// procesar los problemas que pueden ocurrir al comunicarse con el cliente
catch( IOException excepcionES ) {
excepcionES.printStackTrace();
System.exit( 1 );
}
} // fin del método run
// establecer si el subproceso está suspendido o no
public void establecerSuspendido( boolean estado )
{
suspendido = estado;
}
} // fin de la clase Jugador
} // fin de la clase ServidorTresEnRaya
El cliente, que conecta con el servidor y proporciona al usuario una GUI con la que jugar:
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import javax.swing.*;
public class ClienteTresEnRaya extends JApplet implements Runnable {
private JTextField campoID;
private JTextArea areaPantalla;
private JPanel panelTablero, panel2;
private Cuadro tablero[][], cuadroActual;
private Socket conexion;
private DataInputStream entrada;
private DataOutputStream salida;
private char miMarca;
private boolean miTurno;
private final char MARCA_X = 'X', MARCA_O = 'O';
// establecer interfaz de usuario y tablero
public void init()
{
Container contenedor = getContentPane();
// establecer objeto JTextArea para mostrar mensajes al usuario
areaPantalla = new JTextArea( 4, 30 );
areaPantalla.setEditable( false );
contenedor.add( new JScrollPane( areaPantalla ), BorderLayout.SOUTH );
// establecer panel para los cuadros en el tablero
panelTablero = new JPanel();
panelTablero.setLayout( new GridLayout( 3, 3, 0, 0 ) );
// crear tablero
tablero = new Cuadro[ 3 ][ 3 ];
// Al crear un objeto Cuadro, el argumento posicion para el constructor
// es un valor entre 0 y 8, indicando la posición del objeto Cuadro en
// el tablero. Los valores 0, y 2 son la primera fila, los valores 3, 4
// y 5 son la segunda fila. Los valores 6, 7 y 8 son la tercera fila.
for ( int fila = 0; fila < tablero.length; fila++ ) {
for ( int columna = 0; columna < tablero[ fila ].length; columna++ ) {
// crear objeto Cuadro
tablero[ fila ][ columna ] = new Cuadro( ' ', fila * 3 + columna );
panelTablero.add( tablero[ fila ][ columna ] );
}
}
// campo de texto para mostrar la marca del jugador
campoID = new JTextField();
campoID.setEditable( false );
contenedor.add( campoID, BorderLayout.NORTH );
// establecer panel para contener a panelTablero (para su distribución en la pantalla)
panel2 = new JPanel();
panel2.add( panelTablero, BorderLayout.CENTER );
contenedor.add( panel2, BorderLayout.CENTER );
} // fin del método init
// Realizar conexión con el servidor y obtener flujos asociados.
// Iniciar subproceso separado para permitir a este subprograma
// actualizar continuamente su salida en el área de texto de la pantalla.
public void start()
{
// conectarse con el servidor, obtener los flujos e iniciar subprocesoSalida
try {
// realizar la conexión
conexion = new Socket( getCodeBase().getHost(), 12345 );
// obtener los flujos
entrada = new DataInputStream( conexion.getInputStream() );
salida = new DataOutputStream( conexion.getOutputStream() );
}
// atrapar los problemas que pueden ocurrir al establecer la conexión y los flujos
catch ( IOException excepcionES ) {
excepcionES.printStackTrace();
}
// crear e iniciar subproceso de salida
Thread subprocesoSalida = new Thread( this );
subprocesoSalida.start();
} // fin del método start
// controlar subproceso que permite actualización continua de areaPantalla
public void run()
{
// obtener la marca del jugador (O o X)
try {
miMarca = entrada.readChar();
// mostrar ID del jugador en subproceso despachador de eventos
SwingUtilities.invokeLater(
new Runnable() {
public void run()
{
campoID.setText( "Usted es el jugador \"" + miMarca + "\"" );
}
}
);
miTurno = ( miMarca == MARCA_X ? true : false );
// recibir mensajes enviados al cliente y mostrarlos en pantalla
while ( true ) {
procesarMensaje( entrada.readUTF() );
}
} // fin del bloque try
// procesar los problemas que pueden ocurrir al comunicarse con el servidor
catch ( IOException excepcionES ) {
excepcionES.printStackTrace();
}
} // fin del método run
// procesar los mensajes recibidos por el cliente
private void procesarMensaje( String mensaje )
{
// ocurrió un movimiento válido
if ( mensaje.equals( "Movimiento válido." ) ) {
mostrarMensaje( "Movimiento válido, por favor espere.\n" );
establecerMarca( cuadroActual, miMarca );
}
// ocurrió un movimiento inválido
else if ( mensaje.equals( "Movimiento inválido, intente otra vez" ) ) {
mostrarMensaje( mensaje + "\n" );
miTurno = true;
}
// el oponente realizó un movimiento
else if ( mensaje.equals( "El oponente hizo un movimiento" ) ) {
// obtener posición del movimiento y actualizar el tablero
try {
int posicion = entrada.readInt();
int fila = posicion / 3;
int columna = posicion % 3;
establecerMarca( tablero[ fila ][ columna ],
( miMarca == MARCA_X ? MARCA_O : MARCA_X ) );
mostrarMensaje( "El oponente hizo un movimiento. Su turno.\n" );
miTurno = true;
} // fin del bloque try
// procesar los problemas que pueden ocurrir al comunicarse con el servidor
catch ( IOException excepcionES ) {
excepcionES.printStackTrace();
}
} // fin de la instrucción else if
// mostrar simplemente el mensaje
else
mostrarMensaje( mensaje + "\n" );
} // fin del método procesarMensaje
private void bloquearJuego(){
}
// método utilitario que es llamado desde otros subprocesos para manipular a
// areaSalida en el subproceso despachador de eventos
private void mostrarMensaje( final String mensajeAMostrar )
{
// mostrar mensaje del subproceso de ejecución despachador de eventos
SwingUtilities.invokeLater(
new Runnable() { // clase interna para asegurar que la GUI se actualice apropiadamente
public void run() // actualiza areaPantalla
{
areaPantalla.append( mensajeAMostrar );
areaPantalla.setCaretPosition(
areaPantalla.getText().length() );
}
} // fin de la clase interna
); // fin de la llamada a SwingUtilities.invokeLater
}
// método utilitario para establecer la marca en el tablero, en el subproceso despachador de eventos
private void establecerMarca( final Cuadro cuadroAMarcar, final char marca )
{
SwingUtilities.invokeLater(
new Runnable() {
public void run()
{
cuadroAMarcar.establecerMarca( marca );
}
}
);
}
// enviar mensaje al servidor indicando el cuadro en el que se hizo clic
public void enviarCuadroClic( int posicion )
{
if ( miTurno ) {
// enviar posicion al servidor
try {
salida.writeInt( posicion );
miTurno = false;
}
// procesar los problemas que pueden ocurrir al comunicarse con el servidor
catch ( IOException excepcionES ) {
excepcionES.printStackTrace();
}
}
}
// establecer el cuadro actual
public void establecerCuadroActual( Cuadro cuadro )
{
cuadroActual = cuadro;
}
// clase interna privada para los cuadros en el tablero
private class Cuadro extends JPanel {
private char marca;
private int posicion;
public Cuadro( char marcaCuadro, int posicionCuadro )
{
marca = marcaCuadro;
posicion = posicionCuadro;
addMouseListener(
new MouseAdapter() {
public void mouseReleased( MouseEvent e )
{
establecerCuadroActual( Cuadro.this );
enviarCuadroClic( obtenerPosicionCuadro() );
}
}
);
} // fin del constructor de Cuadro
// devolver tamaño preferido del Cuadro
public Dimension getPreferredSize()
{
return new Dimension( 30, 30 );
}
// devolver tamaño mÃnimo del Cuadro
public Dimension getMinimumSize()
{
return getPreferredSize();
}
// establecer marca para Cuadro
public void establecerMarca( char nuevaMarca )
{
marca = nuevaMarca;
repaint();
}
// devolver posición del Cuadro
public int obtenerPosicionCuadro()
{
return posicion;
}
// dibujar el Cuadro
public void paintComponent( Graphics g )
{
super.paintComponent( g );
g.drawRect( 0, 0, 29, 29 );
g.drawString( String.valueOf( marca ), 11, 20 );
}
} // fin de la clase interna Cuadro
} // fin de la clase ClienteTresEnRaya








shakaran dice:
Abril 19th, 2008 a las 5:29 pm
Muy interesante la clase. Aunque serÃa aun mejor y estarÃa mas completa si se le añade un modo computador, que tuviera inteligencia para jugar ordenador y jugador.
Casidiablo dice:
Abril 19th, 2008 a las 5:49 pm
Ya hay uno con lógica en los demos que vienen en el SDK
shakaran dice:
Abril 19th, 2008 a las 5:54 pm
SerÃas tan amable de ponerme la url?
Gracias.
Casidiablo dice:
Abril 19th, 2008 a las 6:10 pm
URL? Cuando instalas el SDK de Java, buscas una carpeta que dice “demos”, en donde hay ejemplos de programas, incluyendo el TicTacToe con IA y sonido.
Un saludo!
shakaran dice:
Abril 19th, 2008 a las 6:20 pm
ah ok! Creia que estaba colgado en la documentación via web o algo por el estilo
Ya lo encontre. Muchas gracias.
joe dice:
Junio 19th, 2008 a las 2:29 pm
oigan no encuentro nada =(
Casidiablo dice:
Junio 19th, 2008 a las 2:58 pm
No sé que estaba pasando, pero parece que ya está solucionado
Pau dice:
Octubre 18th, 2008 a las 4:02 pm
Hola los codigos estan muy bien, solo tengo un problemita con el de cliente que me marca una excepcion en el main, no se si alguien me pudiera, ayudar se lo agradeceria mucho.