// Class ClickerController is the controller for this application. The idea
// is that the computer and the user alternate turns, clicking where circles
// should appear. This class constructs a single view that has a mouse
// listener. Obviously the computer doesn't actually click anywhere, so this
// class simulates random computer moves when appropriate, including a short
// delay to make it seem like the computer had to decide where to click.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.io.*;
import javax.swing.Timer;
public class ClickerController {
private JFrame frame; // top-level application frame
private ClickerModel model; // the model
private ClickerPanel panel; // the view
private boolean userFirst; // is the user supposed to go first?
private Random random; // used to make random computer moves
private Timer timer; // used to delay the computer move
private boolean clickOkay; // whether we are accepting user clicks
private Action save; // save current state
private Action restore; // restore from previous save
private JPopupMenu pop; // popup menu
// Constructs a controller for the given model, with the second argument
// indicating whether or not the user should go first.
public ClickerController(ClickerModel model, boolean userFirst) {
this.model = model;
frame = new JFrame("CSE190L Clicker");
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// construct the view, add it to the frame, register it with the model,
// set up its one control (a mouse listener)
panel = new ClickerPanel(model);
frame.add(panel, BorderLayout.CENTER);
model.addListener(panel);
panel.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
handleClick(e);
}
});
random = new Random();
this.userFirst = userFirst;
timer = new Timer(2000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
computerMove();
clickOkay = true;
}
});
timer.setRepeats(false);
addControls();
}
// launch the program, asking the computer for a move if it is first
public void run() {
frame.setVisible(true);
if (!userFirst)
computerMove();
clickOkay = true;
pop.show(panel, 50, 50);
pop.repaint();
}
// Handle a mouse click from the user. The user might have clicked while
// the computer was making a move. Such clicks should be ignored. We
// accomplish this by keeping a flag indicating whether user clicks are
// okay. If not, we ignore them.
private void handleClick(MouseEvent e) {
// ignore user clicks that happened while the computer was moving
if (!clickOkay)
return;
save.setEnabled(true);
model.add(e.getPoint(), Player.HUMAN);
clickOkay = false;
timer.start();
}
// Makes a random computer move, "clicking" on a random position that will
// lead to a circle that is within the bounds of the view.
private void computerMove() {
int x = ClickerPanel.RADIUS +
random.nextInt(panel.getWidth() - 2 * ClickerPanel.RADIUS);
int y = ClickerPanel.RADIUS +
random.nextInt(panel.getHeight() - 2 * ClickerPanel.RADIUS);
model.add(new Point(x, y), Player.COMPUTER);
}
private void addControls() {
// initialize buttons and actions
save = new SaveAction();
restore = new RestoreAction();
restore.setEnabled(new File("clicker.dat").exists());
// construct a menu
JMenuBar bar = new JMenuBar();
JMenu menu = new JMenu("File");
menu.add(save);
menu.add(restore);
bar.add(menu);
frame.setJMenuBar(bar);
// construct a panel
JPanel p = new JPanel();
p.add(new JButton(save));
p.add(new JButton(restore));
frame.add(p, BorderLayout.SOUTH);
// construct a toolbar
JToolBar tbar = new JToolBar();
tbar.add(save);
tbar.add(restore);
frame.add(tbar, BorderLayout.NORTH);
// construct a popup menu
pop = new JPopupMenu();
pop.add(save);
pop.add(restore);
panel.setComponentPopupMenu(pop);
}
private class SaveAction extends AbstractAction {
public SaveAction() {
super("Save");
putValue(Action.SMALL_ICON, new ImageIcon("save.gif"));
putValue(Action.MNEMONIC_KEY, new Integer('S'));
putValue(Action.SHORT_DESCRIPTION, "save current points");
putValue(Action.ACCELERATOR_KEY,
KeyStroke.getKeyStroke(KeyEvent.VK_S,
InputEvent.CTRL_MASK));
}
public void actionPerformed(ActionEvent e) {
try {
FileOutputStream fos = new FileOutputStream("clicker.dat");
ObjectOutputStream output = new ObjectOutputStream(fos);
output.writeObject(model);
} catch (IOException error) {
throw new RuntimeException(error.toString());
}
restore.setEnabled(true);
setEnabled(false);
}
}
private class RestoreAction extends AbstractAction {
public RestoreAction() {
super("Restore");
putValue(Action.SMALL_ICON, new ImageIcon("open.gif"));
putValue(Action.MNEMONIC_KEY, new Integer('R'));
putValue(Action.SHORT_DESCRIPTION, "restore previous points");
putValue(Action.ACCELERATOR_KEY,
KeyStroke.getKeyStroke(KeyEvent.VK_R,
InputEvent.CTRL_MASK));
}
public void actionPerformed(ActionEvent e) {
timer.stop();
try {
FileInputStream fis = new FileInputStream("clicker.dat");
ObjectInputStream input = new ObjectInputStream(fis);
model = (ClickerModel) input.readObject();
} catch (Exception error) {
throw new RuntimeException(error.toString());
}
panel.update(model);
model.addListener(panel);
clickOkay = true;
}
}
}