Game.java

package fr.umontpellier.iut.conquest;

import fr.umontpellier.iut.conquest.board.memento.BoardCaretaker;
import fr.umontpellier.iut.conquest.strategies.Strategy;

import static java.lang.Math.pow;

import java.io.InputStream;
import java.util.Scanner;

/**
 * Modélise u
 * ne partie de Conquest.
 */

public class Game {
    /**
     * Scanner permettant de lire les entrées.
     */
    private static Scanner scan;
    /**
     * Le plateau de jeu.
     */
    private Board board;
    /**
     * Les joueurs.
     */
    private Player[] players = new Player[2];

    private BoardCaretaker caretaker = new BoardCaretaker();
    /**
     * Constructeur.
     * Crée un plateau à partir de sa taille (impaire).
     * Crée les joueurs à partir de leur stratégie et leur nom.
     */
    public Game(int size, Strategy strategy1, String name1, Strategy strategy2, String name2) {
        board = new Board(size);
        players[0] = new Player(strategy1, this, name1, 1);
        players[1] = new Player(strategy2, this, name2, 2);
    }

    /**
     * Constructeur pour Test.
     * Prend un plateau prédéfini.
     * Crée les joueurs à partir de leur stratégie et leur nom.
     */
    public Game(Board board, Strategy strategy1, String name1, Strategy strategy2, String name2) {
        this.board = board;
        players[0] = new Player(strategy1, this, name1, 1);
        players[1] = new Player(strategy2, this, name2, 2);
    }

    /**
     * Les méthodes suivantes sont utilisées pour les tests automatiques. Il ne faut pas les utiliser.
     */
    public static Scanner getScan() {
        return scan;
    }

    public static void initInput(InputStream inputStream) {
        scan = new Scanner(inputStream);
    }

    public Player[] getPlayers() {
        return players;
    }

    /**
     * Getter.
     */
    public Board getBoard() {
        return board;
    }

    /**
     * Fait tourner une partie.
     *
     * @param hardcore : Si hardcore = 1, les joueurs ne peuvent pas annuler les coups joués.
     *                 Sinon harcdore = 0 et les joueurs ont le droit d'annuler un ou plusieurs coups à chaque fois qu'un coup est joué.
     */
    public void run(int hardcore) {
        // Le joueur qui commence est le premier joueur.
        Player player = players[0];

        // Initialise le jeu.
        initGame();

        // Fait tourner le jeu.
        while (!isFinished()) {

            // Affiche le plateau.
            System.out.println(board);

            // Demande au joueur courant de jouer.
            if (!board.getValidMoves(player).isEmpty()) {
                board.movePawn(player.play());
            }

            // En mode non-hardcore, demande au joueur s'il veut annuler un ou plusieurs coups.
            if (hardcore == 0) {
                player = confirmOrUndoMove(player);
            }
            // Change de joueur.
            player = getOtherPlayer(player);
        }

        // Affiche le plateau final.
        System.out.println(board);

        // Affiche le nom du vainqueur.
        System.out.println("Le vainqueur est " + getWinner().getName() + " !");
    }

    /**
     * Initialise le jeu.
     */
    private void initGame() {
        board.initField(players[0], players[1]);
        caretaker.addMemento(board.saveToMemento());
    }

    /**
     * Prends un joueur en entrée et retourne l'autre joueur.
     */
    public Player getOtherPlayer(Player player) {
        return (player.equals(players[0])) ? players[1] : players[0];
    }

    /**
     * Teste si la partie est finie.
     * Rappel :
     * - La partie est finie quand il n'y a plus de case libre.
     * - La partie est finie quand l'un des deux joueurs n'a plus de pions.
     */
    public boolean isFinished() {
        return noFreeSquare() || noPawnsPlayer();
    }

    /**
     * Retourne le joueur qui a gagné la partie.
     * Rappel : Le joueur qui gagne est celui qui possède le plus de pions.
     */
    public Player getWinner() {
        return (nbPawnsPlayer0() > nbPawnsPlayer1()) ? players[0] : players[1];
    }

    /**
     * Demande au joueur s'il veut annuler (ou pas) un ou plusieurs coups.
     * <p>
     * Tant que le joueur player le désire et que l'on n'est pas revenu au début de la partie en cours,
     * propose à player de reculer d'un coup en faisant saisir 1, ou 0 sinon.
     * Cette méthode doit donc modifier l'état de l'attribut board.
     *
     * @param player : le joueur courant.
     * @return Player : le joueur dont il est le tour de jouer.
     */
    private Player confirmOrUndoMove(Player player) {
        int nbUndoMove = 0;
        while (canUndoMove() && playerWantsUndoMoveInShowedBoard()) {
            nbUndoMove++;
            board.undoFromMemento(caretaker.getMemento());
        }
        caretaker.addMemento(board.saveToMemento());
        return getPlayerOfTurn(player, nbUndoMove);
    }

    private boolean canUndoMove() {
        return !caretaker.isEmpty();
    }

    private Player getPlayerOfTurn(Player player, int nbUndoMove) {
        boolean nbUndoIsPair = (nbUndoMove % 2) == 0;
        return nbUndoIsPair ? player : getOtherPlayer(player);
    }

    private boolean playerWantsUndoMoveInShowedBoard() {
        int nbUndo;
        System.out.println(board);
        do {
            System.out.println("Voulez-vous annuler le tour (1 pour Oui : 0 pour Non) :");
            nbUndo = scan.nextInt();
        } while (nbUndo > 1 || nbUndo < 0);
        return nbUndo == 1;
    }

    // #region méthode privées isFinished()
    private boolean noPawnsPlayer() {
        assert (nbPawnsPlayer0() != 0 || nbPawnsPlayer1() != 0) : "board should be have pawns";
        return nbPawnsPlayer0() == 0 || nbPawnsPlayer1() == 0;
    }

    private boolean noFreeSquare() {
        final double NUMBER_OF_SQUARE = pow(board.getSize(), 2);
        int numberOfPawns = nbPawnsPlayer0() + nbPawnsPlayer1();

        return numberOfPawns == NUMBER_OF_SQUARE;
    }

    private int nbPawnsPlayer1() {
        return board.getNbPawns(players[1]);
    }

    private int nbPawnsPlayer0() {
        return board.getNbPawns(players[0]);
    }
    // #endregion méthode privées isFinished()
}