/** * Reversi hexagonal. * * NOM......... : * PRÉNOM...... : * GROUPE de TP : */ class Reversi { /** Paramètre de la fenêtre : titre. */ static final String TITRE = "Reversi"; /** Paramètre de la fenêtre : taille. */ static final int TAILLE_FENETRE = 600; /** Taille par défaut pour le plateau de jeu. */ static final int RAYON_PLATEAU_DEFAUT = 3; /** Contenu d'une case : case vide. */ static final int PION_VIDE = 0; /** Contenu d'une case : pion noir. */ static final int PION_NOIR = 1; /** Contenu d'une case : pion blanc. */ static final int PION_BLANC = 2; /** Couleur de dessin : le fond. */ static final String COULEUR_FOND = "gray"; /** Couleur de dessin : les bords. */ static final String COULEUR_BORD = "black"; /** Couleur de dessin : les pions noirs. */ static final String COULEUR_PION_NOIR = "black"; /** Couleur de dessin : les pions blancs. */ static final String COULEUR_PION_BLANC = "white"; /** Constante précalculée : √3. */ static final float SQRT_3 = (float)Math.sqrt(3); /** Constante précalculée : √3/2. */ static final float SQRT_3_2 = (float)(Math.sqrt(3) / 2); /** Taille du plateau de jeu (rayon). */ static int rayonPlateau = RAYON_PLATEAU_DEFAUT; /** Taille du plateau de jeu (diamètre). */ static int taille; /** Rayon d'une case sur le dessin. */ static float rayon; /** Coordonnée du centre de la case (0, 0) das la fenêtre. */ static float[] origine = new float[2]; /** Plateau de jeu (dimensions {@link #taille} × {@link #taille}). */ static int[][] plateau; /** La fenêtre graphique. */ static DrawingWindow w; /** * Initialise les différentes variables globales, en fonction de la taille * souhaitée pour le jeu, et de la taille de la fenêtre. */ static void initialiserParametres() { w = new DrawingWindow(TITRE, TAILLE_FENETRE, TAILLE_FENETRE); taille = 2 * rayonPlateau + 1; rayon = w.height / ((taille + 1) * SQRT_3); origine[0] = rayon * (taille + 1) * SQRT_3_2; origine[1] = rayon * taille * SQRT_3; plateau = new int[taille][taille]; } /** * Indique si les coordonnées passées désignent une case valide sur le * plateau. * * @param i première coordonnée de la case * @param j deuxième coordonnée de la case * @return vrai (true) si les coordonnées (i, j) désignent une * case valide */ static boolean caseValide(int i, int j) { return i >= 0 && j >= 0 && i < taille && j < taille && 2 * Math.abs(i - j) < taille; } /** * Calcule les coordonnées d'une case dans la fenêtre à partir de ses * coordonnées sur le plateau. Les coordonnées retournées sont celle du * centre de la case. Celles-ci sont retournées sous la forme de tableau à * deux éléments : [0] pour l'abscisse et [1] pour l'ordonnée. * * @param i première coordonnée de la case sur le plateau * @param j deuxième coordonnée de la case sur le plateau * @return coordonnées du centre de la case dans la fenêtre */ static float[] calculerCoord(int i, int j) { float x = origine[0] + (i - j) * 1.5f * rayon; float y = origine[1] - (i + j) * SQRT_3_2 * rayon; float res[] = {x, y}; return res; } /** * Calcule les coordonnées d'une case sur le plateau à partir de coordonnées * dans la fenêtre. Les coordonnées de la case sont retournées sous la * forme d'un tableau à deux éléments : [0] pour la première coordonnée de * la case et [1] pour la deuxième. * * @param x abscisse du point dans la fenêtre * @param y ordonnée du point dans la fenêtre * @return coordonnées de la case correspondant */ static int[] calculerCase(float x, float y) { // Calcul des coordonnées de la case (première approximation). float dx = (origine[0] - x) / (rayon * 3); float dy = (origine[1] - y) / (rayon * SQRT_3); float ri = dx - dy; float rj = dx + dy; float rk = -2 * dx; int i = Math.round(ri); int j = Math.round(rj); int k = Math.round(rk); // Ici, (i, j, k) désignent la case souhaitée ou bien une de ses // voisines. // On calcule les erreurs d'arrondi, puis on corrige i et j si besoin. // La valeur de k n'étant plus utilisée par la suite, il n'est pas // nécessaire de la corriger. float ei = Math.abs(ri - i); float ej = Math.abs(rj - j); float ek = Math.abs(rk - k); if (ei > ej && ei > ek) i = -(j + k); else if (ej > ek) j = -(i + k); int[] res = {-i, j}; return res; } /** * Dessine une case du plateau. La case dessinée a la forme d'un hexagone * régulier. * * @param i première coordonnée de la case sur le plateau * @param j deuxième coordonnée de la case sur le plateau */ static void dessinerCase(int i, int j) { // Calcul des coordonées du centre de l'hexagone. float[] c = calculerCoord(i, j); int x0 = Math.round(c[0]); int y0 = Math.round(c[1]); // Calcul des coordonnées des sommets de l'hexagone. // NB: le dernier point (7ième) est égal au premier. int[] px = new int[7]; // abscisses int[] py = new int[7]; // ordonnées px[0] = px[6] = Math.round(c[0] + rayon); px[1] = px[5] = Math.round(c[0] + rayon / 2); px[2] = px[4] = Math.round(c[0] - rayon / 2); px[3] = Math.round(c[0] - rayon); py[0] = py[3] = py[6] = y0; py[1] = py[2] = Math.round(c[1] - rayon * SQRT_3_2); py[4] = py[5] = Math.round(c[1] + rayon * SQRT_3_2); // Dessin du fond. w.setColor(COULEUR_FOND); for (int k = 0; k < 6; k++) w.fillTriangle(x0, y0, px[k], py[k], px[k + 1], py[k + 1]); // Dessin du cadre. w.setColor(COULEUR_BORD); for (int k = 0; k < 6; k++) w.drawLine(px[k], py[k], px[k + 1], py[k + 1]); } /** * Dessine un pion dans la case donnée. Le paramètre pion qui doit valoir * {@link #PION_NOIR} ou {@link #PION_BLANC} définit la couleur du pion à * dessiner. * * @param i première coordonnée de la case où dessiner le pion * @param j deuxième coordonnée de la case où dessiner le pion * @param pion pion à dessiner */ static void dessinerPion(int i, int j, int pion) { float[] c = calculerCoord(i, j); String couleur; switch (pion) { case PION_NOIR: couleur = COULEUR_PION_NOIR; break; case PION_BLANC: couleur = COULEUR_PION_BLANC; break; default: throw new IllegalArgumentException("Pion invalide: " + pion); } w.setColor(couleur); w.fillCircle(Math.round(c[0]), Math.round(c[1]), (int)Math.floor(0.7f * rayon)); } /** * Dessine la fenêtre du jeu. Dessine l'intégralité de la fenêtre ave les * différents boutons, ainsi que les cases du plateau et les pions déjà * placés. */ static void dessinerPlateau() { w.setBgColor(COULEUR_BORD); w.clearGraph(); for (int i = 0; i < taille; i++) { for (int j = 0; j < taille; j++) { if (caseValide(i, j)) { dessinerCase(i, j); } } } } /** * Initialise le plateau de jeu (tableau {@link #plateau}) avec toutes les * cases vides. Place les pions de la configuration de départ. */ static void initialiserPlateau() { // FIXME } /** * Calcule l'opposant d'un joueur donné ({@link #PION_NOIR} ou {@link * #PION_BLANC}). * * @param joueur joueur dont il faut calculer l'opposant * @return opposant du joueur donné */ static int inverserJoueur(int joueur) { switch (joueur) { case PION_NOIR: return PION_BLANC; case PION_BLANC: return PION_NOIR; default: throw new IllegalArgumentException("Joueur invalide: " + joueur); } } /** * Tente de jouer un coup pour le joueur donné. Indique en retour si le * coup a pu être joué. * * @param i première coordonnée de la case où jouer * @param j deuxième coordonnée de la case où jouer * @param joueur joueur qui joue * @return vrai (true) si le coup a pu être joué */ static boolean jouerCoup(int i, int j, int joueur) { if (!caseValide(i, j)) { return false; } plateau[i][j] = joueur; dessinerPion(i, j, joueur); return true; } /** * Lance une partie du jeu. Retourne le vainqueur ({@link #PION_NOIR} ou * {@link #PION_BLANC}). En cas de match nul, retourne {@link #PION_VIDE}. * * @return le vainqueur, ou {@link #PION_VIDE} */ static int jouerPartie() { int joueur = PION_NOIR; // les noirs commencent boolean continuer = true; while (continuer) { // attend un clic de souris w.waitMousePress(); // récupère les coordonnées (x, y) du clic dans la fenêtre int x = w.getMouseX(); int y = w.getMouseY(); // trouve les coordonnées (i, j) de la case correspondant int[] ij = calculerCase(x, y); int i = ij[0]; int j = ij[1]; System.err.println("# pixel (" + x + ", " + y + ") ->" + " case (" + i + ", " + j + ") [" + (caseValide(i, j) ? "VALIDE" : "INVALIDE") + "]"); if (jouerCoup(i, j, joueur)) { joueur = inverserJoueur(joueur); } } return PION_VIDE; // FIXME } /** * Demande à l'utilisateur s'il veut recommencer une partie. * * @return vrai (true) si l'utilisateur veut recommencer */ static boolean recommencerPartie() { return false; // FIXME } /** * Programme principal. */ public static void main(String[] args) { if (rayonPlateau < 1) { System.err.println("Taille invalide : " + rayonPlateau); System.exit(1); } int[] comptes = { 0, 0, 0 }; initialiserParametres(); do { initialiserPlateau(); dessinerPlateau(); int res = jouerPartie(); comptes[res]++; } while (recommencerPartie()); w.closeGraph(); System.out.println(",----[ Résultats ]"); System.out.println("| Blancs : " + comptes[PION_BLANC]); System.out.println("| Noirs : " + comptes[PION_NOIR]); System.out.println("| Nuls : " + comptes[PION_VIDE]); System.out.println("`----"); } }