
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.