import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
/**
Graphical display of the Darwin Game 2.0 simulator for single matches.
Run with:
<pre>
java Darwin [-3D | -2D] <i>mapfile</i> Creature0 Creature1 ...
</pre>
e.g.,
<pre>
java Darwin -3D ns_arena.txt Rover Pirate
</pre>
@sa Tournament
<p>Morgan McGuire
<br>morgan@cs.williams.edu
<br>http://graphics.cs.williams.edu
*/
public class Darwin extends JFrame {
public final static String SYNTAX_HELP =
"java Darwin [-3D | -2D] mapname Class0 Class1 ...";
private Simulator simulator;
/** GUI elements for setting delay time. */
private JToggleButton[] speedButton = new JToggleButton[4];
final static private long MS = 1000000;
/** Delay time in nanoseconds corresponding to each of the speedButtons. */
final static private long[] delayTime = {100000 * MS, 200 * MS, 30 * MS, 1 * MS};
final private JLabel timeDisplay = new JLabel("0");
/** List of all current competitors */
private ArrayList<Class> remainingCompetitors = new ArrayList<Class>();
private Simulator.PopulationGraph populationGraph;
private boolean announcedTimeLimit = false;
private boolean announcedHalfTime = false;
private Debugger debugger;
private JFrame populationWindow;
private String mapFilename;
private String[] creatureClassNames;
final JToggleButton view2DButton = makeToggleToolButton("2D.gif");
final JToggleButton view3DButton = makeToggleToolButton("3D.gif");
private JLabel display;
public Darwin(boolean view3D, String mapFilename, String[] creatureClassNames) {
this.mapFilename = mapFilename;
this.creatureClassNames = creatureClassNames;
debugger = new Debugger();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Display the simulator
makeGUI();
if (view3D) {
view3DButton.setSelected(true);
}
System.setSecurityManager(new MaximumSecurityManager(new String[]{"readFileDescriptor"}));
reload();
// Render at most desiredFPS or once per time step
float desiredFPS = 20;
new java.util.Timer().schedule(new java.util.TimerTask() {
public void run() {
tick();
}}, 0, (int)(1000 / desiredFPS));
}
private synchronized void reload() {
if (simulator != null) {
if (simulator.isRunning()) {
simulator.stop();
}
populationWindow.getContentPane().removeAll();
simulator = null;
display.setIcon(null);
}
lastTime = -1;
remainingCompetitors.clear();
Class[] creatureClasses = new Class[creatureClassNames.length];
for (int i = 0; i < creatureClasses.length; ++i) {
try {
creatureClasses[i] = Simulator.loadClass(creatureClassNames[i]);
if (! remainingCompetitors.contains(creatureClasses[i])) {
remainingCompetitors.add(creatureClasses[i]);
}
} catch (Exception e) {
System.err.println("Warning: while loading " + creatureClassNames[i]);
e.printStackTrace();
creatureClasses[i] = null;
}
}
simulator = new Simulator(mapFilename, creatureClasses);
setTitle("The Darwin Game - " + simulator.getTitle());
simulator.setView3D(view3DButton.isSelected());
display.setIcon(simulator);
populationGraph = simulator.getPopulationGraph();
populationWindow.getContentPane().add(populationGraph);
populationWindow.pack();
speedButton[0].setSelected(true);
}
/** Reports the simulator version number. */
static public String getVersion() {
return Simulator.getVersion();
}
private int lastTime = -1;
private void tick() {
if ((simulator == null) || ! simulator.isRunning()) {
// The simulator is not live
return;
}
int t = simulator.getTime();
if (t == lastTime) {
// Nothing has happened since the last step
return;
} else {
synchronized (this) {
lastTime = t;
}
}
timeDisplay.setText("" + t);
repaint();
debugger.tick();
populationGraph.tick();
maybeAnnounceTime(t);
Simulator.Result result = simulator.getResult();
if (result != null) {
// Game over!
simulator.stop();
// Pause:
speedButton[0].setSelected(true);
simulator.setDelayTime(delayTime[0]);
say(result.result + " " + result.why);
// Show dialog
String message = result.result + "\n" + result.why + "\nin " + result.timeSteps + " time steps";
if (result.icon == null) {
JOptionPane.showMessageDialog(this, message, result.result, JOptionPane.PLAIN_MESSAGE);
} else {
JOptionPane.showMessageDialog(this, message, result.result, JOptionPane.PLAIN_MESSAGE,
new ImageIcon(result.icon));
}
return;
}
// Run elimination messages after checking for a winner so that we don't announce a final elimination
maybeAnnounceElimination();
}
private void maybeAnnounceTime(int t) {
if (! announcedHalfTime && (t > Simulator.TIME_STEP_LIMIT * 0.5)) {
say("Half-time!");
announcedHalfTime = true;
}
if (! announcedTimeLimit && (t > Simulator.TIME_STEP_LIMIT * 0.9)) {
say("Time limit approaching!");
announcedTimeLimit = true;
}
}
/** Check to see if a species was eliminated. */
private void maybeAnnounceElimination() {
for (int i = 0; i < remainingCompetitors.size(); ++i) {
Class c = remainingCompetitors.get(i);
int n = simulator.getCreatureCount(c);
if (n == 0) {
say(c.getName() + " was eliminated.");
remainingCompetitors.remove(i);
--i;
}
}
}
/** Speak text out loud on OS X */
static private void say(String message) {
boolean osx = System.getProperty("os.name").toUpperCase().contains("MAC OS X");
if (osx) {
try { Runtime.getRuntime().exec("say -v Zarvox \"" + message + "\""); } catch (Exception e) {}
}
}
/** Called from constructor. */
private void makeGUI() {
Container pane = getContentPane();
JToolBar controls = new JToolBar("Darwin Controls");
JButton reloadButton = makeToolButton("Redo24.gif");
controls.add(reloadButton);
controls.addSeparator(new Dimension(24, 24));
reloadButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
reload();
}});
{
view2DButton.setSelected(true);
view3DButton.setSelected(false);
ActionListener L = new ActionListener() {
public void actionPerformed(ActionEvent e) {
simulator.setView3D(view3DButton.isSelected());
Darwin.this.repaint();
}};
view2DButton.addActionListener(L);
view3DButton.addActionListener(L);
ButtonGroup group = new ButtonGroup();
group.add(view2DButton);
group.add(view3DButton);
controls.add(view2DButton);
controls.add(view3DButton);
}
controls.addSeparator(new Dimension(24, 24));
{
ButtonGroup speedGroup = new ButtonGroup();
ActionListener L = new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < speedButton.length; ++i) {
if (speedButton[i].isSelected()) {
simulator.setDelayTime(delayTime[i]);
}
}
}};
for (int i = 0; i < 4; ++i) {
speedButton[i] = makeToggleToolButton("speed" + i + ".gif");
speedButton[i].addActionListener(L);
speedGroup.add(speedButton[i]);
controls.add(speedButton[i]);
}
speedButton[0].setSelected(true);
}
controls.addSeparator(new Dimension(24, 24));
JButton debuggerButton = makeToolButton("Information24.gif");
debuggerButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
debugger.setVisible(true);
}});
controls.add(debuggerButton);
{
populationWindow = new JFrame("Population");
populationWindow.setAlwaysOnTop(true);
populationWindow.setVisible(false);
final JButton viewPopGraph = makeToolButton("Graph.gif");
viewPopGraph.setSelected(false);
ActionListener L = new ActionListener() {
public void actionPerformed(ActionEvent e) {
populationWindow.setVisible(true);
}};
viewPopGraph.addActionListener(L);
controls.add(viewPopGraph);
}
controls.addSeparator(new Dimension(40, 24));
JButton helpButton = makeToolButton("Help24.gif");
helpButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog
(Darwin.this,
getVersion() + "\n\nWilliams College\n" +
"http://cs.williams.edu/~morgan/cs136/darwin2.0\n\n" +
SYNTAX_HELP, "About The Darwin Game",
JOptionPane.PLAIN_MESSAGE, new ImageIcon("Flytrap-E.png"));
}});
controls.add(helpButton);
controls.addSeparator(new Dimension(24, 24));
Font fixedFont = new Font("Monospaced", Font.PLAIN, 16);
timeDisplay.setFont(fixedFont);
controls.add(new JLabel("Elapsed Time:"));
controls.add(timeDisplay);
pane.setLayout(new BorderLayout());
pane.add(controls, BorderLayout.PAGE_START);
// Map view in the center
display = new JLabel();
pane.add(display, BorderLayout.CENTER);
display.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
click(e.getX(), e.getY());
}});
setSize(1024, 768);
}
/** Called when the user clicks on the display */
private void click(int x, int y) {
if (simulator.getView3D()) {
// In 3D view; no click allowed
return;
}
Point offset = simulator.getGridDrawXY2D();
x = (x - offset.x) / Simulator.SCALE2D;
y = (y - offset.y) / Simulator.SCALE2D;
debugger.setCreature(simulator.getCreature(x, y));
}
static protected JToggleButton makeToggleToolButton(String icon) {
JToggleButton b = new JToggleButton(new ImageIcon(icon));
b.setSize(24, 24);
return b;
}
static protected JButton makeToolButton(String icon) {
JButton b = new JButton(new ImageIcon(icon));
b.setSize(24, 24);
return b;
}
public static void main(String[] arg) {
if (arg.length < 1) {
// Default arguments
System.out.println(getVersion());
System.out.println();
System.out.println("Syntax:");
System.out.println(" " + SYNTAX_HELP);
System.out.println();
String[] s = {"-3D", "ns_faceoff.txt", "Rover", "Pirate"};
arg = s;
}
int a = 0;
boolean view3D = false;
if (arg[a].toUpperCase().equals("-3D")) {
view3D = true;
++a;
} else if (arg[a].toUpperCase().equals("-2D")) {
view3D = false;
++a;
}
String mapname = arg[a];
++a;
String[] creatureClassNames = new String[arg.length - a];
System.arraycopy(arg, a, creatureClassNames, 0, creatureClassNames.length);
new Darwin(view3D, mapname, creatureClassNames).setVisible(true);
}
private class Debugger extends JFrame {
private Creature creature;
private JLabel authorLabel;
private JLabel classLabel;
private JLabel idLabel;
private JTextArea descriptionDisplay;
private JLabel iconLabel;
private JLabel positionLabel;
private JTextArea stringDisplay;
public Debugger() {
super("Creature Debugger");
setAlwaysOnTop(true);
JPanel pane = new JPanel();
getContentPane().add(new JScrollPane(pane));
pane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.FIRST_LINE_START;
iconLabel = new JLabel();
c.gridx = 0; c.gridy = 0;
classLabel = new JLabel();
pane.add(new JLabel("Class:"), c); ++c.gridx;
pane.add(classLabel, c); ++c.gridx;
c.gridheight = 4;
c.anchor = GridBagConstraints.LINE_END;
pane.add(iconLabel, c);
c.anchor = GridBagConstraints.FIRST_LINE_START;
c.gridheight = 1;
c.gridx = 0; ++c.gridy;
idLabel = new JLabel();
pane.add(new JLabel("ID:"), c); ++c.gridx;
pane.add(idLabel, c);
c.gridx = 0; ++c.gridy;
c.gridwidth = 1;
authorLabel = new JLabel();
pane.add(new JLabel("Author:"), c); ++c.gridx;
pane.add(authorLabel, c);
c.gridx = 0; ++c.gridy;
c.gridwidth = 1;
positionLabel = new JLabel();
pane.add(new JLabel("Location:"), c); ++c.gridx;
pane.add(positionLabel, c);
int W = 40;
int H = 33;
c.gridx = 0; ++c.gridy; c.gridwidth = 1;
descriptionDisplay = new JTextArea(3, W/2);
descriptionDisplay.setEditable(false);
descriptionDisplay.setLineWrap(true);
descriptionDisplay.setWrapStyleWord(true);
pane.add(new JLabel("Description:"), c); ++c.gridx;
c.gridwidth = 2;
c.fill = GridBagConstraints.HORIZONTAL;
pane.add(new JScrollPane(descriptionDisplay), c);
c.fill = GridBagConstraints.NONE;
c.gridx = 0; ++c.gridy; c.gridwidth = 1;
stringDisplay = new JTextArea(H, W);
stringDisplay.setFont(new Font("Monospaced", Font.PLAIN, 11));
pane.add(new JLabel("toString():"), c); ++c.gridx;
c.gridwidth = 2;
pane.add(new JScrollPane(stringDisplay), c);
stringDisplay.setEditable(false);
c.gridx = 1; ++c.gridy; c.gridwidth = 2;
pane.add(new JLabel("Click on a creature in 2D mode to debug"), c);
pack();
setSize(new Dimension(380, 565));
}
public synchronized void setCreature(Creature c) {
creature = c;
simulator.setSelectedCreature(c);
if (c != null) {
setTitle("Debugger - " + c.getClassName() + " " + c.getId());
classLabel.setText(c.getClassName());
idLabel.setText("" + c.getId());
authorLabel.setText(c.getAuthorName());
descriptionDisplay.setText(c.getDescription());
Image im = simulator.getImage(c, Direction.SOUTH);
if (im == null) {
iconLabel.setIcon(null);
} else {
iconLabel.setIcon(new ImageIcon(im));
}
tick();
setVisible(true);
} else {
setTitle("Debugger");
classLabel.setText("");
idLabel.setText("");
authorLabel.setText("");
descriptionDisplay.setText("");
stringDisplay.setText("");
positionLabel.setText("");
iconLabel.setIcon(null);
}
}
public synchronized Creature getCreature() {
return creature;
}
public synchronized void tick() {
if (creature != null) {
stringDisplay.setText(creature.toString());
Point pos = simulator.getPosition(creature);
Direction dir = simulator.getDirection(creature);
if (pos != null && dir != null) {
positionLabel.setText("(" + pos.x + ", " + pos.y + ") " + dir);
} else {
positionLabel.setText("Not alive.");
}
}
}
}
}