package netgame.tictactoe;

import java.io.Serializable;



/**
 * This class holds all the necessary information to represent
 * the state of a game of networked TicTacToe.  It includes the 
 * method apply(hub,sender,message), which modifies the state to
 * reflect a message that was received from a player.  The protocol is
 * that two types of messages from client are understood.
 * One is that a TicTacToe client sends the String "newgame" as
 * a message when it wants to start a new game.  The other is that
 * when the user makes a mover into one of the squares on the board,
 * the client sends an array of two ints containing the row
 * and column where the user played.  Note that to keep things
 * simple, each time a game is started, this class decides at random 
 * which of the two players will play 'X' and which will play 'O'.  
 * X always makes the first move.
 */
public class TicTacToeGameState implements Serializable {
    
    //-------------- state variables recording the state of the game -------------------
    
    public boolean playerDisconnected;  // This is true if one of the two players has left the game.
                                        // The new state, with this value set to true, is sent to
                                        // the other player as a signal that the game is over.  That
                                        // client will respond by ending the program.

    public char[][] board;  // The contents of the board. Values are ' ', 'X', or 'O'.
                            // This variable is null before the first game starts.
    
    public boolean gameInProgress;  // True while a game is being played; 
                                    // false before first game and between games.

    // The next three variables are meant for use while a game is in progress.
    // Note that the ID numbers of the players will always be 1 and 2.
    
    public int playerPlayingX;   // The ID of the player who is playing X.
    public int playerPlayingO;   // The ID of the player who is playing O.
    public int currentPlayer;    // The ID of the player who is to make the next move.
    
    // The next two variables are meant for use between games.
    
    public boolean gameEndedInTie; // Tells whether the game ended in a tie.
    public int winner;   // The name of winner of the game that just ended, if it was not a tie.
    
    
    
    //----------- the method that is called by the Hub to react to messages from the players -----------
    
    /**
     *  Respond to a message that was sent by one of the players to the hub.
     *  Note that illegal messages (of the wrong type or coming at an illegal
     *  time) are simply ignored.  The messages that are understood are
     *  the string "newgame" for starting a new game and an array of two
     *  ints giving the row and column of a move that the user wants to make.
     *  @param sender the ID number of the player who sent the message.
     *  @param message the message that was received from that player.
     */
    public void applyMessage(int sender, Object message) {
        if (gameInProgress && message instanceof int[] && sender == currentPlayer) {
                // The message represents a move by the current player.
            int[] move = (int[])message;
            if (move == null || move.length != 2)
                return;
            int row = move[0];
            int col = move[1];
            if (row < 0 || row > 2 || col < 0 || col > 2 || board[row][col] != ' ')
               return;
            board[row][col] = (currentPlayer == playerPlayingX)? 'X' : 'O'; // Make the move.
            if (winner()) { // CurrentPlayer has won.
                gameInProgress = false;
                winner = currentPlayer;
            }
            else if (tie()) { // The board is full but there is no winner; game ends in a tie.
                gameInProgress = false;
                gameEndedInTie = true;
            }
            else {  // It's the other player's turn now.
                currentPlayer = (currentPlayer == playerPlayingX)? playerPlayingO : playerPlayingX;
            }
        }
        else if (!gameInProgress && message.equals("newgame")) {
            startGame();
        }
    }
    
    /**
     * This package private method is called by the hub when the second player
     * connects.  Its purpose is to start the first game.
     */
    void startFirstGame() {
        startGame();
    }
    
    
    //------------------- Some private utility methods used by the apply() method ---------------
    
    /**
     * Start a game.  Board is initialized to empty.  Players are
     * randomly assigned to play 'X' or 'O'. 
     */
    private void startGame() {
        board = new char[3][3];
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++) {
                board[i][j] = ' ';
            }
        int xPlr = (Math.random() < 0.5)? 1 : 2;
        playerPlayingX = xPlr;  // Will be 1 or 2.
        playerPlayingO = 3 - xPlr;  // The other player ( 3 - 1 = 2, and 3 - 2 = 1 )
        currentPlayer = playerPlayingX;
        gameEndedInTie = false;
        winner = -1;
        gameInProgress = true;
    }
    
    /**
     * Check if there is a winner, i.e. three pieces of the same kind in a row.
     */
    private boolean winner() {
        if (board[0][0] != ' ' && 
                (board[0][0] == board[1][1]&& board[1][1] == board[2][2]))
            return true;
        if (board[0][2] != ' ' && 
                (board[0][2] == board[1][1]&& board[1][1] == board[2][0]))
            return true;
        for (int row = 0; row < 3; row++) {
            if (board[row][0] != ' ' &&
                    (board[row][0] == board[row][1] && board[row][1] == board[row][2]))
                return true;
        }
        for (int col = 0; col < 3; col++) {
            if (board[0][col] != ' ' &&
                    (board[0][col] == board[1][col] && board[1][col] == board[2][col]))
                return true;
        }
        return false;
    }
    
    /**
     * Check if the board is full.  (This is called after the winner method
     * has returned false, so a full board means that the game is a tie.)
     */
    private boolean tie() {
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                if (board[i][j] == ' ')
                    return false;
        return true;
    }
    
}
