/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.app.hexabugs;
import sim.engine.*;
import sim.display.*;
import sim.portrayal.grid.*;
import sim.portrayal.simple.*;
import java.awt.*;
import javax.swing.*;
import sim.util.gui.*;
import sim.portrayal.*;
public class HexaBugsWithUI extends GUIState
{
public Display2D display;
public JFrame displayFrame;
HexaSparseGridPortrayal2D bugPortrayal = new HexaSparseGridPortrayal2D();
// we have heat as EITHER hexagons OR rectangles. The current portrayal is always
// currentHeatPortrayal, which can be changed in the model inspector (see
// getInspector() below)
FastHexaValueGridPortrayal2D heatPortrayal= new FastHexaValueGridPortrayal2D("Heat");
HexaValueGridPortrayal2D heatPortrayal2 = new HexaValueGridPortrayal2D("Heat");
HexaValueGridPortrayal2D currentHeatPortrayal = heatPortrayal;
public static void main(String[] args)
{
new HexaBugsWithUI().createController();
}
public HexaBugsWithUI() { super(new HexaBugs(System.currentTimeMillis())); }
public HexaBugsWithUI(SimState state) { super(state); }
public static String getName() { return "HexaBugs"; }
public Object getSimulationInspectedObject() { return state; }
// One approach to creating the Hexagons/Rectangles popup menu is to hand-code the
// menu ourselves in a wrapper inspector. This requires some knowledge of Java's
// event handling etc.
// Another approach to doing the Hexagon/Rectangle code is to create a class which has a single
// readable, writable integer property called DisplayGridCellsAs. If the property is set to 0 or 1
// the object changes the heat portrayal appropriately just like was done above. We make the
// popup menu appear by providing a domain in the form of an array (for the values 0 and 1).
// Then we just make an inspector which encapsulates the previous inspector and a new SimpleInspector
// on an instance of this object.
public class HexagonChoice
{
int cells = 0;
public Object domDisplayGridCellsAs() { return new Object[] { "Rectangles", "Hexagons"}; }
public int getDisplayGridCellsAs() { return cells; }
public void setDisplayGridCellsAs(int val)
{
if (val == 0)
{
cells = val;
currentHeatPortrayal = heatPortrayal;
}
else if (val == 1)
{
cells = val;
currentHeatPortrayal = heatPortrayal2;
}
// reattach the portrayals
display.detachAll();
display.attach(currentHeatPortrayal,"Heat");
display.attach(bugPortrayal,"Bugs");
// redisplay
display.repaint();
}
}
public Inspector getInspector()
{
// we'll make a fancy inspector which has a nicely arranged update button and
// two subinspectors. In fact the inspector doesn't need an update button --
// there's nothing that ever changes on update. But since the inspector
// has been declared non-volatile, just to be consistent, we'll add an update
// button up top to show how it's done. First we get our two subinspectors,
// one for the hexagon choice menu and one for the model inspector proper.
final Inspector originalInspector = super.getInspector();
final SimpleInspector hexInspector = new SimpleInspector(new HexagonChoice(),this);
// The originalInspector is non-volatile. It's a SimpleInspector, which shows
// its update-button automagically when non-volatile. We WANT it to be non-volatile,
// but not show its update button because that just updates the inspector and nothing
// else. So we declare the inspector to be volatile. It won't matter because it'll
// NEVER receive updateInspector() calls except via the outer inspector we're
// constructing next (which will be NON-volatile).
originalInspector.setVolatile(true);
// our wrapper inspector
Inspector newInspector = new Inspector()
{
public void updateInspector() { originalInspector.updateInspector(); } // don't care about updating hexInspector
};
newInspector.setVolatile(false);
// NOW we want our outer inspector to be NON-volatile, but show an update button.
// While SimpleInspectors add their own buttons automagically, plain Inspectors
// do not. Instead we have to add it manually. We grab an update-button from
// the inspector, put it in a box so it doesn't stretch when the inspector does.
// And we want to move it in a bit border-wise because that's what the SimpleInspector
// does.
Box b = new Box(BoxLayout.X_AXIS)
{
public Insets getInsets() { return new Insets(2,2,2,2); } // in a bit
};
b.add(newInspector.makeUpdateButton());
b.add(Box.createGlue());
// okay, great. But we want the button to be up top, followed by the hex inspector,
// and then the originalInspector taking up the rest of the room. Sadly, there's
// no layout manager to do that. So we do it thus:
Box b2 = new Box(BoxLayout.Y_AXIS);
b2.add(b);
b2.add(hexInspector);
b2.add(Box.createGlue());
// all one blob now. We can add it at NORTH.
newInspector.setLayout(new BorderLayout());
newInspector.add(b2,BorderLayout.NORTH);
newInspector.add(originalInspector,BorderLayout.CENTER);
return newInspector;
}
public void start()
{
super.start();
// set up our portrayals
setupPortrayals();
}
public void load(SimState state)
{
super.load(state);
// we now have new grids. Set up the portrayals to reflect that
setupPortrayals();
}
// This is called by start() and by load() because they both had this code
// so I didn't have to type it twice :-)
public void setupPortrayals()
{
// tell the portrayals what to portray and how to portray them
ColorMap map = new sim.util.gui.SimpleColorMap(0,HexaBugs.MAX_HEAT,Color.black,Color.red);
heatPortrayal.setField(((HexaBugs)state).valgrid);
heatPortrayal.setMap(map);
heatPortrayal2.setField(((HexaBugs)state).valgrid);
heatPortrayal2.setMap(map);
bugPortrayal.setField(((HexaBugs)state).buggrid);
bugPortrayal.setPortrayalForAll(new MovablePortrayal2D(
new sim.portrayal.simple.OvalPortrayal2D(Color.white))); // all the HexaBugs will be white ovals
// reschedule the displayer
display.reset();
// redraw the display
display.repaint();
}
/** The ratio of the width of a hexagon to its height: 1 / Sin(60 degrees), otherwise known as 2 / Sqrt(3) */
public static final double HEXAGONAL_RATIO = 2/Math.sqrt(3);
public void init(Controller c)
{
super.init(c);
// Make the Display2D. We'll have it display stuff later.
// Horizontal hexagons are staggered. This complicates computations. Thus
// if you have a M x N grid scaled to SCALE, then
// your height is (N + 0.5) * SCALE
// and your width is ((M - 1) * (3/4) + 1) * HEXAGONAL_RATIO * SCALE
// You might need to adjust by 1 or 2 pixels in each direction to get circles
// which usually come out as circles and not as ovals.
final double scale = 4;
final double m = 100;
final double n = 100;
final int height = (int) ( (n + 0.5) * scale );
final int width = (int) ( ((m - 1) * 3.0 / 4.0 + 1) * HEXAGONAL_RATIO * scale );
display = new Display2D(width, height, this);
displayFrame = display.createFrame();
c.registerFrame(displayFrame); // register the frame so it appears in the "Display" list
displayFrame.setVisible(true);
// attach the portrayals
display.attach(currentHeatPortrayal,"Heat");
display.attach(bugPortrayal,"Bugs");
// specify the backdrop color -- what gets painted behind the displays
display.setBackdrop(Color.black);
}
public void quit()
{
super.quit();
if (displayFrame!=null) displayFrame.dispose();
displayFrame = null; // let gc
display = null; // let gc
}
}