Pancet.java
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

/**
 * Title: Pancet<p>
 * Copyright: Copyright (c) 2005<p>
 * Description:<p>
 * Pancet is a game played with four bowls and eight balls.  Two
 * bowls are controlled by each player.  Each bowl starts with two
 * stones in it.  Each player takes turns picking up the stones out
 * of one of their bowls and distributing them clockwise around the
 * the bowls.  If the last stone lands in an empty bowl, then it
 * is removed from play.  If a player has no stones in either of
 * his bowls when it is his turn, then he loses.<p>
 * The computer version here pits a human player against the
 * program.  The program keeps a self pruning move tree of moves 
 * and learns as it plays.<p>
 * @author Mark Bondurant
 * @version 3.0
 */

public class Pancet extends Applet implements ActionListener,
        GameConstants, Runnable {
    // signifies initial state
    private boolean justStarting = true;

    private MediaTracker tracker = new MediaTracker(this);
    private Thread me = null;

    // the move library
    private MoveLib moveLibrary;

    // the display objects for the game board and controls
    private GameBoard board;
    private GameControls controls;
    private int gameBoard[] = new int[4];

    // the bowl images and their contents
    private Image bowls[] = new Image[9];

    /**
     * Initializes the applet.<br>
     * This sets up the screen layout, loads the bowl images, 
     * instantiates the control panel and  move library, and creates
     * the game's thread.
     */
    public void init() {
        // load bowl images
        for (int i = 0; i <= 8; i++) {
            bowls[i] = getImage(getCodeBase(),
                                "bowl" + i + ".gif");
            tracker.addImage(bowls[i], 0);
        }

        try {
            tracker.waitForAll();
        } catch (InterruptedException e) {
            System.exit(0);
        }

        if (tracker.isErrorAny())
            System.exit(0);

        for (int i = 0; i <= 8; i++) {
            tracker.removeImage(bowls[i]);
        }

        // set up move library
        moveLibrary = new MoveLib();

        // set up the game area
        setSize(400, 200);
        setLayout(new GridLayout(1, 0));
        setBackground(Color.white);

        board = new GameBoard(bowls[0]);
        controls = new GameControls(moveLibrary.storedMoves());

        add("BowlArea", board);
        add("Controls", controls);

        // point the game controls back here
        controls.connectControls(this);
    }

    /**
     * In order to maintain animation timing during button
     * ActionEvents, everything has to be run in an accessible
     * thread. Start the game off with the setup animation.
     */
    public void run() {
        resetBoard();
    }

    public void start() {
        if (me == null) {
            me = new Thread(this);
            me.start();
        }
    }

    public void stop() {
        me = null;
    }

    /**
     * This clears and refills the bowls one ball at a time
     * at the start of each game.  It gives the move library
     * a chance to catch up with after game purging and it 
     * looks nice.
     */
    private void resetBoard() {
        // animation - fill the bowls, one ball at a time
        for (int j = 0; j < 3; j++) {
            for (int i = 0; i < 4; i++) {
                board.setImage(i, bowls[j]);
                pause(150);
            }
        }

        // set the initial values for the game bowls
        gameBoard[0] = gameBoard[1] = gameBoard[2] = gameBoard[3] = 2;
    }

    
    /**
     * pause(ms) is a wrapper for sleep().
     * @param ms int
     * Sleep time in milliseconds
     */
    public static void pause(int ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            // it's OK if this is interrupted
        }
    }

    /**
     * Get Applet information
     */
    public String getAppletInfo() {
        return "Pancet; Mark Bondurant, written 1997, revised 2005";
    }

    /**
     * This catches the action events from the two buttons on the
     * control panel.  It controls the move cycle with each button
     * click performing a human and then a computer move.  The game
     * can end and restart in the the process of performing this cycle.
     * Therefore the last player to move in the last game will be the
     * second in the new game.
     * <p>
     * Move validation is performed here.  If the human player
     * attempts to pick an empty bowl, the cycle is broken and the
     * message to try again is sent back.
     * @param ae ActionEvent
     * This is going specify the bowl D or bowl C buttons
     */
    public void actionPerformed(ActionEvent ae) {
        final ActionEvent myEvent;
        myEvent = ae;

        Runnable r = new Runnable() {
            public void run() {
                boolean goResponse = true;

                if (myEvent.getActionCommand().equals("Bowl C")) {
                    if (gameBoard[BOWL_C] == 0) {
                        controls.setMessage("The bowl is empty!");
                        goResponse = false;
                    } else {
                        doMove(BOWL_C);
                    }
                } else {
                    if (gameBoard[BOWL_D] == 0) {
                        controls.setMessage("The bowl is empty!");
                        goResponse = false;
                    } else {
                        doMove(BOWL_D);
                    }
                }

                if (goResponse) {
                    // did we lose?
                    if (gameBoard[BOWL_A] + gameBoard[BOWL_B] == 0) {
                        controls.setMessage("I resign");
                        resetBoard();
                        moveLibrary.lostGame();
                        controls.setStoredMoves(moveLibrary.storedMoves());
                    }

                    // get our response from the move library
                    int m = moveLibrary.getMove(gameBoard);
                    pause(300);
                    if (m == BOWL_A) {
                        controls.setMessage("I move A");
                    } else {
                        controls.setMessage("I move B");
                    }
                    doMove(m);
                    controls.setStoredMoves(moveLibrary.storedMoves());

                    // did we win?
                    if (gameBoard[BOWL_C] + gameBoard[BOWL_D] == 0) {
                        controls.setMessage("I win!");
                        pause(400);
                        resetBoard();
                        moveLibrary.resetParent();
                    }
                }
            }
        };
        Thread t = new Thread(r);
        t.start();
    }

    /**
     * doMove() executes a single move, computer or human.  It
     * performs the move animation.
     * @param b int
     * This is the bowl that leads the move.  Its contents are
     * removed and distributed clockwise around the bowls.
     */
    private synchronized void doMove(int b) {
        // pick up the balls in the selected bowl
        int carry = gameBoard[b];
        gameBoard[b] = 0;
        board.setImage(b, bowls[0]);

        // distribute the balls around the bowls
        while (carry-- > 0) {
            if (++b > 3) b = 0;
            gameBoard[b]++;
            board.setImage(b, bowls[gameBoard[b]]);
            pause(300);
        }

        // drop a ball if last bowl is empty
        if (gameBoard[b] == 1) {
            gameBoard[b] = 0;
            board.setImage(b, bowls[0]);
            controls.setMessage("Ball dropped");
            pause(300);
        }
    }
}


Pancet.java