Hola y bienvenido al blog personal de Eduardo Valenzuela.
Preparate a dar un paseo por los pensamientos de una de las mentes más brillantes de la casa donde vivo.
¿Por qué "El código en azul"?, te estarás preguntando. Me gustaría decirlo, pero es algo que irán notando conforme vean mis trabajos y mis futuras entradas. Creanme, es tan interesante como hilarante.
Sin más, un gusto saludar a quien esté leyendo esto, y espero contar con tu presencia en el futuro.
P.D.
Hoy llegó mi colchón nuevo. Si no me reporto en varios días, es porque no he salido de la cama.
El código en azul
miércoles, 1 de enero de 3000
miércoles, 3 de diciembre de 2014
TIIIIIIMBIRICHE
BUENAS. Es hora de la tan aclamada actualización. Finalmente concluí mi proyecto final, y éste fué un juego, nada menos que timbiriche, y no hablo de la banda.
El cariñoso proyecto consta de 10 clases, que acomodé en 3 paquetes para la lógica, la vista y la comunicación.
Aqui les dejo un "gameplay" que recorre de principio a fin el desarrollo del juego. Espero lo disfruten, porque yo no.
Como verán, no quedó muy diferente de cuando formulé mis prototipos en Pencil!
Comenzaré mostrando las reglas del juego, las cuales brotarán en la pantalla si aprietas el boton de "ayuda".
El funcionamiento básico del juego es el siguiente:
El tablero de juego consiste en una matriz de botones de 19x19. Algunos de ellos tienen forma de líneas horizontales y verticales, otros son solo pequeños puntos, y algunos otros son cuadrados, donde se guardan los puntos y el nombre de su dueño.
Al presionar una de las líneas (solo esos botones se pueden presionar), la maquina marca ese boton como "tomado" y lo marca de negro, simulando una línea siendo dibujada, y luego envía las coordenadas de ese boton a través del cliente de juego, para que el servidor de destino las reciba, el interprete las reconozca, y ordene a la nueva maquina hacer lo mismo que la primera.
Hablando del interprete, hay varios mensajes que está programado para recibir:
Esta serie de reglas y caminos que le permiten a los dos programas comunicarse apropiadamente son lo que define al protocolo.
Un protocolo bien planeado desde el principio permite agregarle comportamientos tan fácil como poner una ficha sobre otra, y aseguran un correcto funcionamiento del programa.
A continuación mostraré las clases una por una, comenzando con la primera vista, y la que resulta ser el "main" del juego. Es la ventana de configuración inicial, y presenta al jugador con 4 campos que puede o no llenar, al menos en el caso de los primeros dos. En el primer campo deberá introducir su nombre (con un caractér basta) . En el segundo va la dirección IP del segundo jugador. En caso de jugar en la misma computadora, podeis teclear simplemente "localhost" indicandole al programa que el juego será hosteado en la dirección local. En caso de querer conocer tu direccióon IP, basta con preguntarle a Google.
También se presenta la opción "avanzada" de modificar los puertos, pero esto es totalmente opcional, ya que el juego los asigna automáticamente dependiendo de si el jugador crea la partida o se unirá a ella.
Cuando la ventana recibe los datos y no existe ningún problema al validarlos, sea crea la vista principal del juego y la maquina de estados. Ésta última a su vez crea el cliente, el servidor y su interprete.
El cliente y el servidor consisten en los típicos Socket y ServerSocket, que conectan con una IP específica a través de un puerto igual de específico. El cliente y el servidor siempre tienen puertos diferentes y son exactamente opuestos al par del jugador contrario.
El interprete es una de las clases importantes del juego. Es quien decide como responderá la maquina a los mensajes que recibe del otro jugador. Por defecto está acomodado para recibir Strings, y el interprete los desbarata para averiguar su destino y contenido.
Para el correcto funcionamiento del juego, utilizé mi propia clase Boton, que hereda de JButton, para introducir unas cuantas variables que necesitaré.
Para dejar las cosas más claras, estas son las interfaces que conectan todas las clases del juego. Si pusieron atención a lo anterior, notarán que la StartVista conecta a la primera ventana con la vista, VistaMaquina conecta la vista con la maquina de estados, y por último la interfaz Enlace conecta la maquina con el servidor y el interprete.
Para descargar los diagramas de clase, haced click aqui. Recomiendo visualizar el archivo XML en Draw.io
Como alternativa, puede previsualizar los diagramas aqui.
Por último, si desea utilizar el proyecto completo, puede descargarlo aqui.

El cariñoso proyecto consta de 10 clases, que acomodé en 3 paquetes para la lógica, la vista y la comunicación.
Aqui les dejo un "gameplay" que recorre de principio a fin el desarrollo del juego. Espero lo disfruten, porque yo no.
Como verán, no quedó muy diferente de cuando formulé mis prototipos en Pencil!

Comenzaré mostrando las reglas del juego, las cuales brotarán en la pantalla si aprietas el boton de "ayuda".

El funcionamiento básico del juego es el siguiente:

El tablero de juego consiste en una matriz de botones de 19x19. Algunos de ellos tienen forma de líneas horizontales y verticales, otros son solo pequeños puntos, y algunos otros son cuadrados, donde se guardan los puntos y el nombre de su dueño.
Al presionar una de las líneas (solo esos botones se pueden presionar), la maquina marca ese boton como "tomado" y lo marca de negro, simulando una línea siendo dibujada, y luego envía las coordenadas de ese boton a través del cliente de juego, para que el servidor de destino las reciba, el interprete las reconozca, y ordene a la nueva maquina hacer lo mismo que la primera.
Hablando del interprete, hay varios mensajes que está programado para recibir:
- coordenadas de un boton
- mensaje de victoria
- mensaje de derrota
- ping/pong
- mensaje destinado al chat del juego
Esta serie de reglas y caminos que le permiten a los dos programas comunicarse apropiadamente son lo que define al protocolo.
Un protocolo bien planeado desde el principio permite agregarle comportamientos tan fácil como poner una ficha sobre otra, y aseguran un correcto funcionamiento del programa.
A continuación mostraré las clases una por una, comenzando con la primera vista, y la que resulta ser el "main" del juego. Es la ventana de configuración inicial, y presenta al jugador con 4 campos que puede o no llenar, al menos en el caso de los primeros dos. En el primer campo deberá introducir su nombre (con un caractér basta) . En el segundo va la dirección IP del segundo jugador. En caso de jugar en la misma computadora, podeis teclear simplemente "localhost" indicandole al programa que el juego será hosteado en la dirección local. En caso de querer conocer tu direccióon IP, basta con preguntarle a Google.
También se presenta la opción "avanzada" de modificar los puertos, pero esto es totalmente opcional, ya que el juego los asigna automáticamente dependiendo de si el jugador crea la partida o se unirá a ella.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.vista; | |
import mx.uabc.poo2.pf.comunicacion.StartVista; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.awt.event.ItemEvent; | |
import java.awt.event.ItemListener; | |
import javax.swing.JButton; | |
import javax.swing.JCheckBox; | |
import javax.swing.JComboBox; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JOptionPane; | |
import javax.swing.JTextField; | |
/** | |
* Esta es la clase que empieza todo. Podria llamarla el main, pero no todo | |
* sucede aqui, solo comienza el juego. | |
* | |
* @version 1.4 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public class Start extends JFrame implements ActionListener, ItemListener, StartVista{ | |
private JTextField ip, port1, port2, name; | |
private JLabel lip, lport1, lport2, lname; | |
private JComboBox combo; | |
private JButton play; | |
private JCheckBox check; | |
private String p="p1"; | |
public Start(){ | |
super("Timbiriche"); | |
inicializar(); | |
} | |
private void inicializar(){ | |
setDefaultCloseOperation(EXIT_ON_CLOSE); | |
setSize(300,300); | |
setLocationRelativeTo(null); | |
setResizable(false); | |
lname=new JLabel("Nombre:"); | |
lname.setBounds(10,10,50,20); | |
add(lname); | |
name=new JTextField(); | |
name.setBounds(150,10,130,20); | |
add(name); | |
lip=new JLabel("IP:"); | |
lip.setBounds(10,40,50,20); | |
add(lip); | |
ip=new JTextField(); | |
ip.setBounds(150,40,130,20); | |
add(ip); | |
lport1=new JLabel("Puerto de entrada:"); | |
lport1.setBounds(10,70,150,20); | |
lport1.setVisible(false); | |
add(lport1); | |
port1=new JTextField("9000"); | |
port1.setBounds(150,70,130,20); | |
port1.setVisible(false); | |
add(port1); | |
lport2=new JLabel("Puerto de salida:"); | |
lport2.setBounds(10,100,150,20); | |
lport2.setVisible(false); | |
add(lport2); | |
port2=new JTextField("9001"); | |
port2.setBounds(150,100,130,20); | |
port2.setVisible(false); | |
add(port2); | |
String[] opcs={"Crear partida", "Unirse a una partida"}; | |
combo=new JComboBox(opcs); | |
combo.setBounds(150,210,130,20); | |
combo.addItemListener(this); | |
add(combo); | |
play=new JButton("Jugar"); | |
play.setBounds(150,240,130,20); | |
play.setFocusable(false); | |
play.addActionListener(this); | |
add(play); | |
check=new JCheckBox("Avanzado"); | |
check.setBounds(10,240,100,20); | |
check.addActionListener(this); | |
add(check); | |
add(new JLabel()); | |
setVisible(true); | |
} | |
public static void main(String[] args) { | |
Start start=new Start(); | |
} | |
@Override | |
public void actionPerformed(ActionEvent ae) { | |
//REVISA SI LA CASILLA FUE RELLENADA, EN CUYO CASO MUESTRA LAS OPCIONES | |
//DE LOS PUERTOS DE ENTRADA Y SALIDA, EN CASO CONTRARIO LOS OCULTA | |
if(ae.getSource()==this.check){ | |
if(check.isSelected()){ | |
lport1.setVisible(true); | |
lport2.setVisible(true); | |
port1.setVisible(true); | |
port2.setVisible(true); | |
} | |
else{ | |
lport1.setVisible(false); | |
lport2.setVisible(false); | |
port1.setVisible(false); | |
port2.setVisible(false); | |
} | |
} | |
//SI EL BOTON DE JUGAR FUE PRESIONADO REALIZA UNA CONSULTA PARA VALIDAR | |
//TODAS LAS OPCIONES. LAS EXCEPCIONES MOSTRADAS NO SON REALMENTE LAS | |
//ADECUADAS, PERO NO ES POSIBLE QUE APAREZCAN ASÍ QUE SE USAN COMO | |
//IDENTIFICADORES PARA LOS DIFERENTES ERRORES | |
if(ae.getSource()==this.play){ | |
int band=0; | |
try{ | |
if(this.name.getText().equalsIgnoreCase("")) | |
throw new ClassNotFoundException(); | |
band=1; | |
if(this.ip.getText().equalsIgnoreCase("localhost")){ | |
} | |
else{ | |
String[] ip=this.ip.getText().split("."); | |
if(this.ip.getText().equalsIgnoreCase("")) | |
throw new ClassNotFoundException(); | |
for(int x=0;x<3;x++) | |
for(int y=0;y<ip[x].length();y++) | |
if(Character.isDigit(ip[x].charAt(y))) | |
throw new Exception(); | |
} | |
band=2; | |
Integer.parseInt(this.port1.getText()); | |
if(this.port1.getText().equalsIgnoreCase("")) | |
throw new ClassNotFoundException(); | |
band=3; | |
Integer.parseInt(this.port2.getText()); | |
if(this.port2.getText().equalsIgnoreCase("")) | |
throw new ClassNotFoundException(); | |
if(this.port2.getText().equalsIgnoreCase(this.port1.getText())) | |
throw new ArithmeticException(); | |
crear(name.getText(), ip.getText(), | |
Integer.parseInt(port1.getText()), | |
Integer.parseInt(port2.getText()), p); | |
} | |
catch(ArithmeticException e){ | |
JOptionPane.showMessageDialog(null,"Los puertos no pueden ser iguales. \nPorfavor cambia uno de los dos"); | |
} | |
catch(ClassNotFoundException e){ | |
JOptionPane.showMessageDialog(null,"Procura no dejar espacios vacíos. \nToda la información es importante"); | |
} | |
catch(Exception e){ | |
if(band==1) | |
JOptionPane.showMessageDialog(null,"Formato de IP incorrecto. \nej. 189.220.191.252"); | |
if(band==2) | |
JOptionPane.showMessageDialog(null,"Formato de puerto 1 incorrecto"); | |
if(band==3) | |
JOptionPane.showMessageDialog(null,"Formato de puerto 2 incorrecto"); | |
} | |
} | |
} | |
@Override | |
public void crear(String nombre, String ip, int port1, int port2, String p){ | |
Vista vista=new Vista(nombre, ip, port1, port2, p); | |
setVisible(false); | |
} | |
@Override | |
public void itemStateChanged(ItemEvent ie) { | |
Object[] obj=ie.getItemSelectable().getSelectedObjects(); | |
if(obj[0].toString().equalsIgnoreCase("Crear partida")){ | |
p="p1"; | |
port1.setText("9000"); | |
port2.setText("9001"); | |
} | |
if(obj[0].toString().equalsIgnoreCase("Unirse a una partida")){ | |
p="p2"; | |
port1.setText("9001"); | |
port2.setText("9000"); | |
} | |
} | |
} |
Cuando la ventana recibe los datos y no existe ningún problema al validarlos, sea crea la vista principal del juego y la maquina de estados. Ésta última a su vez crea el cliente, el servidor y su interprete.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.vista; | |
import mx.uabc.poo2.pf.comunicacion.VistaMaquina; | |
import java.awt.Color; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.awt.event.KeyEvent; | |
import java.awt.event.KeyListener; | |
import javax.swing.ImageIcon; | |
import javax.swing.JButton; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JOptionPane; | |
import javax.swing.JPanel; | |
import javax.swing.JScrollPane; | |
import javax.swing.JTextPane; | |
import mx.uabc.poo2.pf.log.Boton; | |
import mx.uabc.poo2.pf.log.Maquina; | |
/** | |
* Vista principal del tablero de juego y el cliente de chat. Todo lo que | |
* procesa la maquina se ve reflejado aqui y es lo que el jugador verá la | |
* mayoría del tiempo de juego. | |
* | |
* @version 1.6 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public class Vista extends JFrame implements VistaMaquina, KeyListener, ActionListener{ | |
private Boton[][] matriz=new Boton[19][19]; | |
private JPanel p1, p2; | |
private JButton send, help, surrender; | |
private JTextPane history, message; | |
private JLabel player1, player2, puntaje1, puntaje2; | |
private JScrollPane sp1, sp2; | |
private Maquina machine; | |
public Vista(String nombre, String ip, int port1, int port2, String p){ | |
crearMaquina(nombre, ip, port1, port2, p); | |
inicializar(); | |
} | |
public void inicializar(){ | |
setSize(805,600); | |
setLocationRelativeTo(null); | |
setDefaultCloseOperation(EXIT_ON_CLOSE); | |
setResizable(false); | |
setBackground(Color.WHITE); | |
p1=new JPanel(); | |
p1.setLayout(null); | |
// p1.setLocation(10, 10); | |
machine.acomodar(matriz, true); | |
history=new JTextPane(); | |
sp1=new JScrollPane(history); | |
sp1.setBounds(510,10,280,450); | |
add(sp1); | |
message=new JTextPane(); | |
message.addKeyListener(this); | |
sp2=new JScrollPane(message); | |
sp2.setBounds(510,470,230,30); | |
add(sp2); | |
send=new JButton(">>"); | |
send.setBounds(740,470,49,29); | |
send.setContentAreaFilled(false); | |
send.addActionListener(machine); | |
send.setEnabled(false); | |
add(send); | |
player1=new JLabel("Player 1"); | |
player1.setBounds(10,550,100,20); | |
add(player1); | |
puntaje1=new JLabel("0"); | |
puntaje1.setBounds(110,550,100,20); | |
add(puntaje1); | |
player2=new JLabel("Player 2"); | |
player2.setBounds(200, 550, 100, 20); | |
add(player2); | |
puntaje2=new JLabel("0"); | |
puntaje2.setBounds(300,550,100,20); | |
add(puntaje2); | |
help=new JButton("Ayuda"); | |
help.setBounds(510, 550, 100, 20); | |
help.addActionListener(this); | |
add(help); | |
surrender=new JButton("Rendirse"); | |
surrender.setBounds(620,550,100,20); | |
surrender.addActionListener(machine); | |
add(surrender); | |
add(p1); | |
setVisible(true); | |
} | |
//REVISA SI LA CAJA DE TEXTO ESTÁ VACIA PARA PERMITIR O NO EL ENVIO DE | |
//MENSAJES | |
public void check(){ | |
if(message.getText().equalsIgnoreCase("")) | |
send.setEnabled(false); | |
else | |
send.setEnabled(true); | |
} | |
@Override | |
public Vista obtenerVista() { | |
return this; | |
} | |
@Override | |
public void crearMaquina(String nombre, String ip, int port1, int port2, String p) { | |
machine=new Maquina(this, nombre, ip, port1, port2, p); | |
} | |
@Override | |
public void keyTyped(KeyEvent ke) { | |
check(); | |
} | |
@Override | |
public void keyPressed(KeyEvent ke) { | |
check(); | |
} | |
@Override | |
public void keyReleased(KeyEvent ke) { | |
check(); | |
} | |
@Override | |
public void puntajes(Integer uno, Integer dos) { | |
puntaje1.setText(uno.toString()); | |
puntaje2.setText(dos.toString()); | |
} | |
@Override | |
public void actionPerformed(ActionEvent ae) { | |
if(ae.getSource()==help){ | |
// JOptionPane.showInputDialog(null, "Introducir un numero","Ventana",2,new ImageIcon("src/tree.jpg"),null,null); | |
JOptionPane.showMessageDialog(null, null, null, WIDTH, new ImageIcon("src/reglas.png")); | |
// JOptionPane.showInputDialog(null, "hi"); | |
} | |
} | |
@Override | |
public void agregar(Boton boton) { | |
p1.add(boton); | |
} | |
@Override | |
public Boton getBoton(int x, int y) { | |
return matriz[x][y]; | |
} | |
@Override | |
public Boton[][] getMatriz() { | |
return matriz; | |
} | |
@Override | |
public void setHistory(String history) { | |
this.history.setText(history); | |
} | |
@Override | |
public String getHistory() { | |
return this.history.getText(); | |
} | |
@Override | |
public void setMessage(String message){ | |
this.message.setText(message); | |
} | |
@Override | |
public String getMessage() { | |
return this.message.getText(); | |
} | |
@Override | |
public JButton getEnviar(){ | |
return this.send; | |
} | |
@Override | |
public JButton getRendir(){ | |
return this.surrender; | |
} | |
@Override | |
public void nombres(String player1, String player2) { | |
this.player1.setText(player1+":"); | |
this.player2.setText(player2+":"); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.log; | |
import mx.uabc.poo2.pf.comunicacion.Enlace; | |
import mx.uabc.poo2.pf.comunicacion.VistaMaquina; | |
import java.awt.Color; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import static java.awt.image.ImageObserver.WIDTH; | |
import javax.swing.ImageIcon; | |
import javax.swing.JOptionPane; | |
/** | |
* Maquina de estados del juego. Se enlaza por medio de la interface con el | |
* cliente, el interprete y el servidor. Maneja prácticamente todo el | |
* funcionamiento interno del juego. | |
* | |
* @version 2.4 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public class Maquina implements ActionListener, Enlace{ | |
private VistaMaquina vistamaquina; | |
private Entrada entrada; | |
private Interprete interprete; | |
private int port1, port2; | |
private String nombre, ip; | |
private final String victoria="victoria", rendicion="rendicion", | |
continuar="continuar", nocontinuar="nocontinuar", ping="ping", | |
pong="pong", pingpong="pingpong"; | |
private int player1=0, player2=0; | |
private boolean turno, cont1=false, cont2=false, connect=false; | |
//RECIBE UN OBJETO DE LA INTERFAZ VISTA/MAQUINA, EL NOMBRE DEL JUGADOR, LA | |
//DIRECCION IP, LOS PUERTOS 1 Y 2, Y EL IDENTIFICADOR DE JUGADOR | |
public Maquina(VistaMaquina vistamaquina, String nombre, String ip, | |
int port1, int port2, String p){ | |
this.vistamaquina=vistamaquina; | |
this.nombre=nombre; | |
this.ip=ip; | |
this.port1=port1; | |
this.port2=port2; | |
// this.vista=vistamaquina.obtenerVista(); | |
interprete=new Interprete(this); | |
entrada=new Entrada(port1,this); | |
entrada.start(); | |
if(p.equalsIgnoreCase("p1")) | |
turno=true; | |
else | |
turno=false; | |
} | |
//RECIBE LA MATRIZ DE BOTONES Y LOS CREARÁ Y ACOMODARÁ DE ACUERDO A LAS | |
//NECESIDADES DEL TABLERO | |
public void acomodar(Boton[][] matriz, boolean add){ | |
for(int x=0;x<19;x++){ | |
for(int y=0;y<19;y++){ | |
if(add){ | |
matriz[x][y]=new Boton(); | |
matriz[x][y].addActionListener(this); | |
} | |
else{ | |
matriz[x][y].setOwner(null); | |
matriz[x][y].setTaken(false); | |
} | |
if(x%2==0){ | |
if(y%2==0){ | |
matriz[x][y].setBounds(0+(55*(y/2)),0+(55*(x/2)),5,5); | |
matriz[x][y].setBorderPainted(false); | |
matriz[x][y].setBackground(Color.black); | |
matriz[x][y].setEnabled(false); | |
} | |
else{ | |
matriz[x][y].setBounds(5+(55*(y/2)),0+(55*(x/2)),50,5); | |
matriz[x][y].setBorderPainted(false); | |
matriz[x][y].setBackground(Color.white); | |
} | |
} | |
else{ | |
if(y%2==0){ | |
matriz[x][y].setBounds(0+(55*(y/2)),5+(55*(x/2)),5,50); | |
matriz[x][y].setBorderPainted(false); | |
matriz[x][y].setBackground(Color.white); | |
} | |
else{ | |
matriz[x][y].setBounds(5+(55*(y/2)),5+(55*(x/2)),50,50); | |
matriz[x][y].setSquare(true); | |
matriz[x][y].setBorderPainted(false); | |
matriz[x][y].setBackground(Color.white); | |
matriz[x][y].setEnabled(false); | |
} | |
} | |
if(add) | |
vistamaquina.agregar(matriz[x][y]); | |
} | |
} | |
} | |
//REVISA SI HAY NUEVAS CASILLAS CERRADAS, EN CUYO CASO LAS MARCA CON EL | |
//NOMBRE QUE RECIBE Y OTORGA PUNTOS | |
public void check(String player, Boton[][] matriz, int who){ | |
for(int x=0;x<19;x++){ | |
for(int y=0;y<19;y++){ | |
if(matriz[x][y].getSquare() && !matriz[x][y].getTaken()){ | |
if(matriz[x-1][y].getTaken() && matriz[x+1][y].getTaken() | |
&& matriz[x][y-1].getTaken() | |
&& matriz[x][y+1].getTaken()){ | |
matriz[x][y].setOwner(player); | |
matriz[x][y].setTaken(true); | |
if(who==1) | |
player1++; | |
if(who==2) | |
player2++; | |
vistamaquina.puntajes(player1, player2); | |
turno=true; | |
} | |
} | |
} | |
} | |
} | |
//REVISA SI TODOS LOS BOTONES HAN SIDO USADOS, EN CUYO CASO REGRESA UN TRUE | |
public boolean forTheWin(){ | |
for(int x=0;x<19;x++){ | |
for(int y=0;y<19;y++){ | |
if(vistamaquina.getBoton(x,y).isEnabled()) | |
return false; | |
} | |
} | |
return true; | |
} | |
//TERMINA LA PARTIDA. REVISA LOS PUNTAJES PARA DECIDIR UN GANADOR, A MENOS | |
//QUE HAYA RECIBIDO UN MENSAJE DE RENDICION, EN CUYO CASO SOLO LO AVISA | |
public void finDePartida(String maybe){ | |
if(maybe.equalsIgnoreCase("")){ | |
if(player1>player2) | |
JOptionPane.showMessageDialog(null, null, "Victoria", WIDTH, | |
new ImageIcon("src/victoria.png")); | |
else if(player1<player2) | |
JOptionPane.showMessageDialog(null, null, "Derrota", WIDTH, | |
new ImageIcon("src/derrota.png")); | |
else | |
JOptionPane.showMessageDialog(null, null, "Empate", WIDTH, | |
new ImageIcon("src/empate.png")); | |
} | |
else{ | |
if(maybe.equalsIgnoreCase("rendicion")) | |
JOptionPane.showMessageDialog(null, null, "Derrota", WIDTH, | |
new ImageIcon("src/derrota.png")); | |
else | |
JOptionPane.showMessageDialog(null, null, "Victoria", WIDTH, | |
new ImageIcon("src/victoria.png")); | |
} | |
String[] opcs={"Si", "No"}; | |
if(JOptionPane.showOptionDialog(null, "¿Desea reiniciar la partida?", | |
"Continuar", JOptionPane.YES_NO_OPTION, | |
JOptionPane.QUESTION_MESSAGE, null, opcs, opcs[0])==0){ | |
cont1=true; | |
enviar(continuar); | |
continuar(); | |
} | |
else{ | |
enviar(nocontinuar); | |
System.exit(0); | |
} | |
} | |
public void reiniciar(){ | |
player1=0; | |
player2=0; | |
vistamaquina.puntajes(player1, player2); | |
acomodar(vistamaquina.getMatriz(),false); | |
} | |
public void continuar(){ | |
if(cont1 && cont2) | |
reiniciar(); | |
} | |
@Override | |
public void continuar(String cont){ | |
if(cont.equalsIgnoreCase(continuar)) | |
cont2=true; | |
else if(cont.equalsIgnoreCase(nocontinuar)){ | |
JOptionPane.showMessageDialog(null, "El oponente no desea continuar"); | |
System.exit(0); | |
} | |
if(cont1 && cont2) | |
reiniciar(); | |
} | |
@Override | |
public void actionPerformed(ActionEvent ae) { | |
//SI EL BOTON PRESIONADO FUE EL ENVIO DEL CHAT, ENVIA EL MENSAJE DEL | |
//CHAT | |
if(ae.getSource()==vistamaquina.getEnviar()){ | |
enviar(); | |
vistamaquina.setMessage(""); | |
vistamaquina.getEnviar().setEnabled(false); | |
} | |
//SI EL BOTON PRESIONADO FUE EL DE RENDICION, ENVIA EL MENSAJE DE | |
//RENDICION Y TERMINA EL JUEGO | |
else if(ae.getSource()==vistamaquina.getRendir()){ | |
enviar(victoria); | |
finDePartida(rendicion); | |
} | |
//SI NO ES NINGUNO DE LOS DOS CASOS ANTERIORES, PROCEDE A AVERIGUAR QUE | |
//BOTON DE LA MATRIZ FUE PRESIONADO, NO SIN ANTES CONSULTAR SI | |
//EFECTIVAMENTE EL TURNO LE PERTENECE AL JUGADOR | |
else if(turno && connect){ | |
turno=false; | |
Integer x=0; | |
Integer y=0; | |
boolean ban=false; | |
for(x=0;x<19;x++){ | |
for(y=0;y<19;y++){ | |
if(ae.getSource()==vistamaquina.getBoton(x, y)){ | |
vistamaquina.getBoton(x, y).setTaken(true); | |
vistamaquina.getBoton(x, y).setBackground(Color.black); | |
vistamaquina.getBoton(x, y).setEnabled(false); | |
ban=true; | |
break; | |
} | |
} | |
if(ban) | |
break; | |
} | |
check(nombre, vistamaquina.getMatriz(), 1); | |
//ENVIA LAS COORDENADAS Y EL NOMBRE DEL JUGADOR, SEPARADOS CON | |
//ESPACIOS PARA QUE EL INTERPRETE LOS RECONOZCA | |
enviar(x.toString()+" "+y.toString()+" "+nombre); | |
// enviar(vista.matriz); | |
if(forTheWin()){ | |
finDePartida(""); | |
} | |
} | |
} | |
@Override | |
public void interpretar(Object obj) { | |
interprete.interpretar(obj); | |
} | |
@Override | |
public void actualizar(String mensaje) { | |
if(vistamaquina.getHistory().equalsIgnoreCase("")) | |
vistamaquina.setHistory(mensaje); | |
else | |
vistamaquina.setHistory(vistamaquina.getHistory()+"\n"+mensaje); | |
} | |
@Override | |
public void enviar() { | |
String mensaje=nombre+" dice: "+vistamaquina.getMessage(); | |
new Salida(ip,port2,"##-"+mensaje); | |
actualizar(mensaje); | |
} | |
@Override | |
public void reAcomodar(Boton[][] matriz) { | |
// boolean ban=false; | |
for(int x=0;x<19;x++) | |
for(int y=0;y<19;y++){ | |
if(matriz[x][y].getTaken()!=vistamaquina.getBoton(x, y).getTaken()){ | |
vistamaquina.getBoton(x, y).setTaken(matriz[x][y].getTaken()); | |
vistamaquina.getBoton(x, y).setOwner(matriz[x][y].getOwner()); | |
vistamaquina.getBoton(x, y).setBackground(matriz[x][y].getBackground()); | |
if(matriz[x][y].getTaken()) | |
vistamaquina.getBoton(x, y).setEnabled(false); | |
// ban=true; | |
// break; | |
} | |
// if(ban) | |
// break; | |
} | |
turno=true; | |
if(forTheWin()){ | |
finDePartida(""); | |
} | |
} | |
@Override | |
public void enviar(String obj) { | |
new Salida(ip,port2,obj); | |
} | |
@Override | |
public void reAcomodar(int x, int y, String nombre2) { | |
vistamaquina.nombres(nombre, nombre2); | |
// System.out.println("Re-acomodando según coordenadas"); | |
try{ | |
vistamaquina.getBoton(x, y).setTaken(true); | |
vistamaquina.getBoton(x, y).setBackground(Color.black); | |
check(nombre2,vistamaquina.getMatriz(), 2); | |
turno=true; | |
} | |
catch(Exception e){ | |
turno=true; | |
} | |
if(forTheWin()){ | |
finDePartida(""); | |
} | |
} | |
@Override | |
public void ganar(String maybe) { | |
finDePartida(maybe); | |
} | |
@Override | |
public void conectado() { | |
connect=true; | |
} | |
} |
El cliente y el servidor consisten en los típicos Socket y ServerSocket, que conectan con una IP específica a través de un puerto igual de específico. El cliente y el servidor siempre tienen puertos diferentes y son exactamente opuestos al par del jugador contrario.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.log; | |
import java.io.ObjectOutputStream; | |
import java.net.Socket; | |
/** | |
* Cliente de salida. Recibe mensajes para enviar al otro jugador. | |
* | |
* @version 1.0 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public class Salida { | |
private int port2; | |
private Socket cliente; | |
private ObjectOutputStream flujo_de_salida; | |
private String ip; | |
private Object mensaje; | |
public Salida(String ip, int port2, String mensaje){ | |
this.port2=port2; | |
this.ip=ip; | |
this.mensaje=mensaje; | |
mensaje_Saliente(); | |
} | |
public void mensaje_Saliente(){ | |
try{ | |
// System.out.println("Enviando coordenadas"); | |
cliente = new Socket(ip, port2); | |
flujo_de_salida = new ObjectOutputStream(cliente.getOutputStream()); | |
flujo_de_salida.writeObject(mensaje); | |
cliente.close(); | |
} | |
catch(Exception ex){} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.log; | |
import mx.uabc.poo2.pf.comunicacion.Enlace; | |
import java.io.IOException; | |
import java.io.ObjectInputStream; | |
import java.net.ServerSocket; | |
import java.net.Socket; | |
/** | |
* Servidor de entrada. Solo se encarga de la comunicación, pues todo lo que | |
* entra, lo manda directamente al interprete para ser procesado. | |
* | |
* @version 1.1 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public class Entrada extends Thread{ | |
private Socket socket; | |
private ServerSocket servidor; | |
private ObjectInputStream flujo_entrada; | |
private Enlace enlace; | |
public Entrada(int port1, Enlace enlace){ | |
this.enlace=enlace; | |
try { | |
servidor=new ServerSocket(port1); | |
} | |
catch(IOException ex ){ | |
System.out.println("puerto ocupado"); | |
} | |
enlace.enviar("ping"); | |
} | |
@Override | |
public void run(){ | |
String ej=""; | |
while(true){ | |
Object input=null; | |
try{ | |
socket=servidor.accept(); | |
flujo_entrada=new ObjectInputStream(socket.getInputStream()); | |
input=flujo_entrada.readObject(); | |
socket.close(); | |
} | |
catch(IOException | ClassNotFoundException e){ | |
} | |
//RECIBE UN MENSAJE/DATO Y LO ENVIA AL INTERPRETE PARA SU PROCESAMIENTO | |
enlace.interpretar(input); | |
} | |
} | |
} |
El interprete es una de las clases importantes del juego. Es quien decide como responderá la maquina a los mensajes que recibe del otro jugador. Por defecto está acomodado para recibir Strings, y el interprete los desbarata para averiguar su destino y contenido.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.log; | |
//import javax.swing.JTextPane; | |
import mx.uabc.poo2.pf.comunicacion.Enlace; | |
//import javax.swing.text.StyledDocument; | |
/** | |
* Clase encargada de diferenciar un mensaje de otro y notificar a la maquina. | |
* | |
* @version 1.8 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public class Interprete { | |
// JTextPane panel; | |
private Enlace enlace; | |
private final String victoria="victoria", rendicion="rendicion", | |
continuar="continuar", nocontinuar="nocontinuar", ping="ping", | |
pong="pong", pingpong="pingpong"; | |
public Interprete(Enlace enlace){ | |
this.enlace=enlace; | |
} | |
public void interpretar(Object obj){ | |
//AQUI SE CREA UNA VARIABLE STRING PARA SU COMPARACION | |
String verificar=""; | |
//SI EL OBJETO RECIBIDO ES UN STRING LO SEPARA, YA QUE SI ES DEL CHAT, | |
//DEBIÓ LLEGAR CON EL FORMATO DE "##-MENSAJE" | |
if(obj.getClass()==verificar.getClass()){ | |
String mensaje=obj.toString(); | |
String[] mensajes=mensaje.split("-"); | |
if(mensajes[0].equalsIgnoreCase("##")) | |
// Emoticonos(mensajes[1], enlace); | |
enlace.actualizar(mensajes[1]); | |
else{ | |
//EN CASO CONTRARIO PREGUNTA SI ES UN MENSAJE DE RENDICION DEL | |
//OPONENTE | |
if(mensaje.equalsIgnoreCase(victoria)){ | |
enlace.ganar(mensaje); | |
} | |
else if(mensaje.equalsIgnoreCase(continuar) || | |
mensaje.equalsIgnoreCase(nocontinuar)){ | |
enlace.continuar(mensaje); | |
} | |
else if(mensaje.equalsIgnoreCase(ping)){ | |
enlace.enviar(pong); | |
} | |
else if(mensaje.equalsIgnoreCase(pong)){ | |
enlace.conectado(); | |
enlace.enviar(pingpong); | |
} | |
else if(mensaje.equalsIgnoreCase(pingpong)) | |
enlace.conectado(); | |
else{ | |
//O SIMPLEMENTE RECIBIO LAS COORDENADAS DE UN MOVIMIENTO | |
String[] coord=mensaje.split(" "); | |
int x=Integer.parseInt(coord[0]); | |
int y=Integer.parseInt(coord[1]); | |
String nombre2=coord[2]; | |
enlace.reAcomodar(x, y, nombre2); | |
} | |
} | |
} | |
} | |
} |
Para el correcto funcionamiento del juego, utilizé mi propia clase Boton, que hereda de JButton, para introducir unas cuantas variables que necesitaré.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.log; | |
import javax.swing.JButton; | |
/** | |
* Extensión de la clase JButton que permite el funcionamiento de los metodos | |
* de la maquina. | |
* | |
* @version 1.2 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public class Boton extends JButton{ | |
public Boton(){} | |
public Boton(boolean square){ | |
this.square=square; | |
} | |
//TAKEN ME DICE SI ESTE BOTON PARTICULAR YA FUÉ USADO | |
private boolean taken=false; | |
//SQUARE ME DIRÁ SI EL BOTON ES UN CUADRO(TRUE) O NO(FALSE) | |
private boolean square=false; | |
//OWNER GUARDA EL NOMBRE DEL JUGADOR QUE CERRÓ EL CUADRO, EN CASO DE SERLO | |
private String owner; | |
public boolean getTaken(){ | |
return taken; | |
} | |
public void setTaken(boolean taken){ | |
this.taken=taken; | |
} | |
public String getOwner(){ | |
return owner; | |
} | |
public void setOwner(String owner){ | |
this.owner=owner; | |
try{ | |
//EL CUADRO MOSTRARÁ EL PRIMER CARACTER DEL NOMBRE DEL DUEÑO | |
this.setText(Character.toString(owner.charAt(0))); | |
} | |
catch(Exception e){ | |
this.setText(null); | |
} | |
} | |
public boolean getSquare(){ | |
return square; | |
} | |
public void setSquare(boolean square){ | |
this.square=square; | |
} | |
} |
Para dejar las cosas más claras, estas son las interfaces que conectan todas las clases del juego. Si pusieron atención a lo anterior, notarán que la StartVista conecta a la primera ventana con la vista, VistaMaquina conecta la vista con la maquina de estados, y por último la interfaz Enlace conecta la maquina con el servidor y el interprete.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.comunicacion; | |
/** | |
* Interfaz que enlaza la ventana de inicio con la vista del juego y permite la | |
* creación de la maquina de estados. | |
* | |
* @version 1.0 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public interface StartVista { | |
//RECIBE LOS DATOS QUE VAN A CREAR LA VISTA Y LA MAQUINA. ESTOS DATOS SE | |
//VAN A PASEAR POR UNA INTERFACE MÁS ANTES DE LLEGAR A SU DESTINO | |
public void crear(String nombre, String ip, int port1, int port2, String p); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.comunicacion; | |
import javax.swing.JButton; | |
import mx.uabc.poo2.pf.log.Boton; | |
import mx.uabc.poo2.pf.vista.Vista; | |
/** | |
* Interfaz que enlaza la vista principal del juego con la maquina de estados. | |
* Permite a la maquina obtener un numero de elementos de la vista para | |
* modificarlos segun se necesite. | |
* | |
* @version 1.3 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
public interface VistaMaquina { | |
//OBTIENE EL OBJETO DE LA VISTA PARA PODER USARLO | |
public Vista obtenerVista(); | |
//CREAR LA MAQUINA | |
public void crearMaquina(String nombre, String ip, int port1, int port2, String p); | |
//RECIBE LA PUNTUACION Y LA ACTUALIZA EN LA VISTA | |
public void puntajes(Integer uno, Integer dos); | |
public void nombres(String player1, String player2); | |
//AGREGA LA MATRIZ DE BOTONES AL TABLERO | |
public void agregar(Boton boton); | |
//OBTIENE EL OBJETO DEL BOTON ESPECIFICO | |
public Boton getBoton(int x, int y); | |
//OBTIENE LA MATRIZ COMPLETA | |
public Boton[][] getMatriz(); | |
//SETS Y GETS DE CHAT | |
public void setHistory(String history); | |
public String getHistory(); | |
public void setMessage(String message); | |
public String getMessage(); | |
//BOTONES | |
public JButton getEnviar(); | |
public JButton getRendir(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mx.uabc.poo2.pf.comunicacion; | |
/** | |
* Interfaz que enlaza la maquina, el interprete y el servidor de entrada. | |
* | |
* @version 1.3 30 nov 2014 | |
* @autor Eduardo Valenzuela | |
*/ | |
import mx.uabc.poo2.pf.log.Boton; | |
public interface Enlace { | |
//RECIBE EL MENSAJE Y ACTUALIZA EL HISTORIAL DE CONVERSACION EN LA VISTA | |
public void actualizar(String mensaje); | |
//ENVIA UN MENSAJE DEL CHAT | |
public void enviar(); | |
//ENVIA VARIOS TIPOS DE MENSAJES | |
public void enviar(String obj); | |
//RECIBE UN MENSAJE/DATO Y LO ENVIA AL INTERPRETE PARA SU PROCESAMIENTO | |
public void interpretar(Object obj); | |
//RECIBE UNA MATRIZ DE OBJETOS Y REACOMODA LA ACTUAL | |
public void reAcomodar(Boton[][] matriz); | |
//RECIBE COORDENADAS Y NOMBRE Y ACTUALIZA EL TABLERO | |
public void reAcomodar(int x, int y, String nombre2); | |
//EN CASO DE QUE EL JUEGO TERMINE | |
public void ganar(String maybe); | |
//CONTINUAR O NO | |
public void continuar(String cont); | |
//VERIFICA LA CONEXION | |
public void conectado(); | |
} |
Para descargar los diagramas de clase, haced click aqui. Recomiendo visualizar el archivo XML en Draw.io
Como alternativa, puede previsualizar los diagramas aqui.
Por último, si desea utilizar el proyecto completo, puede descargarlo aqui.
domingo, 26 de octubre de 2014
Primer intento de un chat en forma
Como digo arriba, este es el primer chat verdaderamente funcional que he hecho, y a continuación les presento mis clases. Está bastante simple por el momento, con solo 2 clases, pero de que funciona, funciona.
Primero la vista y el socket que envía los mensajes.
Y ahora la clase que funciona comos servidor. Es un hilo que constantemente recibe y actualiza mensajes.
Primero la vista y el socket que envía los mensajes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package chat; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.awt.event.KeyEvent; | |
import java.awt.event.KeyListener; | |
import java.io.ObjectOutputStream; | |
import java.net.Socket; | |
import javax.swing.JButton; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JScrollPane; | |
import javax.swing.JTextArea; | |
public class chat extends JFrame implements ActionListener, KeyListener{ | |
public chat(int port1, int port2, int pos){ | |
this.port1=port1; | |
this.port2=port2; | |
this.pos=pos; | |
inicializar(); | |
entrada=new recepcion(this,port1); | |
entrada.start(); | |
} | |
private Socket cliente; | |
private ObjectOutputStream flujo_de_salida; | |
int port1, port2; | |
int pos; | |
recepcion entrada; | |
JTextArea history, message; | |
JScrollPane sp1, sp2; | |
JButton send; | |
JLabel gp; | |
public void inicializar(){ | |
setDefaultCloseOperation(EXIT_ON_CLOSE); | |
setBounds(300*pos,300,300,300); | |
history=new JTextArea(); | |
sp1=new JScrollPane(history); | |
sp1.setBounds(10,10,265,180); | |
history.setEditable(false); | |
add(sp1); | |
message=new JTextArea(); | |
sp2=new JScrollPane(message); | |
sp2.setBounds(10,200,200,50); | |
message.addKeyListener(this); | |
add(sp2); | |
send=new JButton(">>"); | |
send.setBounds(220,200,54,49); | |
send.setEnabled(false); | |
send.addActionListener(this); | |
add(send); | |
gp=new JLabel(); | |
add(gp); | |
setVisible(true); | |
} | |
public void mensaje_Saliente(String ip, String mensaje){ | |
try{ | |
cliente = new Socket(ip, port2); | |
flujo_de_salida = new ObjectOutputStream(cliente.getOutputStream()); | |
flujo_de_salida.writeObject(mensaje); | |
cliente.close(); | |
} | |
catch(Exception ex){} | |
} | |
@Override | |
public void actionPerformed(ActionEvent ae) { | |
if(!message.getText().equalsIgnoreCase("")){ | |
mensaje_Saliente("localhost",message.getText()); | |
if(!history.getText().equalsIgnoreCase("")) | |
history.setText(history.getText()+"\n"+message.getText()); | |
else | |
history.setText(history.getText()+message.getText()); | |
message.setText(""); | |
send.setEnabled(false); | |
} | |
} | |
@Override | |
public void keyTyped(KeyEvent ke) { | |
check(); | |
} | |
@Override | |
public void keyPressed(KeyEvent ke) { | |
check(); | |
} | |
@Override | |
public void keyReleased(KeyEvent ke) { | |
check(); | |
} | |
public void check(){ | |
if(message.getText().equalsIgnoreCase("")) | |
send.setEnabled(false); | |
else | |
send.setEnabled(true); | |
} | |
public static void main(String[] args) { | |
chat chat1=new chat(9000,9001,1); | |
} | |
} |
Y ahora la clase que funciona comos servidor. Es un hilo que constantemente recibe y actualiza mensajes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package chat; | |
import java.io.IOException; | |
import java.io.ObjectInputStream; | |
import java.net.ServerSocket; | |
import java.net.Socket; | |
public class recepcion extends Thread{ | |
chat cht; | |
int port; | |
public recepcion(chat cht, int port){ | |
this.cht=cht; | |
this.port=port; | |
try { | |
servidor = new ServerSocket(port); | |
} | |
catch(IOException ex ){ | |
System.out.println("puerto ocupado"); | |
} | |
} | |
private Socket socket; | |
private ServerSocket servidor; | |
private ObjectInputStream flujo_entrada; | |
@Override | |
public void run(){ | |
while(true){ | |
String mensaje = null; | |
try{ | |
socket =servidor.accept(); | |
flujo_entrada = new ObjectInputStream(socket.getInputStream()); | |
mensaje = (String) flujo_entrada.readObject(); | |
if(!cht.history.getText().equalsIgnoreCase("")) | |
cht.history.setText(cht.history.getText()+"\n"+mensaje); | |
else | |
cht.history.setText(cht.history.getText()+mensaje); | |
socket.close(); | |
} | |
catch(IOException | ClassNotFoundException ex){} | |
} | |
} | |
} |
miércoles, 1 de octubre de 2014
Los filosofos comensales (venganza con semaforos)
En mi nuevo intento asistido por internet, verán que mi lógica es muy similar a la anterior, con la excepción de que en lugar de las colas uso semaforos, y que ya se como crear threads en ciclos xD
Esta vez, cambian básicamente tres cosas:
Primero, ya no son tenedores, porque me pareció ridiculo que necesitaces dos tenedores para comer, así que lo cambié por palillos. Tiene sentido, ¿no?
Segundo, guardo los palillos a los que accede cada comensal en una matriz de enteros, donde por defecto el primer valor será el numero de identificación del comensal, y el segundo será 0 o 1, para cada palillo.
Tercero, SEMAFOROS. ¿por qué? Porque me dejo de preocupar por que se mezclen los permisos para tomar los palillos. En mi programa anterior, eran dos sentencias diferentes la que revisa la disponibilidad de los tenedores, y la que efectúa el cambio de disponibilidad, por lo que si dos hilos accedían casi al mismo tiempo a la revisión, era posible que uno de ellos tomara los tenedores e inmediatamente después el otro también, y luego ambos "reportaban" que los tenedores estaban tomados, y luego, muy cinicamente, se ponían a comer. Además así puedo especificar que solo un comensal puede sostener cada palillo.
Oh y también volví aleatorios los tiempos de retardo, porque noté que hay menos problemas así.
Ignoro la razón de esto último.
En la siguiente clase notarán como cree la matriz de la que hablé antes. La gran diferencia en este caso con respecto a mi intento anterior, es que ya solo creo los 5 comensales, y ellos mismos funcionan como buffers.
Y solo para que quede claro, sigo sin usar sincronización.
Esta vez, cambian básicamente tres cosas:
Primero, ya no son tenedores, porque me pareció ridiculo que necesitaces dos tenedores para comer, así que lo cambié por palillos. Tiene sentido, ¿no?
Segundo, guardo los palillos a los que accede cada comensal en una matriz de enteros, donde por defecto el primer valor será el numero de identificación del comensal, y el segundo será 0 o 1, para cada palillo.
Tercero, SEMAFOROS. ¿por qué? Porque me dejo de preocupar por que se mezclen los permisos para tomar los palillos. En mi programa anterior, eran dos sentencias diferentes la que revisa la disponibilidad de los tenedores, y la que efectúa el cambio de disponibilidad, por lo que si dos hilos accedían casi al mismo tiempo a la revisión, era posible que uno de ellos tomara los tenedores e inmediatamente después el otro también, y luego ambos "reportaban" que los tenedores estaban tomados, y luego, muy cinicamente, se ponían a comer. Además así puedo especificar que solo un comensal puede sostener cada palillo.
Oh y también volví aleatorios los tiempos de retardo, porque noté que hay menos problemas así.
Ignoro la razón de esto último.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.concurrent.Semaphore; | |
public class ejemplo2 extends Thread { | |
private final int id; | |
private final Semaphore[] semaforo; | |
private final int[][] palillos; | |
private final int palilloL; | |
private final int palilloR; | |
// private int hambre=5; | |
public ejemplo2(int id, Semaphore[] semaforo, int[][] palillos){ | |
this.id=id; | |
this.semaforo=semaforo; | |
this.palillos=palillos; | |
this.palilloL=palillos[id][0]; | |
this.palilloR=palillos[id][1]; | |
} | |
protected void comer(){ | |
if(semaforo[palilloR].tryAcquire()){ | |
if(semaforo[palilloR].tryAcquire()){ | |
System.out.println("FILÓSOFO " + id + " ESTÁ COMIENDO. USA LOS PALILLOS "+palilloL+" Y "+palilloR); | |
// hambre--; | |
try{ | |
int time=0; | |
while(time<=0) | |
time=new Random().nextInt()%2000; | |
sleep(time); | |
}catch (InterruptedException ex){ | |
System.out.println("Error : " + ex.toString()); | |
} | |
System.out.println("Filósofo " + id + " terminó de comer. Liberó los palillos " + palilloL + " y " + palilloR); | |
semaforo[palilloR].release(); | |
} | |
semaforo[palilloL].release(); | |
}else{ | |
System.out.println("Filósofo " + id + " está hambriento."); | |
} | |
} | |
protected void pensar(){ | |
System.out.println("Filósofo " + id + " está pensando."); | |
try{ | |
int time=0; | |
while(time<=0) | |
time=new Random().nextInt()%2000; | |
sleep(time); | |
}catch(InterruptedException ex){ | |
System.out.println("Error en el método pensar(): " + ex.toString()); | |
} | |
} | |
@Override | |
public void run(){ | |
while(true){ | |
pensar(); | |
comer(); | |
// if(hambre<=0){ | |
// System.out.println("Filosofo "+id+" está satisfecho"); | |
// break; | |
// } | |
} | |
} | |
} |
En la siguiente clase notarán como cree la matriz de la que hablé antes. La gran diferencia en este caso con respecto a mi intento anterior, es que ya solo creo los 5 comensales, y ellos mismos funcionan como buffers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package filosofos; | |
import java.util.concurrent.Semaphore; | |
public class ejemplo1 { | |
final static int nFilosofos = 5; | |
final static int[][] palillos = { | |
{0, 4}, // filosofo 0 | |
{1, 0}, // filosofo 1 | |
{2, 1}, // filosofo 2 | |
{3, 2}, // filosofo 3 | |
{4, 3} // filosofo 4 | |
}; | |
final static Semaphore[] semaforo=new Semaphore[nFilosofos]; | |
public static void main(String[] args){ | |
for (int i = 0; i < nFilosofos; i++){ | |
semaforo[i]=new Semaphore(1); | |
} | |
for(int idFilosofo=0; idFilosofo<nFilosofos; idFilosofo++){ | |
new ejemplo2(idFilosofo, semaforo, palillos).start(); | |
} | |
} | |
} |
Y solo para que quede claro, sigo sin usar sincronización.
Los filosofos comensales (first try)
Aqui les traigo mi primer intento a codificar el conocido problema de los filosofos comensales.
"Cinco filosofos se sientan ante la mesa a comer. Cada uno necesita dos palillos para sujetar su alimento, pero solo hay 5 palillos en la mesa. Los filosofos se turnan entre pensar y comer, y siempre hay máximo dos de ellos comiendo"
La primera clase que les muestro es la del filosofo. Aqui básicamente tengo todo lo que requiere éste para cambiar entre pensar y comer. Lo que hace es revisar si ambos tenedores (un objeto por cada uno) están tomados, si no es así, los toma y luego se sitúa al final de la cola de espera.
La clase que sigue es el tenedor. Mi clase main crea 5 de estos junto con 5 filosofos, y funciona con los metodos de colas circulares. Si el filosofo que quiere tomar el tenedor se encuentra hasta arriba de la cola en ambos tenedores, entonces puede tomarlos.
Lo que hace mi clase main es crear 5 de cada uno de estos, y luego los pone a trabajar.
Al principio me pareció una idea bastante buena lo de las colas, pero luego noté que en mi programa solo come un filosofo a la vez. Así que después de moverle por un rato decidí intentar con una estrategia diferente: semaforos. (lo cual es muy parecido a lo que hice, pero es mejor)
Eso lo veremos en el siguiente post.
"Cinco filosofos se sientan ante la mesa a comer. Cada uno necesita dos palillos para sujetar su alimento, pero solo hay 5 palillos en la mesa. Los filosofos se turnan entre pensar y comer, y siempre hay máximo dos de ellos comiendo"
La primera clase que les muestro es la del filosofo. Aqui básicamente tengo todo lo que requiere éste para cambiar entre pensar y comer. Lo que hace es revisar si ambos tenedores (un objeto por cada uno) están tomados, si no es así, los toma y luego se sitúa al final de la cola de espera.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package filosofos; | |
public class Filosofo extends Thread{ | |
public int hambre; | |
public int muerte; | |
public int id; | |
public Tenedor t1; | |
public Tenedor t2; | |
public Filosofo(int id, int hambre, Tenedor t1, Tenedor t2){ | |
this.id=id; | |
this.hambre=hambre; | |
muerte=hambre; | |
this.t1=t1; | |
this.t2=t2; | |
t1.Insertar(this); | |
t2.Insertar(this); | |
} | |
public void run() { | |
while(hambre>0){ | |
System.out.println("Filosofo #"+id+" tiene hambre. "); | |
try{ | |
Thread.sleep(id*500); | |
} | |
catch(InterruptedException e){ | |
} | |
if(t1.Consultar()==this && t2.Consultar()==this && t1.taken==false && t2.taken==false){ | |
t1.taken=true; | |
t2.taken=true; | |
// muerte+=3; | |
t1.Remover(); t1.Insertar(this); | |
t2.Remover(); t2.Insertar(this); | |
System.out.println("Filosofo #"+id+" toma los tenedores #"+t1.id+" y #"+t2.id+" y empieza a comer. "); | |
try{ | |
Thread.sleep(10000); | |
} | |
catch(InterruptedException e){ | |
} | |
hambre-=3; | |
System.out.println("Filosofo #"+id+" suelta los tenedores #"+t1.id+" y #"+t2.id); | |
t1.taken=false; | |
t2.taken=false; | |
} | |
else{ | |
System.out.println("Filosofo #"+id+" se queda con hambre. "); | |
// muerte-=3; | |
// if(muerte<=0){ | |
// System.out.println("El filosofo #"+id+" ha muerto de hambre"); | |
// System.exit(0); | |
// } | |
} | |
} | |
System.out.println("Filosofo #"+id+" está satisfecho"); | |
} | |
} |
La clase que sigue es el tenedor. Mi clase main crea 5 de estos junto con 5 filosofos, y funciona con los metodos de colas circulares. Si el filosofo que quiere tomar el tenedor se encuentra hasta arriba de la cola en ambos tenedores, entonces puede tomarlos.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package filosofos; | |
public class Tenedor extends ColaCircular{ | |
public boolean taken=false; | |
public int id; | |
public Tenedor(int id){ | |
super(2); | |
this.id=id; | |
} | |
} |
Lo que hace mi clase main es crear 5 de cada uno de estos, y luego los pone a trabajar.
Al principio me pareció una idea bastante buena lo de las colas, pero luego noté que en mi programa solo come un filosofo a la vez. Así que después de moverle por un rato decidí intentar con una estrategia diferente: semaforos. (lo cual es muy parecido a lo que hice, pero es mejor)
Eso lo veremos en el siguiente post.
miércoles, 24 de septiembre de 2014
Un ejemplo de hilos no sincronizados (Consumidor y Productor)
He aqui lo último en trabajos sencillos pero efectivos. Un problema de productores y consumidores con hilos en java. 2 productores, 3 consumidores, todo el drama.
El productor
El consumidor
Y finalmente, el main. Aquí llamo a 2 productores y 3 consumidores.
Como pueden ver, los hilos no están sincronizados de ninguna manera. Así que es divertido ver como se pelean por los recursos (literalmente, los recursos).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ejemplo; | |
public class DatosCompartidos { | |
public String dato; | |
public String getDato(){ | |
return dato; | |
} | |
public void newDato(String dato){ | |
this.dato=dato; | |
} | |
} |
El productor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ejemplo; | |
public class Productor extends Thread{ | |
public DatosCompartidos datos; | |
public String nombre; | |
Productor(DatosCompartidos dc, String nmbr){ | |
datos=dc; | |
nombre=nmbr; | |
} | |
public void run(){ | |
Integer i=0; | |
while(true){ | |
i++; | |
datos.newDato(i.toString()); | |
System.out.println(nombre+" produce "+i); | |
} | |
} | |
} |
El consumidor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ejemplo; | |
public class Consumidor extends Thread{ | |
public DatosCompartidos datos; | |
public String nombre; | |
Consumidor(DatosCompartidos dc, String nmbr){ | |
datos=dc; | |
nombre=nmbr; | |
} | |
public void run(){ | |
while(true){ | |
System.out.println(nombre+" consume "+datos.getDato()); | |
} | |
} | |
} |
Y finalmente, el main. Aquí llamo a 2 productores y 3 consumidores.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ejemplo; | |
public class ProducerConsumerSinSincro { | |
public static void main(String[] args){ | |
DatosCompartidos datos=new DatosCompartidos(); | |
Productor p1=new Productor(datos, "p1"); | |
Productor p2=new Productor(datos, "p2"); | |
Consumidor c1=new Consumidor(datos, "c1"); | |
Consumidor c2=new Consumidor(datos, "c2"); | |
Consumidor c3=new Consumidor(datos, "c3"); | |
p1.start(); | |
p2.start(); | |
c1.start(); | |
c2.start(); | |
c3.start(); | |
} | |
} |
Como pueden ver, los hilos no están sincronizados de ninguna manera. Así que es divertido ver como se pelean por los recursos (literalmente, los recursos).
domingo, 21 de septiembre de 2014
La verguenza termina
Este es el cronometro terminado, del que no me averguenzo taaanto.
Primero les paso mi clase lógica, donde manejo el incremento del tiempo segun el estado del thread. Oh si, es un thread.
Ahora esta es mi clase Tiempo, la cual contiene todos los datos de, pues, el tiempo.
Ahora finalmente, mi clase Vista y la interfaz que implementa. El metodo que usa la vista para acomodar todo es el que recibe de la interfaz, y su unico parámetro es un objeto de Tiempo, que contiene todo lo que necesita para acomodar los números.
La clase main simplemente crea el objeto de Vista2 y arranca el thread. No hace nada más.
Y así declaro terminada mi tortura :)
Primero les paso mi clase lógica, donde manejo el incremento del tiempo segun el estado del thread. Oh si, es un thread.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cronometro; | |
public class Logica2 implements Runnable{ | |
Vista2 vis; | |
public void run(){ | |
Tiempo time=new Tiempo(); | |
while(true){ | |
int msec=time.getMilisec(); | |
int sec=time.getSegundo(); | |
int min=time.getMinuto(); | |
int hora=time.getHora(); | |
try{ | |
Thread.sleep(1000); //Lo duermo 1000 milisegundos, lo que equivale a 1 segundo | |
} | |
catch(InterruptedException e){ | |
} | |
// msec++; | |
// if(msec>=100){ | |
// sec++; | |
// msec=0; | |
// } | |
sec++; | |
if(sec>=60){ | |
min++; | |
sec=0; | |
} | |
if(min>=60){ | |
hora++; | |
min=0; | |
} | |
if(hora>=24){ | |
hora=0; | |
} | |
if(vis.getAction()) | |
time=new Tiempo(); | |
if(vis.getEstado()){ | |
time.setHora(hora); | |
time.setMilisec(msec); | |
time.setMinuto(min); | |
time.setSegundo(sec); | |
vis.Acomodar(time); //es el método de la vista que acomoda todo de manera visualmente agradable | |
} | |
} | |
} | |
public Logica2(Vista2 vista){ | |
vis=vista; | |
} | |
} |
Ahora esta es mi clase Tiempo, la cual contiene todos los datos de, pues, el tiempo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cronometro; | |
public class Tiempo { | |
private Integer hora; | |
private Integer minuto; | |
private Integer segundo; | |
private Integer milisec; | |
public Tiempo(){ | |
hora=0; | |
minuto=0; | |
segundo=0; | |
milisec=0; | |
} | |
public void setHora(int hora){ | |
this.hora=hora; | |
} | |
public void setMinuto(int minuto){ | |
this.minuto=minuto; | |
} | |
public void setSegundo(int segundo){ | |
this.segundo=segundo; | |
} | |
public void setMilisec(int milisec){ | |
this.milisec=milisec; | |
} | |
public Integer getHora(){ | |
return hora; | |
} | |
public Integer getMinuto(){ | |
return minuto; | |
} | |
public Integer getSegundo(){ | |
return segundo; | |
} | |
public Integer getMilisec(){ | |
return milisec; | |
} | |
} |
Ahora finalmente, mi clase Vista y la interfaz que implementa. El metodo que usa la vista para acomodar todo es el que recibe de la interfaz, y su unico parámetro es un objeto de Tiempo, que contiene todo lo que necesita para acomodar los números.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cronometro; | |
import javax.swing.*; | |
import java.awt.*; | |
import java.awt.event.*; | |
public class Vista2 extends JFrame implements Interfaz2{ | |
JPanel p1, p2, p3; | |
JLabel hr, min, sec, msec, dot1, dot2; | |
JButton start, restart, stop, resume; | |
private boolean state=false; | |
private boolean action=false; | |
public Vista2(){ | |
super("Cronometro"); | |
this.setDefaultCloseOperation(EXIT_ON_CLOSE); | |
this.setBounds(50, 50, 480, 300); | |
p1=new JPanel(new GridLayout(1,5)); | |
p3=new JPanel(); | |
hr=new JLabel("00"); min=new JLabel("00"); sec=new JLabel("00"); msec=new JLabel("00"); | |
dot1=new JLabel(":"); dot2=new JLabel(":"); | |
hr.setFont(new Font("Arial", Font.BOLD, 70)); | |
min.setFont(new Font("Arial", Font.BOLD, 70)); | |
sec.setFont(new Font("Arial", Font.BOLD, 70)); | |
dot1.setFont(new Font("Arial", Font.BOLD, 70)); | |
dot1.setBounds(140,60,100,100); | |
dot2.setFont(new Font("Arial", Font.BOLD, 70)); | |
dot2.setBounds(280,60,100,100); | |
p1.add(hr); | |
add(dot1); | |
p1.add(min); | |
add(dot2); | |
p1.add(sec); | |
msec.setBounds(420, 85,100,100); | |
add(msec); | |
this.add(p1, BorderLayout.CENTER); | |
p3.setPreferredSize(new Dimension(44,44)); | |
this.add(p3, BorderLayout.WEST); | |
p2=new JPanel(new FlowLayout()); | |
start=new JButton("Iniciar"); start.setPreferredSize(new Dimension(100,20)); | |
restart=new JButton("Reiniciar"); restart.setPreferredSize(new Dimension(100, 20)); | |
stop=new JButton("Detener"); stop.setPreferredSize(new Dimension(100, 20)); | |
resume=new JButton("Continuar"); resume.setPreferredSize(new Dimension(100, 20)); | |
p2.add(start); p2.add(restart); | |
p2.add(stop); p2.add(resume); | |
this.add(p2, BorderLayout.SOUTH); | |
start.setEnabled(true); | |
stop.setEnabled(false); | |
restart.setEnabled(false); | |
resume.setEnabled(false); | |
this.setVisible(true); | |
start.addActionListener(new ActionListener(){ | |
public void actionPerformed(ActionEvent e){ | |
state=true; | |
start.setEnabled(false); | |
stop.setEnabled(true); | |
restart.setEnabled(false); | |
resume.setEnabled(false); | |
} | |
}); | |
stop.addActionListener(new ActionListener(){ | |
public void actionPerformed(ActionEvent e){ | |
state=false; | |
stop.setEnabled(false); | |
restart.setEnabled(true); | |
resume.setEnabled(true); | |
} | |
}); | |
restart.addActionListener(new ActionListener(){ | |
public void actionPerformed(ActionEvent e){ | |
hr.setText("00"); | |
min.setText("00"); | |
sec.setText("00"); | |
msec.setText("00"); | |
restart.setEnabled(false); | |
start.setEnabled(true); | |
resume.setEnabled(false); | |
action=true; | |
} | |
}); | |
resume.addActionListener(new ActionListener(){ | |
public void actionPerformed(ActionEvent e){ | |
state=true; | |
stop.setEnabled(true); | |
resume.setEnabled(false); | |
restart.setEnabled(false); | |
} | |
}); | |
} | |
public boolean getEstado(){ | |
return state; | |
} | |
public boolean getAction(){ | |
return action; | |
} | |
public void Acomodar(Tiempo time){ | |
//Estetica | |
if(time.getHora()<10) | |
hr.setText("0"+time.getHora().toString()); | |
else | |
hr.setText(time.getHora().toString()); | |
if(time.getMilisec()<10) | |
msec.setText("0"+time.getMilisec().toString()); | |
else | |
msec.setText(time.getMilisec().toString()); | |
if(time.getMinuto()<10) | |
min.setText("0"+time.getMinuto().toString()); | |
else | |
min.setText(time.getMinuto().toString()); | |
if(time.getSegundo()<10) | |
sec.setText("0"+time.getSegundo().toString()); | |
else | |
sec.setText(time.getSegundo().toString()); | |
action=false; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cronometro; | |
public interface Interfaz2 { | |
public void Acomodar(Tiempo time); | |
} |
La clase main simplemente crea el objeto de Vista2 y arranca el thread. No hace nada más.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cronometro; | |
public class Test { | |
public static void main(String[] args){ | |
//Vista vis=new Vista(); | |
//(new Thread(new Logica(vis))).start(); | |
Vista2 vis=new Vista2(); | |
(new Thread(new Logica2(vis))).start(); | |
} | |
} |
Y así declaro terminada mi tortura :)
Suscribirse a:
Entradas (Atom)