/**
 * sudoku.java
 * Génère de façon aléatoire une grille de sudoku
 * de dimension 4x4 ou 9x9
 * Jean-Paul Quelen, 25 août 2010
 * petites modifications et ajout de main() le 08/01/18, modifié le 06/08/22 et le 21/08/22
 */ 

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

public class sudoku extends JPanel implements ActionListener, FocusListener {
	static final long serialVersionUID = 220822L;
	Random rnd = new Random();
	JButton gg = new JButton ("Init");
	JTextField cv;
	JButton sol = new JButton ("Solution");
	JButton bF = new JButton ("F");
	JLabel lcompteur = new JLabel ("");

// n0 = 2 pour une grille 4 x 4, n0 = 3 pour une grille 9 x 9
	int n0 = 3;
	int n = n0 * n0;
	int n2 = n * n;
	int [][] grille = new int [n][n];
	JTextField [][] tfgrille = new JTextField [n][n]; 
	Font f = new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, 12);
	Font F, Fs3;
	int marge = 20;
	JPanel p;
	JTextField tfgrilleij;
	int gw, gh;

	public void init() {
		setLayout (new BorderLayout());
		setFont (f);

		p = new JPanel();
		p.setBackground (Color.WHITE);
		add (p, BorderLayout.NORTH);

		p.add (gg);
		gg.addActionListener (this);

		p.add (cv = new JTextField (Integer.toString (n0 * n0 * n0), 5));
		JLabel lb = new JLabel ("cases visibles.");
		p.add (lb);
		lb.setBackground (Color.WHITE);

		p.add (bF);
		bF.addActionListener (this);

		p.add (sol);
		sol.addActionListener (this);

		p.add (lcompteur);

		grille [0][0] = -1;

// montage de la grille affichée
// un JPanel avec une grille de dimension n0xn0.
// chaque case contient n0 x n0 cases de type JTextField
		p = new JPanel();
		p.setBackground (Color.BLACK);
		p.setLayout (new GridLayout (n0, n0, 1, 1));
		add (p, BorderLayout.CENTER);
		JPanel[][] pn0 = new JPanel [n0][n0];
		for (int i = 0; i < n0; i++)
			for (int j = 0; j < n0; j++) {
				p.add (pn0 [i][j] = new JPanel());
				pn0 [i][j] . setLayout (new GridLayout (n0, n0));
			}
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				pn0 [i / n0][j / n0].add (tfgrille [i][j] = new JTextField (" "));
		if (gw == 0) {
			gw = getWidth();
			gh = getHeight();
		}
		int npx = (Math.min (gw, gh) - marge - marge) / n;
		F = new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, npx);
		Fs3 = new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, npx / 3);
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++) {
				tfgrille [i][j].setFont (F);
				tfgrille [i][j].addFocusListener (this);
			}
	}

// retourne un nombre aléatoire entre 0 et n-1
// n'appartenant pas à la liste passée en paramètre
// retourne -1 si impossibilité
	private int alea (int[] nbres) {
		boolean[] crible = new boolean [n];
// false par défaut
		for (int i = 0; i < nbres.length; i++)
 	if (nbres [i] >= 0)
			crible [nbres [i]] = true;
		int nf = 0;
		for (int i = 0; i < n; i++)
			if (crible [i])
				nf++;
		int nmnf = n - nf;
		if (nmnf > 0) {
			int rni = rnd.nextInt (nmnf);
			int i;
			for (i = 0; i < n; i++) {
				if (! crible [i])
					rni--;
				if (rni < 0)
					break;
			}
			return i;
		} else {
			grille [0][0] = -1;
			return -1;
		}
	}

	public void actionPerformed (ActionEvent evt) {
		if (evt.getSource() == gg) {
// génération d'une grille
			for (int i = 0; i < n; i++)
				for (int j = 0; j < n; j++) {
					grille [i][j] = -1;
					tfgrille [i][j].setText (" ");
					tfgrille [i][j].setFont (F);
					tfgrille [i][j].setForeground (Color.BLACK);
				}

			int compteur = 1;
			while (grille [0][0] == -1) {
				for (int i = 0; i < n; i++)
					for (int j = 0; j < n; j++)
						grille [i][j] = -1;

// affiche sur STDOUT le nombre d'essais avant d'arriver à une grille complète
// System.out.println (compteur++);

			lcompteur.setText (Integer.toString (compteur++));

			for (int i = 0; i < n; i++)
				for (int j = 0; j < n; j++) {
					int [] nbres = new int [i + j + n];
					for (int i0 = 0; i0 < i; i0++)
						nbres [i0] = grille [i0][j];
						int k = i;
						for (int j0 = 0; j0 < j; j0++) {
							nbres [k] = grille [i][j0];
							k++;
						}
						for (int i0 = i / n0 * n0; i0 < i / n0 * n0 + n0; i0++)
							for (int j0 = j / n0 * n0; j0 < j / n0 * n0 + n0; j0++) {
								int nk = grille [i0][j0];
								nbres [k] = nk;
								k++;
							}
						grille [i][j] = alea (nbres);
				}
			}
// cases visibles
			compteur = 0;
			try {
				compteur = Integer.parseInt (cv.getText());
			}
			catch (NumberFormatException e) {}
			if (compteur > n2) {
				cv.setText (Integer.toString (n2));
				afficheSolution();
			} else {
				if (compteur <= 0)
					cv.setText ("0");
				while (compteur > 0) {
					int i = rnd.nextInt (n);
					int j = rnd.nextInt (n);
					String s = tfgrille [i][j].getText();
					if (s.compareTo (" ") == 0) {
						tfgrille [i][j].setText (Integer.toString (grille [i][j] + 1));
						compteur--;
					}
				}
			}
		} else if (evt.getSource() == sol)
			afficheSolution();
		else if (evt.getSource() == bF) {
// un appui sur le bouton F met le contenu de la case
// sur laquelle il y a le focus en gros caractères
			if (tfgrilleij != null) {
				tfgrilleij.setFont (F);
				tfgrilleij.setText(tfgrilleij.getText().trim());
			}
		}
	}

	private void afficheSolution() {
		if (grille [0][0] != -1)
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				if (tfgrille[i][j].getText().trim().length() == 0) {
					tfgrille[i][j].setForeground (Color.BLUE);
				}
				tfgrille[i][j].setText (Integer.toString (grille [i][j] + 1));
			tfgrille[i][j].setFont (F);
			}
		}
	}

	public void focusGained (FocusEvent e) {
		tfgrilleij = (JTextField) e.getSource();
		tfgrilleij.setFont (f);
		tfgrilleij.setForeground (Color.BLUE);
	}

	public void focusLost (FocusEvent e) {}

	public static void main (String [] args) {
		int w = 450;
		int h = 450;

// Création et lancement de l'applet
		sudoku s = new sudoku();
		s.gw = 400;
		s.gh = 400;
		s.init();

// Création de la fenêtre contenant l'applet
		JFrame f = new JFrame ("sudoku");
		f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
		f.add (s);
		f.setSize (w, h);
		f.setVisible (true);
}

}
