Running
[Agent practical 10 of 9]
Before we look at the specifics of threading our model, let's set up the code so we can control one of the agents using the keyboard. Our agent will stay still, unless we use the arrow keys on our keyboard to move it around.
Here's the two new classes: Model.java and Protector.java. In the case of the former, you can compare it with the old Model.java listed above by opening both in Notepad++. If you right-clicking on the tab of one of the files and select Move to Other View, you'll see the two files next to each other. If you then select Plugins --> Compare, making sure Align Matches and Detect Moves are turned on, before clicking Compare on that menu, you should see the differences highlighted and the code aligned, and Notepad++ should scroll through both files together.
So, to the code. Firstly, we've added a new agent label to the instance variables of Model.java:
private Protector protector = null;
This uses our new class Protector
. It's called "Protector" because
it does nothing other than sitting where you put it. As we'll add it to
the list of agents that all Nibbler agents check the neighbourhoods of, Nibbler agents won't
enter its area, and as it doesn't eat the area will remain protected from
agent nibbling. Note that there's little in the class except code to
randomise the starting location and mutator methods for x and y, which
we'll use to control the position.
Next, after we've made our agents in buildAgents()
we
build and add the Protector:
protector = new Protector(world,agents);
agents.add(protector);
And then we add a KeyListener to control the Protector based on key clicks. Note that we use an anonymous inner class, rather than a separate class, just because the code is short:
addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
String keyString = KeyEvent.getKeyText(keyCode);
switch(keyString) {
case ("Up") :
protector.setY(protector.getY() - 1);
break;
case ("Down") :
protector.setY(protector.getY() + 1);
break;
case ("Left") :
protector.setX(protector.getX() - 1);
break;
case ("Right") :
protector.setX(protector.getX() + 1);
break;
}
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
});
Note that the
protector
object has scope within the inner class. This
is pretty helpful -- it saves us having to pass the Protector object
into the class, as we'd have to do with an external KeyListener class. Note, as
a helpful hint for the future, that if you want to refer to the
external this
object within such an inner class, the simplest
thing to do is set up a method-level variable and assign it the value
this
, thus:
Frame f = this;
addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {
f.setBackground(Color.BLACK);
Finally, in the model paint()
method, we've added code to
paint the new agent:
for (Agent agent : agents) {
cg.setColor(Color.GREEN);
cg.fillOval(agent.getX() - 1,agent.getY() - 1,2,2);
cg.drawPolygon(agent.getNeighbourhood());
cg.setColor(Color.BLACK);
}
if (protector != null) {
cg.setColor(Color.YELLOW);
cg.fillOval(protector.getX() - 1,protector.getY() - 1,2,2);
cg.drawPolygon(protector.getNeighbourhood());
cg.setColor(Color.BLACK);
}
Note that we've added a if-statement to check whether the protector
is set up, just to stop the call to it throwing a nullPointerException
(the for-each loop doesn't run if the others aren't set up, so there's no problem there).
Note also that because protector
is also
in the agents
array, it actually gets painted green and then
yellow over that. We'll leave it like this to minimise change, but if it
bothers you you can change the for-each loop to a counting for-loop
that ends one before the end of the array length.
At the moment, if we added all the above and ran our model we'd find it runs through to the end before user interactions are enacted. This is because the running of the Nibbler agents is in the EDT event associated with the user pushing "run", and any other user events are added after it in the queue. The simplest solution to this is to place the running of the agents in a new thread, separate from either the EDT or main. This will then add separate update events to the EDT, between which it is possible for the system to place user interaction events.
Let's now look at how we do this.