Main Page | Recent changes | View source | Page history

Printable version | All content dual-licensed under the GNU FDL and the Creative Commons Share-alike Attribution license. | Privacy policy

Not logged in
Log in | Help
 

Jheatbugs-3.0.txt

From SwarmWiki

document name: jheatbugs-3.0.txt

Straight-Shot Unambiguous Instructions for Installing and Confirming Swarm (Java Development Setup) and Swarm Sample Application jheatbugs-3.0 on Windows XP and Other Operating Systems

With Complete Source Code for jheatbugs-3.0 (An Unauthorized Version)

A Complete Step-by-Step Procedure Assuming No Prerequisites and Almost No Foreknowledge of Unix, Cygwin, Windows, Java, or Swarm

With Experiment-Suite Tools replicator.pl and repswarm.pl

With Suggestions for Your Next Steps

By Timothy Howe.


NOTE: Swarm does not work with Cygwin, August, 2004:

Swarm Java on Windows does not work with Cygwin's cygwin.dll version 1.5.9-1 nor version 1.5.10-3, which are the only versions of cygwin.dll available from cygwin.org as of 2004-08-09. So unless you can get an older version of cygwin.dll (for example, 1.5.4.1 or 1.5.5.1), you cannot run any Swarm Java program until the Swarm engine gets fixed (according to a note from Alex at http://wiki.swarm.org/wiki/Talk:Swarm:_applications). In particular, if you use the current Cygwin release, jheatbugs will die silently in the call to initSwarm() in StartHeatbugs. Bytecode compilation does not solve the problem; it merely generates error messages regarding natInflater.o.


Contents

Part 1: (Not) editing this document.

This document is jheatbugs-3.0.txt, version 2004-08-10.

Do not edit this document -- if you find errors, omissions, or other problems, please email your findings to me at timothyrhowe@hotmail.com so that I can integrate your findings and maintain version control over this document. It's fine to post questions about Swarm installation to discussion forums, but if you leave the answers on those forums, the unfortunate newcomer to Swarm will need to search piecemeal here and there for fragments of the solutions to Swarm installation problems -- exactly the problem that this document is written to solve. If you send the fixes to me, I can test and integrate them and maintain this "one-stop shopping" comprehensive solution to such problems.

Part 2: This document.

This document tells how to install Java-based Swarm on Windows XP, and how to confirm the installation by compiling and invoking the Java Swarm sample program jheatbugs. I have included supporting notes for readers who might not be familiar with Unix or Cygwin or Java. The document contains the complete code for what I call jheatbugs-3.0, an enhancement based on jheatbugs-2001-03-28. The document also contains the experiment-suite tools replicator.pl and repswarm.pl.

If you already have a working installation of Swarm and are interested only in jheatbugs-3.0, skip ahead to Part 6.

Swarm is a free agent-based modelling (ABM) software system available from www.swarm.org. Agent-based modelling is also known as individual-based modelling (IBM).

For an indefinite time, I support this document by answering questions. Most of the time, I do not read the Swarm forum or wiki; don't expect that I will see any message you post there. Email me at timothyrhowe@hotmail.com.

If something goes wrong with your installation, I can help you if you do this: Go back to the beginning of the Part in which the problem occurred and re-do that Part of the instructions. Follow the instructions without deviating. When your experience first seems to diverge from these instructions, send me an email explaining exactly what happened. Usually it's best to paste the contents of your Cygwin window into your email. You might also include your .bash_profile file.

Although Swarm has been reported in the past to work without Cygwin (the free Unix-like software toolset from Cygnus Corporation), as of January, 2004, Swarm appears to require Cygwin. Even if Cygwin isn't absolutely required, it is very useful when you're working with Swarm, for example in invoking ancillary tools such as replicator.pl. So these instructions assume the need to install Cygwin, as well as Java, Swarm itself, and jheatbugs. Swarm does not require Sun's implementation of Java, but these instructions mention only that implementation.

Thanks to Mary Conner of Utah State University, to Paul E. Johnson of the University of Kansas, to Eric McLeskey of Utah State University, and to others, for helping to remove the kinks from earlier versions of these instructions.

Part 3: Installing Cygwin.

Install the default packages of Cygwin, and in addition, explicitly install the few extra packages required by Swarm (see the list below).

In detail: open cygwin.com in a browser. Choose "Install or update now!" That merely downloads a program named setup.exe. Run setup.exe and accept all defaults; for Local Package Directory, just use any directory on a drive with plenty of space; for Download Site, pick a mirror at random from nearby mirrors.

Do not install cygwin.dll version 1.5.6-1 nor 1.5.9-1 nor 1.5.10-3, all of which will make Swarm fail. Swarm will succeed with version 1.5.4-1 or 1.5.5-1; I don't know about other versions. You will find cygwin.dll in the Base Category when you run setup.exe.

If you get the error message:

 Unable to get setup.ini from <ftp:...>

try again with a new Download Site. There is probably nothing wrong with the Download Site you chose, but setup.exe can get confused about what it has transacted with the Download Site. I have encountered this problem at least five times (August, 2004) and have always been able to get past it by choosing another Download Site.

You can use setup.exe in one-stage mode (Install from Internet), or in two-stage mode (Download from Internet and then Install from Local Directory). You probably have an equal chance of success either way, despite some web postings that claim that one or the other mode is better. Both modes are economized -- setup.exe checks your files and refrains from downloading anything you already have. I prefer two-stage mode, since its Local Package Directory makes re-installs much faster.

When you choose Install from Local Directory, you may need to set the Root Directory -- usually c:\cygwin will be best -- because Cygwin sometimes defaults it to a path based on the Local Package Directory, which is usually not what you want.

Occasionally people have persistent problems with a Cygwin installation and feel the need to scrub Cygwin out completely before starting again. Cygwin's Uninstall (at the top level of setup.exe's Select Packages window) removes Cygwin completely, except for the Local Package Directory, the Desktop icon and the Start Menu icon, and the two Cygnus Software entries in the Windows registry. Use the Windows regedit command to delete the registry entrie. It's probably not necessary to uninstall the Desktop icon and the Start Menu icon. For more information, google [[cygwin uninstall registry]].

In addition to the Cygwin default packages, explicitly install

 Devel - make
 Editors - VIM       (some may prefer Emacs or another editor)
 Interpreters - Perl
 Libs - libpng12
 Libs - tcltk
 Text - less

In detail: for each of the items above, click the "+" on the appropriate Category, scroll to the appropriate package, and then click the little rotary icon until it shows a version number (use the first, highest version number unless you know of some particular reason to choose an older version). You can install these packages at the same time that you install the Cygwin base packages, or afterward.

Text - less is needed because Cygwin's "man" requires "less", and as of January, 2004, there seems to be a bug in Cygwin's installation logic such that it sometimes installs "man" without installing "less", even when "less" is marked for installation.

To confirm the installation, choose Start - Programs - Cygwin - Cygwin Bash Shell. That should open a window containing the Bash shell. In the shell, invoke

    pwd
    ls
    man ls


You should see no error messages; "man" should show you the user manual for the command "ls". Press q to exit from the user-manual utility "man".

Note: in Cygwin, the path-element delimiter is "/" (slash), not "\" (backslash) as it is most of the time in Windows. If you use a backslash rather than a slash in Cygwin, unwanted things will happen and strange error messages may appear.

Note: In Cygwin, there are typically several different syntaxes you can use to refer to a file. The following commands and their responses illustrate five of these syntaxes; the -i option of ls demonstrates that they all refer to the same file:

 ls -i /bin/less.exe
 # ... 7354659666473040608 /bin/less.exe
 ls -i /BIN/LESS.EXE
 # ... 7354659666473040608 /BIN/LESS.EXE
 ls -i /cygdrive/c/cygwin/bin/less.exe
 # ... 7354659666473040608 /cygdrive/c/cygwin/bin/less.exe
 ls -i c:/cygwin/bin/less.exe
 # ... 7354659666473040608 c:/cygwin/bin/less.exe
 ls -i c:\\cygwin\\bin\\less.exe
 # ... 7354659666473040608 c:\cygwin\bin\less.exe

Some programs accept some of these formats and other programs accept others. When you run into a problem specifying a file, a different format may sometimes succeed.

Part 4: Installing Java.

In a browser, open java.sun.com and download the latest stable release of the Java Development Kit (JDK) Standard Edition (J2SE).

From here on, I will assume that the latest stable release is Version 1.4.2_03 and the download file is j2sdk-1_4_2_03-windows-i586-p.exe, and I will assume that c: is your primary hard drive; change these instructions appropriately if those assumptions are incorrect.

Invoke the downloaded file and accept all installation defaults. The installation program will create the directory c:/j2sdk1.4.2_03 and place the SDK in that directory. To confirm the installation, in a Cygwin window, invoke

  java -version

You should see no error messages; you should see the correct Java version number.

Also download the Java documentation: open java.sun.com/docs/index.html and download the file to the directory c:/j2sdk1.4.2_03.

From here on, I will assume that the download file is j2sdk-1_4_2-doc.zip (the latest Java documentation does not always have the same version number as the latest Java software); change these instructions appropriately if that assumption is incorrect.

Invoke

 cd c:/j2sdk1.4.2_03
 jar xvf j2sdk-1_4_2-doc.zip

Note: we use jar -- the Java ARchiving tool -- here because neither tar nor gzip can handle .zip files.

The jar command creates the directory c:/j2sdk1.4.2_03/docs and places the Java documentation in that directory.

To confirm the installation, use your browser to open index.html in the docs directory. Click on API & Language. Click on Java 2 Platform API Specification. In the All Classes frame, click on a class at random and observe the documentation.

After you confirm the installation, you can invoke

    rm j2sdk-1_4_2-doc.zip

Part 5: Installing Swarm.

From here on, I will assume that you want to put your Swarm installation in the directory c:/software/swarm; change these instructions appropriately if that assumption is incorrect.

Invoke

    mkdir -p c:/software/swarm

Download Swarm-2.2-pretest-11.tar.gz (or a later stable release, if one is available) from swarm.org - Development snapshots into c:/software/swarm. Then invoke

    cd c:/software/swarm
    tar xvfz Swarm-2.2-pretest-11.tar.gz

The tar command creates the directory c:/software/swarm/Swarm-2.2 and places the Swarm software in that directory.

If you see the error message

	tar: Error exit delayed from previous errors

and if you then scroll back up through the output of tar and find error message

	tar: Swarm-2.2/lib/libXpm.a: Cannot open: No such file or directory

you can ignore the messages.

To check the existing mount points (as suggested by SwarmOnlineFAQ.html, which you can find through google), in a Cygwin window, invoke

    mount -m

The response should be something like

    mount -f -s -b "C:/cygwin/bin" "/usr/bin"
    mount -f -s -b "C:/cygwin/lib" "/usr/lib"
    mount -f -s -b "C:/cygwin" "/"
    mount -s -b --change-cygdrive-prefix "/cygdrive"

These are the default Cygwin mount points. Yours may differ without harm. It would probably be wise to save the output of the mount -m command, in case of subsequent problems, so invoke

    mount -m > c:/mount-original.txt

Note: in Unix, a mount point is a virtual top-based directory that translates to some physical directory. For example, given the mount points shown above, /usr/bin is virtual because it is defined by a mount command, and it is top-based its name starts with "/" (which in Unix indicates the top of the directory tree, just as "c:" or "d:" indicates the top of a directory tree in Windows). Given the mount points above, the mount point (virtual directory) /usr/bin is exactly equivalent to the physical directory c:/cygwin/bin.

Swarm requires a mount point for /Swarm-2.2, so invoke

  mount -f c:/software/swarm/Swarm-2.2 /Swarm-2.2

Note: in response to a mount command, the error message "Mount device busy" indicates that the logical name you're trying to define is already defined. You can invoke mount -m to see how the name is defined; umount to unmount it; and then mount to define it as you want it.

Note: mount is a persistent command; there is no need to repeat the mount command after a reboot or when you create a Cygwin window. For more information, invoke

 mount --help

and

 umount --help

Note: if you neglect to use the "-f" flag, you will see the warning message "/Swarm-2.2 does not exist", which you can ignore; it merely means that /Swarm-2.2 was not already defined.

Confirm the mount by invoking

    ls /Swarm-2.2

You should see a list of files (actually the seven top-level directories within Swarm) and no error messages.

Also, create a mount point that refers to the entire drive: invoke

	mount -f c:/ /top

You can choose a name other than "top" if you prefer. Defining a mount point for the entire drive provides a Unix-style pathname for every file on your drive.

Note: Yes, /cygdrive/c also provides a Unix-style pathname for every file on your drive, but it's a lot to type, and it's not device independent.

Next, create a profile file to define your Cygwin environment for working with Swarm: in any Cygwin window in which you have not invoked "cd", invoke

    pwd

to find out what your initial directory is. Then invoke

    notepad .bash_profile

Using Notepad (or vim or vi or emacs or any other editor), add these commands:

    profile="(Replace this sentence, including the parens, with the output of
the pwd command.)/.bash_profile"
    echo "Begin $profile ..."
        # ... diagnostic echo statements make debugging easier
    export WINDOWS_HOME=c:
    export HOME=/top
    export SWARMHOME="$HOME/software/swarm/Swarm-2.2"
    export JAVA_HOME="$HOME/j2sdk1.4.2_03"
    PATH="."
    PATH="$PATH:$SWARMHOME/bin"
    PATH="$PATH:$HOME/cygwin/bin"
    PATH="$PATH:$JAVA_HOME/bin"
    PATH="$PATH:$HOME/windows"
    PATH="$PATH:$HOME/windows/system32"
    export PATH
    alias h="history | tail -20"
    alias ls="ls --color=yes"
    alias sp=". $profile"   # source profile
    export PS1='\033[32;40m-$PWD \!- \033[37;40m'
        # ... 37 is white; 32 green for prompt; 40 is default background color
        # ... "-$PWD " displays working directory
        # ... "\!- " displays command number
    if [ "$LOGIN_DONE" != 1 ] ; then
        cd "$HOME"   # or any other directory you choose
        export LOGIN_DONE=1
    fi
    echo "... End $profile."
        # ... diagnostic echo statements make debugging easier

Save the file and exit from the editor.

Note: the export command defines environment variables, which you can use in many programs throughout your Cygwin installation. If the difference between an environment variable and a mount point is not clear, that may be because their features overlap. For example, you could use either mechanism to achieve device independence, as these commands do:

            mount c:/dir1 /mydir1
            export MY_DIR2=/cygdrive/c/dir2
            export PATH="$PATH:/mydir1:$MY_DIR2"

The main differences are that mount points are permanent and global -- they're recorded in the file system, not in scripts or program code.

By convention, environment variables are usually spelled with all caps and underscores.

Software systems on Windows tend to use environment variables rather than mount points; Swarm is an exception. Note that Swarm's mount point, /Swarm-2.2, is version-dependent; I suggest that, as much as possible, you use the mount point for version-dependent access to Swarm, and use $SWARMHOME (as defined in .bash_profile above) for version-independent access -- that is, whenever you want to refer to "the latest version" of Swarm.

To confirm that .bash_profile is correct, close all Cygwin windows and open a new Cygwin window; it will load the definitions from your new profile file. Invoke

    echo $SWARMHOME

and see if the response looks correct. From then on, whenever you change your profile file, invoke

    sp

in every open Cygwin window to load ("source") the new definitions.

For more information, invoke

    man bash

and press the space bar till you find to an explanation of profiles, or google cygwin profile windows.

Note also that the "." at the beginning of the name ".bash_profile" makes that file a hidden file in Unix, so plain ls will not list it; you need to invoke ls -a or ls .* to list it. For more information, invoke

    man ls

Do not define SYSTEMROOT in your profile. If you define it, you will be unable to run network programs, such as ping, wget, and any Java programs that try to access the Internet.

Note that Swarm requires that you define the environment variable SWARMHOME as well as the mount point /Swarm-2.2, both with the same value.

Note: For many Java programs, you need to define the environment variable CLASSPATH, but for jheatbugs you do not, because javacswarm and javaswarm (discussed below) control the definition within their contexts (appending your CLASSPATH to their definition). If you do define CLASSPATH, keep these rules in mind:

1. Use a semicolon as the delimiter between elements. (When you define PATH, on the other hand, use a colon as the delimiter.)

2. Don't use mount points in CLASSPATH; use only $WINDOWS_HOME as defined in the .bash_profile shown above, at least if you are using Sun's Java engine. Sun's Java tools accept only Windows directories.

3. It's generally useful to make "." (the working directory) be the first element of your CLASSPATH.

Thus, a good CLASSPATH definition might be

 export CLASSPATH=".;c:/myjavacode;c:/software/mylib"

Since $SWARMHOME/bin/javacswarm has "/cygdrive/j2sdk..." hardcoded in, modify $SWARMHOME/bin/javacswarm (in two places) to change the hardcoded path to $JAVA_HOME. Also, change "jikes" to "javac". (Jikes is a Java compiler -- an alternative to Sun's javac.) Thus $SWARMHOME/bin/javacswarm, instead of the lines

    ...
    _JAVACLASSES='/cygdrive/c/j2sdk1.4.2/jre/lib/rt.jar'
    ...
    jdkdir="/cygdrive/c/j2sdk1.4.2"
    ...
    JAVAC="jikes"
    ...

should contain

    ...
    _JAVACLASSES="$JAVA_HOME/jre/lib/rt.jar"
    ...
    jdkdir="$JAVA_HOME"
    ...
    JAVAC="javac"
    ...

Modify $SWARMHOME/bin/javaswarm also (in two places) to fix the definitions of _JAVACLASSES and jdkdir. Since javaswarm contains no reference to jikes, you need to make only two changes to the file, not three as for javacswarm. Note that, as in the example above, you should use double-quotes, which are safer than the single-quotes used in the original.

Part 6: Installing Jheatbugs-3.0.

I created jheatbugs-3.0 by enhancing jheatbugs-2001-03-28. In particular, I

  • simplified the implementation by eliminating unnecessary classes;
  • gave the heatbugs different colors based on their personal heat preferences;
  • added a Java Properties interface for program control from the command line;
  • added an initial-clustering option and an immobility option for convenience in testing;
  • added a diagnostic output option and other run-time options;
  • documented application behavior and general Swarm behavior;
  • documented Swarm's Probe and ProbeMap structure (for interactive control of variables and methods); and
  • documented Swarm's scheduling structure.

I asked no one for authorization or approval, so "3.0" is an unofficial version number.

To install jheatbugs-3.0, assuming that you want to put your Swarm work in the directory /top/swarm, invoke

    mkdir -p /top/swarm/src/jheatbugs-3.0
    cd /top/swarm/src/jheatbugs-3.0

Next, extract the files from Part 7 below into your working directory, and invoke

    chmod -R 755 *   # one time only

Next, invoke

    make
    ls -lart

Observe the class files newly created by make.

Invoke

    javaswarm -Dn=30 -Dc=1 StartHeatbugs

The program should run correctly. In particular, it should open several windows, and in one of those windows you should find a button labelled Start, and if you click that button, you should see 30 heatbugs metabolizing, starting in a tight cluster, since the option -Dc=1 sets initial clustering to true.

If you get an error message, that may be due to a missing or incorrect Cygwin library: Cygwin's dependencies sometimes induce setup.exe not to do exactly what you request. Run setup.exe and proceed to the screen that lists all your Current packages. Make sure that all the packages listed above are installed. Check especially the version number of cygwin.dll. If you install any packages at this point, check the list again after the installation completes.

You can confirm that you are running jheatbugs-3.0 rather than an earlier version of jheatbugs by observing that the heatbugs start in a contiguous cluster; that they have different colors (which indicate their personal heat preferences); and that the yellower heatbugs (which prefer warmth) tend to stay closer to the center of the cluster than the greener heatbugs (which prefer coolness).

Click Quit to stop the program.

To confirm that the program runs properly in batch mode, invoke

    javaswarm StartHeatbugs -b

The program should run correctly, displaying some text and producing some output files. The program may generate some Grid2d error messages, which you can ignore -- they indicate merely that the heatbugs are stepping on each other. If the program appears to do nothing at all, your .scm file might be missing or corrupted.

Part 7. Jheatbugs-3.0 source files.

This document contains jheatbugs-3.0 source code and a Perl program to extract that source code. The extraction works only if this document is in the form of a text file. So if you are looking at this document in HTML format, first use Windows Edit-Copy to grab the text, then use Windows Edit-Paste in a text editor such as vim to paste the text into a file. Don't worry about the boundaries -- if you Edit-Copy a bit too much or too little, that won't cause a problem. Save the file as jheatbugs-3.0.txt. Instead of a text editor, you can use Microsoft Word; save as Text Only.

Next, follow the instructions below, working on the text file, not on the HTML file. You can work in Word, as long as you save as Text Only.

The first file included below, textract.pl, is a Perl program that for your convenience will extract all the other files from jheatbugs-3.0.txt. To extract textract.pl, cut out the text from the first instance of "start cut here" to the first instance of "end cut here", and paste it into a file named textract.pl. To extract the other files, invoke


    perl textract.pl jheatbugs-3.0.txt

Next you will probably want to return to Part 6 above to continue the installation of jheatbugs-3.0.

textract.pl


 $inname = $ARGV[0];

 use FileHandle; 
 my $out = new FileHandle; 
 my $in = new FileHandle; 
 my $printing = false;
 open ($in, "$inname") || die ("$inname won't open: $!.");

 while (<$in>) 
 {
 	next if ($_ =~ m/textract.pl/);
 	if ($_ =~ s/^\s*-*\s*start cut here\b\W*//)
 	{
 		$inname = $_;
 		chomp $inname;
 		print "Writing     <$inname> ...\n";
 		open ($out, ">$inname") || die ("$inname won't open: $!.");
 		$printing = true;
 		next;
 	}
 	elsif ($_ =~ s/^\s*=*\s*end cut here\b//)
 	{
 		close ($out);
 		print "... Closing <$inname>.\n";
 		$printing = false;
 		next;
 	}
 	elsif ($printing) 
 	{ 
 		$_ =~ s/^ //;   
 		# ... Remove initial space inserted to preserve formatting in Wiki.
 		print $out $_; 
 	}
 }

HeatSpace.java

 // jheatbugs-3.0

 // Java Heatbugs application. Copyright � 1999-2000 Swarm Development Group.
 // This library is distributed without any warranty; without even the
 // implied warranty of merchantability or fitness for a particular
 // purpose.  See file COPYING for details and terms of copying.

 // Changes (from jheatbugs-2001-03-28) by Timothy Howe. 

 import swarm.Globals;

 import swarm.defobj.Zone;

 import swarm.space.Diffuse2dImpl;
 import swarm.space.Grid2d;

 import java.awt.*;   // We use this just for class Point.
 import java.util.ArrayList;

 /**
 See HeatbugModelSwarm for an overview of the heatbugs application.

 <p>
 Swarm's class Diffuse2dImpl provides a 2-dimensional array of integer values.
 Behavior of Diffuse2dImpl includes both diffusion and "evaporation" of whatever
 the values represent, at rates we can specify.

 <p>
 The class HeatSpace specializes Diffuse2dImpl; we use the integer values
 to represent heat (which Heatbugs produce as well as seek).

 <p>
 We use <tt>Diffuse2dImpl.getValueAtX$Y()</tt> 
 and <tt>Diffuse2dImpl.putValue$atX$Y()</tt> to
 access the integer values.

 <p>
 You may wonder why the name of the method is <i>putValue$atX$Y()</i> rather 
 than <i>setValue$atX$Y()</i>. The name reflects the fact that the change is 
 buffered and therefore does not have the instant effect that you would expect 
 from a method named <i>setValue$atX$Y()</i>. In particular, if you change a 
 value by invoking <tt>putValue$atX$Y()</tt> and then immediately invoke
 <tt>getValue$atX$Y()</tt>, you will still see the old heat value.

 */
 public class HeatSpace extends Diffuse2dImpl
 {

 public static final int COLD = 0, HOT = 1;
 public static final int MAX_HEAT = 0x7fff;

 protected double _discardedHeat = 0.0;
     public double getDiscardedHeat () { return _discardedHeat; }

 protected int _printDiagnostics = 0;
     public void setPrintDiagnostics (int printDiagnostics)
     { _printDiagnostics = printDiagnostics; }

 public HeatSpace
  (Zone aZone,
   int worldXSize,
   int worldYSize,
   double diffusionConstant,
   double evaporationRate,
   int printDiagnostics
  )
 {
     super (aZone, worldXSize, worldYSize, diffusionConstant, evaporationRate);
     _printDiagnostics = printDiagnostics;
 } /// constructor

 /**
 This method adds heat to the current cell, never exceeding MAX_HEAT.
 */
 public Object addHeat (int moreHeat, int x, int y)
 {
     int heatHere;

     heatHere = getValueAtX$Y (x, y);      // read the heat
     int oldHeatHere = heatHere;

     if (heatHere + moreHeat <= MAX_HEAT)   // would add be too big?
         heatHere = heatHere + moreHeat;      // no, just add
     else
     {
         heatHere = MAX_HEAT;                  // yes, use max
         double discardedHeat = heatHere + moreHeat - MAX_HEAT;
         _discardedHeat += discardedHeat;
         if (_printDiagnostics >= 21)
             System.out.println
              ("In HeatSpace.addHeat() at (" + x + "," + y + "), I discarded heat " + heatHere + " + " + moreHeat + " - " + MAX_HEAT + " = " + discardedHeat + ".");
     }

     putValue$atX$Y (heatHere, x, y);      // set the heat
     // Monitor the heat at an arbitrary cell (2, 2) (HeatbugModelSwarm monitors 
     // the same cell):
     if (_printDiagnostics >= 10 && x == 2 && y == 2)
     {
         System.out.println 
          ("In HeatSpace.addHeat(), heat " + moreHeat + " was added at (" + x + ", " + y + ") to change from " + oldHeatHere + " to " + heatHere + ".");
     }
     return this;
 } /// addHeat()

 /**
 This method searches targetCell's 9-cell neighborhood for the requested 
 extreme (COLD or HOT). 

 <p>Note that a HeatSpace has a wrap-around geography: 
 the south-eastern neighbor of the cell at (x, y) is the cell at ((x + 1) % 
 getSizeX (), (y - 1) % getSizeY ()). Of course, <tt>getSizeX()</tt> 
 and <tt>getSizeY()</tt> are inherited from Diffuse2dImpl.

 @param type (in)
     HeatSpace.COLD or HeatSpace.HOT
 @param targetCell (inout)
     the Point at the center of the 9-cell neighborhood; we change it to a Point
     randomly selected from among those with the most-ideal temperature

 */
 public int findExtremeType$X$Y (int type, Point targetCell, Grid2d world)
 {
     int x, y;
     Point candidate;

     // Prime the loop by assuming that the extreme heat is right at targetCell:
     int bestHeat = getValueAtX$Y (targetCell.x, targetCell.y);

     // Scan through the 9-cell neighborhood, keeping a list of all cells
     // that tie for most extreme:
     ArrayList heatList = new ArrayList ();

     for (y = targetCell.y - 1; y <= targetCell.y + 1; y++)
     {
         for (x = targetCell.x - 1; x <= targetCell.x + 1; x++)
         {

             candidate = new Point (x % getSizeX (), y % getSizeY ());

             int candidateHeat = getValueAtX$Y (candidate.x, candidate.y);

             boolean candidateIsBetter 
              = (type == COLD)
              ? (candidateHeat < bestHeat)
              : (candidateHeat > bestHeat);

             boolean candidateIsEqual = (candidateHeat == bestHeat);

             if (candidateIsBetter)
             {
             // ... This cell is more extreme than any so far.
                 // Delete all the other cells we have accumulated:
                 heatList.clear ();
                 heatList.add (new Point (x, y));
                 bestHeat = candidateHeat;
             }
             else if (candidateIsEqual)
             {
                 heatList.add (new Point (x, y));
             }
         }
     }


     // Choose a point at random from the list of Points tied for most extreme:
     int offset = Globals.env.uniformIntRand.getIntegerWithMin$withMax
      (0, (heatList.size () - 1));
     Point bestCell = (Point) heatList.get (offset);

     // We've found the requested extreme. Set the (inout) variable targetCell,
     // applying geographic wrap-around, and return the heat we found:
     targetCell.x = ((bestCell.x + getSizeX ()) % getSizeX ());
     targetCell.y = ((bestCell.y + getSizeY ()) % getSizeY ());

     return bestHeat;

 } /// findExtremeType$X$Y()

 /**
 Did you think you could override Diffuse2d.stepRule()? You can -- as we have
 done here -- but the method will never be invoked.

 <p>
 Diffuse2d inherits from Ca2d, which inherits from DblBuffer2d, which maintains
 two lattices (old and new). As the Swarm Reference Guide states for 
 DblBuffer2d, <tt>putValue()</tt>, which <tt>stepRule()</tt> presumably
 invokes, is "overridden so writes happen to newLattice".

 */
 public Object stepRule ()
 {
     // super.stepRule ();
     System.out.println ("This method never gets invoked.");
     return this;
 }

 /**
 This method returns the sum of the heat in each cell of the HeatSpace.

 */
 public double totalHeat ()
 {
     double totalHeat = 0.0;
     for (int x = 0; x < getSizeX (); x++)
         for (int y = 0; y < getSizeY (); y++)
             totalHeat += getValueAtX$Y (x, y);
     return totalHeat;
 }

 } /// class HeatSpace

Heatbug.java

 // jheatbugs-3.0

 // Java Heatbugs application. Copyright � 1999-2000 Swarm Development Group.
 // This library is distributed without any warranty; without even the
 // implied warranty of merchantability or fitness for a particular
 // purpose.  See file COPYING for details and terms of copying.

 // Changes (from jheatbugs-2001-03-28) by Timothy Howe. 

 import java.awt.*;

 import swarm.Globals;
 import swarm.space.Grid2d;
 import swarm.space.Grid2dImpl;
 import swarm.gui.Raster;

 /**
 See HeatbugModelSwarm for an overview of the heatbugs application.

 <p>
 A Heatbug is an agent in a 2-dimensional world. A Heatbug has the following
 behavior:

  <dir>
  Each Heatbug has its own ideal temperature, and a color that indicates the 
  Heatbug's ideal temperature (more green for cool-loving Heatbugs, more yellow 
  for warmth-loving Heatbugs).
  </dir>

  <dir>
  A Heatbug senses the temperature of the cells in its 9-cell neighborhood.
  </dir>

  <dir>
  A Heatbug has an unhappiness, which is the difference between the Heatbug's 
  ideal temperature and the temperature of the cell where it sits.
  We call a Heatbug unhappy if its unhappiness is non-zero.
  </dir>

  <dir>
  With an arbitrary probability (which is a property of the individual
  Heatbug), an unhappy Heatbug will try to move to a randomly-chosen cell in 
  its 8-cell neighborhood.
  </dir>

  <dir>
  If an unhappy Heatbug does not try to move in the arbitrary fashion described 
  in the previous paragraph, it will try to move to the cell in
  its 8-cell neighborhood whose temperature is closest to
  its ideal temperature. If there is more than once such cell, it will 
  choose at random among the cells with that closest-to-ideal temperature.
  </dir>

  <dir>
  If the cell that a Heatbug tries to move to, as described in the previous two
  paragraphs, is not empty, the Heatbug will make 10 attempts to move to a 
  randomly-chosen empty cell in its 8-cell neighborhood. 
  </dir>

  <dir>
  Two or more Heatbugs may not occupy a given cell simultaneously (but at 
  initialization, double occupancy merely generates a warning). 
  </dir>

  <dir>
  If there is no evaporation in the HeatSpace, heat will increase continually
  until it reaches MAX_HEAT times the number of cells. With evaporation, the 
  Heatbugs will typically become happier as they heat their neighborhoods up 
  from the initial zero temperatures; then, after the heat in many cells hits
  its MAX_HEAT ceiling, they will become unhappier as the capped heat diffuses
  to distant cells; then, after the heat homogenizes, their unhappiness will
  stabilize. Check it out: invoke javaswarm -Dr=0 -De=1 [-Di=1] [-Dc=1] [-Dd=0] 
  StartHeatbugs. 
  </dir>

  <dir>
  If there is no evaporation in the HeatSpace, heat will increase continually
  until it reaches MAX_HEAT times the number of cells. With evaporation, 
  the Heatbugs will typically become happier till a certain time (determined by 
  ideal temperatures, "evaporation" rate, and MAX_HEAT), then become unhappier, 
  and then stabilize. Check it out: invoke javaswarm -Dr=0 -De=1 [-Di=1] [-Dc=1] 
  [-Dd=0] StartHeatbugs. 
  </dir>

  <dir>
  A Heatbug produces heat (the amount is a property of the individual Heatbug).
  It deposits the heat at the cell where it was sitting, not at the cell it is 
  going to.
  </dir>

 */
 public class Heatbug
 {
 // The amount of heat I produce (units are undefined):
 public int outputHeat;
     public Object setOutputHeat (int outputHeat)
     { this.outputHeat = outputHeat; return this; }
 // The temperature I prefer:
 public int idealTemperature;
     public int getIdealTemperature () { return idealTemperature; }
     public Object setIdealTemperature (int idealTemperature)
     { this.idealTemperature = idealTemperature; return this; }
 // The difference between my temperature and my ideal temperature:

 public double unhappiness;
     public double getUnhappiness () { return unhappiness; }
    // The chance that I will move arbitrarily:
 public double randomMoveProbability;
     public void setRandomMoveProbability (double randomMoveProbability)
     { this.randomMoveProbability = randomMoveProbability; }
 // My location in _world as well as in _heatSpace:
 public int x, y;
 // My index into the ColorMap defined in HeatbugModelSwarm:
 public byte colorIndex;
     public void setColorIndex (byte colorIndex)
     { this.colorIndex = colorIndex; }
 // The 2-dimensional world of motion:
 protected Grid2d _world;
 // The 2-dimensional world of heat:
 protected HeatSpace _heatSpace;
 // The model I belong to:
 protected HeatbugModelSwarm _model;
 // My index in the Heatbug list (needed only for diagnostics):
 protected int _heatbugIndex;
 // The level of diagnostics to write to standard output:
 protected int _printDiagnostics = 0;
     public void setPrintDiagnostics (int printDiagnostics)
     { _printDiagnostics = printDiagnostics; }

 public Heatbug 
  (Grid2d world, 
   HeatSpace heatSpace, 
   HeatbugModelSwarm model,
   int heatbugIndex,
   int printDiagnostics
  )
 {
     _world = world;
     _heatSpace = heatSpace;
     _model = model;
     _heatbugIndex = heatbugIndex;
     _printDiagnostics = printDiagnostics;

     if (_world == null)
         System.err.println ("Heatbug was created without a world");

     if (_heatSpace == null)
         System.err.println ("Heatbug was created without a heatSpace");

 } /// constructor

 public Object drawSelfOn (Raster raster)
 {
     raster.drawPointX$Y$Color (x, y, colorIndex);
     return this;
 }

 /**
 This method defines what the Heatbug does whenever the Schedule triggers it. 

 <p>
 The method is synchronized, which means the compiler will not let it be 
 multi-threaded, which means it cannot be parallelized. It is synchronized 
 because to avoid collisions, the Heatbugs must decide one at a time which 
 cell to move to. 

 <p>
 There may be other methods in this simulation that should be synchronized. 

 */
 public synchronized void heatbugStep ()
 {
     int newX, newY;

     // Get the heat where I am sitting:
     int heatHere = _heatSpace.getValueAtX$Y (x, y);

     // Update my current unhappiness:
     int step = _model.getActivity ().getScheduleActivity ().getCurrentTime ();
     unhappiness = Math.abs (idealTemperature - heatHere);

     if (unhappiness != 0 && ! _model.getImmobile ())
     {

         double uDR = Globals.env.uniformDblRand.getDoubleWithMin$withMax (0.0, 1.0);
         if (uDR < randomMoveProbability)
         {
             if (_printDiagnostics >= 100)
                 System.out.print ("Trying to move randomly ... ");
             // Pick a random cell within the 9-cell neighborhood, applying
             // geographic wrap-around:
             newX =
              (x + Globals.env.uniformIntRand.getIntegerWithMin$withMax (-1, 1)
               + _world.getSizeX ()
              ) % _world.getSizeX ();
             newY =
              (y + Globals.env.uniformIntRand.getIntegerWithMin$withMax (-1, 1)
               + _world.getSizeY ()
              ) % _world.getSizeY ();
         } else
         {
             if (_printDiagnostics >= 100)
                 System.out.print ("Trying to move rationally ... ");
             Point scratchPoint = new Point (x, y);
             // Ask the HeatSpace for a cell in the 9-cell neighborhood
             // with the closest-to-ideal temperature: 
             _heatSpace.findExtremeType$X$Y
              ((heatHere < idealTemperature ? HeatSpace.HOT : HeatSpace.COLD),
               scratchPoint,   // scratchPoint is an inout parameter
               _world
              );
             newX = scratchPoint.x;
             newY = scratchPoint.y;
         }
         // ... Whether it chose randomly or rationally, a Heatbug may have
         // chosen an already-occupied cell -- including the cell it occupies. 
 		// If it chose its own cell, the choice is about
         // to be rejected, since the code below checks to see whether the cell
         // is already occupied, without asking which Heatbug is occupying it:
         if (_world.getObjectAtX$Y (newX, newY) != null)
         {
             int tries = 0;
             int location, xm1, xp1, ym1, yp1;
             // 10 is an arbitrary choice for the number of tries; it is
             // *not* implied by the number of cells in the neighborhood:
             while ((_world.getObjectAtX$Y (newX, newY) != null) && 
                    (tries < 10)
                   )
             {
                 // Choose randomly among the 8 cells in the neighborhood
                 location = Globals.env.uniformIntRand.getIntegerWithMin$withMax (1,8);
                 xm1 = (x + _world.getSizeX () - 1) % _world.getSizeX ();
                 xp1 = (x + 1) % _world.getSizeX ();
                 ym1 = (y + _world.getSizeY () - 1) % _world.getSizeY ();
                 yp1 = (y + 1) % _world.getSizeY ();
                 switch (location)
                 {
                 case 1:  
                     newX = xm1; newY = ym1;   // NW
                 break;  
                 case 2:
                     newX = x ; newY = ym1;    // N
                 break;  
                 case 3:
                     newX = xp1 ; newY = ym1;  // NE
                 break;  
                 case 4:
                     newX = xm1 ; newY = y;    // W
                 break;  
                 case 5:
                     newX = xp1 ; newY = y;    // E
                 break;  
                 case 6:
                     newX = xm1 ; newY = yp1;  // SW
                 break;  
                 case 7:
                     newX = x ; newY = yp1;    // S
                 break;  
                 case 8:
                     newX = xp1 ; newY = yp1;  // SE
                 default:
                 break;
                 }
                 tries++;
             }
             if (tries == 10)
             {
                 if (_printDiagnostics >= 100)
                     System.out.println ("no, staying put ... ");
                 newX = x;
                 newY = y;
             }
             else
             {
                 if (_printDiagnostics >= 100)
                     System.out.println ("no, desperately ... ");
             }
         }

         _heatSpace.addHeat (outputHeat, x, y);
         _world.putObject$atX$Y (null, x, y);
         x = newX;
         y = newY;
         _world.putObject$atX$Y (this, x, y);

     } /// if unhappiness != 0
     else
     {
         if (_printDiagnostics >= 100)
         {
             System.out.println ("Too happy to move ... ");
         }
         _heatSpace.addHeat (outputHeat, x, y);
     }

     if (_printDiagnostics >= 100)
         System.out.println ("Heatbug " + this);

 } /// heatbugStep()

 /**

 This method does not check to see whether the target cell is already occupied.
 */
 public Object putAtX$Y (int inX, int inY)
 {
     x = inX;
     y = inY;
     _world.putObject$atX$Y (this, x, y);
     return this;
 }

 /**
 The Java compiler will invoke this method whenever we use a Heatbug where the
 compiler is expecting a String. That gives us an easy way to print diagnostics;
 for example, Heatbug heatbug = new Heatbug (...); System.out.println 
 ("I initialized Heatbug " + heatbug + ".");.
 */
 public String toString ()
 {
     return _heatbugIndex + " at (" + x + "," + y + "), heat " + _heatSpace.getValueAtX$Y (x, y);
 }

 } /// class Heatbug

HeatbugBatchSwarm.java

 // jheatbugs-3.0

 // Java Heatbugs application. Copyright � 1999-2000 Swarm Development Group.
 // This library is distributed without any warranty; without even the
 // implied warranty of merchantability or fitness for a particular
 // purpose.  See file COPYING for details and terms of copying.

 // Changes (from jheatbugs-2001-03-28) by Timothy Howe. 

 import swarm.Globals;
 import swarm.Selector;

 import swarm.defobj.Zone;

 import swarm.activity.Activity;
 import swarm.activity.ActionGroup;
 import swarm.activity.ActionGroupImpl;
 import swarm.activity.Schedule;
 import swarm.activity.ScheduleImpl;

 import swarm.objectbase.Swarm;
 import swarm.objectbase.SwarmImpl;

 import swarm.analysis.EZGraph;
 import swarm.analysis.EZGraphImpl;

 /**
 This class implements the batch-mode observer of the Heatbug model defined in
 HeatbugModelSwarm.java.

 <p>
 See HeatbugModelSwarm for an overview of the heatbugs application.
 */

 public class HeatbugBatchSwarm extends SwarmImpl 
 {

 // Variables referenced by an SCM file or (after Swarm 2.1.1) by a ProbeMap
 // must be public:
 // This is the number of steps to run the simulation:
 public int experimentDuration;
 // This is the number of steps to run before writing to the log:
 public int loggingFrequency;

 protected String _outputFilename = "unhappiness.output";
 protected Schedule _displaySchedule;
 protected Schedule _stopSchedule;
 // The Swarm we're observing:
 protected HeatbugModelSwarm _heatbugModelSwarm;
     public HeatbugModelSwarm getHeatbugModelSwarm ()
     { return _heatbugModelSwarm; }
 // The graph of Heatbug unhappiness, in file mode rather than the usual 
 // graphics mode:
 protected EZGraph _unhappyGraph;

 public HeatbugBatchSwarm (Zone aZone) {
     super (aZone);
     _heatbugModelSwarm = (HeatbugModelSwarm) 
      Globals.env.lispAppArchiver.getWithZone$key (getZone (), "modelSwarm");
     // ... The lispAppArchiver is created automatically from the SCM file. 
 } /// constructor

 public Activity activateIn (Swarm swarmContext) 
 {
     super.activateIn (swarmContext);
     _heatbugModelSwarm.activateIn (this);
     _stopSchedule.activateIn (this);
     if (loggingFrequency > 0)
         _displaySchedule.activateIn (this);
     return getActivity ();
 }

 /**
 This method schedules the actions of this batch Swarm. 

 <p>
 This Swarm contains the Schedules, ActionGroups, and Actions depicted in the 
 following diagram. 

 <xmp>
 Swarm
 this
 |
 +-----------------------------------+
 |                                   |
 Schedule                            Schedule
 _displaySchedule                    _stopSchedule
 |                                   |
 |                                   |
 |                                   |
 ActionGroup                         implied
 displayActions                      ActionGroup
 |                                   |
 |                                   |
 |                                   |
 Action                              Action
 _unhappygraph                       this
 .step()                             .stopRunning()
 </xmp>

 <p>
 See the documentation in HeatbugModelSwarm.buildActions() for an explanation
 of Schedules, ActionGroups, and Actions.

 */
 public Object buildActions () 
 {
     super.buildActions();

     // Let the model Swarm build its own schedule:
     _heatbugModelSwarm.buildActions();

     if (loggingFrequency > 0) 
     {

         // Define the Schedule to run once every loggingFrequency steps:
         _displaySchedule = new ScheduleImpl (getZone (), loggingFrequency);

         // Define the explicit ActionGroup:
         ActionGroup displayActions = new ActionGroupImpl (getZone ());

         // Add to the ActionGroup an Action to write the output of the
         // graph to _outputFilename:
         try
         {
         displayActions.createActionTo$message
          (_unhappyGraph,
           new Selector (_unhappyGraph.getClass (), "step", true)
          );
         } catch (Exception e)
         {
             System.err.println
              ("Exception batch _unhappyGraph step: " + e.getMessage ());
         }

         // Insert the ActionGroup displayActions into the Schedule:
         _displaySchedule.at$createAction 
          (0,
           // ... The Schedule will execute ActionGroup displayActions at 
           // time-step 0 relative to the beginning of the Schedule.
           displayActions
          );
     }

     _stopSchedule = new ScheduleImpl (getZone (), true);
     // ... The "true" indicates autoDrop: unlike _displaySchedule, 
     // _stopSchedule does not have a repeatInterval, because we will 
     // run it just once (at time-step experimentDuration) and then 
     // drop it from the Schedule. 

     // Define the sole Action of the implied ActionGroup:
     try
     {
     _stopSchedule.at$createActionTo$message
      (experimentDuration, 
       this, 
       new Selector (getClass (), "stopRunning", false)
      );
     } catch (Exception e)
     {
         System.err.println ("Exception stopRunning: " + e.getMessage ());
     }
     return this;
 } /// buildActions()

 public Object buildObjects () 
 {
     super.buildObjects();

     // Let the model Swarm build its objects:
     _heatbugModelSwarm.buildObjects ();

     // A user who sets loggingFrequency to 0 is requesting no logging at all:
     if (loggingFrequency > 0) 
     {
         _unhappyGraph =  new EZGraphImpl (getZone (), true);
         try
         {
         // Create a time-series graph of average values:
         _unhappyGraph.createAverageSequence$withFeedFrom$andSelector
           // ... writing the output to a file:
          (_outputFilename,
           // ... averaging over all the Heatbugs:
           _heatbugModelSwarm.getHeatbugList (),
           // ... using values obtained from Heatbug.getUnhappiness ():
           new Selector (Class.forName("Heatbug"), "getUnhappiness", false)
          );
         } catch (Exception e)
         {
             System.err.println
              ("Exception batch getUnhappiness: " + e.getMessage ());
         }
     }
     return this;
 } /// buildObjects()

 public Object go () 
 {
     System.out.println
      ("You specified the -b or --batch option,"
       + " so we're running without graphics."
      );
     System.out.println
      ("The Heatbugs are running for " + experimentDuration + " timesteps.");
     if (loggingFrequency > 0)
         System.out.println
          ("I am logging data every " + loggingFrequency
           + " timesteps to: " + _outputFilename + "."
          );

     getActivity ().getSwarmActivity ().run ();

     if (loggingFrequency > 0)
 // todo: explain this:
         // Close the output file:
         _unhappyGraph.drop ();

     return getActivity ().getStatus ();
 }

 public Object stopRunning () {
     // Terminate the simulation.
     System.out.println
      ("Quitting after step " + Globals.env.getCurrentTime () + ".");
     Globals.env.getCurrentSwarmActivity ().terminate ();
     return this;
 }

 }

HeatbugModelSwarm.java

 // jheatbugs-3.0

 // Java Heatbugs application. Copyright � 1999-2000 Swarm Development Group.
 // This library is distributed without any warranty; without even the
 // implied warranty of merchantability or fitness for a particular
 // purpose.  See file COPYING for details and terms of copying.

 // Changes (from jheatbugs-2001-03-28) by Timothy Howe. 

 import swarm.Globals;
 import swarm.Selector;
 import swarm.defobj.Zone;
 import swarm.defobj.SymbolImpl;

 import swarm.defobj.FArguments;
 import swarm.defobj.FArgumentsImpl;
 import swarm.defobj.FCall;
 import swarm.defobj.FCallImpl;

 import swarm.activity.Activity;
 import swarm.activity.ScheduleActivity;
 import swarm.activity.ActionGroup;
 import swarm.activity.ActionGroupImpl;
 import swarm.activity.Schedule;
 import swarm.activity.ScheduleImpl;
 import swarm.activity.FActionForEach;

 import swarm.objectbase.Swarm;
 import swarm.objectbase.SwarmImpl;
 import swarm.objectbase.VarProbe;
 import swarm.objectbase.MessageProbe;
 import swarm.objectbase.EmptyProbeMapImpl;

 import java.util.ArrayList;

 import swarm.space.Grid2d;
 import swarm.space.Grid2dImpl;

 /**
 Since this class is the core of the jheatbugs simulation, we present 
 here an overview of the entire application.

 <p>
 The following diagram depicts the structure of the six custom classes of the
 jheatbugs application:

 <xmp>
 |                     +-----------------+
 |                     | StartHeatbugs   |
 |                     +-----------------+
 |                              | 1
 |                              |
 |              +---------------+-----------+
 |              |                           |
 |              | 1                         | 1
 |   +---------------------+          +------------------------+
 |   | HeatbugBatchSwarm   |  - OR -  | HeatbugObserverSwarm   |
 |   | extends             |          | extends                |
 |   | SwarmImpl           |          | GUISwarmImpl           |
 |   +---------------------+          +------------------------+
 |              | 1                         | 1
 |              |                           |
 |              +---------------+-----------+
 |                              |
 |                              | 1
 |                    +---------------------+
 |                    | HeatbugModelSwarm   |-----------------+
 |                    | extends             | 1               |
 |                    | SwarmImpl           |                 |
 |                    +---------------------+                 |
 |                              | 1                           | 1
 |                              |                     +---------------+
 |                              |                     | HeatSpace     |
 |                              |                     | extends       |
 |                              |                     | Diffuse2dImpl |
 |                              |                     +---------------+
 |                              | 0..n                        | 1
 |                      +-----------------+                   |
 |                      |     Heatbug     |-------------------+
 |                      +-----------------+ 0..n
 </xmp>

 Five of the custom classes involved in the simulation are:

  <dir>
  1. HeatbugModelSwarm:
  the Heatbug simulation mechanism, which also manages supporting classes 
  such as Heatbug and HeatSpace, and is available for 
  viewing through either of the two observer classes.
  </dir>

  <dir>
  2. StartHeatbugs: the class with the <tt>main()</tt> method that 
  kicks off the simulation. Depending on the command-line option <i>-b</i>,
  StartHeatbugs creates either a HeatbugBatchSwarm or a HeatbugObserverSwarm
  in which to present the view of the HeatbugModelSwarm.
  </dir>

  <dir>
  3. HeatbugBatchSwarm: 
  the non-GUI observer: a view of the model presented through
  text output. <i>Batch</i> does not refer to a
  batch of Heatbugs, but rather to batch mode as opposed to GUI mode.
  HeatbugBatchSwarm writes to files and to standard output.
  </dir>

  <dir>
  4. HeatbugObserverSwarm:
  the GUI observer: a view of the model presented through
  graphs and plots.
  </dir>

  <dir>
  5. Heatbug: the class that defines the individual
  agents. A Heatbug has data (including its position in 2-dimensional
  space), behavior (including the ability to move in 2-dimensional space),
  and the ability to perceive quantities of heat in its immediate neighborhod.
  </dir>

 We use two 2-dimensional data structures in the model: one to record
 the positions of the Heatbugs, and another, of identical size, to record the
 heat that the Heatbugs produce as well as seek.
 The following diagram provides a rough illustration of the two data
 structures:

 <xmp>
 ^ Position:                          ^ Heat:
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 | |   |   |   |   |   |   |   |   |  | |   |   |   |   |   |   |   |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 | |   |   |   |   |   |   |   |   |  | |   | h | h | h |   |   |   |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 | |   |   | b |   |   |   |   |   |  | |   | h | h | h |   |   |   |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 | |   |   | b |   |   |   |   |   |  | |   | h | h | h |   |   |   |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 | |   |   |   |   |   |   |   |   |  | |   | h | h | h | h | h | h |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 y |   |   |   |   |   | b |   |   |  y |   |   |   |   | h | h | h |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 | |   |   |   |   |   |   |   |   |  | |   |   |   |   | h | h | h |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 0 |   |   |   |   |   |   |   |   |  0 |   |   |   |   |   |   |   |   |
 | +---+---+---+---+---+---+---+---+  | +---+---+---+---+---+---+---+---+
 | --0-------x--------------------->  | --0-------x--------------------->
 </xmp>

 where b represents a bug and h represents a non-zero quantity of heat.

 <p>
 HeatbugObserverSwarm displays the positions and the heat together on a 
 ZoomRaster. HeatbugBatchSwarm does not report the positions or heat, but only
 the average unhappiness of the Heatbugs.

 <p>
 For the model's positional data,
 we use Swarm's class Grid2d. For the heat data,
 we use Swarm's Diffuse2dImpl, whose behavior includes the dissipation
 and "evaporation" of heat.
 Rather than use Diffuse2dImpl directly, we need to specialize it, so
 we define HeatSpace, the last of the six custom classes involved in the
 Heatbug simulation:

  <dir>
  6. HeatSpace: a 2-dimensional array of heat values.
  We don't specify the heat units, but they might be degrees Celsius.
  HeatSpace inherits diffusion and evaporation behaviors from Diffuse2dImpl, 
  and it also provides the ability to
  report the hottest or coldest cells within a neighborhood.
  </dir>

 */

 public class HeatbugModelSwarm extends SwarmImpl
 {

 // Variables referenced by an SCM file or (after Swarm 2.1.1) by a ProbeMap
 // must be public:
 public int numBugs = 101;
     public void setNumBugs (int numBugs) 
     { if (numBugs != -1) this.numBugs = numBugs; }
     // ... See StartHeatbugs.java for an explanation of the value -1. 
 public int minIdealTemp = 17000;
 public int maxIdealTemp = 31000;
 public int minOutputHeat = 3000;
 public int maxOutputHeat = 10000;
 public boolean randomizeHeatbugUpdateOrder = false;
 public double randomMoveProbability = 0.4;
     public double getRandomMoveProbability ()
     { return randomMoveProbability; }
     public void setRandomMoveProbability (double randomMoveProbability)
     { this.randomMoveProbability = randomMoveProbability; }
 public int worldXSize = 80;
 public int worldYSize = 80;
 // Todo: diffusionConstant and some other variables are copies of
 // variables in Diffuse2d -- can we figure out a way to get rid of them?
 public double diffusionConstant = 1.0; 
     // ... 0 = minimum, 1 = maximum diffusion of heat in _heatSpace.
     public double getDiffusionConstant ()
     { return diffusionConstant; }
     public Object setDiffusionConstant (double diffusionConstant)
     { this.diffusionConstant = diffusionConstant; return this; }
 public double evaporationRate = 0.99; 
     // ... 0 = minimum, 1 = maximum retention of heat in _heatSpace.
     public double getEvaporationRate ()
     { return evaporationRate; }
     public Object setEvaporationRate (double evaporationRate)
     { this.evaporationRate = evaporationRate; return this; }
 // ... According to the documentation for Diffuse2d, newHeat = "evapRate * 
 // (self + diffusionConstant*(nbdavg - self)) where nbdavg is the weighted 
 // average of the 8 neighbours" -- but what does "weighted" mean?

 protected Schedule _modelSchedule;
 protected ArrayList _heatbugList;
     public ArrayList getHeatbugList ()
     { return _heatbugList; }
 protected Grid2d _world;

     public Grid2d getWorld ()
     { return _world; }
 protected HeatSpace _heatSpace;
     public HeatSpace getHeatSpace ()
     { return _heatSpace; }
 protected FActionForEach _actionForEach;
 protected boolean _immobile = false;
 // ... If _immobile == true we will see what happens when Heatbugs never move. 
     public boolean getImmobile () { return _immobile; }
     public void setImmobile (boolean immobile) { _immobile = immobile; }
 protected boolean _startInOneCluster = false;
 // ... If _startInOneCluster == true we will see what happens when Heatbugs all
 // start in a contiguous cluster.
     public boolean getStartInOneCluster () { return _startInOneCluster; }
     public void setStartInOneCluster (boolean startInOneCluster) 
     { _startInOneCluster = startInOneCluster; }
     public int printDiagnostics = 0;
     public void setPrintDiagnostics (int printDiagnostics) 
     { this.printDiagnostics = printDiagnostics; }

 /**
 The only task this constructor performs is to construct and install a ProbeMap. 

 <p>
 A Probe is a mechanism through which any object can access a 
 variable or method you define in a Swarm. In practice, Probes are used 
 almost exclusively by Swarm GUIs
 to provide interactive access to variable and methods. 

 <p>
 A ProbeMap is a set of Probes, stored as a Map. 
 (A Map is a set of name-value pairs. 
 In a ProbeMap, each name is the name of the variable or method; 
 each value is a pointer to the variable or method itself.)

 <p>
 All the Probes in a ProbeMap must belong to the same class; and all the
 Probes in a particular class must be in one ProbeMap (unless you are choosing
 one among several ProbeMaps at run time).

 <p>
 A ProbeDisplay is a GUI widget that presents a ProbeMap interactively. 
 You can see an example by running this application in GUI mode: note that 
 the variables and methods shown in the ProbeDisplay for HeatbugModelSwarm 
 exactly match the variables and methods 
 listed in addProbe() statements in this constructor.

 <p>
 Probes do not use getters and setters. After Swarm 2.1.1, the variables and 
 methods in a ProbeMap must be public (as must the variables referenced by an 
 SCM file).  

 <p>
 Because Probes do not use getters and setters, you cannot use Probes to
 control write access differently from read access. 

 <p>
 By default, a ProbeMap will contain <i>all</i> the variables of the specified 
 class. To control explicitly which variables are in the ProbeMap, create
 an EmptyProbeMap rather than a ProbeMap, and insert into 
 the EmptyProbeMap the variables and methods you want, as we do in this method.

 <p>
 ProbeMaps are collected into a ProbeLibrary. A Swarm has at most one 
 ProbeLibrary, and a ProbeLibrary has one ProbeMap for each class that has
 one or more Probes, as illustrated in this diagram:

 <xmp>
 swarmObject1
 |
 |
 |
 probeLibrary (for swarmObject1)
 |
 +----------------------------+
 |                            |
 probeMap1 (for Class1)       probeMap2 (for Class2)
 |                            |
 +------+------+              +-------+-------+-------+------------+
 |      |      |              |       |       |       |            |
 var11  var12  method11()     var21   var22   var23   method21()   method22()
 </xmp>

 */
 public HeatbugModelSwarm (Zone aZone)
 {
     super (aZone);

     EmptyProbeMapImpl heatbugModelProbeMap = new EmptyProbeMapImpl
      (aZone, getClass ());

     heatbugModelProbeMap.addProbe (probeVariable ("numBugs"));
     heatbugModelProbeMap.addProbe (probeVariable ("minIdealTemp"));
     heatbugModelProbeMap.addProbe (probeVariable ("maxIdealTemp"));
     heatbugModelProbeMap.addProbe (probeVariable ("minOutputHeat"));
     heatbugModelProbeMap.addProbe (probeVariable ("maxOutputHeat"));
     heatbugModelProbeMap.addProbe (probeVariable ("randomMoveProbability"));
     heatbugModelProbeMap.addProbe (probeVariable ("printDiagnostics"));
     heatbugModelProbeMap.addProbe (probeVariable ("diffusionConstant"));
     heatbugModelProbeMap.addProbe (probeVariable ("evaporationRate"));
     heatbugModelProbeMap.addProbe (probeVariable ("worldXSize"));
     heatbugModelProbeMap.addProbe (probeVariable ("worldYSize"));
     // The number of colons after the name of each method must match the number
     // of arguments in the method's signature:
     heatbugModelProbeMap.addProbe (probeMessage ("addHeatbugs:"));

     Globals.env.probeLibrary.setProbeMap$For
      (heatbugModelProbeMap, getClass ());

 } /// constructor

 /**
 This method activates the schedules so they're ready to run.

 <p>
 Todo: explain more thoroughly what this method does. Explain who calls it.
 Explain what an Activity is.  
  
 @param swarmContext (in)
     the larger context within which this model is activated; a model swarm
     usually runs in the context of an observer swarm or a super-model swarm;
     the effect of specifying a context is to specify a native memory 
     allocation zone
 */
 public Activity activateIn (Swarm swarmContext)
 {
     super.activateIn (swarmContext);
     // Add our own schedule to the base class's schedule:
     _modelSchedule.activateIn (this);
     // Return the schedule that we inherit from the base class:
     return getActivity ();
 } /// activateIn()

 /**
 If Heatbugs gave birth or immigrated, you could use this method to introduce
 them into the model. 

 <p>
 Since the program does not consult numBugs after initialization, invocation
 of this method from the probe display after the simulation has started will
 have no effect. 
 */
 public Object addHeatbugs (int numNewBugs)
 {
     numBugs += numNewBugs;
     System.out.println ("I will add " + numNewBugs + " new Heatbugs.");
     return this;
 }

 /**
 This method schedules the actions of this model Swarm. 

 <p>
 Why do we need to "build actions"? Why can't we just write standard Java
 methods? -- they build actions, don't they?

 <p>
 The answer is that Swarm's simulation engine runs in Objective-C code, behind 
 the Java Native Interface (JNI). To invoke the behavior of the objects we model
 in Java, the simulation engine relies on callbacks, which are methods that we
 ask the engine to invoke at the appropriate times. We use 
 <tt>buildActions()</tt> to define those methods and the timing of the calls.

 <p>
 This Swarm contains the Schedules, ActionGroups, and Actions depicted in the 
 following diagram. 

 <xmp>
 Swarm
 this
 |
 |
 |
 Schedule
 modelSchedule
 |
 +-------------------------------------------------------+
 |                                                       |
 ActionGroup                                             implied
 modelActions                                            ActionGroup
 |                                                       |
 +--------------+--------------------+                   |
 |              |                    |                   |
 Action         Action               Action              Action
 _heatSpace     _heatbugList.get()   _heatSpace          this
 .stepRule()    .heatbugStep()       .updateLattice()    .modelStep()
 </xmp>

 <p>
 In the remainder of this documentation section we discuss Schedules, 
 ActionGroups, Actions, repeat intervals, and time values. If you're happy
 letting Swarm invoke every behavior at every step of the simulation, 
 you don't need
 to understand all this -- just create one Schedule and no ActionGroups, and
 for each behavioral method in your simulation invoke 

 <xmp>
     <schedule>.createActionTo$message
      (this, new Selector (this.getClass (), <methodname>, false))
 </xmp>

 or another <tt>createAction...()</tt> or <tt>createFAction...()</tt> method.

 <p>
 A Schedule is a three-level hierarchy whose full structure
 is illustrated by this example:

 <xmp>
 Simulation
 |
 +-----------------+
 |                 |
 Schedule          Schedule
 |                 |
 |                 +-------------+-------------+------------+--------+--------+
 |                 |             |             |            |        |        |
 ActionGroup       ActionGroup   ActionGroup   ActionGroup  |        |        |
 |                 |             |             |            |        |        |
 +--------+        |             |             |            |        |        |
 |        |        |             |             |            |        |        |
 Action   Action   Action        Action        Action       Action   Action   Action
 </xmp>

 Consider first a Schedule with this subset of that full sample structure:

 <xmp>
 Simulation
 |
 +-----------------+
 |                 |
 Schedule          Schedule
 RI = 1            RI = 3
 |                 |
 |                 +-------------+-------------+
 |                 |             |             |
 ActionGroup       ActionGroup   ActionGroup   ActionGroup
 TV = 0            TV = 0        TV = 1        TV = 2
 |                 |             |             |
 +--------+        |             |             |
 |        |        |             |             |
 Action   Action   Action        Action        Action
 </xmp>

 where RI indicates the <i>repeat interval</i> of a Schedule, 
 as specified in the invocation of the Schedule, and
 TV indicates the <i>time value</i> of an ActionGroup, 
 as specified in the invocation 
 of the Schedule's method <tt>at$createAction()</tt>. 

 <p>
 That Schedule would have the following timeline:

 <xmp>
 |       |       |       |       |       |       |       |
 S1      S1      S1      S1      S1      S1      S1      S1 
 S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0
 |       |       |       |       |       |       |       |
 S3      |       |       S3      |       |       |       S3
 S3-tv0  |       |       S3-tv0  |       |       |       S3-tv0
 |       S3-tv1  |       |       S3-tv1  |       |       |
 |       |       S3-tv2  |       |       S3-tv2  |       |
 |       |       |       S3-tv3  |       |       S3-tv3  | Error: see note.
 |       |       |       |       |       |       |       |
 time --->
 </xmp>

 where

  <p><dir>
  S1 represents the Schedule with a repeat interval of 1;
   <p><dir>
   S1-tv0 represents the ActionGroup in S1 (with a repeat interval of 0);
   </dir>
  </dir>

  <p><dir>
  S3 represents the Schedule with a repeat interval of 3;
   <p><dir>
   S3-tv0 represents the ActionGroup in S3 with a time value of 0;
   </dir>
   <p><dir>
   S3-tv1 represents the ActionGroup in S3 with a time value of 1;
   </dir>
   <p><dir>
   and so on.
   </dir>
  </dir>

 <p>
 Note: We introduced Action S3-tv3 into the diagram to illustrate what
 happens when the time value of an action group is greater than or equal to
 the repeat interval of its ActionGroup. S3-tv3 will cause a run-time error,
 because its time value will make the ActionGroup 
 "run over" into the next iteration of the Schedule. 

 <p>
 As depicted in the first illustration at the top of this section, you can also
 insert an Action directly into the Schedule, rather than insert the Action
 into an ActionGroup that you insert into the Schedule. 
 If you insert Actions directly into a Schedule, 
 you must specify the time value of the Action, and (as for ActionGroups)
 the time value must be less than the repeat inverval of the Schedule. 
 The Schedule collects all Actions with a particular time value
 into an implied ActionGroup with that time value. For 
 example, inserting two Actions
 with a time value of 0 and one with a time value of 1 directly into
 a Schedule with a repeat interval of 3
 would result in the following timeline:

 <xmp>
 |       |       |       |       |       |       |       |
 S3      |       |       S3      |       |       S3      |
 IAG-tv0 |       |       IAG-tv0 |       |       IAG-tv0 |
 a-tv0-p |       |       a-tv0-p |       |       a-tv0-p |
 a-tv0-q |       |       a-tv0-q |       |       a-tv0-q |
 |       |       |       |       |       |       |       |
 |       IAG-tv1 |       |       IAG-tv1 |       |       IAG-tv1
 |       a-tv1   |       |       a-tv1   |       |       a-tv1
 |       |       |       |       |       |       |       |
 time --->
 </xmp>

 where

  <p><dir>
  IAG-tv0 represents an implied ActionGroup with a time value of 0;
   <p><dir>
   a-tv0-p represents an Action with a time value of 0;
   </dir>
   <p><dir>
   a-tv0-q represents the other Action with a time value of 0; and
   </dir>
  </dir>

  <p><dir>
  IAG-tv1 represents an implied ActionGroup with a time value of 1;
   <p><dir>
   a-tv1 represents the Action with a time value of 1.
   </dir>
  </dir>

 <p>
 In conclusion, the following diagram and timeline summarize the entire
 discussion above, without presenting any additional scheduling concepts:

 <xmp>
 Simulation
 |
 +-----------------+
 |                 |
 Schedule          Schedule
 RI = 1            RI = 3
 |                 |
 |                 +-------------+-------------+------------+-----------------+
 |                 |             |             |            |                 |
 ActionGroup       ActionGroup   ActionGroup   ActionGroup  IAG-tv0           IAG-tv1
 TV = 0            TV = 0        TV = 1        TV = 2       |                 |
 |                 |             |             |            |                 |
 +--------+        |             |             |            +--------+        |
 |        |        |             |             |            |        |        |
 Action   Action   Action        Action        Action       Action   Action   Action
 -        -        -             -             -            TV = 0   TV = 0   TV = 1
 </xmp>

 <xmp>
 |       |       |       |       |       |       |       |
 S1      S1      S1      S1      S1      S1      S1      S1 
 S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0  S1-tv0
 |       |       |       |       |       |       |       |
 S3      |       |       S3      |       |       |       S3
 S3-tv0  |       |       S3-tv0  |       |       |       S3-tv0
 |       S3-tv1  |       |       S3-tv1  |       |       |
 |       |       S3-tv2  |       |       S3-tv2  |       |
 |       |       |       S3-tv3  |       |       S3-tv3  | Error: TV >= RI.
 IAG-tv0 |       |       IAG-tv0 |       |       IAG-tv0 |
 a-tv0-p |       |       a-tv0-p |       |       a-tv0-p |
 a-tv0-q |       |       a-tv0-q |       |       a-tv0-q |
 |       |       |       |       |       |       |       |
 |       IAG-tv1 |       |       IAG-tv1 |       |       IAG-tv1
 |       a-tv1   |       |       a-tv1   |       |       a-tv1
 |       |       |       |       |       |       |       |
 time --->
 </xmp>

 */
 public Object buildActions ()
 {
     super.buildActions();

     ActionGroup modelActions = new ActionGroupImpl (getZone ());

     // Define the first Action of ActionGroup modelActions:
     try
     {
     modelActions.createActionTo$message
      (_heatSpace, new Selector (_heatSpace.getClass (), "stepRule", false));
       // ... "stepRule" is the name of a callback method in Diffuse2d, which
       // HeatSpace inherits from. It is Diffuse2d's sole non-inherited 
       // behavioral method. In other words, it's the only thing that a 
       // Diffuse2d as such knows how to do at any step of the simulation. 
       // In particular, it applies diffusion and evaporation.
       // 
       // ... We haven't yet figured out what "false" does; the Swarm Reference
       // Guide has no entry for Selector. 
     } catch (Exception e)
     { System.err.println ("Exception stepRule: " + e.getMessage ()); }

     // Define the second Action of ActionGroup modelActions:
     try
     {
     // Use Heatbug #0 as a prototype for indicating the class that the 
     // heatbugStep Action will access and for traversing _heatbugList:
     Heatbug proto = (Heatbug) _heatbugList.get (0);
     Selector sel = new Selector (proto.getClass (), "heatbugStep", false);
     _actionForEach = modelActions.createFActionForEachHomogeneous$call
      (_heatbugList,
       new FCallImpl (this, proto, sel, new FArgumentsImpl (this, sel))
       // ... Through Swarm 2.1, FArgumentsImpl() takes 3 arguments (of which
       // the last argument should be the boolean value true). After Swarm 2.1, 
       // it takes 2 arguments.
      );
     } catch (Exception e)
     { e.printStackTrace (System.err); }
     // Tell Swarm to update Heatbugs sequentially, not randomly: 
     _actionForEach.setDefaultOrder (Globals.env.Sequential);

     // Define the third Action of ActionGroup modelActions:
     try
     {
     modelActions.createActionTo$message
      (_heatSpace, new Selector (_heatSpace.getClass (), "updateLattice", false));
       // ... "updateLattice" is the name of a callback method (see the 
       // discussion of stepRule above). It is a method in DblBuffer2d. 
       // HeatSpace extends Diffuse2d, which extends Ca2d, which extends 
       // DblBuffer2d.  
     } catch (Exception e)
     { System.err.println ("Exception updateLattice: " + e.getMessage ()); }

     // Define the Schedule:
     _modelSchedule = new ScheduleImpl 
      (getZone (), 
       1 
       // ... The repeat interval is 1, so _modelSchedule will begin every step
       // of the simulation.
      );

     // Insert the ActionGroup modelActions into the Schedule:
     _modelSchedule.at$createAction 
      (0,
       // ... The Schedule will execute ActionGroup modelActions at time 
       // value 0 relative to the beginning of the Schedule.
       modelActions
      );

     // Define the sole Action of the implied ActionGroup:
     try
     {
     _modelSchedule.createActionTo$message
      (this, new Selector (this.getClass (), "modelStep", false));
     // ... This method invocation implies a time value of 0; it is equivalent 
     // to invoking _modelSchedule.at$createActionTo$message (0, ...).
     } catch (Exception e)
     { System.err.println ("Exception modelStep: " + e.getMessage ()); }

     return this;
 } /// buildActions()

 /**
 This method constructs the custom objects of this model Swarm. 

 <p>
 Why do we need to "build objects"? Why can't we just use standard Java
 constructors? -- they build objects, don't they?

 <p>
 The answer is that Swarm's simulation engine runs in Objective-C code, behind 
 the Java Native Interface (JNI). We use <tt>buildObjects()</tt> to define the 
 data structures that the Swarm infrastructure will replicate behind JNI.

 */
 public Object buildObjects ()
 {
     // Let our parent class build anything it needs to:
     super.buildObjects();

     // Create a 2-dimensional array of Heatbug positions:
     _world = new Grid2dImpl (getZone (), worldXSize, worldYSize);

     // Create a HeatSpace, which is a 2-dimensional array of heat values:
     _heatSpace = new HeatSpace
      (getZone (), 
       worldXSize, 
       worldYSize, 
       diffusionConstant, 
       evaporationRate, 
       printDiagnostics
      );

     // Create a list to keep track of the Heatbugs:
     _heatbugList = new ArrayList ();

     int x = 0;
     int y = 0;
     // Create numBugs Heatbugs:
     for (int heatbugIndex = 0; heatbugIndex < numBugs; heatbugIndex++)
     {

         // Create a Heatbug:
         Heatbug heatbug = new Heatbug 
          (_world, 
           _heatSpace, 
           this, 
           heatbugIndex, 
           printDiagnostics
          );

         // Add the bug to the end of the list:
         _heatbugList.add (heatbug);

         // Randomly choose an ideal temperature and an output heat, within the
         // allowable range:
         int idealTemp =
          Globals.env.uniformIntRand.getIntegerWithMin$withMax
           (minIdealTemp, maxIdealTemp);
         int outputHeat =
          Globals.env.uniformIntRand.getIntegerWithMin$withMax
           (minOutputHeat, maxOutputHeat);

         // Initialize the rest of the Heatbug's state:
         heatbug.setIdealTemperature (idealTemp);
         heatbug.setOutputHeat (outputHeat);
         heatbug.setRandomMoveProbability (randomMoveProbability);

         _world.setOverwriteWarnings (true);
         if (_startInOneCluster)
         {
             // This would be all we'd need, if collisions were OK:
             ///heatbug.putAtX$Y (worldXSize/2, worldYSize/5);
             // But we're avoiding collisions, so:
             // We will allow no collisions, so we'll squeeze them into a box
             // about sqrt (numBugs) high and by sqrt (numBugs) wide:
             heatbug.putAtX$Y 
              ((worldXSize/2 + x) % worldXSize, 
               (worldYSize/5 + y) % worldYSize
              );
             if (++x >= Math.pow (numBugs, 0.5))
             {
                 x = 0;
                 ++y;
             }
         }
         else
         {
             // For simpler code, we allow collisions here: -- the Heatbugs 
             // quickly separate themselves: 
             heatbug.putAtX$Y
              (Globals.env.uniformIntRand.getIntegerWithMin$withMax
                (0, (worldXSize-1)),
               Globals.env.uniformIntRand.getIntegerWithMin$withMax
                (0, (worldYSize-1))
              );
             // ... We could eliminate collision-warning messages by invoking
             // world.setOverwriteWarnings (false) before this loop. We could

             // run more safely by setting it true again after the loop. But we
             // could, with small probability, still get a warning message if
             // two Heatbugs are initialized at the same cell and, being hemmed
             // in by other Heatbugs, they both choose to stay in the cell. 
         }
         if (printDiagnostics >= 1)
             System.out.println 
              ("I initialized Heatbug " + heatbug + ".");
     } /// for each Heatbug

     return this;
 } /// buildObjects()

 public Object modelStep ()
 {
     _heatSpace.setPrintDiagnostics (printDiagnostics);
     // Monitor the heat at an arbitrary cell (2, 2) (HeatSpace monitors 
     // the same cell):
     int x = 2; int y = 2;
     if (printDiagnostics >= 10)
         System.out.println 
          ("In modelStep(), at step "
           + getActivity ().getScheduleActivity ().getCurrentTime ()
           + ", heat at (" + x + ", " + y + ") is "
           + _heatSpace.getValueAtX$Y (x, y) + "."
          );
     // See if historical heat is a function of the number of steps:

     if (printDiagnostics >= 20)
     {
         System.out.println 
          ("Historical heat / step count is " + 
           (_heatSpace.totalHeat () + _heatSpace.getDiscardedHeat ())
            / (getActivity ().getScheduleActivity ().getCurrentTime () + 1)
           + "."
          );
     }
     return this;
 }

 protected MessageProbe probeMessage (String name)
 {
     return Globals.env.probeLibrary.getProbeForMessage$inClass
      (name, HeatbugModelSwarm.this.getClass ());
 }

 protected VarProbe probeVariable (String name)
 {
     return Globals.env.probeLibrary.getProbeForVariable$inClass
      (name, HeatbugModelSwarm.this.getClass ());
 }

 } /// class HeatbugModelSwarm
</code>

'''HeatbugObserverSwarm.java'''

<code>
 // jheatbugs-3.0

 // Java Heatbugs application. Copyright � 1999-2000 Swarm Development Group.
 // This library is distributed without any warranty; without even the
 // implied warranty of merchantability or fitness for a particular
 // purpose.  See file COPYING for details and terms of copying.

 // Changes (from jheatbugs-2001-03-28) by Timothy Howe. 

 import java.util.ArrayList;

 import swarm.Globals;
 import swarm.Selector;
 import swarm.defobj.Zone;

 import swarm.activity.Activity;
 import swarm.activity.ActionGroup;
 import swarm.activity.ActionGroupImpl;
 import swarm.activity.Schedule;
 import swarm.activity.ScheduleImpl;

 import swarm.objectbase.Swarm;
 import swarm.objectbase.VarProbe;
 import swarm.objectbase.MessageProbe;
 import swarm.objectbase.EmptyProbeMapImpl;

 import swarm.gui.Colormap;
 import swarm.gui.ColormapImpl;
 import swarm.gui.ZoomRaster;
 import swarm.gui.ZoomRasterImpl;

 import swarm.analysis.EZGraph;
 import swarm.analysis.EZGraphImpl;

 import swarm.simtoolsgui.GUISwarm;
 import swarm.simtoolsgui.GUISwarmImpl;

 import swarm.space.Value2dDisplay;
 import swarm.space.Value2dDisplayImpl;
 import swarm.space.Object2dDisplay;
 import swarm.space.Object2dDisplayImpl;

 /**
 This class implements the GUI-mode observer of the Heatbug model 
 defined in HeatbugModelSwarm.java.

 <p>
 See HeatbugModelSwarm for an overview of the heatbugs application.

 */
 public class HeatbugObserverSwarm extends GUISwarmImpl
 {

 // This defines the number of steps after which we display a snapshot of the 
 // simulation; we could speed up the simulation by displaying less frequently:
 public int displayFrequency = 1;

 // This defines the timing of the Swarm's Actions:  
 protected Schedule _displaySchedule;

 // This is the model we're observing:
 protected HeatbugModelSwarm _heatbugModelSwarm;
     public HeatbugModelSwarm getHeatbugModelSwarm ()
     { return _heatbugModelSwarm; }

 // This is the index to the palette we will use to paint heat and Heatbugs:
 protected Colormap _colormap;

 // This is the 2-dimensional display we will use to paint both heat
 // and Heatbugs:
 protected ZoomRaster _worldRaster;

 // This is the time-series graph we will use to display average
 // Heatbug unhappiness:
 protected EZGraph _unhappyGraph;

 // This is the 2-dimensional graph of the heat; we will display
 // it on the ZoomRaster:
 protected Value2dDisplay _heatDisplay;

 // This is the 2-dimensional graph of the Heatbugs; we will display
 // it on the ZoomRaster, layered over the _heatDisplay:
 protected Object2dDisplay _heatbugDisplay;

 public HeatbugObserverSwarm (Zone aZone)
 {
     super(aZone);

     // Create the model that this observer will observe:
     _heatbugModelSwarm = new HeatbugModelSwarm (getZone ());

     // Create a data structure to hold the Probes:
     EmptyProbeMapImpl heatbugObserverProbeMap = new EmptyProbeMapImpl 
      (aZone, getClass ());

     // Create Probes for some variables and methods (see HeatbugModelSwarm.java
     // for an explanation of Probes, ProbeMaps, and ProbeDisplays):
     heatbugObserverProbeMap.addProbe (probeVariable ("displayFrequency"));
     heatbugObserverProbeMap.addProbe (probeMessage ("graphBug:"));

     Globals.env.probeLibrary.setProbeMap$For
      (heatbugObserverProbeMap, getClass ());

     System.out.println 
      (
 "\n" +
 "In each field you change in the probe display, press Enter.\n" +
 "\n" +
 "For method invocations, enter an appropriate value in each argument\n" +
 "textbox after the method button, then click the button.\n" +
 "\n" +
 "Method invocations are subject to acceptance by the program; for example,\n" +
 "addHeatbugs() has no effect after you click Start or Next.\n"
      );

 } /// constructor

 /**
 This method activates the schedules so they're ready to run.

 @param swarmContext (in)
     the larger context within which this Swarm is activated; an observer swarm 
     is usually the top-level Swarm, so the context is usually null; for
     sub-Swarms such as _heatbugModelSwarm, this HeatbugObserverSwarm will be 
     the swarmContext 
 */
 public Activity activateIn (Swarm swarmContext)
 {
     super.activateIn (swarmContext);
     _heatbugModelSwarm.activateIn (this);
     _displaySchedule.activateIn (this);
     return getActivity();
 }

 /**
 This method schedules the actions of this GUI observer Swarm. 

 <p>
 This Swarm contains the Schedules, ActionGroups, and Actions depicted in the 
 following diagram. 

 <xmp>
 Swarm
 this
 |
 |
 |
 Schedule
 _displaySchedule
 |
 +-----------------------------------+
 |                                   |
 ActionGroup                         ActionGroup
 updateActions                       tkActions
 |                                   |
 +-------------+                     |
 |             |                     |
 Action        Action                Action
 this          probeDisplayManager   getActionCache()   
 ._update_()   .update()             doTkEvents()
 </xmp>

 <p>
 See the documentation in HeatbugModelSwarm.buildActions() for an explanation
 of Schedules, ActionGroups, and Actions.

 */
 public Object buildActions ()
 {
     super.buildActions();

     // Let the model Swarm build its own schedule:
     _heatbugModelSwarm.buildActions();

     ActionGroup updateActions = new ActionGroupImpl (getZone());
     ActionGroup tkActions = new ActionGroupImpl (getZone());

     // Define the first Action of ActionGroup updateActions:
     try
     {
     updateActions.createActionTo$message
      (this, new Selector (getClass (), "_update_", false));

     // Define the second Action of ActionGroup updateActions:
     updateActions.createActionTo$message
      (Globals.env.probeDisplayManager,
       new Selector (Globals.env.probeDisplayManager.getClass (), "update", true)
      );

     // Define the sole Action of ActionGroup tkActions:
     tkActions.createActionTo$message
      (getActionCache (),
       new Selector (getActionCache ().getClass (), "doTkEvents", true)
      );
     } catch (Exception e)
     {
         System.err.println ("Exception in setting up tkActions : "
          + e.getMessage ());
     }

     // Define the Schedule:
     _displaySchedule = new ScheduleImpl (getZone (), displayFrequency);

       // ... The repeat interval is displayFrequency, so the schedule will
       // begin once every displayFrequency steps of the simulation.
     // Insert the updateActions ActionGroup into the Schedule:
     _displaySchedule.at$createAction 
      (0, 
       // ... Execute the ActionGroup at step 0 relative to the beginning of 
       // the schedule.
       updateActions
      );
     // Insert the tkActions ActionGroup into the Schedule:
     _displaySchedule.at$createAction 
      (0, 
       // ... Execute the ActionGroup at step 0 relative to the beginning of 
       // the schedule.
       tkActions
      );

     return this;
 } /// buildActions()

 /**
 This method creates the plots and graphs that present the 
 results of the simulation. It delegates the building of the Heatbug model 
 to HeatbugModelSwarm.buildObjects().

 */
 public Object buildObjects ()
 {
     super.buildObjects ();

     // Create probe objects on the model and on this observer, to provide
     // GUI channels for reading and writing parameters:
     Globals.env.createArchivedProbeDisplay
      (_heatbugModelSwarm, "_heatbugModelSwarm");
     Globals.env.createArchivedProbeDisplay (this, "heatbugObserverSwarm");

     // Wait here until the user clicks Start or Next after optionally changing 
     // parameters:
     getControlPanel ().setStateStopped ();

     _heatbugModelSwarm.buildObjects ();

     // Create a Colormap for displaying Heatbugs and heat:
     _colormap = new ColormapImpl (getZone ());

     // Assign colors [ 0.. 63] to shades of red, for heat display;
     // assign colors [64..127] to shades of yellow-green, for Heatbug display:
     for (double i = 0; i < 64; i++)
     {
         _colormap.setColor$ToRed$Green$Blue ((byte) i,        i / 63, 0, 0);
         _colormap.setColor$ToRed$Green$Blue ((byte) (64 + i), i / 63, 1, 0);
     }

     // Set the colors of the heatbugs from yellow through green (the higher
     // the ideal temperature, the more the yellow):
     double tempRange 
      = _heatbugModelSwarm.maxIdealTemp - _heatbugModelSwarm.minIdealTemp;
     ArrayList heatbugList = _heatbugModelSwarm.getHeatbugList ();
     for (int i = 0; i < heatbugList.size (); i++)
     {
         Heatbug bug = (Heatbug) heatbugList.get (i);
         bug.setColorIndex 
          ((byte) 
           (64 + 63 * 
            (bug.getIdealTemperature () - _heatbugModelSwarm.minIdealTemp) 
            / tempRange
           )
          );
     }

     // Create another window for display, and set its attributes:
     _worldRaster = new ZoomRasterImpl (getZone (), "_worldRaster");
     try
     {
     _worldRaster.enableDestroyNotification$notificationMethod
      (this, new Selector (getClass (), "_worldRasterDeath_", false));
     } catch (Exception e)
     {
         System.err.println ("Exception _worldRasterDeath_: " + e.getMessage ());
     }
     _worldRaster.setColormap (_colormap);
     _worldRaster.setZoomFactor (4);
     _worldRaster.setWidth$Height
      ((_heatbugModelSwarm.getWorld ()).getSizeX (),
       (_heatbugModelSwarm.getWorld ()).getSizeY ()
      );
     _worldRaster.setWindowTitle ("Heat World");
     _worldRaster.pack();                  // draw the window

     // Create a Value2dDisplay, to display the HeatSpace on the ZoomRaster:
     _heatDisplay = new Value2dDisplayImpl
      (getZone (), _worldRaster, _colormap, _heatbugModelSwarm.getHeatSpace ());

     _heatDisplay.setDisplayMappingM$C (512, 0); // map [0..32767] to [0,63]

     // The Heatbug positional data is in the Grid2d, which we can obtain from 
     // getWorld(). The display widget is the ZoomRaster _worldRaster. An 
     // Object2dDisplay knows how to draw such data on such a raster: 
     try
     {
     _heatbugDisplay = new Object2dDisplayImpl
      (getZone (),
       _worldRaster,
       _heatbugModelSwarm.getWorld (),
       new Selector (Class.forName ("Heatbug"), "drawSelfOn", false)
      );
     } catch (Exception e)
     {
         System.err.println ("Exception drawSelfOn: " + e.getMessage ());
     }

     // The Grid2d knows what Heatbugs are on it, and _heatbugDisplay has it, so
     // _heatbugDisplay could draw it without any more help from us. But it has 
     // getSizeX () times getSizeY () cells. If we give it the Heatbug list,
     // which has only numBugs elements, it can draw the Heatbugs more 
     // efficiently: 
     _heatbugDisplay.setObjectCollection
      (_heatbugModelSwarm.getHeatbugList ());

     // Tell the world raster to send mouse clicks to the
     // _heatbugDisplay. This will allow the user to right-click on the
     // display to probe the bugs:
     try
     {
     _worldRaster.setButton$Client$Message
      (3,
       _heatbugDisplay,
       new Selector (_heatbugDisplay.getClass (), "makeProbeAtX$Y", true)
      );
     } catch (Exception e)
     {
         System.err.println ("Exception makeProbeAtX$Y: " + e.getMessage ());
     }

     // Create the graph widget to display unhappiness:
     _unhappyGraph = new EZGraphImpl
      (getZone (),
       "Unhappiness of bugs vs. time",
       "time", 
       "unhappiness",
       "_unhappyGraph"
      );

     // Todo: deal with this now-commented-out code:
     // Globals.env.setWindowGeometryRecordName (_unhappyGraph, "_unhappyGraph");

     // Assign the method to be used for destroying _unhappyGraph:
     try
     {
     _unhappyGraph.enableDestroyNotification$notificationMethod
      (this,
       new Selector (getClass (), "_unhappyGraphDeath_", false)
      );
     } catch (Exception e)
     {
         System.err.println
          ("Exception _unhappyGraphDeath_: " + e.getMessage ());
     }

     // Create the mechanism for computing the average heatbug unhappiness:
     try
     {
     _unhappyGraph.createAverageSequence$withFeedFrom$andSelector
      ("unhappiness", 
       _heatbugModelSwarm.getHeatbugList (),
       new Selector (Class.forName ("Heatbug"), "getUnhappiness", false)
      );
     } catch (Exception e)
     {
         System.err.println ("Exception getUnhappiness: " + e.getMessage ());
     }
     return this;
 } /// buildObjects()

 public void drop ()
 {
     if (_unhappyGraph != null)
         _unhappyGraph.disableDestroyNotification ();
     if (_worldRaster != null)
         _worldRaster.disableDestroyNotification ();
     super.drop ();
 }

 public Object graphBug (Heatbug aBug)
 {
     if (_unhappyGraph != null)
     try
     {
     _unhappyGraph.createSequence$withFeedFrom$andSelector
      ("Bug", 
       aBug,
       new Selector (aBug.getClass (), "getUnhappiness", false)
      );
     } catch (Exception e)
     {
         System.err.println ("Exception graphBug: " + e.getMessage());
     }
     return this;
 }

 public Object _unhappyGraphDeath_ (Object caller)
 {
     _unhappyGraph.drop ();
     _unhappyGraph = null;
     return this;
 }

 /**
 This callback method defines what the observer does whenever the Schedule 
 triggers it. 

 */
 public Object _update_ ()
 {
     if (_worldRaster != null)
     {
         _heatDisplay.display ();
         _heatbugDisplay.display ();
         _worldRaster.drawSelf ();
     }

     if (_unhappyGraph != null)
         _unhappyGraph.step ();
     return this;
 }

 public Object _worldRasterDeath_ (Object caller)
 {
     _worldRaster.drop ();
     _worldRaster = null;
     return this;
 }

 protected VarProbe probeVariable (String name) 
 {
     return Globals.env.probeLibrary.getProbeForVariable$inClass
      (name, HeatbugObserverSwarm.this.getClass ());
 }

 protected MessageProbe probeMessage (String name) {
     return Globals.env.probeLibrary.getProbeForMessage$inClass
      (name, HeatbugObserverSwarm.this.getClass ());
 }

 } /// class HeatbugObserverSwarm

StartHeatbugs.java

 // jheatbugs-3.0

 // Java Heatbugs program. Copyright � 1999-2000 Swarm Development Group.
 // This library is distributed without any warranty; without even the
 // implied warranty of merchantability or fitness for a particular
 // purpose.  See file COPYING for details and terms of copying.

 // Changes (from jheatbugs-2001-03-28) by Timothy Howe. 

 import swarm.Globals;
 import swarm.objectbase.Swarm;
 import swarm.objectbase.SwarmImpl;
 import swarm.simtoolsgui.GUISwarm;
 import swarm.simtoolsgui.GUISwarmImpl;

 /**

 <p>
 See HeatbugModelSwarm for an overview of the jheatbugs program.

 <p>
 The remainder of this discussion is confined to the issue of command-line
 parameters. 

 <p> <b> Controlling this program </b>

 <p>
 The following Java properties are recognized by this program.

  <dir>
  c=<boolean>: Start the Heatbugs all in a contiguous cluster
  </dir>

  <dir>
  d=<double>:  Specify the diffusion rate (0..1)
  </dir>

  <dir>
  e=<double>:  Specify the "evaporation" (really retention) rate (0..1)
  </dir>

  <dir>
  i=<boolean>: Make Heatbugs immobile
  </dir>

  <dir>
  n=<int>:     Specify the number of heatbugs
  </dir>

  <dir>
  p=<int>:     Specify level of diagnostic messages (try 10 or 100)
  </dir>

  <dir>
  r=<double>:  Specify the random-move probability
  </dir>

 <p>
 You can set Java properties on the java command line; for example, invoke

  <dir>
  <xmp>
   javaswarm -Dn=300 StartHeatbugs
  </xmp>
  </dir>

 <p>
 to start jheatbugs in GUI mode with 300 Heatbugs, or invoke

  <dir>
  <xmp>
   javaswarm -Dn=300 StartHeatbugs -b
  </xmp>
  </dir>

 <p>
 to start jheatbugs in batch mode with 300 Heatbugs.

 <p> <b> Precedence of controls </b>

 <p>
 In batch mode, the Java properties mechanism herein 
 takes precedence over all other setting of variables, 
 overriding the SCM file, 
 which itself overrides the Java initializers and constructors. 

 <p>
 In GUI mode, the probe display 
 takes precedence over all other setting of variables, 
 overriding the Java properties mechanism herein, 
 which itself overrides the Java initializers and constructors. 

 <p> <b> Modifying this program's properties </b>

 <p>
 To modify this program to accept an additional boolean property:

  <dir>
  Add to this documentation section a line analogous to 
   <dir>
   <xmp>
     c=<boolean>: Start the Heatbugs all in a contiguous cluster
   </xmp>
   </dir>
  </dir>

  <dir>
  and add to the code a statement analogous to 
   <dir>
   <xmp>
     model.setStartInOneCluster (getBooleanProperty ("c", false));
   </xmp>
   </dir>
  </dir>

 <p>
 To modify this program to accept an additional double property:

  <dir>
  Add to this documentation section a line analogous to 
   <dir>
   <xmp>
     r=<double>:  Specify the random-move probability
   </xmp>
   </dir>
  </dir>

  <dir>
  If you do not want this file to apply a default
  overriding the model's default, 
  add to the code a statement analogous to 
   <dir>
   <xmp>
     model.setRandomMoveProbability 
      (getDoubleProperty ("r", model.getRandomMoveProbability ()));
   </xmp>
   </dir>

  If you do want this file to apply a default
  overriding the model's default,
  add a statement analogous to 

   <dir>
   <xmp>
     model.setRandomMoveProbability (getDoubleProperty ("r", 0.5));
   </xmp>
   </dir>
  </dir>

 <p>
 Similarly for int properties, String properties, etc.

 */
 public class StartHeatbugs
 {

 public static void main (String[] args)
 {
     // Swarm initialization: all Swarm apps must call this first:
 	System.out.println ("This is StartHeatbugs.main().");
     Globals.env.initSwarm
      ("jheatbugs", "2.1", "bug-swarm@swarm.org", args);
 	System.out.println ("after initSwarm()");
     HeatbugModelSwarm model;

     if (Globals.env.guiFlag)
     {
         // We want graphics, so make an ObserverSwarm to get GUI objects:
         HeatbugObserverSwarm topLevelSwarm
          = new HeatbugObserverSwarm (Globals.env.globalZone);
         Globals.env.setWindowGeometryRecordName (topLevelSwarm, "topLevelSwarm");
         model = topLevelSwarm.getHeatbugModelSwarm ();
         build (topLevelSwarm, model);
         topLevelSwarm.go ();
         unbuild (topLevelSwarm, model);
     }
     else
     {
         // We do not want graphics, so make a BatchSwarm for writing to files: 
         HeatbugBatchSwarm topLevelSwarm
          = (HeatbugBatchSwarm) Globals.env.lispAppArchiver.getWithZone$key
          (Globals.env.globalZone, "batchSwarm");
         model = topLevelSwarm.getHeatbugModelSwarm ();
         build (topLevelSwarm, model);
         topLevelSwarm.go ();
         unbuild (topLevelSwarm, model);
     }
 } /// main()

 protected static void build (Swarm swarm, HeatbugModelSwarm model)
 {
     // ... We'd like to move as much code as possible from the if/then sections
     // in main() into this common method build(), but if we move 
     // getHeatbugModelSwarm(), we get this compile error: *** Error: No method 
     // named "getHeatbugModelSwarm" was found in type "swarm/objectbase/Swarm". 
     // Similarly when we try to move go(). So we moved the calls we could
     // move and left getHeatbugModelSwarm() and go() behind. 

     // We could pass immobile to the batch buildObjects(), but we get a compile
     // error if we try to pass it to the GUI buildObjects(). So instead we use 
     // setImmobile() for both:
     model.setImmobile (getBooleanProperty ("i", false));

     model.setPrintDiagnostics (getIntProperty ("p", 0));

     model.setStartInOneCluster (getBooleanProperty ("c", false));

     model.setRandomMoveProbability 
      (getDoubleProperty ("r", model.getRandomMoveProbability ()));
       // ... In cases such as this, when we happen not to want to override any 
       // default set by HeatbugModelSwarm, we apply that value as our own 
       // default. 

     model.setDiffusionConstant 
      (getDoubleProperty ("d", model.getDiffusionConstant ()));

     model.setEvaporationRate 
      (getDoubleProperty ("e", model.getEvaporationRate ()));

     model.setNumBugs (getIntProperty ("n", -1));
     // ... The special value -1 tells HeatbugModelSwarm to use its own default
     // value for numBugs. Use of Integer rather than int would avoid the need
     // for this special value. 

     swarm.buildObjects ();
     swarm.buildActions ();
     swarm.activateIn (null);
 } /// build()

 protected static void unbuild (Swarm swarm, HeatbugModelSwarm model)
 {
     swarm.drop ();
 }

 /**
 This method and the other get...Property() methods are generic convenience
 methods that would ideally be defined in some utility library.
 */
 protected static boolean getBooleanProperty (String propertyName, boolean dflt)
 {
     String property = System.getProperty (propertyName);
     if (property == null || property.equals ("")) return dflt;
     else return property.equals ("true") || property.equals ("1");
 }
 protected static double getDoubleProperty (String propertyName, double dflt)
 {
     String property = System.getProperty (propertyName);
     if (property == null || property.equals ("")) return dflt;
     else return Double.parseDouble (property);
 }
 protected static int getIntProperty (String propertyName, int dflt)
 {
     String property = System.getProperty (propertyName);
     if (property == null || property.equals ("")) return dflt;
     else return Integer.parseInt (property);
 }
 protected static String getStringProperty (String propertyName, String dflt)
 {
     String property = System.getProperty (propertyName);
     if (property == null) return dflt;
     return property;
 }

 }

jheatbugs.scm

 (list
  (cons 'batchSwarm
        (make-instance 'HeatbugBatchSwarm
                       #:loggingFrequency 1
                       #:experimentDuration 49))
  (cons 'modelSwarm
        (make-instance 'HeatbugModelSwarm
                       #:numBugs 77
                       #:minIdealTemp 10000
                       #:maxIdealTemp 20000
                       #:minOutputHeat 10000
                       #:maxOutputHeat 20000
                       #:randomMoveProbability 0.6)))

makefile

 # jheatbugs-3.0

 # By Timothy Howe. 



 JAVA_CLASSES = \
 HeatbugModelSwarm.class \
 Heatbug.class \
 HeatbugBatchSwarm.class \
 HeatbugObserverSwarm.class \
 HeatSpace.class \
 StartHeatbugs.class



 all: executable

 run : executable runcurrent

 runbatch : executable runcurrentbatch

 runcurrent :
 	$(SWARMHOME)/bin/javaswarm StartHeatbugs

 runcurrentbatch :
 	$(SWARMHOME)/bin/javaswarm StartHeatbugs -b

 executable : $(JAVA_CLASSES)

 clean :
 	rm -f *.class

 scrub :
 	@echo
 	@echo
 	@echo "Caution: this removes *.html."
 	@echo
 	@echo "Maybe you would want to name any HTML files you create \"*.htm\""
 	@echo "and leave \"*.html\" for javadoc and \"make scrub\"."
 	@echo
 	@echo
 	sleep 3
 	make clean
 	rm -f *.bak
 	rm -f *dump*
 	rm -f *junk*
 	rm -f *.out
 	rm -f *.output
 	rm -f *.html
 	rm -f package-list

 # Here we edit HeatbugModelSwarm.java depending on the translation of 
 # $SWARMHOME; we have to be sure that HeatbugModelSwarm.java is first in the
 # list $JAVA_CLASSES (otherwise another rule may compile it without editing 
 # it):
 HeatbugModelSwarm.class : HeatbugModelSwarm.java
 	echo $(SWARMHOME) | grep    "swarm-[12]\.[01]" \
 	 && (grep "new FArgumentsImpl (this, sel)" $< >/dev/null && \
 		 perl -pi.bak -e's/new FArgumentsImpl \(this, sel\)/new FArgumentsImpl (this, sel, true)/' $< \
 		) || :
 	echo $(SWARMHOME) | grep -v "swarm-[12]\.[01]" \
 	 && (grep "new FArgumentsImpl (this, sel, true)" $< >/dev/null && \
 		 perl -pi.bak -e's/new FArgumentsImpl \(this, sel, true\)/new FArgumentsImpl (this, sel)/' $< \
 		) || :
 	$(SWARMHOME)/bin/javacswarm $<



 .SUFFIXES: .java-ifdef .java .class .applet .applic

 # Setup-free applic from .java, for versions of make that do support
 # empty target suffixes:
 .java :
 	make $*.class
 	$(SWARMHOME)/bin/javaswarm $*

 # Setup-free applic from .class, for versions of make that do support
 # empty target suffixes:
 .class :
 	$(SWARMHOME)/bin/javaswarm $*

 # Setup-free applet from .java, for versions of make that do not support
 # empty target suffixes:
 .java.applet :
 	make $*.class
 	appletviewer $*.java

 # Setup-free applic from .java, for versions of make that do not support
 # empty target suffixes:
 .java.applic :
 	make $*.class
 	$(SWARMHOME)/bin/javaswarm $*

 # Setup-free .class from .java:
 .java.class :
 	$(SWARMHOME)/bin/javacswarm $<

 .java-ifdef :
 	make $*.class
 	$(SWARMHOME)/bin/javaswarm $*

 .java-ifdef.applet : 
 	make $*.class
 	appletviewer $*.java-ifdef

 .java-ifdef.class :
 	cpp -E $< | sed -e '/^#/d;' >tmp/$*.java
 	cd tmp && $(SWARMHOME)/bin/javacswarm -d .. $*.java

Part 8. Experiment-suite tools.

When you invoke javaswarm, your program runs only once. To conduct an experiment, you will probably want to invoke your program many times, with varying arguments and possibly with a varying random seed. For all that you can use replicator.pl, which is a general-purpose argument-varying program invoker. You may also want to use repswarm.pl, which is a general-purpose interface between replicator.pl and any Java Swarm program that accepts its run-time parameters in the form of Java Properties (class java.util.Properties).

To run jheatbugs-3.0 multiple times using replicator.pl and repswarm.pl, copy the files from Part 9 below into your working directory (unless textract.pl has already placed them there), and invoke

    chmod -R 755 *   # one time only
    export SWARMSTARTCLASS=StartHeatbugs
    replicator.pl --program=repswarm.pl --sweep p=10 --sweep n=20,30,40
    ls -Rl exp-*

The ls command will show the files that contain the output of your three experiments.

To run jheatbugs with clustering switched off and switched on, three randomly seeded runs each (not that jheatbugs pays any attention to the random seed), invoke

    replicator.pl --program=repswarm.pl --sweep c=0,1 --nruns=3

To review all your experiment invocations, invoke

    cat exp-*/*/exp*

For more information, read the documentation in replicator.pl and repswarm.pl.

Part 9. Experiment-suite source files.

replicator.pl

 #!/usr/bin/perl -w


 use strict;
 use Getopt::Long;  # command line processing
 # For File::Copy and IO::Dir, I needed to 
 # export PERLLIB="$PERLLIB://c/Swarm-2.1.1/lib/perl5/5.00563":
 use File::Copy; 
 use IO::Dir; 

 #Paul Johnson
 # June 16, 2001

 # WHAT IS THIS FOR?

 # I just learned Perl, and because people having no Perl knowledge
 # have difficulty writing their own scripts to manage parameter sweeps
 # and repetition of simulations, I offer this!  It
 # runs a program over and over and passes various command line
 # options to it.  You can sweep through AS MANY PARAMETERS AS YOU WANT
 # with AS MANY RUNS AS YOU WANT per setting.

 # HOW DO I USE IT?

 # You can give command line options or edit the CONFIGURATION
 # section below.  Either way should end up the same.  Either way, you
 # give the name of your program, the number of runs you want for each
 # setting, and the parameters you want to sweep.  If you enter the
 # values in the CONFIGURATION section below, you just get into a
 # terminal and type

 # perl replicator.pl 

 # Otherwise, you pass command line options, as in (type all this on
 # one line!)

 # perl replicator.pl --program=rb --directory=/home/pauljohn/swarm/PJProjects 
 # --NRUNS=3 --sweep numPPL=100 --sweep aRebConstPOM=0.1,0.2  --sweep vision=1,2,3

 # And the thing should start working.

 # This sets the numPPL variable at 100, and then two possible
 # values for our variable aRebConstPOM, and then 3 values for 
 # vision.  That means this script runs a total of 6 experiments,
 # corresponding to these values:

 # -vision=1 -aRebConstPOM=0.1 -numPPL=100
 # -vision=1 -aRebConstPOM=0.2 -numPPL=100
 # -vision=2 -aRebConstPOM=0.1 -numPPL=100
 # -vision=2 -aRebConstPOM=0.2 -numPPL=100
 # -vision=3 -aRebConstPOM=0.1 -numPPL=100
 # -vision=3 -aRebConstPOM=0.2 -numPPL=100 

 # Or if you use the "shortform" options to your program (short means
 # one dash and no equal sign, as in -n100), and tell the script so
 # with the shortform option:

 # perl replicator.pl --program=rb --directory=/home/pauljohn/swarm/PJProjects 
 #    --NRUNS=3 --shortform --sweep n=100  --sweep c=0.1,0.2  --sweep r=1,2,3

 # In short or long form, the key is the "sweep" option, which you
 # repeat for each parameter you want to examine. You can put one or
 # more comma-separated options to sweep various combinations.
 # Or you can specify a range; for example, 5..10.
 # If you don't specify --directory, it looks in the current working
 # directory.  If you don't specify NRUNS, it runs the program 1 time,
 # or whatever number is set in the CONFIGURATION section below.

 # WHY NOT USE DRONE?
 # I love Drone for this kind of work, but Windows users around here
 # have a hell of a time compiling expect, and we don't need all the drone
 # features for networking and such, so we needed an alternative.  

 # WHAT IS THE MEANING OF "EXPERIMENT" AND "RUN"

 # I use the drone terminology here.  An "experiment" is a set of runs
 # for a given set of parameters.  Every time you run this script, it
 # runs at least one experiment.  A run is an execution of your program
 # with a certain set of parameters.  You can have several runs of a
 # program at a certain set of parameters, the only thing that changes
 # is the random number seed that is fed into the program.

 # Each time you run this script, by typing "perl runRB.pl", it should
 # create a subdirectory exp-001, or if that exists, exp-002, and so forth.
 # Inside there, you should see one directory for each parameter
 # setting you select (see below).  You have the responsibility of
 # creating output files with the run number in them if you want
 # separate records for each run of the program.  It will create new 
 # experiment directories every time it runs, but if your program
 # writes on top of its old files when it repeats itself, it is 
 # your own fault.

 # This script will chdir to an experiment directory before it invokes 
 # your program, so your program must be invocable from that directory.
 # The script takes care of any SCM files by copying them from your invocation
 # directory to the experiment directory. If your program is a Java
 # program, you will probably need to list your invocation directory in
 # $CLASSPATH. 

 # HOW TO PREPARE YOUR SWARM PROGRAM
 # The assumption is that you have a swarm batch program (though you can 
 # use this script to run non-swarm programs as well) that runs with
 # command line options. Suppose you can run a program by typing its
 # name and a bunch of command line options, such as 

 # ./rb -b --run=1 -seed=1234 --numPPL=110 --vision=2 --aRebConstPOM=0.1 

 # or the short option form

 # ./rb -b -R1 -S1234 -n110 -r2 -f0.1 

 # -b is for batch, Swarm creates that flag. You create the rest within
 # your Swarm arguments code.

 # Any program you use with this script MUST accept at least 2 command
 # line options (though it is free to ignore them):  

 # 1. Run number.  Short form -Rx
 #                  Long form --run=x

 # 2. Seed Value.  Short form -Sx
 #                  Long form --seed=x

 # This script will take care of the seeds, replication, and so forth,
 # but your program must accept the options. The script will
 # create directories, and any output generated by your runs will be
 # sorted into them.  You are rendered almost superfluous by its mighty
 # power!  (not really...)

 # You cannot use this script if you want to specify no-argument options.
 # That's because GetOptions returns, for example, key z with value 1, whether 
 # the command line contains "--sweep z" or "--sweep z=1". So there is no way 
 # for this script to know that your program will accept -z but fail on -z1.

 # Now, about the random number seeds.  I started the Perl random
 # number generator with the number 1234321, you can change the seed
 # number there if you want.  Then the script chooses random numbers
 # from that stream as the seeds for following runs.


 # *************CONFIGURATION*****************

 # If you don't use command line options, HERE IS THE PART YOU EDIT:

 # Give a full path to the directory that holds your program. 
 # If you don't specify anything, or leave this blank, then the script
 # assumes the directory is your current working directory. Even if 
 # you are in MS-Windows, don't use separators like "\". Figure out 
 # what your bash environment needs for paths by typing "pwd" in your
 # terminal.

 # Edit the following settings if you care to:

 # Lets assume your program is in the current directory, the one where
 # you plan to run this script:

 my $dir_name = ".";

 # That's not necessary, you can put your program wherever you want
 # and tell the script so:
 # my $dir_name = '/home/pauljohn/swarm/PJProjects/valinux/Protest';


 # What is the name of your program:
 my $prog_name= 'rb';

 # How many runs do you want for each set of parameters.
 my $NRUNS=1;

 # Put in your command line parameters of interest here!  Here is an
 # example, assuming you want to pass through the "long form" command
 # line options.  Those are the ones like --option=value. Observe the
 # structure of a line below. You need a command line parameter in long form
 # (the kind that goes with -- in the command), the => symbol, and
 # bracketed parameter values to be swept.  If you put just one value,
 # it just uses that one value.  
 my %parameters = (
            ### numPPL =>   [ 100 ],
            ### aRebConstPOM  =>   [0.1, 0.2],
            ### vision        =>   [1, 2, 3]
            );

 # I personally like longform, but you may like shortform options.
 # If you want shortform options, change this variable $shortform to 1:

 #my $shortform = 0;
 my $shortform = 1;

 # and then give a %parameters statement like:
 #my %parameters = (
 #               n  =>   [ 100 ],
 #               f  =>   [0.1, 0.2],
 #               r  =>   [1, 2, 3]
 #               );



 # You don't really need to mess around below here. I think
 # you should leave it alone, as a matter of fact.
 ###################################################




 my @command_strings = "";

 &main ();

 sub main {
     &processCLI();
     
     foreach my $parameter ( keys %parameters ) {
     @command_strings = &updateCommandStrings( $parameters{$parameter}, $parameter ); 
     }

     my $expSuperDir = "";
     $expSuperDir = &createSuperDirectoryName();

     foreach my $string (@command_strings) {
     #beautify the string for directory creation purposes
     my $stringNoSpaces = $string;
     $stringNoSpaces =~ s/\ //g;
     $stringNoSpaces =~ s/--/-/g;
     my $fullPath = "$expSuperDir/$prog_name$stringNoSpaces";
     mkdir ($fullPath,0777);
     my $d = new IO::Dir "$dir_name"; 
     if (defined $d) { 
         while (defined (my $f = $d->read)) { 
             if ($f =~ m/scm$/i) { 
                 File::Copy::copy ($f, $fullPath);
             }
         } 
     }
     chdir ($fullPath);
     
         if ($dir_name eq ".") { $dir_name = $ENV{PWD};}
     my $program = "$dir_name/$prog_name";

     open(COMMANDS, ">>experiment_summary");
     srand 1234321;
     my $seedValue = 0;

     for (my $i=0; $i < $NRUNS; $i++) {
          my $progString;
          $seedValue += int (rand 100000) + 1;
          if ($shortform ==1) {
          $progString = join("",$program," -b"," -R",$i," -S",$seedValue," ", $string);
          }
          else {
          $progString = join("",$program," -b"," --run=",$i," --seed=",$seedValue," ", $string);
          }
          
            print COMMANDS "$progString \n";

          print "Now trying to run = $progString \n";
          my $outputFile = "stdout$i";
          #diverts stderr to a file
          my $output = `$progString 2>&1 >> $outputFile`;
          print $output;
         
      }

     close(COMMANDS);
     chdir ($ENV{PWD});
     }
 }


 sub processCLI {
     my %clinput = ();

     GetOptions ('NRUNS:i' => \$NRUNS, 'directory:s' => \$dir_name, 'program:s' => \$prog_name , 'shortform' => \$shortform, "sweep=s" => \%clinput) || die "Invalid options \n";

     print "Your program is in directory: $dir_name \n";
     print "Your program names is: $prog_name \n";
     print "Your desired number of runs is: $NRUNS \n";
     print "Your command line input parameters: \n";
     if ($shortform == 1){print "You said you were using short form parameters "};

     foreach (keys %clinput) { print "$_ $clinput{$_} \n";}

     if (%clinput) {
     print "We are using your command line input for parameters \n";
     %parameters = ();
     foreach (keys %clinput) { 
         ## print "key is $_; value is $clinput{$_}\n";
         my @range_expansion;
         eval "\@range_expansion = ($clinput{$_})";
         $parameters{$_}=\@range_expansion;
     }
     }
 }


 sub updateCommandStrings {
   
     my @valArray = @{$_[0]};
     my $key = $_[1];

    # defined or $_ = " " foreach @{$local_strings};
    # @local_strings = @{$local_strings};

     
     my @oldStringArray = @command_strings;
     
     @command_strings=();
     
     foreach my $st (@oldStringArray){
     foreach my $val (@valArray){
         my $newString;
         if ($shortform == 0) {
         $newString = join("",$st," --",$key,"=",$val);
         }
         else {
         $newString = join("",$st," -",$key,$val);
         }
         push (@command_strings, $newString);
     }
     }
     return @command_strings;
 }

 sub createSuperDirectoryName {
     my $dirName = "exp-001";
     my $i=1;
   
     while (-e $dirName ){
     $i++;
     $dirName = join ("-","exp",sprintf("%03d",$i));
     }
     mkdir ($dirName,0777);
     return $dirName
 }


 exit;

repswarm.pl

 #!/bin/perl

 # jheatbugs-3.0

 # Java Heatbugs application. Copyright � 1999-2000 Swarm Development Group.
 # This library is distributed without any warranty; without even the
 # implied warranty of merchantability or fitness for a particular
 # purpose.  See file COPYING for details and terms of copying.

 # By Timothy Howe. 

 # This program invokes a Swarm application. The program is written to be
 # invoked by replicator.pl; always specify --program=repswarm.pl, though you
 # may use a different path. 
 #
 # The program supports replicator.pl's shortform and longform options. The 
 # program does not support bundled options nor spaces between an option and 
 # its option argument.
 #
 # If, for example, you specify the parameter
 #
 #		--sweep q=3..7
 #
 # to replicator.pl, that program will invoke this program (for the first run) 
 # with the parameter
 #
 #		-q3
 #
 # and this program will invoke your Java Swarm program with the parameter
 #
 #		-Dq=3
 #
 # and your Java Swarm program can access the value in the standard fashion
 # through the Java System Property named "q". Java System Properties are 
 # implemented by the class java.util.Properties.
 #
 # For boolean values, since this program does not support options without
 # arguments, use the values 0 for false and 1 for true; for example,
 #
 #		--sweep c=0,1
 #
 # The program handles the Swarm batch option -b properly, regardless of where
 # it appears among the application-specific parameters.  
 #
 # The program requires that the environment variable SWARMSTARTCLASS be defined 
 # to indicate the initial Java class for javaswarm to invoke. 
 #
 # Note that replicator.pl handles numeric values only; for example, it can
 # handle "--sweep x=173" or "--sweep scale=-0.5,-5,-50", but not "--sweep 
 # color=r,g,b". That explains why replicator.pl can't pass the starting class 
 # name to this program -- hence this program gets the starting class name from 
 # the environment variable $SWARMSTARTCLASS. 


 $swarmhome = $ENV{"SWARMHOME"};

 if (! $ENV{SWARMSTARTCLASS})
 {
 	print STDOUT "(stdout) Fatal error: SWARMSTARTCLASS is undefined; exiting.\n";
 	print STDERR "(stderr) Fatal error: SWARMSTARTCLASS is undefined; exiting.\n";
 	exit 1;
 }

 for ($paramI = 0; $paramI <= $#ARGV; $paramI++)
 {
     $param = $ARGV[$paramI];
 	if ($param !~ m/^-./ || $param eq "--")
 	# ... "-" alone is not an option; "--" is the option-ending option. 
 	{ last; }
 	if ($param eq "-b")
     {
 		$argstring .= "$param ";
 	} elsif ($param =~ m/^--(.*?)=(.*)/)
     {
 		$propstring .= "-D$1=$2 ";
 	} elsif ($param =~ m/^--(.*)/)
     {
 		$propstring .= "-D$1=1 ";
 	} elsif ($param =~ m/^-(.)(.+)/)
     {
 		$propstring .= "-D$1=$2 ";
 	} else
     {
 		$param =~ s/^-+//;
 		$propstring .= "-D$param=1 ";
 	}
 }

 for (; $paramI <= $#ARGV; $paramI++)
 {
     $param = $ARGV[$paramI];
 	$argstring .= " $param";
 }

 # Because this program is run by replicator.pl, the application's Java files
 # are two directory levels up; hence we need to modify CLASSPATH. 
 $cmd = qq 
  {CLASSPATH="../..;$ENV{CLASSPATH}" $swarmhome/bin/javaswarm $propstring $ENV{SWARMSTARTCLASS} $argstring};
 print STDERR "In $0, invoking: $cmd ...\n";
 $result = system $cmd;
 exit $result;

Part 10. Suggestions for Your Next Steps.

Suggestion 1: never write a new Swarm program -- just modify an existing one.

For example, suppose your goal is to model the behavior of foxes and lemmings.

Start by getting jheatbugs-3.0 to run.

Next, invoke

    mv Heatbug.java Fox.java
    make

That will generate error messages. Get the program running again, and you will understand a bit about Java class names, file names, makefiles, and compiling.

Next, invoke

    cp Fox.java Lemming.java

Change Lemming.java only to make all the lemmings blue. Get your new program to run -- which you can confirm by observing the yellow/green foxes and the blue lemmings -- and you will understand more about class names, file names, makefiles, and compiling.

Then slowly give Fox more fox-like behavior and Lemming more lemming-like behavior.

Suggestion 2: write your Swarm application in Java, not in Objective-C.

The Swarm engine is written in Objective-C, but that fact is irrelevant to your choice of programming language, since Swarm's Java interface provides the same access to the engine that its Objective-C interface provides.

If you write in Objecctive-C, you can borrow code from existing Swarm applications written in Objective-C. But in general, re-usable logic implemented in Objective-C has probably migrated into the Swarm engine by now, so unless you know of some specific Objective-C code you want to build on, Objective-C will probably provide no advantage.

If you write in Java, you can use freely available Java libraries. Since Java is used throughout the world vastly more than Objective-C (see http://www.cs.berkeley.edu/~flab/languages.html), many more Java libraries are available than Objective-C.

Suggestion 3: learn how to use Java assertions (first available in Java 1.4; not used in jheatbugs-3.0) and regression testing.

This suggestion and the following suggestion are not especially tied to Swarm; they apply to Java programming in general, in areas that I think new Java programmers are most likely to overlook, to the detriment of their programs.

Here is a demonstration of regression testing using Java assertions:

RegressionTest.java

 // Copyright (c) 2004 Timothy Howe. All Rights Reserved.
 // 
 // Timothy Howe grants you ("Licensee") a non-exclusive, royalty-free license 
 // to use, modify and redistribute this software, provided that this copyright 
 // notice and license appear on all copies of the software.

 public class RegressionTest 
 {

 /**
 This method is trivial, but ignore that fact; instead, imagine that this method 
 implements some logic that you would like to regression-test. 
 */
 public double sqrt (double x)
 {
 	return Math.pow (x, 0.5);
 }

 /**
 This method tests the logic of one or more methods in this class. 
 */
 public void test ()
 {
 	double x1 = sqrt (1);
 	double x2 = sqrt (2);
 	assert x1 < x2 : x1 + " < " + x2;
 	double x4 = sqrt (4);
 	assert x2 < x4 : x2 + " < " + x4;

 	System.out.println ("End of successful test of " + getClass ());
 }

 public static void main (String[] arg)
 {

 	System.out.println (
 "This program demonstrates a straightforward technique for using Java\n" +
 "assertions to implement regression testing, which means testing to confirm\n" +
 "that logic that worked once continues to work as the code is modified to\n" +
 "add features or to fix bugs.\n" +
 "\n" +
 "You can apply the technique to all the supporting classes of your\n" +
 "application. Typically, such classes do not have a main() method.\n" +
 "To use the technique, write a main() method for each such class, and have\n" +
 "main() call a test method, and have the test method implement some simple\n" +
 "or sophisticated tests, and for each test use assert to determine pass or\n" +
 "fail. Sometimes it's better to make the test method static, and sometimes\n" +
 "it's better to make it an instance method.\n" +
 "\n" +
 "In your makefile, write a very simple \"test\" target that exercises all\n" +
 "your test classes.\n" +
 "\n" +
 "To compile with assertions, invoke\n" +
 "\n" +
 "    javac -source 1.4 RegressionTest.java\n" +
 "\n" +
 "To run with assertions enabled, invoke\n" +
 "\n" +
 "    java -ea RegressionTest\n" +
 "\n" +
 "And now we begin the test ...\n"
 	                   );

 	new RegressionTest ().test ();

 }

 }

Suggestion 4: learn how to read javadoc and to write your code to generate good

javadoc. First steps: invoke
    cd /top/swarm/src/jheatbugs-3.0
    javadoc *.java

and then open index.html and read about the jheatbugs classes. Also look more at the Java documentation from Sun.

Part 11. Naming and typographic conventions used in jheatbugs-3.0.

I prefix every instance-variable name with an underscore. This convention I borrowed from Sriram Srinivasan of "Advanced Perl Programming" fame and Martin Fowler of UML fame.

To make programming structures obvious, I put each matching token -- that is, every token among ( ) { } [ ] < > -- either on the same line or in the same column as its mate. Similarly for matching compound tokens, such as /* */ <tag> </tag>.

I generally begin a comment with ellipsis ("...") if it explains the preceding code; I end it with a colon (":") if it explains the subsequent code.

I make visibility protected rather than private whenever possible, so that subclassing will be easier.

In the Javadoc comments, I indicate the role of every parameter with the string "(in)" or "(out)" or "(inout)" to indicate, respectively, whether the parameter is only read or is only written or is read and written by the method; for example, "@param numBugs (in)". Thus, if I pass an array to a method, and the method or a delegate of the method might ever write an element of the array and might ever also read an element of the array, I indicate "(inout)". This convention I borrowed from Corba IDL.

In documentation, I use the form "m()", with no space before the parentheses, to mean "the method m, however many arguments it takes"; I use "m ()" to mean "the method m, which takes zero arguments"; "m (a)" to mean "the method m, which takes the argument a"; etc.

At the close of some of the longer methods, I put a comment so you can see what method you're reading when the screen shows only the tail; for example, "} /// buildObjects()". For constructors, the comment is "} /// constructor". For the same reason, I sometimes put a comment at the end of a long loop; for example, "} /// if _unhappiness != 0". There's no special reason for the triple slashes, except that after a while one learns to look for the triple slash to get oriented.

I define nearly every accessor (getter or setter) immediately after, and indented one tab stop in from, its variable. This convention prevents accessors from cluttering up the more interesting methods, and keeps you from having to wonder about accessibility when you're examining the data members. But recently I prefer Bill Vogel's convention: for simple data members that you want to give public write-access to, just make the data public.

The fundamental idea of exception handling is to remove unusual conditions from the normal flow of processing. Applying that concept to the typography, I do not indent try-blocks. But I do indent catch-blocks.

In the top-level class of each Java file, which typically contains most of the code, I save one tab stop by not indenting method and variable definitions (in other words, methods and variables begin at the left margin). For other classes in the file, only the class definition begins at the left margin; methods and variables start one tab stop in. The goal of this convention is to avoid wasting a rather uninformative tab stop through the bulk of the code, while giving a visual clue as to whether the displayed code is in the eponymous class or some other class.


[Main Page]
Main page
About SwarmWiki
News
Recent changes
Random page
Help

View source
Discuss this page
Page history
What links here
Related changes

Special pages