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);
}
}
}