|
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
/**
* Tic tac toe applet.
* <p>
* Protocol:
* <ul>
* <li>request a game: 0x00
* <li>reject or quit a game: 0x01
* <li>playing a game: 18 bits, for each 2 bits, 00 = clear, 01 = X, 10 = O
* </ul>
* <p>
* The protocol sucks and there are probably lots of race conditions.
* This isn't a good example, and it isn't good code, but it is an example.
*
* @author Quowong P Liu <qpliu@users.sourceforge.net>
* @version $Id: TicTacToe.java,v 1.2 2000/09/11 19:24:25 qpliu Exp $
*/
public class TicTacToe extends Applet
{
private static final byte REQUEST_GAME = 0;
private static final byte QUIT_GAME = 1;
private static final int STATE_NO_GAME = 0;
private static final int STATE_INVITING = 1;
private static final int STATE_PLAYING_MY_TURN = 2;
private static final int STATE_PLAYING_WAITING = 3;
private Image[] grid = new Image[9];
private Image X;
private Image O;
private int state = STATE_NO_GAME;
private User opponent = null;
private JPanel panel;
private TTT ttt;
private JPanel noGame;
private JPanel inviting;
private JPanel myTurn;
private JPanel waiting;
private JComboBox users;
private JLabel opponentLabel;
private JButton play;
private JButton quit;
private class TTT extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.black);
Dimension size = getSize(null);
int w1 = (int) (size.getWidth()/3.0);
int w2 = (int) (2.0*size.getWidth()/3.0);
int w3 = (int) size.getWidth();
int h1 = (int) (size.getHeight()/3.0);
int h2 = (int) (2.0*size.getHeight()/3.0);
int h3 = (int) size.getHeight();
g.drawLine(0, h1, w3, h1);
g.drawLine(0, h2, w3, h2);
g.drawLine(w1, 0, w1, h3);
g.drawLine(w2, 0, w2, h3);
for (int i = 0; i < 9; i++)
if (grid != null)
g.drawImage(grid, (i%3)*w1, (i/3)*h1, w1, h1, null);
g.setColor(Color.red);
for (int i = 0; i < 3; i++) {
if (grid != null
&& grid == grid[i+3] && grid == grid[i+6])
g.drawLine(w1/2 + i*w1, h1/3, w1/2 + i*w1, h3 - h1/3);
if (grid[3*i] != null
&& grid[3*i] == grid[3*i+1] && grid[3*i] == grid[3*i+2])
g.drawLine(w1/3, h1/2 + i*h1, w3 - w1/3, h1/2 + i*h1);
}
if (grid[4] != null) {
if (grid[0] == grid[4] && grid[4] == grid[8])
g.drawLine(w1/3, h1/3, w3 - w1/3, h3 - h1/3);
if (grid[2] == grid[4] && grid[4] == grid[6])
g.drawLine(w3 - w1/3, h1/3, w1/3, h3 - h1/3);
}
}
}
private Runnable updatePanel = new Runnable() {
public void run()
{
switch (state) {
case STATE_NO_GAME:
opponentLabel.setText("No game");
noGame.add(opponentLabel, BorderLayout.WEST);
noGame.add(users, BorderLayout.CENTER);
noGame.add(play, BorderLayout.EAST);
panel.removeAll();
panel.add(noGame, BorderLayout.NORTH);
panel.add(ttt, BorderLayout.CENTER);
break;
case STATE_INVITING:
opponentLabel.setText("Challenging " + opponent.getNick());
inviting.add(opponentLabel, BorderLayout.WEST);
inviting.add(users, BorderLayout.CENTER);
inviting.add(quit, BorderLayout.EAST);
panel.removeAll();
panel.add(inviting, BorderLayout.NORTH);
panel.add(ttt, BorderLayout.CENTER);
break;
case STATE_PLAYING_WAITING:
opponentLabel.setText("Playing: " + opponent.getNick());
waiting.add(opponentLabel, BorderLayout.WEST);
waiting.add(quit, BorderLayout.EAST);
panel.removeAll();
panel.add(waiting, BorderLayout.NORTH);
panel.add(ttt, BorderLayout.CENTER);
break;
case STATE_PLAYING_MY_TURN:
opponentLabel.setText
("My turn, playing: " + opponent.getNick());
myTurn.add(opponentLabel, BorderLayout.WEST);
myTurn.add(quit, BorderLayout.EAST);
panel.removeAll();
panel.add(myTurn, BorderLayout.NORTH);
panel.add(ttt, BorderLayout.CENTER);
}
panel.repaint();
}
};
public TicTacToe(AppletContext appletContext)
{
super(appletContext);
panel = new JPanel();
setComponent(panel);
panel.setLayout(new BorderLayout());
panel.setBorder(new EmptyBorder(5, 5, 5, 5));
ttt = new TTT();
panel.add(ttt, BorderLayout.CENTER);
ttt.setPreferredSize(new Dimension(150, 150));
ttt.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e)
{
if (state != STATE_PLAYING_MY_TURN)
return;
Dimension size = panel.getSize(null);
int w1 = (int) (size.getWidth()/3.0);
int h1 = (int) (size.getHeight()/3.0);
int x = e.getX()/w1;
int y = e.getY()/h1;
if (x > 2 || y > 2)
return;
if (grid[x+3*y] != null)
return;
int count = 0;
for (int i = 0; i < 9; i++)
if (grid != null)
count++;
grid[x+3*y] = count%2 == 0 ? X : O;
sendAppletOutput(opponent, getGrid());
state = gameOver() ? STATE_NO_GAME : STATE_PLAYING_WAITING;
updatePanel.run();
}
});
X = new ImageIcon(getClass().getResource("X.png")).getImage();
O = new ImageIcon(getClass().getResource("O.png")).getImage();
opponentLabel = new JLabel();
quit = new JButton("Quit");
quit.addActionListener(new AbstractAction("quit") {
public void actionPerformed(ActionEvent e)
{
sendAppletOutput(opponent, new byte[] { QUIT_GAME });
state = STATE_NO_GAME;
updatePanel.run();
}
});
play = new JButton("Play game");
play.addActionListener(new AbstractAction("play") {
public void actionPerformed(ActionEvent e)
{
opponent = getUser((String) users.getSelectedItem());
if (opponent == null)
return;
sendAppletOutput(opponent, new byte[] { REQUEST_GAME });
state = STATE_INVITING;
updatePanel.run();
}
});
users = new JComboBox();
users.setEditable(false);
noGame = new JPanel();
panel.add(noGame, BorderLayout.NORTH);
noGame.setLayout(new BorderLayout());
/* west: no game label, center: users, east: new game button */
noGame.add(opponentLabel, BorderLayout.WEST);
noGame.add(users, BorderLayout.CENTER);
noGame.add(play, BorderLayout.EAST);
inviting = new JPanel();
inviting.setLayout(new BorderLayout());
/* west: opponentLabel, center: users, east: quit */
myTurn = new JPanel();
myTurn.setLayout(new BorderLayout());
/* west: opponentLabel, east: quit */
waiting = new JPanel();
waiting.setLayout(new BorderLayout());
/* west: opponentLabel, east: quit */
updateUsers();
SwingUtilities.invokeLater(updatePanel);
}
protected void receiveApplet
(User user, String sourceApplet, Channel channel, byte[] data)
{
if (channel != null || !sourceApplet.equals(getClass().getName()))
return;
if (data.length == 1)
switch (data[0]) {
case REQUEST_GAME:
gameRequested(user);
return;
case QUIT_GAME:
gameQuit(user);
return;
default:
return;
}
switch (state) {
case STATE_INVITING:
case STATE_PLAYING_WAITING:
break;
default:
return;
}
if (data.length != 3 || user != opponent)
return;
Image[] images = new Image[] { null, X, O, null };
for (int i = 0; i < 9; i++)
grid = images[(data[i/4]>>(2*(3-i%4)))&3];
state = gameOver() ? STATE_NO_GAME : STATE_PLAYING_MY_TURN;
SwingUtilities.invokeLater(updatePanel);
}
private boolean gameOver()
{
for (int i = 0; i < 3; i++) {
if (grid != null
&& grid == grid[i+3] && grid == grid[i+6])
return true;
if (grid[3*i] != null
&& grid[3*i] == grid[3*i+1] && grid[3*i] == grid[3*i+2])
return true;
}
if (grid[4] == null)
return false;
if (grid[0] == grid[4] && grid[4] == grid[8])
return true;
if (grid[2] == grid[4] && grid[4] == grid[6])
return true;
return grid[0] != null && grid[1] != null && grid[2] != null
&& grid[3] != null && grid[4] != null && grid[5] != null
&& grid[6] != null && grid[7] != null && grid[8] != null;
}
private void gameRequested(User user)
{
switch (state) {
case STATE_NO_GAME:
opponent = user;
for (int i = 0; i < 9; i++)
grid = null;
state = STATE_PLAYING_MY_TURN;
break;
case STATE_INVITING:
if (opponent != user) {
sendAppletOutput(user, new byte[] { QUIT_GAME });
return;
}
state = STATE_PLAYING_MY_TURN;
break;
default:
if (opponent != user) {
sendAppletOutput(user, new byte[] { QUIT_GAME });
return;
}
state = STATE_PLAYING_MY_TURN;
break;
}
SwingUtilities.invokeLater(updatePanel);
}
private void gameQuit(User user)
{
if (user != opponent)
return;
opponent = null;
state = STATE_NO_GAME;
SwingUtilities.invokeLater(updatePanel);
}
private byte[] getGrid()
{
byte[] b = new byte[] { 0, 0, 0 };
for (int i = 0; i < 9; i++)
b[i/4] |= (grid == X ? 1 : grid == O ? 2 : 0) << 2*(3-i%4);
return b;
}
private void updateUsers()
{
Object selected = users.getSelectedItem();
users.removeAllItems();
Map userMap = getUsers();
int count = 0;
for (Iterator i = userMap.values().iterator(); i.hasNext(); ) {
User user = (User) i.next();
if (user.getNick().equals(getNick()))
continue;
if (!user.hasApplets())
continue;
users.addItem(user.getNick());
if (user.getNick().equals(selected))
users.setSelectedIndex(count);
count++;
}
if (opponent != null && !opponent.canSendTo())
gameQuit(opponent);
}
private final Runnable updateUsersLater = new Runnable() {
public void run()
{
updateUsers();
}
};
public void usersChanged()
{
SwingUtilities.invokeLater(updateUsersLater);
}
} |
|