/*
 URAL: University of Regina Artificial Life.

 Version 1.2

 This program creates a simulated world where a number of creatures can live.

 Sun's Java Development Kit (JDK) 1.2 or higher is needed to compile and run
 this program.

 Developed using JDK 1.2 under Windows 95.
 Compile with: javac URAL.java
 Run with: java URAL


 Written by Kamran Karimi
 Copyright (C) Kamran Karimi

 Kamran Karimi
 Department of Computer Science
 University of Regina
 Regina, Saskatchewan
 CANADA  S4S 0A2

 karimi@cs.uregina.ca
 http://www.cs.uregina.ca/~karimi

The Java code follows the documentation.


 OVERVIEW:

 The Application creates an artificial world and some creatures living in it. The 
creatures try to learn about the world. They all have an initial amount of energy, 
which can get decreased by the application. Each creature has a brain in which it 
records the information received by its sensors.  This information can then be used for 
devising a plan which might later be executed by the creature's actuators.


 IMPLEMENTATION:

 This demonstration application is written in Java and uses Object Oriented programming 
techniques to make the code more readable and maintainable. Everything, including the 
world, the creatures and their brains, is implemented as an object with methods than 
can operate on itself. In this way a change in one part of the code will have minimal 
effect on the other parts. 

 The world and each creature are implemented as a separate thread, which runs 
concurrently with other threads. Depending on the Java Virtual Machine implementation, 
these threads can actually run at the same time on a multi-processor computer. Java for 
Solaris for example has this ability. This better simulates the real physical world 
where many different activities can go on at the same time. Synchronization while 
accessing common resources is achieved by making use of Java language's synchronization 
facilities.

 Java supports data structures like sets, trees and linked lists. The application uses 
these native data structures and thus saves a lot on the time and space involved in 
re-implementing them. This has made the implementation faster and less error prone, 
while keeping the code smaller at the same time.

 The program behavior is controlled by a number of parameters. The user can change most 
of these parameters from the application's graphical user interface. These include:

* The number of creatures in the world.
* The size of each creature in pixels
* Number of Obstacles in the world.
* Size of the world along the X axis in pixels.
* Size of the world along the Y axis in pixels.
* The initial amount of energy that is given to each creature.
* The amount of energy gained by a creature after eating a food item.
* The amount of energy each creature looses in each "epoch"
* Whether the generated food will always be put in the same spots.

 URAL 1.1 can write all the encountered situations, and the action it takes
in each situation, to a log file.

 The program uses features of Java version 2.0 specifications. To compile and run the 
program, Java Development Kit (JDK) 1.2 or higher is required. The source code can not 
be used under earlier Java compilers and runtime systems. The latest JDK packages can 
be downloaded for free from Sun's web pages at http://java.sun.com.


 THE WORLD:

 The world is considered a separate entity from the creatures. In this regard, the 
creatures are not considered part of the world.

 The world consists of a rectangular area where the creatures can move around in two 
dimensions. This area is divided into a number of spots, the size of which is determined
by the creature size. The movements are based on this size, not pixels. There can be 
obstacles in this area, each the size of a creature, and occupying a spot. Food is 
generated by the application and placed in the world. The Food items will disappear 
after a creature eats them. New food can be put at random places, or it can always be 
put in fixed places. The second option allows testing the creature's ability to go back 
for food.

 A separate, master thread manages the world and each of the creatures. This thread is 
responsible for the following:

* Creating the world.
* Creating the creatures.
* Applying the physics of the world on the creatures.
* Controlling the running of the creatures, so they execute their code in "epochs"
* Displaying a graphical representation of the world and the creatures.


 THE CREATURES:

 Each creature has a brain and a set of sensors, and can affect the world by taking some
actions. 


 SENSORS:

 Internal sensors gather information about the creature itself and provide them to the 
brain. External sensors gather information about the current real world situation for 
the brain. The creature's sensors in the current version of the application are simply 
two variables that hold the creature's X and Y positions, plus a boolean variable that 
indicates if the current position holds food or not. 


 ACTUATORS:

The creatures in the current version can do two things: Move and eat.

 When moving, each creature can choose between four actions: Going up, down, left or 
right. The creature will not be able to move if there is an obstacle in the destination 
spot. It can also not get out of the world. It automatically eats any food it finds.


 THE BRAIN:

This is the most important part of the creature. It does the following:

1. Memorizes all the situations, which were explored by the creature. It does this by 
taking a snapshot of them.  This snapshot contains the input of all the sensors of the 
creature.

2. Decides on the next action that should be performed by the creature. This can be done
by choosing an action randomly (when playing/exploring), or by following an 
already-existing plan (when trying to reach a goal like finding food).

3. Maintains links between the situations explored by the creature, so the creature 
knows what action causes transition from one situation to the next.


 PLAYING (EXPLORING):

 The creature starts in a random position in the world, and, not knowing anything about 
its surroundings, starts to do some random actions, and possibly goes to a new situation.
If it actually detects being in a new situations, then the creature records the 
resulting transition in its brain. The main issues in a transition are the following:

* The action taken in the current situation may have no effect. For example, the 
creature is adjacent to an obstacle, and decides to move to the obstacle's position. 
The result will be that the creature remains in the same situation.

* The action may result in the creature moving to a new situation in the world, but the 
creature's sensors are unable to differentiate between the two states. This means that 
as far as the creature is concerned, there is no state change.

 After moving to a situation, the creature checks to see if it has already been there. 
If not, the situation is memorized. The links from the previous situation to the 
current one, and from the current one to the previous situation are added to the 
situation graph. The implication is that all the situations are connected to each other.
However, this connection is not two-sided. One may be able to go from one situation to 
the other, but not the other war around. This is because using an action to go from one 
situation to the next does not necessarily mean the ability to go the other way, even if
the "dual" of that action is known. One example is a door that can only be opened from 
one way.

 Playing can be considered the training time of a creature. It can also be done whenever
the creature has nothing else to do.  If a number of creatures will be working in similar
places, then it is conceivable to have one creature train for some time and then copy 
its brain into other creatures, thus reducing the need for all the future creatures to 
undergo a training phase.


 PLANNING:

 Planning uses the information memorized in the brain to guide a creature in reaching a 
goal. In concrete terms this means finding a path from a "source" situation, probably 
the current one, to a destination situation. For this to work, both the source and the 
destination should be present in the graph of situations. Other than that, the creature 
should have been able to go from the source situation to the destination.

*/

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

class myUtils
{
 static void sleep(int time)
 {
  try
  {
   Thread.currentThread().sleep(time);
  }
  catch(InterruptedException e)
  {
  }
 }
} 


class Const
{
 static final int INFO_BOARD_SIZE_X = 50;
 static final int INFO_BOARD_SIZE_Y = 70;

 static final int INC_X = 1;
 static final int INC_Y = 1;

 static final int TICK = 50;

 static final int MAX_FOOD_PER_TICK = 2;

 static final int MAX_TEMPERATURE = 15;
 static final int MAX_HUMIDITY = 15;
}

class ParamsApp
{
 static URAL URAL;

 static int numCreature = 1;
 static int creatureSize = 10;
 static int maxObstacles = 10;
 static int maxFood = 5;

 /* the actual board size is 2 * creatureSize pixels bigger along each axis */
 static int boardSizeX = 150;
 static int boardSizeY = 150;

 static int initEnergy = 1000;

 static int energyPerFood = 100;

 static boolean decreaseEnergy = false;
 static boolean disappearFood = false;

 static boolean logSituations = true;
 static int logLastField = 3;
 static char logFieldSeparator = ',';
 static int logStartSituation = 1;
 static int logNumberSituation = 7000;
 static int logNumber = 0; //must be reset each time!!!
 static int loggedSituations[][];
 static boolean doLogField[];
 static int timeWindowWidth = 2;
 static String logFileName = new String("");
 static boolean causalLog = true;

 static boolean logOpen = false;
 static BufferedWriter logFile;

 static final boolean randomFood = false;

 //These are computed by the application
 static int boardMaxPosX = boardSizeX / creatureSize;
 static int boardMaxPosY = boardSizeY / creatureSize;
}

class runControl
{
 Object obj = new Object();

 synchronized void waitForRun()
 {
  try
  {
   Thread.currentThread().sleep(Const.TICK);
   wait();
  }    
  catch(InterruptedException e)
  {
  }
 }

 synchronized void letRun()
 {
  notifyAll();
 }
}

class Actions
{
 static final int UP = 0;
 static final int DOWN = 1;
 static final int LEFT = 2;
 static final int RIGHT = 3;

 static final int NUM_ACTIONS = 4;
}


class Situation
{
 int posX, posY;
 int temperature, humidity;
 boolean anyFood;

 static boolean same(Situation s1, Situation s2)
 {
  if(s1.posX == s2.posX &&
     s1.posY == s2.posY)
   return true;
  else
   return false;
 }

 static void copy(Situation dest, Situation source)
 {
  dest.posX = source.posX;
  dest.posY = source.posY;
  dest.anyFood = source.anyFood;
  dest.temperature = source.temperature;
  dest.humidity = source.humidity;
 }

}

class ActionSituation implements Comparable
{
 int action;
 Node node;

 // needed for TreeSet operations
 synchronized public int compareTo(Object o)
 {
  if(Situation.same(node.situation, ((ActionSituation)o).node.situation))
   return 0;
  else if(node.situation.posX < ((ActionSituation)o).node.situation.posX)
   return -1;
  else return 1;
 }
}

// a Node has one situation, plus sets of Action-Situation pairs
class Node implements Comparable
{
 Situation situation = new Situation();
 TreeSet actionNextSituation = new TreeSet();
 TreeSet actionPrevSituation = new TreeSet();

 // needed for TreeSet operations
 synchronized public int compareTo(Object o)
 {
  if(Situation.same(situation, ((Node)o).situation))
   return 0;
  else if(situation.posX < ((Node)o).situation.posX)
   return -1;
  else return 1;
 }
}

class PlanNode
{
 Node node;
 int action;

 PlanNode next = null, prev = null;
}

class aPlan
{
 LinkedList moves = new LinkedList();
 PlanNode sourceNode = new PlanNode();

 void print()
 {
  Iterator i = moves.iterator();
  PlanNode pn;
  while(i.hasNext())
  {
   pn = (PlanNode)i.next();
   System.out.print("(" + pn.node.situation.posX + "," +
                    pn.node.situation.posY + ")");
   if(!i.hasNext())
    System.out.print("->");
  }
  System.out.println();
 }

 void clear()
 {
  while(moves.size() > 0)
   moves.removeLast();

  sourceNode = new PlanNode();
 }

 boolean exists(Node node)
 {
  Iterator i = moves.iterator();
  Node ntemp;

  if(firstNode(node))
   return true;

  while(i.hasNext())
  {
   ntemp = ((PlanNode)i.next()).node;
   if(Situation.same(ntemp.situation, node.situation))
    return true;
  }
  return false;
 }

 boolean follows(Node follower, Node followed)
 {
  Iterator i = moves.iterator();
  Node ntemp;

  if(firstNode(followed) && moves.size() > 0)
  {
   Node temp = ((PlanNode)moves.getFirst()).node;

   if(Situation.same(follower.situation, temp.situation))
    return true;
   else
    return false;
  }

  while(i.hasNext())
  {
   ntemp = ((PlanNode)i.next()).node;
   if(Situation.same(ntemp.situation, followed.situation))
    break;
  }
  if(!i.hasNext())
   return false;

  ntemp = ((PlanNode)i.next()).node;
  if(Situation.same(ntemp.situation, follower.situation))
   return true;

  return false;
 }

 boolean firstNode(Node node)
 {
  if(node == sourceNode.node)
   return true;
  else
   return false;
 }

 boolean lastNode(Node node)
 {
  if(moves.size() > 0 && (node == ((PlanNode)moves.getLast()).node))
   return true;
  else
   return false;
 }

}


class Brain
{
 // used for random action selection.
 Random MasterRandom = new Random();
 int number; //creature number

 int brainLinks[][][];

 boolean followPlan = false;

 aPlan currentPlan = new aPlan();

 // the creature will do this.
 int chosenAction = Actions.UP;

 // used for adding (deleting?) nodes to the network.
 Situation currentSituation = new Situation(), prevSituation = new Situation();

 // starting point
 Node firstNode = new Node();

 // The actual network
 TreeSet nodeMemory = new TreeSet();

 int SituationNum = 0;

 Brain(int number)
 {
  this.number = number;
  firstNode.situation.posX = currentSituation.posX = -1;
  firstNode.situation.posY = currentSituation.posY = -1;
  firstNode.situation.anyFood = currentSituation.anyFood = false;
  firstNode.situation.temperature = currentSituation.temperature = 0;
  firstNode.situation.humidity = currentSituation.humidity = 0;

  brainLinks = new int[ParamsApp.boardMaxPosX][ParamsApp.boardMaxPosY][5];
 }

 void planFood()
 {
  Node destNode = findFood();

  if(destNode == null)
  {
   JOptionPane.showMessageDialog(null, "Can't find a situation with Food", "Food Finder",
    JOptionPane.ERROR_MESSAGE);
   return;
  }
  if(doPlanning(currentPlan, currentSituation, destNode.situation) == false)
  {
   JOptionPane.showMessageDialog(null, "Couldn't find a plan for food", "Food Finder",
    JOptionPane.ERROR_MESSAGE);
   return;
  }

  followPlan = true;

 }

 void printPlan(aPlan plan)
 {
  PlanNode pn;
  Iterator i = plan.moves.iterator();

  while(i.hasNext())
  {
   pn = (PlanNode)i.next();
   switch(pn.action)
   {
    case Actions.UP:    System.out.println("Up");
                        break;
    case Actions.DOWN:  System.out.println("Down");
                        break;
    case Actions.LEFT:  System.out.println("Left");
                        break;
    case Actions.RIGHT: System.out.println("Right");
                        break;
   }
  }
 }

 boolean doPlanning(aPlan plan, Situation src, Situation dst)
 {
  Node sourceNode, finalNode;

  sourceNode = findNode(src);
  if(sourceNode == null)
  {
   JOptionPane.showMessageDialog(null, "unknown source situation", "Planner",
    JOptionPane.ERROR_MESSAGE);
   return false;
  }

  finalNode = findNode(dst);

  if(finalNode == null)
  {
   JOptionPane.showMessageDialog(null, "unknown destination situation", "Planner",
    JOptionPane.ERROR_MESSAGE);
   return false;
  }
  plan.moves.clear();

  plan.sourceNode.node = sourceNode;
  return searchPath(plan, sourceNode, finalNode);
 }

 boolean searchPath(aPlan plan, Node cNode, Node fNode)
 {
  ActionSituation as;
  Node node;
  PlanNode pn = null;
  int counter = 0;
  
  if(Situation.same(cNode.situation, fNode.situation))
   return true;

  pn = new PlanNode();
  pn.node = fNode;
  if(plan.exists(pn.node))
   return false;
  plan.moves.addFirst(pn);

  Iterator nodeIterator = fNode.actionPrevSituation.iterator();
  while(nodeIterator.hasNext())
  {
   counter++;

   plan.print();

   as = (ActionSituation)nodeIterator.next();

   pn.action = as.action;
   
   if(searchPath(plan, cNode, as.node) == true)
    return true;
  }
  plan.moves.remove(pn);
  return false;
 }

 void fillNode(Node node, Situation s)
 {
  Situation.copy(node.situation, s);
 }


 void logNode()
 {
  ParamsApp.loggedSituations[ParamsApp.logNumber][0] = prevSituation.posX;
  ParamsApp.loggedSituations[ParamsApp.logNumber][1] = prevSituation.posY;
  ParamsApp.loggedSituations[ParamsApp.logNumber][2] = prevSituation.anyFood? 1 : 0;
  ParamsApp.loggedSituations[ParamsApp.logNumber][3] = prevSituation.humidity;
  ParamsApp.loggedSituations[ParamsApp.logNumber][4] = prevSituation.temperature;
  ParamsApp.loggedSituations[ParamsApp.logNumber][5] = chosenAction;

  ParamsApp.logNumber++;

  if(/*ParamsApp.logNumber == 500  ||
     ParamsApp.logNumber == 1000 ||
     ParamsApp.logNumber == 1500 ||
     ParamsApp.logNumber == 4000 ||
     ParamsApp.logNumber == 6000 ||*/
     ParamsApp.logNumber == ParamsApp.logNumberSituation)
  {
   ParamsApp.URAL.saveLogFile();
//   if(ParamsApp.logNumber == ParamsApp.logNumberSituation)
//    System.exit(0);
  }

 }

 void addCurrentSituation()
 {
  ActionSituation asNext, asPrev;
  Node nextNode;
  Node prevNode;

  SituationNum++;
  if(number == 1 && ParamsApp.logSituations &&
     SituationNum > ParamsApp.logStartSituation &&
     SituationNum <= ParamsApp.logStartSituation + ParamsApp.logNumberSituation)
   logNode();

  nextNode = findNode(currentSituation);

  // copy the node anyway, so that the food info is updated!
  if(nextNode == null)
  {
   nextNode = new Node();
   Situation.copy(nextNode.situation, currentSituation);
   nodeMemory.add(nextNode);
  }
  else
   Situation.copy(nextNode.situation, currentSituation);

  prevNode = findNode(prevSituation);
  if(prevNode == null)
  {
   //happens only when there was no prev node
   prevNode = firstNode;
   fillNode(prevNode, prevSituation);
  }
  // Don't add the node to itself
  if(prevNode == nextNode)
   return;

  asPrev = new ActionSituation();
  asPrev.action = chosenAction;
  asPrev.node = prevNode;
  nextNode.actionPrevSituation.add(asPrev);

  asNext = new ActionSituation();
  asNext.action = chosenAction;
  asNext.node = nextNode;
  prevNode.actionNextSituation.add(asNext);

  {
   if(nextNode.situation.posX == prevNode.situation.posX && nextNode.situation.posY == prevNode.situation.posY)
    brainLinks[prevNode.situation.posX][prevNode.situation.posY][0] = 1;
   if(nextNode.situation.posX == prevNode.situation.posX && nextNode.situation.posY == prevNode.situation.posY + 1)
    brainLinks[prevNode.situation.posX][prevNode.situation.posY][1] = 1;
   if(nextNode.situation.posX == prevNode.situation.posX && nextNode.situation.posY == prevNode.situation.posY - 1)
    brainLinks[prevNode.situation.posX][prevNode.situation.posY][2] = 1;
   if(nextNode.situation.posX == prevNode.situation.posX + 1 && nextNode.situation.posY == prevNode.situation.posY)
    brainLinks[prevNode.situation.posX][prevNode.situation.posY][3] = 1;
   if(nextNode.situation.posX == prevNode.situation.posX - 1 && nextNode.situation.posY == prevNode.situation.posY)
    brainLinks[prevNode.situation.posX][prevNode.situation.posY][4] = 1;
  }

 }

 Node findFood()
 {
  Iterator iterator = nodeMemory.iterator();
  Node node;

  while(iterator.hasNext())
  {
   node = (Node)iterator.next();
   if(node.situation.anyFood)
    return node;
  }
  return null;
 } 


 Node findNode(Situation situation)
 {
  Iterator iterator = nodeMemory.iterator();
  Node node;

  while(iterator.hasNext())
  {
   node = (Node)iterator.next();
   if(Situation.same(node.situation, situation))
    return node;
  }
  return null;
 } 

 void decidePlay()
 {
  Node node = null;

  if(followPlan)
  {
   PlanNode pn;

   pn = currentPlan.sourceNode;
   node = pn.node;

   Iterator i = currentPlan.moves.iterator();

   boolean foundNode = false;
   while(i.hasNext())
   {
    pn = (PlanNode)i.next();

    if(Situation.same(currentSituation, node.situation))
    {
     foundNode = true;
     break;
    }

    node = pn.node;
   }
   if(foundNode)
   {
    if(currentPlan.lastNode(pn.node))
     followPlan = false;

    chosenAction = pn.action;
    return;
   }
   else
   {
    JOptionPane.showMessageDialog(null, "Could not follow the plan", "Planner",
     JOptionPane.ERROR_MESSAGE);

    followPlan = false;
   }

  } 

  //Random rand = new Random(MasterRandom.nextInt());

  switch(Math.abs(MasterRandom.nextInt()) % Actions.NUM_ACTIONS)
  {
   case 0: chosenAction = Actions.UP;
           break;
   case 1: chosenAction = Actions.DOWN;
           break;
   case 2: chosenAction = Actions.LEFT;
           break;
   case 3: chosenAction = Actions.RIGHT;
           break;
  }
 }
} 

class worldSquare
{
  char space;
  int food, humidity, temperature;
}

class World
{

 static worldSquare board[][];

 static boolean hasFood(int posX, int posY)
 {
  return (board[posX][posY].food == 1);
 }

 static int humidity(int posX, int posY)
 {
  return (board[posX][posY].humidity);
 }

 static int temperature(int posX, int posY)
 {
  return (board[posX][posY].temperature);
 }

 static boolean hasObstacle(int posX, int posY)
 {
  if((posX >= ParamsApp.boardMaxPosX) ||
     (posX < 0) ||

     (posY >= ParamsApp.boardMaxPosY) ||
     (posY < 0) ||
     (board[posX][posY].space == 'X'))

   return true;
  else
   return false;
 }

 World(int x, int y)
 {
  board = new worldSquare[x][y];
  for(int count = 0; count < x; count++)
   for(int count2 = 0; count2 < y; count2++)
    board[count][count2] = new worldSquare();

  Random rand = new Random();

  for(int count1 = 0; count1 < ParamsApp.boardMaxPosX; count1++)
   for(int count2 = 0; count2 < ParamsApp.boardMaxPosY; count2++)
   {
    board[count1][count2].food = 0;
    board[count1][count2].space = ' ';
    board[count1][count2].temperature = Math.abs(rand.nextInt()) % Const.MAX_TEMPERATURE;
    board[count1][count2].humidity = Math.abs(rand.nextInt()) % Const.MAX_HUMIDITY;
   }
  
/*
  board[6][5].space = 'X';
  board[6][6].space = 'X';
  board[6][7].space = 'X';
  board[7][5].space = 'X';
  board[7][6].space = 'X';
  board[7][7].space = 'X';
  board[8][5].space = 'X';
  board[8][6].space = 'X';
  board[8][7].space = 'X';
  board[9][5].space = 'X';
*/  

  int count1 = 0;
  while (count1 < ParamsApp.maxObstacles)
  {

   int posX, posY;

   posX = Math.abs(rand.nextInt()) % ParamsApp.boardMaxPosX;
   posY = Math.abs(rand.nextInt()) % ParamsApp.boardMaxPosY;

   if(board[posX][posY].space != 'X')
   {
    board[posX][posY].space = 'X';
    count1++;
   }
  }
 }

 void putFood(int posX, int posY)
 {
  if(board[posX][posY].space != 'X')
   board[posX][posY].food = 1;
 }

 static void getFood(int posX, int posY)
 {
  if(ParamsApp.disappearFood)
   board[posX][posY].food = 0;
 }
}

class ds
{
 Creature creature;
 Thread thread;

 ds()
 {
  creature = null;
  thread = null;
 }
}

class Creature extends JFrame implements Runnable, ActionListener
{
 boolean showBrain = false, showPlan = false, directControl = false;
 boolean doPlan = false;
 boolean canPlay = true, freezed = false, canPlan = false;
 runControl run_control;
 Brain brain;
 int number;
 boolean shouldDie = false;
 int energy = 0;
 int posX, posY;
 int prevPosX, prevPosY;
 Insets insets;
 JFrame brainFrame = new JFrame();

 JFrame directControlFrame = new JFrame("Direct Control");
 Button uButton = new Button("Up");
 Button lButton = new Button("Left");
 Button dButton = new Button("Down");
 Button rButton = new Button("Right");

 JFrame planFrame = new JFrame("Plan Frame");
 GridBagLayout  gb = new GridBagLayout();
 GridBagConstraints gbc = new GridBagConstraints();

 TextField srcX = new TextField(2);
 TextField srcY = new TextField(2);
 TextField dstX = new TextField(2);
 TextField dstY = new TextField(2);
 Button pButton = new Button("Start Planning"); 
                                             
 class SimulationMenu extends Menu implements ActionListener, ItemListener
 {
  CheckboxMenuItem play = new CheckboxMenuItem("Play");

  CheckboxMenuItem freeze = new CheckboxMenuItem("Freeze");

  CheckboxMenuItem plan = new CheckboxMenuItem("Plan");

  CheckboxMenuItem dcontrol = new CheckboxMenuItem("Direct Control");

  CheckboxMenuItem shbrain = new CheckboxMenuItem("Show Brain");
  CheckboxMenuItem shplan = new CheckboxMenuItem("Show plan");


  public SimulationMenu()
  {
   super("Simulation", true);
   add(play);
   add(freeze);
   add(plan);
   add(dcontrol);
   add(shbrain);
   add(shplan);
   add(new MenuItem("Find Food"));
   addSeparator();
   add(new MenuItem("Kill"));

   addActionListener(this);
   play.addItemListener(this);
   play.setState(canPlay);
   freeze.addItemListener(this);
   freeze.setState(freezed);
   plan.addItemListener(this);
   plan.setState(canPlan);
   dcontrol.addItemListener(this);
   dcontrol.setState(directControl);
   shbrain.addItemListener(this);
   shbrain.setState(showBrain);
   shplan.addItemListener(this);
   shplan.setState(showPlan);
  }

  public void actionPerformed(ActionEvent e)
  { 
   if(e.getActionCommand().equals("Kill"))
    shouldDie = true;
   else if(e.getActionCommand().equals("Find Food"))
    brain.planFood();
  }

  public void itemStateChanged(ItemEvent e)
  {
   Object source = e.getItemSelectable();

   if (source == play)
   {
    if (e.getStateChange() == ItemEvent.SELECTED)
     canPlay = true;
    else
     canPlay = false;
   }
   else if (source == freeze)
   {
    if (e.getStateChange() == ItemEvent.SELECTED)
     freezed = true;
    else
     freezed = false;
   }
   else if (source == plan)
   {
    if (e.getStateChange() == ItemEvent.SELECTED)
    {
     canPlan = true;
     planFrame.show();
    }
    else
    {
     canPlan = false;
     planFrame.setVisible(false);
    }
   }
   else if (source == dcontrol)
   {
    if (e.getStateChange() == ItemEvent.SELECTED)
    {
     directControl = true;
     directControlFrame.show();
    }
    else
    {
     directControl = false;
     directControlFrame.setVisible(false);
    }
   }
   else if (source == shbrain)
   {
    if (e.getStateChange() == ItemEvent.SELECTED)
    {
     showBrain = true;
     if(showPlan || showBrain)
      brainFrame.show();
    }
    else
    {
     showBrain = false;
     if(!showPlan && !showBrain)
      brainFrame.setVisible(false);
     else
      brainFrame.repaint();
    }
   }
   else if (source == shplan)
   {
    if (e.getStateChange() == ItemEvent.SELECTED)
    {
     showPlan = true;
     if(showPlan || showBrain)
      brainFrame.show();
    }
    else
    {
     showPlan = false;
     if(!showPlan && !showBrain)
      brainFrame.setVisible(false);
    }
   }
  }
 }


 public void actionPerformed(ActionEvent e)
 {
  if(e.getActionCommand().equals("Start Planning"))
  {
   Situation src = new Situation(), dst = new Situation();

   Integer integer = new Integer(srcX.getText());
   src.posX = integer.intValue();
   integer = new Integer(srcY.getText());
   src.posY = integer.intValue();

   integer = new Integer(dstX.getText());
   dst.posX = integer.intValue();
   integer = new Integer(dstY.getText());
   dst.posY = integer.intValue();

   planFrame.setTitle("Plan Frame - Working...");

   if(brain.doPlanning(brain.currentPlan, src, dst) == true)
    brain.printPlan(brain.currentPlan);
   else
   JOptionPane.showMessageDialog(null, "Failed to find a plan", "Planner",
    JOptionPane.ERROR_MESSAGE);

   planFrame.setTitle("Plan Frame");
  }
  else if(e.getActionCommand().equals("Left"))
  {
   doAction(Actions.LEFT);
   perceiveWorld();
   repaint();
  }
  else if(e.getActionCommand().equals("Up"))
  {
   doAction(Actions.UP);
   perceiveWorld();
   repaint();
  }
  else if(e.getActionCommand().equals("Down"))
  {
   doAction(Actions.DOWN);
   perceiveWorld();
   repaint();
  }
  else if(e.getActionCommand().equals("Right"))
  {
   doAction(Actions.RIGHT);
   perceiveWorld();
   repaint();
  }
 }

 Creature(int number)
 {
  super();

  brain = new Brain(number);
  SimulationMenu simulationMenu = new SimulationMenu();
  MenuBar menuBar = new MenuBar();
  menuBar.add(simulationMenu);
  setMenuBar(menuBar);

  addWindowListener(
         new WindowAdapter() {
                              public void windowClosing(WindowEvent e) {
                                     handleCloseMainWindow(); }
                             }
                   );

  planFrame.getContentPane().setLayout(gb);
  gbc.gridwidth = 1;
  gbc.gridheight = 1;
  gbc.gridx = 0;
  gbc.gridy = 0;
  gbc.fill = GridBagConstraints.BOTH;

  Label l = new Label(" Source Situation: ");
  gb.setConstraints(l, gbc);
  planFrame.getContentPane().add(l);

  gbc.gridy = 0;
  gbc.gridx  = 1;
  l = new Label("X: ");
  gb.setConstraints(l, gbc);
  planFrame.getContentPane().add(l);

  gbc.gridy = 0;
  gbc.gridx  = 2;
  gb.setConstraints(srcX, gbc);
  srcX.setText("0");
  planFrame.getContentPane().add(srcX);

  gbc.gridy = 0;
  gbc.gridx  = 3;
  l = new Label(" Y: ");
  gb.setConstraints(l, gbc);
  planFrame.getContentPane().add(l);

  gbc.gridy = 0;
  gbc.gridx  = 4;
  gb.setConstraints(srcY, gbc);
  srcY.setText("0");
  planFrame.getContentPane().add(srcY);

  gbc.gridy = 1;
  gbc.gridx = 0;
  l = new Label("Destination situation: ");
  gb.setConstraints(l, gbc);
  planFrame.getContentPane().add(l);

  gbc.gridy = 1;
  gbc.gridx  = 1;
  l = new Label("X: ");
  gb.setConstraints(l, gbc);
  planFrame.getContentPane().add(l);

  gbc.gridy = 1;
  gbc.gridx  = 2;
  gb.setConstraints(dstX, gbc);
  dstX.setText("0");
  planFrame.getContentPane().add(dstX);

  gbc.gridy = 1;
  gbc.gridx  = 3;
  l = new Label(" Y: ");
  gb.setConstraints(l, gbc);
  planFrame.getContentPane().add(l);

  gbc.gridy = 1;
  gbc.gridx  = 4;
  gb.setConstraints(dstY, gbc);
  dstY.setText("0");
  planFrame.getContentPane().add(dstY);

  gbc.gridy = 2;
  gbc.gridx  = 1;
  gb.setConstraints(pButton, gbc);
  planFrame.getContentPane().add(pButton);
  pButton.addActionListener(this);

  planFrame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
  planFrame.setResizable(false);
  planFrame.pack();

  directControlFrame.getContentPane().add(uButton, BorderLayout.NORTH);
  directControlFrame.getContentPane().add(lButton, BorderLayout.WEST);
  directControlFrame.getContentPane().add(dButton, BorderLayout.SOUTH);
  directControlFrame.getContentPane().add(rButton, BorderLayout.EAST);

  uButton.addActionListener(this);
  dButton.addActionListener(this);
  lButton.addActionListener(this);
  rButton.addActionListener(this);

  directControlFrame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
  directControlFrame.setResizable(false);
  directControlFrame.pack();

  Integer integer = new Integer(number);
  setTitle(integer.toString());
  this.number = number;
  Random rand = new Random();
  posX = Math.abs(rand.nextInt() % ParamsApp.boardMaxPosX);
  posY = Math.abs(rand.nextInt() % ParamsApp.boardMaxPosY);
  setResizable(false);
  show();
  insets = getInsets();
  setSize(Const.INFO_BOARD_SIZE_X, Const.INFO_BOARD_SIZE_Y + insets.top);
  setLocation(ParamsApp.boardSizeX + 20, (number - 1) * (Const.INFO_BOARD_SIZE_Y + insets.top));
  brainFrame.setResizable(false);
  brainFrame.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
  brainFrame.setTitle(integer.toString() + "'s brain nodes");
  brainFrame.setSize(ParamsApp.boardSizeX * 3 + insets.left,
                     ParamsApp.boardSizeY * 3 + insets.top);
  brainFrame.setLocation(300, 100);
 }

 public void endIt()
 {
  dispose();
  directControlFrame.dispose();
  brainFrame.dispose();
  planFrame.dispose();
 }

 public void handleCloseMainWindow()
 {
  shouldDie = true;
 }

 public void run()
 {
  Situation s;
  do
  {
   run_control.waitForRun();
   if(shouldDie)
    break;
   if(!freezed)
   {
    if(canPlay)
    {
//     if(energy < ParamsApp.initEnergy / 3)
//      brain.planFood();
     brain.decidePlay();
     doAction(brain.chosenAction);
     perceiveWorld();
     eat();
    }
   }
   repaint(); 
  } while(true);
  endIt();
 }

 // make sure only one creature does this at a time.
 synchronized void perceiveWorld()
 {
  Situation.copy(brain.prevSituation, brain.currentSituation);
  brain.currentSituation.posX = posX;
  brain.currentSituation.posY = posY;
  brain.currentSituation.anyFood = World.hasFood(posX,posY);
  brain.currentSituation.humidity = World.humidity(posX, posY);
  brain.currentSituation.temperature = World.temperature(posX, posY);

  //don't check to see if this is a new situation, as you can get
  // to the same situation from different ways.
  brain.addCurrentSituation();
 }

 void eat()
 {
   // consume the food
  if(brain.currentSituation.anyFood)
  {
   World.getFood(posX,posY);
   energy += ParamsApp.energyPerFood;
  }
 } 

 void doAction(int action)
 {
  prevPosX = posX;
  prevPosY = posY;

  if(action == Actions.RIGHT)
  {
   posX += Const.INC_X;
   if(World.hasObstacle(posX, posY))
    posX -= Const.INC_X;
  }
  else if(action == Actions.LEFT)
  {
   posX -= Const.INC_X;
   if(World.hasObstacle(posX, posY))
    posX += Const.INC_X;
  }
  else if(action == Actions.DOWN)
  {
   posY += Const.INC_Y;
   if(World.hasObstacle(posX, posY))
    posY -= Const.INC_Y;
  }
  else if(action == Actions.UP)
  {
   posY -= Const.INC_Y;
   if(World.hasObstacle(posX, posY))
    posY += Const.INC_Y;
  }
 }

 void showPlanNodes(aPlan plan)
 {
  int startx, starty, endx, endy;
  PlanNode pn1, pn2;
  Insets insets = brainFrame.getInsets();

  Graphics braing = brainFrame.getGraphics();

  Iterator i = plan.moves.iterator();

  // the first node
  pn1 = plan.sourceNode;

  while(true)
  {
   startx = pn1.node.situation.posX * ParamsApp.creatureSize * 3;
   starty = pn1.node.situation.posY * ParamsApp.creatureSize * 3;

   if(pn1 == (PlanNode)plan.moves.getLast() || pn1 == plan.sourceNode)
    braing.setColor(Color.green);
   else
    braing.setColor(Color.pink);
   braing.drawOval(startx + insets.left, starty + insets.top,
                   ParamsApp.creatureSize * 2, ParamsApp.creatureSize * 2);
   
   if(!i.hasNext())
    break;

   pn2 = (PlanNode)i.next();

   endx = pn2.node.situation.posX * ParamsApp.creatureSize * 3;
   endy = pn2.node.situation.posY * ParamsApp.creatureSize * 3;

   braing.setColor(Color.yellow);
   braing.drawLine(startx + insets.left + ParamsApp.creatureSize,
                   starty + insets.top + ParamsApp.creatureSize,
                   endx + insets.left, endy + insets.top);

   pn1 = pn2;
  }
 }


 synchronized void showBrainNodes()
 {
  Iterator brainIterator= brain.nodeMemory.iterator();
  int startx, starty, endx, endy;
  boolean draw;
  Node node, nnode;
  Insets insets = brainFrame.getInsets();

  Graphics braing = brainFrame.getGraphics();

  while(brainIterator.hasNext())
  {
   node = (Node)brainIterator.next();
   startx = node.situation.posX * ParamsApp.creatureSize * 3;
   starty = node.situation.posY * ParamsApp.creatureSize * 3;

   draw = true;
   if(showPlan && (brain.currentPlan.firstNode(node) || brain.currentPlan.lastNode(node)))
    braing.setColor(Color.green);
   else if(showPlan && brain.currentPlan.exists(node))
    braing.setColor(Color.pink);
   else if(showBrain)
   {
    if(Situation.same(node.situation, brain.currentSituation))
     braing.setColor(Color.black);
    else
     braing.setColor(Color.red);
   }
   else draw = false;

   if(draw)
    braing.drawOval(startx + insets.left, starty + insets.top,
                   ParamsApp.creatureSize * 2, ParamsApp.creatureSize * 2);

   Iterator nodeIterator = node.actionNextSituation.iterator();
   while(nodeIterator.hasNext())
   {
    nnode = ((ActionSituation)nodeIterator.next()).node;
    endx = nnode.situation.posX * ParamsApp.creatureSize * 3;
    endy = nnode.situation.posY * ParamsApp.creatureSize * 3;

    draw = true;
    if(showBrain && Situation.same(nnode.situation, brain.currentSituation))
      braing.setColor(Color.black);
    else if(showPlan && (brain.currentPlan.firstNode(nnode) || brain.currentPlan.lastNode(nnode)))
     braing.setColor(Color.green);
    else if(showPlan && brain.currentPlan.exists(nnode))
     braing.setColor(Color.pink);
    else if(showBrain)
      braing.setColor(Color.red);
    else draw = false;

    if(draw)
     braing.drawOval(endx + insets.left, endy + insets.top,
                    ParamsApp.creatureSize * 2, ParamsApp.creatureSize * 2);

    draw = true;
    if(showPlan && brain.currentPlan.exists(node) && brain.currentPlan.exists(nnode) && brain.currentPlan.follows(nnode, node))
     braing.setColor(Color.yellow);
    else if(showBrain)
     braing.setColor(Color.blue);
    else draw = false;

    if(draw)
     braing.drawLine(startx + insets.left + ParamsApp.creatureSize,
                     starty + insets.top + ParamsApp.creatureSize,
                     endx + insets.left, endy + insets.top);
   }
  }
 }
  
 public void paint(Graphics g)
 {
  Dimension d = new Dimension();
  d = getSize(d);
  g.clearRect(0, 0, d.width, d.height);

  Integer integer = new Integer(posX);
  g.drawString("X: " + integer.toString(), 5, insets.top + 10);
 
  integer = new Integer(posY);
  g.drawString("Y: " + "     ", 5, insets.top + 22);
  g.drawString("Y: " + integer.toString(), 5, insets.top + 22);

  integer = new Integer(energy);
  g.drawString("Energy: " + "     ", 5, insets.top + 34);
  g.drawString("Energy: " + integer.toString(), 5, insets.top + 34);

  integer = new Integer(brain.nodeMemory.size());
  g.drawString("Brain: " + "     ", 5, insets.top + 46);
  g.drawString("Brain: " + integer.toString(), 5, insets.top + 46);

  if(showBrain || showPlan)
  {
   showBrainNodes();
  }
  if(showPlan)
  {
  // if(plan.moves.size() > 0)
  //  showPlanNodes(plan);
  }
 }
}

public class URAL extends JFrame implements Runnable
{

 boolean ShouldEnd = false;
 LinkedList creatureList = new LinkedList();
 Insets insets;

 boolean appRunning = false;

 int fixedPlaces[][];
 boolean initFixedPlaces = false;

 runControl run_control = new runControl();

 JFrame simOptionFrame = new JFrame("Simulation Options");
 GridBagLayout  gb = new GridBagLayout();
 GridBagConstraints gbc = new GridBagConstraints();
 Checkbox dE = new Checkbox("Decrease Energy");
 Checkbox dF = new Checkbox("Disappear Food");
 TextField nC = new TextField(4); //number of creatures
 TextField cS = new TextField(4); //creature size
 TextField bSX = new TextField(4); //board size x
 TextField bSY = new TextField(4); //board size y
 TextField mO = new TextField(4); //max no. of obstacles
 TextField mF = new TextField(4); //max no. of food units on board
 TextField iE = new TextField(4); //initial energy
 TextField ePF = new TextField(4); //energy-increase per food

 JFrame logOptionFrame = new JFrame("Logging Options");
 Checkbox lS = new Checkbox("Log Situations");

 CheckboxGroup sepButtons = new CheckboxGroup(); //field separator in log
 Checkbox sepComma = new Checkbox("Comma", sepButtons, true);
 Checkbox sepSpace = new Checkbox("Space", sepButtons, false);
 TextField sSLog = new TextField(6); //start number for logging situations
 TextField nSLog = new TextField(6); //number of logged situations

 Checkbox logX = new Checkbox("X");
 Checkbox logY = new Checkbox("Y");
 Checkbox logFood = new Checkbox("Food");
 Checkbox logHumid = new Checkbox("Humidity");
 Checkbox logTemp = new Checkbox("Temperature");
 Checkbox logAction = new Checkbox("Action");

 CheckboxGroup lastFieldButtons = new CheckboxGroup(); //field separator in log
 Checkbox lastX = new Checkbox("X", lastFieldButtons, true);
 Checkbox lastY = new Checkbox("Y", lastFieldButtons, false);
 Checkbox lastFood = new Checkbox("Food", lastFieldButtons, false);
 Checkbox lastHumid = new Checkbox("Humidity", lastFieldButtons, false);
 Checkbox lastTemp = new Checkbox("Temperature", lastFieldButtons, false);
 Checkbox lastAction = new Checkbox("Action", lastFieldButtons, false);
 TextField rW = new TextField(2); // time Window width

 CheckboxGroup logTypeButtons = new CheckboxGroup(); //causal or association
 Checkbox causalCheck = new Checkbox("Causality", logTypeButtons, false);
 Checkbox associationCheck = new Checkbox("Association", logTypeButtons, false);



 TextField lFN = new TextField(20); //log file name

 MenuItem sp = new MenuItem("Start"); // start/pause

 MenuItem slog = new MenuItem("Save Log"); //save the log file
                           
 World myWorld = new World(ParamsApp.boardMaxPosX, ParamsApp.boardMaxPosY);

 public static void main(String args[])
 {
  URAL me = new URAL();
  ParamsApp.URAL = me;
  Thread tme = new Thread(me);
  tme.start();
 }


 class SimulationMenu extends Menu implements ActionListener
 {
  public SimulationMenu()
  {
   super("Simulation", true);
   add(new MenuItem("New Simulation"));
   add(sp);
   sp.setEnabled(false);
   add(slog);
   addSeparator();
   add(new MenuItem("About"));
   addSeparator();
   add(new MenuItem("Exit"));
   addActionListener(this);
  }

  public void actionPerformed(ActionEvent e)
  { 
   if(e.getActionCommand().equals("Exit"))
    ShouldEnd = true;
   else if(e.getActionCommand().equals("Start"))
    startRunning();
   else if(e.getActionCommand().equals("Pause"))
    stopRunning();
   else if(e.getActionCommand().equals("Save Log"))
    saveLogFile();
   else if(e.getActionCommand().equals("About"))
    About();
   else if(e.getActionCommand().equals("New Simulation"))
    newSimulation();
  }
 }

 void About()
 {
  JOptionPane.showMessageDialog(null, "URAL: University of Regina Artificial Life\nAn Artificial Life Simulation\nUses \"Situation Calculus\" for planning\nVersion 1.1\nWritten by Kamran Karimi", "About",
   JOptionPane.PLAIN_MESSAGE); 
 }

 void getLogOptions()
 {
  ParamsApp.logSituations = lS.getState();
  
  Integer integer = new Integer(sSLog.getText());
  ParamsApp.logStartSituation = integer.intValue();
  integer = new Integer(nSLog.getText());
  ParamsApp.logNumberSituation = integer.intValue();
  ParamsApp.loggedSituations = new int[ParamsApp.logNumberSituation][6];
 }

 void getLogSaveOptions()
 {
  if(sepButtons.getSelectedCheckbox() == sepSpace)
   ParamsApp.logFieldSeparator = ' ';
  else
   ParamsApp.logFieldSeparator = ',';

  ParamsApp.doLogField[0] = logX.getState();
  ParamsApp.doLogField[1] = logY.getState();
  ParamsApp.doLogField[2] = logFood.getState();
  ParamsApp.doLogField[3] = logHumid.getState();
  ParamsApp.doLogField[4] = logTemp.getState();
  ParamsApp.doLogField[5] = logAction.getState();

  if(lastFieldButtons.getSelectedCheckbox() == lastX)
   ParamsApp.logLastField = 0;
  else if(lastFieldButtons.getSelectedCheckbox() == lastY)
   ParamsApp.logLastField = 1;
  else if(lastFieldButtons.getSelectedCheckbox() == lastFood)
   ParamsApp.logLastField = 2;
  else if(lastFieldButtons.getSelectedCheckbox() == lastHumid)
   ParamsApp.logLastField = 3; 
  else if(lastFieldButtons.getSelectedCheckbox() == lastTemp)
   ParamsApp.logLastField = 4; 
  else if(lastFieldButtons.getSelectedCheckbox() == lastAction)
   ParamsApp.logLastField = 5; 

  if(logTypeButtons.getSelectedCheckbox() == causalCheck)
   ParamsApp.causalLog = true;
  else
   ParamsApp.causalLog = false;

  Integer integer = new Integer(rW.getText());
  ParamsApp.timeWindowWidth = integer.intValue();

  ParamsApp.logFileName =  lFN.getText();
 }

 void setLogOptions()
 {
  lS.setState(ParamsApp.logSituations);

  if(ParamsApp.logFieldSeparator == ' ')
   sepButtons.setSelectedCheckbox(sepSpace);
  else
   sepButtons.setSelectedCheckbox(sepComma);

  Integer Int = new Integer(ParamsApp.logStartSituation);
  sSLog.setText(Int.toString());
  Int = new Integer(ParamsApp.logNumberSituation);
  nSLog.setText(Int.toString());

  logX.setState(ParamsApp.doLogField[0]);
  logY.setState(ParamsApp.doLogField[1]);
  logFood.setState(ParamsApp.doLogField[2]);
  logHumid.setState(ParamsApp.doLogField[3]);
  logTemp.setState(ParamsApp.doLogField[4]);
  logAction.setState(ParamsApp.doLogField[5]);

  if(ParamsApp.logLastField == 0)
   lastFieldButtons.setSelectedCheckbox(lastX);
  else if(ParamsApp.logLastField == 1)
   lastFieldButtons.setSelectedCheckbox(lastY);
  if(ParamsApp.logLastField == 2)
   lastFieldButtons.setSelectedCheckbox(lastFood);
  if(ParamsApp.logLastField == 3)
   lastFieldButtons.setSelectedCheckbox(lastHumid);
  if(ParamsApp.logLastField == 4)
   lastFieldButtons.setSelectedCheckbox(lastTemp);
  if(ParamsApp.logLastField == 5) 
   lastFieldButtons.setSelectedCheckbox(lastAction);

  if(ParamsApp.causalLog)
   logTypeButtons.setSelectedCheckbox(causalCheck);
  else
   logTypeButtons.setSelectedCheckbox(associationCheck);

  Int = new Integer(ParamsApp.timeWindowWidth);
  rW.setText(Int.toString());

  lFN.setText(ParamsApp.logFileName);
 } 

 void getSimOptions()
 {
  ParamsApp.decreaseEnergy = dE.getState();
  ParamsApp.disappearFood = dF.getState();

  Integer integer = new Integer(nC.getText());
  ParamsApp.numCreature = integer.intValue();
  
  integer = new Integer(cS.getText());
  ParamsApp.creatureSize = integer.intValue();

  integer = new Integer(bSX.getText());
  ParamsApp.boardSizeX = integer.intValue();

  integer = new Integer(bSY.getText());
  ParamsApp.boardSizeY = integer.intValue();

  integer = new Integer(mO.getText());
  ParamsApp.maxObstacles = integer.intValue();

  integer = new Integer(mF.getText());
  ParamsApp.maxFood = integer.intValue();

  integer = new Integer(iE.getText());
  ParamsApp.initEnergy = integer.intValue();

  integer = new Integer(ePF.getText());
  ParamsApp.energyPerFood = integer.intValue();

  //compute these
  ParamsApp.boardMaxPosX = ParamsApp.boardSizeX / ParamsApp.creatureSize;
  ParamsApp.boardMaxPosY = ParamsApp.boardSizeY / ParamsApp.creatureSize;
 }

 void setSimOptions()
 {
  dE.setState(ParamsApp.decreaseEnergy);
  dF.setState(ParamsApp.disappearFood);

  Integer Int = new Integer(ParamsApp.numCreature);
  nC.setText(Int.toString());

  Int = new Integer(ParamsApp.creatureSize);
  cS.setText(Int.toString());

  Int = new Integer(ParamsApp.boardSizeX);
  bSX.setText(Int.toString());

  Int = new Integer(ParamsApp.boardSizeY);
  bSY.setText(Int.toString());

  Int = new Integer(ParamsApp.maxObstacles);
  mO.setText(Int.toString());

  Int = new Integer(ParamsApp.maxFood);
  mF.setText(Int.toString());

  Int = new Integer(ParamsApp.initEnergy);
  iE.setText(Int.toString());

  Int = new Integer(ParamsApp.energyPerFood);
  ePF.setText(Int.toString());
 }

 void displaySimOptions()
 {
  if(simOptionFrame.isVisible())
   simOptionFrame.setVisible(false);
  else
   simOptionFrame.show();
 }

 void displayLogOptions()
 {
  if(logOptionFrame.isVisible())
   logOptionFrame.setVisible(false);
  else
   logOptionFrame.show();
 }

 class OptionMenu extends Menu implements ActionListener
 {
  public OptionMenu()
  {
   super("Options", true);
   add(new MenuItem("Board"));
   add(new MenuItem("Logging"));

   addActionListener(this);
  }

  public void actionPerformed(ActionEvent e)
  {
   if(e.getActionCommand().equals("Board"))
    displaySimOptions();
   else if(e.getActionCommand().equals("Logging"))
    displayLogOptions();
  }
 }

 void saveLogFile()
 {

  getLogSaveOptions();

  String tempLogName = new String(ParamsApp.logFileName);
/*
  for(int ccc2 = 2; ccc2 <= 10; ccc2++)
  {
   ParamsApp.timeWindowWidth = ccc2;

  for(int ccc = 0; ccc < 2; ccc++)
  {
   ParamsApp.logFileName = tempLogName;
   if(ccc == 0)
   {
    ParamsApp.logFileName = "t" + ParamsApp.logFileName + "x" + ccc2 + "-";
    ParamsApp.logLastField = 0;
   }
   else if(ccc == 1)
   {
    ParamsApp.logFileName = "t" + ParamsApp.logFileName + "y" + ccc2 + "-";
    ParamsApp.logLastField = 1;
   }
   else if(ccc == 2)
   {
    ParamsApp.logFileName = ParamsApp.logFileName + "f";
    ParamsApp.logLastField = 2;
   }

   ParamsApp.logFileName = ParamsApp.logFileName + ParamsApp.logNumber + ".data";
*/  
  if(ParamsApp.logOpen)
  {
   try
   {
    ParamsApp.logFile.close();
   }
   catch(IOException e)
   {
   }
  }

  if(ParamsApp.logSituations)
  {
   ParamsApp.logOpen = true;
   try
   {
    ParamsApp.logFile = new BufferedWriter(new FileWriter(ParamsApp.logFileName));
   }
   catch(IOException e)
   {
    ParamsApp.logOpen = false;

    JOptionPane.showMessageDialog(null, "Could not Open the Log File", "Logger",
     JOptionPane.ERROR_MESSAGE);

    return;
   } 
  }

  ListIterator iterat = creatureList.listIterator();

  ds ds;

  Brain brain;
  ds = (ds)iterat.next();
  brain = ds.creature.brain;

/*
  // outputs the number of links and nodes in the brain
  try
  {
   Integer Int1 = new Integer(brain.nodeMemory.size());
   ParamsApp.logFile.write(Int1.toString());
   ParamsApp.logFile.newLine();

   int links = 0;

   for(int count = 0; count < ParamsApp.boardMaxPosX; count++)
    for(int count2 = 0; count2 < ParamsApp.boardMaxPosY; count2++)
     for(int count3 = 0; count3 < 5; count3++)
      links += brain.brainLinks[count][count2][count3];

   Int1 = new Integer(links);
   ParamsApp.logFile.write(Int1.toString());
   ParamsApp.logFile.newLine();
  }
  catch(IOException e)
  {
   JOptionPane.showMessageDialog(null, "Could not write to the log file\nTurning off logging", "Logger",
    JOptionPane.ERROR_MESSAGE);
    ParamsApp.logSituations = false;
  }
*/

  try
  {
   for(int count = 0; count < ParamsApp.logNumber - (ParamsApp.timeWindowWidth - 1); count++)
   {
    for(int window = 0; window < ParamsApp.timeWindowWidth; window++)
    {
     if(count + window < ParamsApp.logNumberSituation)
     {
      for(int field = 0; field < 6; field++)
      {
       if(ParamsApp.logLastField == field ||
          (ParamsApp.doLogField[field] == false) ||
          (ParamsApp.causalLog && (window == ParamsApp.timeWindowWidth - 1))) // last record
        continue;

       Integer Int = new Integer(ParamsApp.loggedSituations[count + window][field]);
       ParamsApp.logFile.write(Int.toString());
       ParamsApp.logFile.write(ParamsApp.logFieldSeparator);
      }
      Integer Int = new Integer(ParamsApp.loggedSituations[count + window][ParamsApp.logLastField]);
      ParamsApp.logFile.write(Int.toString());
     }
     if(window < ParamsApp.timeWindowWidth - 1)
      ParamsApp.logFile.write(ParamsApp.logFieldSeparator);
     ParamsApp.logFile.write("\t ");
    }
    ParamsApp.logFile.newLine();
   }
   ParamsApp.logFile.flush();
  }
  catch(IOException e)
  {
   JOptionPane.showMessageDialog(null, "Could not write to the log file\nTurning off logging", "Logger",
    JOptionPane.ERROR_MESSAGE);
    ParamsApp.logSituations = false;
  }

  try
  {
   ParamsApp.logFile.close();
   ParamsApp.logOpen = false;
  }
  catch(IOException e)
  {
  }

// }//for(ccc = ...
// } //for(ccc2 = ...
 }


 URAL()
 {
  super("Main Board");

  System.setErr(System.out);

  ParamsApp.doLogField = new boolean[6];

  for(int count = 0; count < 6; count++)
   ParamsApp.doLogField[count] = true;

  OptionMenu optionMenu = new OptionMenu();
  SimulationMenu simulationMenu = new SimulationMenu();
  MenuBar menuBar = new MenuBar();
  menuBar.add(simulationMenu);
  menuBar.add(optionMenu);
  setMenuBar(menuBar);

  addWindowListener(
         new WindowAdapter() {
                              public void windowClosing(WindowEvent e) {
                                     handleCloseMainWindow(); }
                             }
                   );

  //simulation options
  simOptionFrame.getContentPane().setLayout(gb);
  gbc.gridwidth = 1;
  gbc.gridheight = 1;
  gbc.gridx = 0;
  gbc.gridy = 0;
  gbc.fill = GridBagConstraints.BOTH;

  gb.setConstraints(dE, gbc);
  simOptionFrame.getContentPane().add(dE);

  gbc.gridy = 1;
  gbc.gridx  = 0;
  gb.setConstraints(dF, gbc);
  simOptionFrame.getContentPane().add(dF);

  Label l = new Label("No. of Creatures: ");
  gbc.gridy = 2;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 2;
  gbc.gridx  = 1;
  gb.setConstraints(nC, gbc);
  simOptionFrame.getContentPane().add(nC);

  l = new Label("Creature Size: ");
  gbc.gridy = 3;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 3;
  gbc.gridx = 1;
  gb.setConstraints(cS, gbc);
  simOptionFrame.getContentPane().add(cS);

  l = new Label("Board Size: ");
  gbc.gridy = 4;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  l = new Label("X: ");
  gbc.gridy = 4;
  gbc.gridx = 1;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 4;
  gbc.gridx = 2;
  gb.setConstraints(bSX, gbc);
  simOptionFrame.getContentPane().add(bSX);

  l = new Label(" Y: ");
  gbc.gridy = 4;
  gbc.gridx = 3;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 4;
  gbc.gridx = 4;
  gb.setConstraints(bSY, gbc);
  simOptionFrame.getContentPane().add(bSY);

  l = new Label(" Max. Obstacles: ");
  gbc.gridy = 5;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 5;
  gbc.gridx = 1;
  gb.setConstraints(mO, gbc);
  simOptionFrame.getContentPane().add(mO);

  l = new Label(" Max. Food: ");
  gbc.gridy = 6;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 6;
  gbc.gridx = 1;
  gb.setConstraints(mF, gbc);
  simOptionFrame.getContentPane().add(mF);

  l = new Label("Initial Energy: ");
  gbc.gridy = 7;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 7;
  gbc.gridx = 1;
  gb.setConstraints(iE, gbc);
  simOptionFrame.getContentPane().add(iE);

  l = new Label("Energy/Food: ");
  gbc.gridy = 8;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  simOptionFrame.getContentPane().add(l);

  gbc.gridy = 8;
  gbc.gridx = 1;
  gb.setConstraints(ePF, gbc);
  simOptionFrame.getContentPane().add(ePF);

  setSimOptions();

  simOptionFrame.setResizable(false);
  simOptionFrame.pack();


  /*** The log options Frame ***/
  logOptionFrame.getContentPane().setLayout(gb);
  gbc.gridwidth = 1;
  gbc.gridheight = 1;
  gbc.gridx = 0;
  gbc.gridy = 0;
  gbc.fill = GridBagConstraints.BOTH;

  gb.setConstraints(lS, gbc);
  logOptionFrame.getContentPane().add(lS);

  l = new Label("Separate Fields With: ");
  gbc.gridy = 1;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 1;
  gbc.gridx = 1;
  gb.setConstraints(sepComma, gbc);
  logOptionFrame.getContentPane().add(sepComma);

  gbc.gridy = 1;
  gbc.gridx = 2;
  gb.setConstraints(sepSpace, gbc);
  logOptionFrame.getContentPane().add(sepSpace);

  l = new Label("Start logging at situation: ");
  gbc.gridy = 2;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 2;
  gbc.gridx = 1;
  gb.setConstraints(sSLog, gbc);
  logOptionFrame.getContentPane().add(sSLog);

  l = new Label("Number of logged Situations: ");
  gbc.gridy = 3;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 3;
  gbc.gridx = 1;
  gb.setConstraints(nSLog, gbc);
  logOptionFrame.getContentPane().add(nSLog);

  l = new Label("Write to Log: ");
  gbc.gridy = 4;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 4;
  gbc.gridx = 1;
  gb.setConstraints(logX, gbc);
  logOptionFrame.getContentPane().add(logX);

  gbc.gridy = 4;
  gbc.gridx = 2;
  gb.setConstraints(logY, gbc);
  logOptionFrame.getContentPane().add(logY);

  gbc.gridy = 5;
  gbc.gridx = 1;
  gb.setConstraints(logFood, gbc);
  logOptionFrame.getContentPane().add(logFood);

  gbc.gridy = 5;
  gbc.gridx = 2;
  gb.setConstraints(logHumid, gbc);
  logOptionFrame.getContentPane().add(logHumid);

  gbc.gridy = 6;
  gbc.gridx = 1;
  gb.setConstraints(logTemp, gbc);
  logOptionFrame.getContentPane().add(logTemp);

  gbc.gridy = 6;
  gbc.gridx = 2;
  gb.setConstraints(logAction, gbc);
  logOptionFrame.getContentPane().add(logAction);


  l = new Label("Last Field: ");
  gbc.gridy = 7;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 7;
  gbc.gridx = 1;
  gb.setConstraints(lastX, gbc);
  logOptionFrame.getContentPane().add(lastX);

  gbc.gridy = 7;
  gbc.gridx = 2;
  gb.setConstraints(lastY, gbc);
  logOptionFrame.getContentPane().add(lastY);

  gbc.gridy = 8;
  gbc.gridx = 1;
  gb.setConstraints(lastFood, gbc);
  logOptionFrame.getContentPane().add(lastFood);

  gbc.gridy = 8;
  gbc.gridx = 2;
  gb.setConstraints(lastHumid, gbc);
  logOptionFrame.getContentPane().add(lastHumid);

  gbc.gridy = 9;
  gbc.gridx = 1;
  gb.setConstraints(lastTemp, gbc);
  logOptionFrame.getContentPane().add(lastTemp);

  gbc.gridy = 9;
  gbc.gridx = 2;
  gb.setConstraints(lastAction, gbc);
  logOptionFrame.getContentPane().add(lastAction);

  l = new Label("Time Window: ");
  gbc.gridy = 10;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 10;
  gbc.gridx = 1;
  gb.setConstraints(rW, gbc);
  logOptionFrame.getContentPane().add(rW);

  l = new Label("Log to find: ");
  gbc.gridy = 11;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 11;
  gbc.gridx = 1;
  gb.setConstraints(causalCheck, gbc);
  logOptionFrame.getContentPane().add(causalCheck);

  gbc.gridy = 11;
  gbc.gridx = 2;
  gb.setConstraints(associationCheck, gbc);
  logOptionFrame.getContentPane().add(associationCheck);

  l = new Label("Log File Name: ");
  gbc.gridy = 12;
  gbc.gridx = 0;
  gb.setConstraints(l, gbc);
  logOptionFrame.getContentPane().add(l);

  gbc.gridy = 12;
  gbc.gridx = 1;
  gb.setConstraints(lFN, gbc);
  logOptionFrame.getContentPane().add(lFN);

  setLogOptions();

  logOptionFrame.setResizable(false);
  logOptionFrame.pack();
  /*** End of Log Option ***/

  show();
  insets = getInsets();
  setSize(ParamsApp.boardSizeX + insets.left, ParamsApp.boardSizeY + insets.top);
  setLocation(10,0);
  setResizable(false);
 }

 public void initCreatures()
 {
  myWorld = new World(ParamsApp.boardMaxPosX, ParamsApp.boardMaxPosY);
  
  ds ds;
  ListIterator iterator = creatureList.listIterator();

  if(iterator.hasNext())
   creatureList.clear();
  
  for(int counter = 1; counter <= ParamsApp.numCreature; counter++)
  {
   ds = new ds();
   ds.creature = new Creature(counter);
   ds.thread = new Thread(ds.creature);
   // creatures don't have the same initial energy.
   ds.creature.energy = ParamsApp.initEnergy;
   ds.creature.run_control = run_control;
   creatureList.add(ds);
  }
 }

 public void run()
 {
  do
  {
   if(ShouldEnd)
    endItAll();

   if(appRunning)
   {
    applyPhysics();
    run_control.letRun();
    repaint();
   }
   myUtils.sleep(20);
  }while(true);
 }

 public void endItAll()
 {
  System.exit(0);
 } 

 public void newSimulation()
 {
  ds ds;

  ListIterator iterator = creatureList.listIterator();

  stopRunning();

  while(iterator.hasNext())
  {
   ds = (ds)iterator.next();
   ds.creature.shouldDie = true;
   run_control.letRun();
  }

  getSimOptions();
  getLogOptions();
  ParamsApp.logNumber = 0; //NOT A GOOD PLACE FOR THIS!

  fixedPlaces = new int[ParamsApp.maxFood][2];
  Random rand = new Random();
  fixedPlaces[0][0] = 0;
  fixedPlaces[0][1] = Math.abs(rand.nextInt() % ParamsApp.boardMaxPosY);

  initFixedPlaces = false;

  repaint();
  sp.setEnabled(true);
  myUtils.sleep(100);

  setSize(ParamsApp.boardSizeX + insets.left, ParamsApp.boardSizeY + insets.top);
  initCreatures();
  iterator = creatureList.listIterator();

  while(iterator.hasNext())
  {
   ds = (ds)iterator.next();
   ds.thread.start();
  }

 } 

 public void stopRunning()
 {
  sp.setActionCommand("Start");
  sp.setLabel("Start");

  appRunning = false;
 }

 public void startRunning()
 {
  sp.setActionCommand("Pause");
  sp.setLabel("Pause");
  
  appRunning = true;
 }
 
 public void applyPhysics()
 {
  ds ds;
  ListIterator iterator = creatureList.listIterator();
  
  // all the creatures dead?
  if(!iterator.hasNext())
  {
   //ShouldEnd = true;
   sp.setEnabled(false);
   return;
  }

  generateFood();

  while(iterator.hasNext())
  {
   // decrease the energy
   ds = (ds)iterator.next();
   if(ds.creature.freezed == false || ds.creature.shouldDie)
   {
    if(ParamsApp.decreaseEnergy)
     ds.creature.energy--;
    if((ds.creature.energy == 0) || (ds.creature.shouldDie))
    {
     ds.creature.shouldDie = true;
     ds.creature.dispose();
     iterator.remove();
    }
   }
  }
 }

 public void generateFood()
 {
  Random rand = new Random();
  int numFood = 0;
  int posx, posy;
  // used to put food in fixed places

  if(!ParamsApp.randomFood && !initFixedPlaces)
  {
   for(int food = 1; food < ParamsApp.maxFood; food++)
   {
    fixedPlaces[food][0] = Math.abs(rand.nextInt() % ParamsApp.boardMaxPosX);
    //fixedPlaces[food][1] = fixedPlaces[0][1];

    fixedPlaces[food][1] = Math.abs(rand.nextInt() % ParamsApp.boardMaxPosY);
    //fixedPlaces[food][0] = food;
   }
   initFixedPlaces = true;
  }

  // see how much food is on the board.
  for(posx = 0; posx < ParamsApp.boardMaxPosX; posx++)
   for(posy = 0; posy < ParamsApp.boardMaxPosY; posy++)
    if(World.hasFood(posx, posy))
     numFood++;

  //numFood =  Math.abs(rand.nextInt() % (ParamsApp.maxFood - numFood));
  numFood = ParamsApp.maxFood;
  for(int food = 0; food < numFood; food++)
  {
   if(ParamsApp.randomFood)
   {
    posx = Math.abs(rand.nextInt() % ParamsApp.boardMaxPosX);
    posy = Math.abs(rand.nextInt() % ParamsApp.boardMaxPosY);
   }
   else
   {
    posx = fixedPlaces[food][0];
    posy = fixedPlaces[food][1];
   }
   myWorld.putFood(posx,posy);
  }

 }

 public void handleCloseMainWindow()
 {
  ShouldEnd = true;;
 }

/*
 public void update(Graphics g)
 {
 // no flicker
 paint(g);
 }
*/

 public void paint(Graphics g)
 {
  ds ds;
  ListIterator iterator = creatureList.listIterator();

  while(iterator.hasNext())
  {
   ds = (ds) iterator.next();
   // don't show removed threads
   if(ds.creature.posX >= 0)
   {
/*    g.setColor(Color.white);
    g.fillRect(insets.left + ds.creature.prevPosX * ParamsApp.creatureSize,
               insets.top + ds.creature.prevPosY * ParamsApp.creatureSize,
               ParamsApp.creatureSize, ParamsApp.creatureSize);
*/
    g.setColor(Color.blue);
    g.fillRect(insets.left + ds.creature.posX * ParamsApp.creatureSize,
              insets.top + ds.creature.posY * ParamsApp.creatureSize,
              ParamsApp.creatureSize, ParamsApp.creatureSize);
   }
  }
  for(int pos1 = 0; pos1 < ParamsApp.boardMaxPosX; pos1++)
   for(int pos2 = 0; pos2 < ParamsApp.boardMaxPosY; pos2++)
    if(World.hasFood(pos1,pos2))
    {
     g.setColor(Color.red);
     g.fillRect(insets.left + pos1 * ParamsApp.creatureSize,
                insets.top + pos2 * ParamsApp.creatureSize,
                ParamsApp.creatureSize, ParamsApp.creatureSize);
    }
    else if(World.hasObstacle(pos1, pos2))
    {
     g.setColor(Color.black);
     g.fillRect(insets.left + pos1 * ParamsApp.creatureSize,
                insets.top + pos2 * ParamsApp.creatureSize,
                ParamsApp.creatureSize, ParamsApp.creatureSize);
    }
    else
    {
     g.setColor(Color.white);    
     g.fillRect(insets.left + pos1 * ParamsApp.creatureSize,
                insets.top + pos2 * ParamsApp.creatureSize,
                ParamsApp.creatureSize, ParamsApp.creatureSize);
    }
 }
}
 
