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:
  • 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.

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");
}
}
}
view raw Start.java hosted with ❤ by GitHub

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.

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+":");
}
}
view raw Vista.java hosted with ❤ by GitHub

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;
}
}
view raw Maquina.java hosted with ❤ by GitHub

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.

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){}
}
}
view raw Salida.java hosted with ❤ by GitHub

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);
}
}
}
view raw Entrada.java hosted with ❤ by GitHub

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.

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);
}
}
}
}
}
view raw Interprete.java hosted with ❤ by GitHub

Para el correcto funcionamiento del juego, utilizé mi propia clase Boton, que hereda de JButton, para introducir unas cuantas variables que necesitaré.

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;
}
}
view raw Boton.java hosted with ❤ by GitHub

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.

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);
}
view raw StartVista.java hosted with ❤ by GitHub

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();
}

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();
}
view raw Enlace.java hosted with ❤ by GitHub

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.
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);
}
}
view raw chat.java hosted with ❤ by GitHub


Y ahora la clase que funciona comos servidor. Es un hilo que constantemente recibe y actualiza mensajes.
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){}
}
}
}
view raw recepcion.java hosted with ❤ by GitHub

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.

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;
// }
}
}
}
view raw ejemplo2.java hosted with ❤ by GitHub


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.

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();
}
}
}
view raw ejemplo1.java hosted with ❤ by GitHub


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.

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");
}
}
view raw Filosofo.java hosted with ❤ by GitHub


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.

package filosofos;
public class Tenedor extends ColaCircular{
public boolean taken=false;
public int id;
public Tenedor(int id){
super(2);
this.id=id;
}
}
view raw Tenedor.java hosted with ❤ by GitHub


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.

package ejemplo;
public class DatosCompartidos {
public String dato;
public String getDato(){
return dato;
}
public void newDato(String dato){
this.dato=dato;
}
}

El productor
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);
}
}
}
view raw Productor.java hosted with ❤ by GitHub

El consumidor
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());
}
}
}
view raw Consumidor.java hosted with ❤ by GitHub

Y finalmente, el main. Aquí llamo a 2 productores y 3 consumidores.
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.

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;
}
}
view raw Logica2.java hosted with ❤ by GitHub

Ahora esta es mi clase Tiempo, la cual contiene todos los datos de, pues, el tiempo.

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;
}
}
view raw Tiempo.java hosted with ❤ by GitHub

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.

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;
}
}
view raw Vista2.java hosted with ❤ by GitHub

package cronometro;
public interface Interfaz2 {
public void Acomodar(Tiempo time);
}
view raw Interfaz2.java hosted with ❤ by GitHub

La clase main simplemente crea el objeto de Vista2 y arranca el thread. No hace nada más.

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();
}
}
view raw Test.java hosted with ❤ by GitHub


Y así declaro terminada mi tortura :)

domingo, 14 de septiembre de 2014

Primer fail

 He aqui mi primer intento fallido de hacer un cronometro. Esta es mi clase lógica, que es también un hilo que llamo desde el main. Actualmente tengo una mejor versión y me averguenzo de ella, pero todo tiene su humilde origen, ¿no es cierto?

package cronometro;
public class Logica implements Runnable{
private Integer msec, secs, mins, hrs;
private Vista vis;
public Logica(Vista vis){
this.vis=vis;
msec=0;
secs=0;
mins=0;
hrs=0;
}
public void run(){
while(true){
int wea=vis.state;
int cos=vis.accion;
if(cos==1){
msec=0;
secs=0;
mins=0;
hrs=0;
vis.hr.setText("00");
vis.min.setText("00");
vis.sec.setText("00");
vis.msec.setText("00");
vis.accion=0;
}
if(wea==1){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
msec++;
if(msec==100){
msec=0;
secs++;
}
if(secs==60){
secs=0;
mins++;
if(vis.dot1.isVisible())
vis.dot1.setVisible(false);
else
vis.dot1.setVisible(true);
}
if(mins==60){
mins=0;
hrs++;
}
if(msec<10)
vis.msec.setText("0"+msec.toString());
else
vis.msec.setText(msec.toString());
if(secs<10)
vis.sec.setText("0"+secs.toString());
else
vis.sec.setText(secs.toString());
if(mins<10)
vis.min.setText("0"+mins.toString());
else
vis.min.setText(mins.toString());
if(hrs<10)
vis.hr.setText("0"+hrs.toString());
else
vis.hr.setText(hrs.toString());
}
}
}
}
view raw Logica.java hosted with ❤ by GitHub



Y esta es la interfaz gráfica, que por desgracia interactua demasiado con la clase lógica.


package cronometro;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Vista extends JFrame implements ActionListener{
JPanel p1, p2, p3;
JLabel hr, min, sec, msec, dot1, dot2;
JButton start, restart, stop, resume;
Integer state=0, accion=0;
public Vista(){
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));
start.addActionListener(this);
restart=new JButton("Reiniciar"); restart.setPreferredSize(new Dimension(100, 20));
restart.addActionListener(this);
stop=new JButton("Detener"); stop.setPreferredSize(new Dimension(100, 20));
stop.addActionListener(this);
resume=new JButton("Continuar"); resume.setPreferredSize(new Dimension(100, 20));
resume.addActionListener(this);
p2.add(start); p2.add(restart);
p2.add(stop); p2.add(resume);
this.add(p2, BorderLayout.SOUTH);
start.setVisible(true);
stop.setVisible(false);
restart.setVisible(false);
resume.setVisible(false);
this.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if(e.getSource() == start){
state=1;
start.setVisible(false);
stop.setVisible(true);
restart.setVisible(true);
}
if(e.getSource() == stop){
state=0;
stop.setVisible(false);
resume.setVisible(true);
}
if(e.getSource() == restart){
accion=1;
}
if(e.getSource() == resume){
state=1;
resume.setVisible(false);
stop.setVisible(true);
}
}
}
view raw Vista.java hosted with ❤ by GitHub