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…

tic tac toe - gato -triqui

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]]
            &amp;&amp; tablero[lineas[i][0]] == tablero[lineas[i][2]]
            &amp;&amp; (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

NAPSTER2011

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s