Package com.barrybecker4.game.twoplayer.common.ui

Source Code of com.barrybecker4.game.twoplayer.common.ui.AbstractTwoPlayerBoardViewer

/** Copyright by Barry G. Becker, 2000-2011. Licensed under MIT License: http://www.opensource.org/licenses/MIT  */
package com.barrybecker4.game.twoplayer.common.ui;

import com.barrybecker4.game.common.GameContext;
import com.barrybecker4.game.common.Move;
import com.barrybecker4.game.common.board.Board;
import com.barrybecker4.game.common.player.PlayerList;
import com.barrybecker4.game.common.ui.ComputerMoveProgressBar;
import com.barrybecker4.game.common.ui.panel.GameChangedEvent;
import com.barrybecker4.game.common.ui.panel.GameChangedListener;
import com.barrybecker4.game.common.ui.viewer.GameBoardViewer;
import com.barrybecker4.game.common.ui.viewer.GamePieceRenderer;
import com.barrybecker4.game.twoplayer.common.ComputerMoveRequester;
import com.barrybecker4.game.twoplayer.common.TwoPlayerController;
import com.barrybecker4.game.twoplayer.common.TwoPlayerMove;
import com.barrybecker4.game.twoplayer.common.TwoPlayerViewModel;
import com.barrybecker4.optimization.parameter.ParameterArray;

import javax.swing.*;
import java.util.List;

/**
* This class contains a TwoPlayerController and displays the current state of the Game.
* The TwoPlayerController contains a Board which describes the game state.
* The game specific TwoPlayerController is created upon construction to be used internally.
* This class delegates to a boardRenderer to render the board and its pieces.
* There should be no references to swing classes outside this ui subpackage.
*    This class sends a GameChangedEvent after each move in case there are other
* components (like the GameTreeViewer) that need to updated based on the new board state.
* Since the computer can take a long time to think about its move before playing it, that
* computation is handled asynchronously in a separate thread. The way it works is that the
* TwoPlayerBoardViewer requests the next move from the controller (controller.requestComputerMove(p1)).
* The controller spawns a new thread to actually do the search for the next best move.
* When the next best move has been found, the controller calls computerMoved on the viewer
* (using the TwoPlayerViewerCallbackInterface that it implements) to let it know that the move has
* been found. The instructions in the computerMoved method are called using
*      <i>SwingUtilities.invokeLater()</>
* so that they get executed on the event dispatch thread as soon as the event dispatch
* thread is not busy doing something else (like refreshing the visible board).
* A progress bar is used to show how close the computer is to playing its next move.
* The progressbar updates by polling the controller for its search progress.
* If you open the GameTreeDialog to see the game tree, there are buttons to pause,
* step through, and continue processing the search as it is happening.
*
* This class displays the game and takes input from the user.
* It passes the user's input to the TwoPlayerController, which in turn tells the GameViewer
* things such as whether the user's move was legal or not, and also tells the GameViewer
* what the computer's move is.
*
@author Barry Becker
*/
public abstract class AbstractTwoPlayerBoardViewer extends GameBoardViewer
                                                   implements GameChangedListener, TwoPlayerViewModel {

    /** Responsible for showing move progress visually (with a progress bar). */
    private ComputerMoveRequester moveRequester_;

    private ComputerMoveProgressBar progressBar;

    /**
     * Show this cached board if we are in the middle of processing the next one
     * (to avoid concurrency problems)
     */
    private Board cachedGameBoard_ = null;

    /** we occasionally want to show the computer's considered next moves in the ui. */
    private TwoPlayerMove[] nextMoves_;

    /** playback a sequence of moves  */
    private MoveSequencePlayback moveSequencePlayer_;


    /**
     * Construct the viewer.
     */
    protected AbstractTwoPlayerBoardViewer() {
        controller_.setViewer(this);
        moveRequester_ = new ComputerMoveRequester(get2PlayerController());
        moveSequencePlayer_ = new MoveSequencePlayback(get2PlayerController());
    }

    /**
     * @return our game controller
     */
    public TwoPlayerController get2PlayerController() {
       return (TwoPlayerController)controller_;
    }

    /**
     * Set an optional progress bar for showing progress as the computer thinks about its next move.
     */
    @Override
    public void setProgressBar(ComputerMoveProgressBar progressBar) {
        this.progressBar = progressBar;
    }

    public TwoPlayerMove[] getNextMoves() {
        return nextMoves_;
    }

    void setNextMoves(TwoPlayerMove[] nextMoves) {
        nextMoves_ = nextMoves;
    }

    public GamePieceRenderer getPieceRenderer() {
        return getBoardRenderer().getPieceRenderer();
    }

    /**
     * run many games and use hill-climbing to find optimal weights.
     */
    private void runOptimization() {
        ParameterArray optimizedParams = get2PlayerController().runOptimization();

        JOptionPane.showMessageDialog(this, GameContext.getLabel("OPTIMIZED_WEIGHTS_TXT") +
                optimizedParams, GameContext.getLabel("OPTIMIZED_WEIGHTS"), JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * return the game to its original state.
     */
    @Override
    public void reset() {
        controller_.reset()//clear what's there and start over
        Board board = getBoard();
        commonReset(board);
    }

    /**
     * start over with a new game using the current options.
     */
    @Override
    public void startNewGame() {
        reset();
        TwoPlayerController controller = get2PlayerController();
        if (get2PlayerController().getTwoPlayerOptions().isAutoOptimize())  {
            runOptimization();
        }
        if (controller.getPlayers().allPlayersComputer() ) {
            controller.computerMovesFirst();
            doComputerMove( false );
        }
        else if ( controller.doesComputerMoveFirst() ) {
            // computer vs human opponent
            controller.computerMovesFirst();
            refresh();
        }
        // for all other cases a human moves first
        // see the mouseClicked callback method for details
    }

    /**
     * Register the human's move.
     * @param move the move to make.
     * @return true if the game is over now.
     */
    private boolean manMoves( TwoPlayerMove move) {

        TwoPlayerController c = get2PlayerController();
        if ( GameContext.getUseSound() ) {
            GameContext.getMusicMaker().playNote( c.getTwoPlayerOptions().getPreferredTone(), 45, 0, 200, 1000 );
        }
        // need to clear the cache, otherwise we may render a stale board.
        cachedGameBoard_ = null;
        c.manMoves(move);

        // need to refresh here to show man moves in human only game
        if (c.getPlayers().allPlayersHuman())  {
            refresh();
        }

        // Second arg was true, but then we did final update twice.
        boolean done = c.getSearchable().done(move, false);
        sendGameChangedEvent(move);
        return done;
    }

    /**
     * The computer plays against itself.
     */
    @Override
    public void doComputerVsComputerGame() {

        boolean done = false;

        // if player one has not already moved, make sure they do
        if (get2PlayerController().getMoveList().isEmpty())  {
            get2PlayerController().computerMovesFirst();
            boolean isEmpty = get2PlayerController().getMoveList().isEmpty();
            assert (!isEmpty) : "Error: null before search";
        }

        // don't run this on the event dispatch thread.
        while ( !done ) {
            done = doComputerMove( false );
            // if done the final move was placed
            if ( !done ) {
                done = doComputerMove( true );
            }
        }
    }

    /**
     * Make the computer move and show it on the screen.
     * Since this can take a very long time we will show the user a progress bar
     * to give feedback.
     *   The computer needs to search through vast numbers of moves to find the best one.
     * This will happen asynchronously in a separate thread so that the event dispatch
     * thread can return immediately and not lock up the user interface (UI).
     *   Some moves can be complex (like multiple jumps in checkers). For these
     * We should animate these types of moves so the human player does not get disoriented.
     *
     * @param isPlayer1 if the computer player now moving is player 1.
     * @return done always returns false unless auto optimizing
     */
    private boolean doComputerMove( boolean isPlayer1 ) {
        setCursor(waitCursor_);

        try {
            if (progressBar != null) {
                progressBar.doComputerMove(moveRequester_);
            }
            boolean done = moveRequester_.requestComputerMove(isPlayer1);
            repaint();
            return done;
        }
        catch  (AssertionError ae) {
            // if any errors occur during search, I want to save the state of the game to
            // a file so the error can be easily reproduced.
            assertFailed( ae );
        }

        return false;
    }

    /**
     * Currently this does not actually step forward just one search step, but instead
     * stops after PROGRESS_STEP_DELAY more milliseconds.
     */
    @Override
    public final void step() {
        progressBar.step();
    }

    /**
     * resume computation
     */
    @Override
    public final void continueProcessing()  {
        moveRequester_.continueProcessing();
    }

    /**
     * Called when the controller has found the computer's move (usually after a long asynchronous search).
     * The runnable body will run on the event-dispatch thread when the search has completed.
     * @param m the move that was selected by the computer.
     */
    @Override
    public void computerMoved(final Move m) {

        final Runnable postMoveCleanup = new PostMoveCleanup(m);
        SwingUtilities.invokeLater(postMoveCleanup);
    }

    /**
     * Implements the GameChangedListener interface.
     * Called when the game has changed in some way
     * @param evt change event
     */
    @Override
    public void gameChanged(GameChangedEvent evt) {
        TwoPlayerController c = get2PlayerController();
        assert c == evt.getController();

        // note: we don't show the winner dialog if we are having the computer play against itself.
        if (c.getSearchable().done((TwoPlayerMove)evt.getMove(), true)
                && c.getTwoPlayerOptions().getShowGameOverDialog()) {
            showWinnerDialog();
            //c.reset();
        }
        else {
            if (get2PlayerController().getPlayers().allPlayersComputer() && evt.getMove() != null) {
                continuePlay((TwoPlayerMove)evt.getMove());
            }
        }
    }

    /**
     * let the computer go next if one of the players is a computer.
     *
     * @param move the current move (it must not be null)
     * @return false if the game is at an end, otherwise return true
     */
     public final boolean continuePlay( TwoPlayerMove move ) {
         boolean done;
         TwoPlayerController controller = get2PlayerController();
         if (controller.getPlayers().allPlayersComputer()) {
             refresh();
             done = doComputerMove(!move.isPlayer1());
         }
         else {
             if ( controller.isPlayer1sTurn() ) {
                 assert !controller.isProcessing();
                 done = manMoves( move );
                 if ( !controller.getPlayers().getPlayer2().isHuman() && !done )  {
                     done = doComputerMove( false );
                 }
             }
             else { // player 2s turn
                 done = manMoves( move );
                 if ( !controller.getPlayers().getPlayer1().isHuman() && !done )  {
                     done = doComputerMove( true );
                 }
             }
         }
         return !done;
     }


    /**
     * some moves require that the human players be given some kind of notification.
     * @param m the last move made
     */
    protected void warnOnSpecialMoves( TwoPlayerMove m )  {
        if (m == null) {
            return;
        }
        if (m.isPassingMove() && !get2PlayerController().getPlayers().allPlayersComputer())
            JOptionPane.showMessageDialog( this,
                    GameContext.getLabel("COMPUTER_PASSES"),
                    GameContext.getLabel("INFORMATION"),
                    JOptionPane.INFORMATION_MESSAGE );
    }

    /**
     * return the game to its state before the last human move.
     */
    public void undoLastManMove()  {
        TwoPlayerController c = get2PlayerController();
        PlayerList players = c.getPlayers();
        if ( players.allPlayersComputer() )
            return;
        Move move = c.undoLastMove();
        if ( move != null ) {
            undoneMoves_.add( move );
            if ( !players.allPlayersHuman() ) {
                undoneMoves_.add( c.undoLastMove() );
            }
            refresh();
        }
        else
            JOptionPane.showMessageDialog( this,
                    GameContext.getLabel("NO_MOVES_TO_UNDO"),
                    GameContext.getLabel("WARNING"),
                    JOptionPane.WARNING_MESSAGE );
    }

    /**
     * redo the last human player's move.
     */
    public void redoLastManMove()  {
        TwoPlayerController c = get2PlayerController();
        PlayerList players = c.getPlayers();
        if ( undoneMoves_.isEmpty() ) {
            JOptionPane.showMessageDialog( null,
                    GameContext.getLabel("NO_MOVES_TO_REDO"),
                    GameContext.getLabel("WARNING"),
                    JOptionPane.WARNING_MESSAGE );
            return;
        }
        if ( players.allPlayersComputer() )
            return;
        c.makeMove(undoneMoves_.removeLast());
        if ( !players.allPlayersHuman() ) {
            c.makeMove(undoneMoves_.removeLast());
        }
        refresh();
    }


    public final synchronized void showMoveSequence( List moveSequence ) {
        showMoveSequence( moveSequence, getController().getNumMoves() );
    }

    final synchronized void showMoveSequence( List moveSequence, int numMovesToBackup) {
        showMoveSequence(moveSequence, numMovesToBackup, null);
    }

    public final synchronized void showMoveSequence( List moveSequence, int numMovesToBackup,
                                                     TwoPlayerMove[] nextMoves) {
        moveSequencePlayer_.makeMoveSequence( moveSequence, numMovesToBackup);
        setNextMoves(nextMoves);
        refresh();
    }

    /**
     * @return   the message to display at the completion of the game.
     */
    @Override
    protected String getGameOverMessage() {
        return new GameOverMessage(get2PlayerController()).getText();
    }

    /**
     * @return the cached game board if we are in the middle of processing.
     */
    @Override
    public Board getBoard() {
       TwoPlayerController c = get2PlayerController();

       if (cachedGameBoard_ == null) {
           cachedGameBoard_ = (Board)c.getBoard().copy();
       }
       if (c.isProcessing() && !c.getTwoPlayerOptions().isAutoOptimize()) {
           return cachedGameBoard_;
       }
       else {
           return (Board)c.getBoard();
       }
    }

    private class PostMoveCleanup implements Runnable {
        private final Move lastMove;

        public PostMoveCleanup(Move lastMove) {
            this.lastMove = lastMove;
        }

        @Override
        public void run() {

            setCursor( origCursor_ );
            if ( GameContext.getUseSound() )
                GameContext.getMusicMaker().playNote(
                        get2PlayerController().getTwoPlayerOptions().getPreferredTone(), 45, 0, 200, 1000 );
            showLastMove();
            cachedGameBoard_ = null;
            if (!get2PlayerController().getTwoPlayerOptions().isAutoOptimize()) {
                // show a pop-up for certain exceptional cases.
                // For example, in chess we warn on a checking move.
                warnOnSpecialMoves((TwoPlayerMove) lastMove);
                sendGameChangedEvent(lastMove);
            }
            if (progressBar != null)
                progressBar.cleanup();
       }
    }
}
TOP

Related Classes of com.barrybecker4.game.twoplayer.common.ui.AbstractTwoPlayerBoardViewer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.