No te preocupes por usar mi código, pero sobre todo asegúrate de entender bien que hace cada línea.
Sobre tu programa, muy bien. Has añadido los mensajes tras finalizar partida.
Solo tiene una pega, cuando se elige volver a jugar lo que se hace es reinstanciar un nuevo juego completo, sin que se cierre el anterior.
Y como el anterior es quien hace la instancia, en realidad no se puede cerrar.
Esto implica que si elijo rejugar 20 veces.., tendré en memoria 20 juegos MasterMind.., y esto no es óptimo...
Una forma de solucionarlo sería, en lugar de abrir un nuevo juego, reiniciar el juego actual para que comience todo desde el principio, botones reactivados, seteados a valor 1, nueva combinacion, contadores a 0, etc....
Esto va a implicar añadir métodos a las clases que hemos creado, para que cada una sepa como debe reiniciarse.
A la clase
Boton habrá que darle un método para que recupere el valor inicial de 1 y adopte el color correspondiente:
public void reset() {
valor = 1;
cambiarColor();
}
A
PanelColores un método que recorra los botones para dar orden de resetearlos, reactive todos los botones que habían sido desactivados durante el juego y limpie los campos de texto para que vuelvan a estar vacíos.
public void resetPanel() {
//Botones reinician valor
for (int i = 0; i < botones.length; i++)
botones[i].reset();
//Botones se reactivan
botonesActivos(true);
//Limpiamos campo de text
txtResultado.setText(null);
}
A
Combinación un método similar, que genere nueva combinación y reinicie contadores.
public void resetCombinacion() {
generaCombinacion();
intentos = 0;
muertos = 0;
heridos = 0;
resuelto = false;
}
Hecho esto, ahora nos vamos a encontrar con unos problemas muy interesantes, que pueden servir para aprender cosas.
Ahora mismo los objetos de la clase PanelColores ya tienen un método para reiniciarse.
Pero resulta que no podemos pedirle que los usen

¿Por qué?
Porque hemos creado objetos "desreferenciados", es decir, no les hemos dado una referencia que poder usar para acceder a ellos.
Simplemente dimos orden de crearlos con new PanelColores() y meterlos en la interfaz.
Pero no tienen un "nombre" de variable asignado para "hablarles" y pedirles que hagan cosas.
Existen, tenemos 10 objetos PanelColores, pero al no tener referencia, no podemos acudir a ellos
for (int i = 0; i < 10; i++)
{//Con esto replico el panelColores que contiene los 3 paneles, 10 veces
panelesBotones.add(new PanelColores(combinacion));
add(panelesBotones);
}
Si ahora queremos poder pedirles que se reinicien con el nuevo método que le hemos dado, necesitamos darles una referencia a la que poder acudir.
Como son muchos, lo mejor es referenciarlos usando un vector, así con un bucle podemos acceder a todos ellos con solo un par de líneas.
Así que a la clase MasterMind agregamos un vector como atributo para referenciar estos paneles.
public class MasterMind extends JFrame
{
Combinacion combinacion;
PanelColores[] pnColores;
public MasterMind()
{
combinacion = new Combinacion(this);
pnColores = new PanelColores[10];
JPanel panelesBotones = new JPanel();
panelesBotones.setLayout(new BoxLayout(panelesBotones, BoxLayout.Y_AXIS));
//Layout simple que me permite colocar los elementos en forma vertical 
for (int i = 0; i < 10; i++)
{//Con esto replico el panelColores que contiene los 3 paneles, 10 veces
pnColores[ i] = new PanelColores(combinacion);
panelesBotones.add(pnColores[ i]);
add(panelesBotones);
}
¡Bien!
Ya podemos "hablar" con esos paneles, nos basta con un bucle, un simple FOR EACH dentro de un método para cuando queramos resetear los paneles.
Añadimos también este método a la clase
MasterMind public void resetear() {
for (PanelColores panel: pnColores)
panel.resetPanel();
}
Ahora, hay que decidir en qué momento les decimos a estos paneles que han de resetearse.
Lo lógico es hacerlo en el switch que hay dentro de la clase
Combinación, ahí donde tu dabas orden de crear un nuevo MasterMind.
public void Respuesta()
{
resp =JOptionPane.showConfirmDialog(null, msjRepetir, msjTitulo, JOptionPane.YES_NO_OPTION);
switch (resp) {
case 0:
new MasterMind();
break;
case 1:
System.exit(0);
break;
default:
System.exit(0);
break;
}
}
Como ya no vamos a crear un nuevo juego, sino resetear el juego actual, esa línea marcada en rojo la vamos a sustituir por otras dos líneas.
Una, para llamar al método que resetea a la clase
CombinaciónY otra línea, para pedirle a la clase MasterMind, que resetee sus paneles de colores.
Pero, aquí surge un nuevo problema. Estamos dentro de la clase
Combinación, y esta clase no tiene forma alguna de comunicarse con la clase MasterMind.
Las clases y los métodos/funciones tienen una cosa que se llama "ámbito"(scope), lo que viene a ser su área de influencia.
La clase
Combinación puede influir en todo lo que está dentro de ella, pero no en lo que está "fuera".
De hecho,
Combinación está dentro de
MasterMind (es uno de sus atributos) así que
MasterMind si puede influir en
Combinación.
Pero no al revés, así que
Combinación no puede pedirle a
MasterMind que reinicie sus paneles.
A no ser que abramos un canal de comunicación.
Podemos hacer que
Combinación también tenga un atributo
MasterMind, y al construir un objeto Combinación, este reciba una referencia del objeto MasterMind.
Así ambas clases forman parte del ámbito de la otra, y pueden influirse mutuamente.
A
Combinación le añadimos esa referencia a
Mastermind como atributo, y la recibirá por su constructor
public class Combinacion {
public int [] combinacion;
public int intentos;
public int muertos;
public int heridos;
public int resp;
public boolean resuelto;
public String resultado;
public final String msjTitulo = "MASTERMIND BY JB";
public final String msjGanar = "Enhorabuena, ¡ GANASTE ! ";
public String msjIntentos = "UPS!, HAS GASTADO LOS 10 INTENTOS";
public String msjRepetir = "¿PROBAMOS DE NUEVO?";
MasterMind juegoMM; //Referencia a la clase principal
public Combinacion(MasterMind mm)
{
combinacion = new int[4];
generaCombinacion();
//Le meto al constructor el metodo
intentos = 0;
resuelto = false;
//Con esto controlo los intentos;
juegoMM = mm;
}
Y como ahora ya tenemos referencia a
MasterMind, ya podemos "hablarle" y pedir que resetee sus paneles cuando el jugador quiera repetir partida.
public void Respuesta()
{
resp =JOptionPane.showConfirmDialog(null, msjRepetir, msjTitulo, JOptionPane.YES_NO_OPTION);
switch (resp) {
case 0:
resetCombinacion();
juegoMM.resetear(); //Indicamos que hay que reiniciar los paneles
break;
case 1:
System.exit(0);
break;
default:
System.exit(0);
break;
}
}
Y con esto, ahora el juego se puede reiniciar tras cada partida, sin tener que crear nuevas instancias que se vayan acumulando en memoria.
Esto de la "comunicación entre clases" es algo que puede dar muchos quebraderos de cabeza, porque muchas veces necesitas que una única acción (pulsar el botón "SI") actúe sobre distintas clases y objetos.
Y aunque todas formen parte del mismo programa, no significa que estén comunicadas entre sí. De hecho, lo normal es que cada una tenga su ámbito propio, donde las otras clases no puedan meter mano.
Así que muchas veces habrá que abrir canales de comunicación mediante referencias entre unas y otras.
Por otra parte, esta comunicación se puede mejorar usando "patrones de diseño".
Es probable que hayas oído hablar del patrón "Modelo-Vista-Controlador", y si no tarde o temprano te hablarán de él.
Esto consiste en diseñar el programa de manera que unas clases se encarguen del Modelo del programa, es decir, los datos internos (contadores, evaluar resultados, etc,) y otras se encarguen de la Vista, lo que el usuario ve en pantalla (botones, diseño de interfaz, etc).
Entre estas, habría otra clase haciendo la función de Controlador. Esta clase tendría a todas las demás dentro de su ámbito y así puede encargarse de enviar los datos que una computa para que la otra los muestre, y actuar en todas ellas cuando sea necesario.
El programa que hemos hecho, en realidad, aplica este patrón pero no de la mejor forma.
La clase
Combinación sería el
Modelo.
Boton y
PanelColores serían la
VistaY la función del
Controlador la llevan a medias entre
MasterMind y
Combinación, por eso hemos tenido que intercomunicarlas.
Lo ideal habría sido escribir el código de otro modo, para que solo
MasterMind, o incluso otra clase creada específicamente para este propósito, fuera quien si encargase de "Controlar" si el juego ha terminado o no, de preguntar al usuario si quiere repetir o no, etc... dando y pidiendo datos a las clases del Modelo y de la Vista.
Pero bueno, todo esto lo comento para ampliar conocimientos.
Para ser nuestro primer MasterMind, yo creo que nos ha quedado muy bien je je..
EDITO:
Se me olvidó comentar un pequeño bug que surge tras todos estos cambios.
Al reiniciar partida, resulta que todos los campos se limpian pero luego en el campo de texto del útlimo panel donde hemos jugado, aparece el mensaje de "muertos y heridos".
Esto ocurre cuando se ha ganado, y es porque cuando ganamos, o sea cuando tenemos 4 muertos, el método evaluar() llama al JOptionPane para informar al usuario y si este quiere, se resetean los paneles para que siga jugando.
Pero el método evaluar() no termina aquí, después de que todo se reinicie, el método continua con su algoritmo y retorna el String que contabiliza muertos y heridos.
Por eso tras el "reset" del juego, vuelve a aparecer esto en el campo de texto.
Para evitarlo, debemos poner fin al método evaluar() después de la llamada al JOptionPane, por ejemplo, pidiendo que retorne un valor null
//Si encuentra 4 muertos, el juego está resuelto
if (muertos == 4)
{
resuelto = true;
JOptionPane.showMessageDialog(null, msjGanar, msjTitulo, JOptionPane.INFORMATION_MESSAGE);
Respuesta();
return null;
}
Dejo el código completo en archivo adjunto (hay que estar logado en los foros para poder descargarlo).