import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
/**
* This is a modified version of the TicTacToe applet shipped with
* Java 2 Software Development Kit. Instead of drawing the board and
* pieces by way of overriding paint, this uses
* JEditorPane with an html document. The html document is updated via
* the HTMLDocument method setOuterHTML.
*/
public class TicTacToe extends MouseAdapter {
private static final String computerHTML = "
";
private static final String emptyMoveHTML = "
";
private static final String userHTML = "
";
private static final String yourMoveHTML = "Status: Your Move";
private static final String wonHTML = "Status: I WON!";
private static final String lostHTML = "Status: You WON!";
private static final String stalemateHTML = "Status: Stalemate";
/**
* The squares in order of importance...
*/
private static final int moves[] = {4, 0, 2, 6, 8, 1, 3, 5, 7};
/**
* The winning positions.
*/
private static boolean won[] = new boolean[1 << 9];
private static final int DONE = (1 << 9) - 1;
private static final int OK = 0;
private static final int WIN = 1;
private static final int LOSE = 2;
private static final int STALEMATE = 3;
private int white;
private int black;
private JEditorPane jep;
private HTMLDocument htmlDoc;
private JFrame frame;
/**
* Mark all positions with these bits set as winning.
*/
static void isWon(int pos) {
for (int i = 0 ; i < DONE ; i++) {
if ((i & pos) == pos) {
won[i] = true;
}
}
}
/**
* Initialize all winning positions.
*/
static {
isWon((1 << 0) | (1 << 1) | (1 << 2));
isWon((1 << 3) | (1 << 4) | (1 << 5));
isWon((1 << 6) | (1 << 7) | (1 << 8));
isWon((1 << 0) | (1 << 3) | (1 << 6));
isWon((1 << 1) | (1 << 4) | (1 << 7));
isWon((1 << 2) | (1 << 5) | (1 << 8));
isWon((1 << 0) | (1 << 4) | (1 << 8));
isWon((1 << 2) | (1 << 4) | (1 << 6));
}
public TicTacToe() {
new ImageIcon("circle2.gif");
new ImageIcon("cross2.gif");
frame = new JFrame("TicTacToe II");
jep = new JEditorPane();
jep.setEditable(false);
jep.setEditorKit(new TicTacToeEditorKit());
try {
jep.setPage(getClass().getResource("TicTacToe.html"));
} catch (java.io.IOException ioe) {}
htmlDoc = (HTMLDocument)jep.getDocument();
frame.getContentPane().add(new JScrollPane(jep));
jep.addMouseListener(this);
frame.pack();
frame.setSize(new Dimension(600, 800));
frame.show();
}
/**
* Compute the best move for white.
* @return the square to take
*/
private int bestMove(int white, int black) {
int bestmove = -1;
loop:
for (int i = 0 ; i < 9 ; i++) {
int mw = moves[i];
if (((white & (1 << mw)) == 0) && ((black & (1 << mw)) == 0)) {
int pw = white | (1 << mw);
if (won[pw]) {
// white wins, take it!
return mw;
}
for (int mb = 0 ; mb < 9 ; mb++) {
if (((pw & (1 << mb)) == 0) && ((black & (1 << mb)) == 0)) {
int pb = black | (1 << mb);
if (won[pb]) {
// black wins, take another
continue loop;
}
}
}
// Neither white nor black can win in one move, this will do.
if (bestmove == -1) {
bestmove = mw;
}
}
}
if (bestmove != -1) {
return bestmove;
}
// No move is totally satisfactory, try the first one that is open
for (int i = 0 ; i < 9 ; i++) {
int mw = moves[i];
if (((white & (1 << mw)) == 0) && ((black & (1 << mw)) == 0)) {
return mw;
}
}
// No more moves
return -1;
}
/**
* User move. This will invoke doYourMove to handle
* updating the board if the move is legal.
*
* @return true if legal
*/
private boolean yourMove(int m) {
if ((m < 0) || (m > 8)) {
return false;
}
if (((black | white) & (1 << m)) != 0) {
return false;
}
doYourMove(m);
return true;
}
/**
* Computer move, this will invoke doMyMove if
* the move is legal.
*
* @return true if legal
*/
private boolean myMove() {
if ((black | white) == DONE) {
return false;
}
int best = bestMove(white, black);
doMyMove(best);
return true;
}
/**
* Figure what the status of the game is.
*/
private int status() {
if (won[white]) {
return WIN;
}
if (won[black]) {
return LOSE;
}
if ((black | white) == DONE) {
return STALEMATE;
}
return OK;
}
/**
* Resets the board to an empty state.
*/
public void newGame() {
for (int counter = 0; counter < 9; counter++) {
toggleBoardElementHTML(getBoardElement(counter), emptyMoveHTML);
}
toggleStatus(yourMoveHTML);
black = white = 0;
}
/**
* Invoked when the mouse is released. This will attempt to create a new
* move for the user based on where the mouse is released.
*/
public void mouseReleased(MouseEvent me) {
if (me.getClickCount() == 1) {
int position;
if (status() != OK ||
(position = getBoardPosition(me.getX(), me.getY())) == -1 ||
!yourMove(position)) {
Toolkit.getDefaultToolkit().beep();
}
}
}
/**
* Invoked to do a move for the user. The html document is updated
* as a result of the user moving to position position.
* If no one has won, myMove is invoked to handle the
* computers choice.
*/
private void doYourMove(int position) {
black |= 1 << position;
// Update the position
Element e = getBoardElement(position);
toggleBoardElementHTML(e, userHTML);
// Check the current status
switch(status()) {
case WIN:
won();
break;
case LOSE:
lost();
break;
case STALEMATE:
statelmate();
break;
default:
if (myMove()) {
// We choose a spot, see if the game has ended.
switch (status()) {
case WIN:
won();
break;
case LOSE:
lost();
break;
case STALEMATE:
statelmate();
break;
default:
break;
}
}
break;
}
}
/**
* Updates the board in response to the computer choosing position
* position.
*/
private void doMyMove(int position) {
Element e = getBoardElement(position);
toggleBoardElementHTML(e, computerHTML);
white |= 1 << position;
}
/**
* Invoked when the computer wins.
*/
private void won() {
toggleStatus(wonHTML);
}
/**
* Inovked when the computer loses.
*/
private void lost() {
toggleStatus(lostHTML);
}
/**
* Invoked when the game has resulted in a stalemate.
*/
private void statelmate() {
toggleStatus(stalemateHTML);
}
/**
* Togglets the status of game to show the passed in html string.
*/
private void toggleStatus(String newHTML) {
Element e = htmlDoc.getElement("status");
try {
htmlDoc.setInnerHTML(e, newHTML);
}
catch (BadLocationException ble) {
System.out.println("BLE: " + ble);
}
catch (IOException ioe) {
System.out.println("IOE: " + ioe);
}
}
/**
* Returns the element representing position position.
*/
private Element getBoardElement(int position) {
return htmlDoc.getElement(Integer.toString(position));
}
/**
* Resets the html contents of the Element e to
* the html string html.
*/
private void toggleBoardElementHTML(Element e, String html) {
try {
Object id = e.getAttributes().getAttribute(HTML.Attribute.ID);
int insertIndex = html.indexOf(">");
html = html.substring(0, insertIndex) + " id=" + id +
html.substring(insertIndex);
htmlDoc.setOuterHTML(e, html);
}
catch (BadLocationException ble) {
System.out.println("BLE: " + ble);
}
catch (java.io.IOException ioe) {
System.out.println("IOE: " + ioe);
}
}
/**
* Returns the board position for the element at location x,
* y. This will return -1 if the passed in location does not
* represent a spot on the board.
*/
private int getBoardPosition(int x, int y) {
// Determine the offset for the passed in x, y location
Position.Bias bias[] = new Position.Bias[1];
int offset = jep.getUI().viewToModel(jep, new Point(x, y), bias);
// A backward bias typically (at least in normal left to right text)
// indicates an end of line condition. The passed in point was
// beyond the visible region of the line. In which case the backward
// bias indicates the location is at the end offset of the character
// element. Since we will be using getCharacterElement followed by
// a check of the bounds we subtract one from the offset so that
// getCharacterElement returns the Element representing the end of
// line and NOT the next line.
if (offset > 0 && bias[0] == Position.Bias.Backward) {
offset--;
}
// Get the character Element at that location, and find the
// corresponding TD cell.
Element e = htmlDoc.getCharacterElement(offset);
while (e != null && e.getAttributes().getAttribute
(StyleConstants.NameAttribute) != HTML.Tag.TD) {
e = e.getParentElement();
}
if (e != null) {
// Check that the location is really inside the table cell.
Rectangle bounds;
try {
bounds = jep.getUI().modelToView(jep, e.getStartOffset(),
Position.Bias.Forward);
bounds = bounds.union(jep.getUI().modelToView
(jep, e.getEndOffset(),
Position.Bias.Backward));
if (bounds.contains(x, y)) {
// found it
Object boardLocation = e.getAttributes().getAttribute
(HTML.Attribute.ID);
if (boardLocation != null) {
try {
return Integer.parseInt((String)boardLocation);
} catch (NumberFormatException nfe) {
}
}
}
} catch (BadLocationException ble) {
}
}
return -1;
}
/**
* A subclass of HTMLEditorKit that returns a different ViewFactory.
*/
private class TicTacToeEditorKit extends HTMLEditorKit {
public ViewFactory getViewFactory() {
return new TicTacToeFactory();
}
}
/**
* A subclass of the HTMLFactory that will create a
* TicTacToeFormView for INPUT Elements.
*/
private class TicTacToeFactory extends HTMLEditorKit.HTMLFactory {
public View create(Element e) {
Object o = e.getAttributes().getAttribute
(StyleConstants.NameAttribute);
if (o == HTML.Tag.INPUT) {
return new TicTacToeFormView(e);
}
return super.create(e);
}
}
/**
* A subclass of FormView that invokes newGame
* when the action is performed (the user clicks on the button).
*/
private class TicTacToeFormView extends FormView {
TicTacToeFormView(Element e) {
super(e);
}
public void actionPerformed(ActionEvent ae) {
newGame();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TicTacToe();
}
});
}
}