// Animation.java          Author: Dan Harvey     Written: Spring 2003
// Sample program for cs257 quarter project.

 

import java.awt.*;

import java.util.*;

import java.applet.*;

import java.awt.image.*;

import java.awt.event.*;

     

// The primary class for the applet.

public class Animation extends Applet implements Runnable

{  private final static int APPLET_WIDTH  = 300;

   private final static int APPLET_HEIGHT = 300;    // Applet size.

   private final static int DELAY         =  50;    // Thread sleep time.

   private final static int BUFFERS       =   4;    // # image buffers.

 

   private final static int INTERVAL =100;  // Execution cycles per focus.

   private final static int FOCUS    = 20;  // # of repositioning steps.

 

// Constants for controlling movement about the display.

   public final static int DIRECTIONS = 8;

   public final static int DX[] = { 0,  1, 1, 1, 0,-1,-1,-1};

   public final static int DY[] = {-1, -1, 0, 1, 1, 1, 0, 1};

   public final static int SOUTHEAST = 3;

 

   Rectangle bounds;        // Size of the Universe.

   Rectangle focus;         // Focus to be displayed.

   int       direction;     // Direction focus is moving.

   Image     viewBuffer[];  // Applet buffers to display.

   Graphics  view[];        // Graphics object to write Applet buffers.

 

   Universe  universe;      // Where the creatures live.

   boolean   running;       // determine if animation is running.

   int       time;          // Number of universe time steps;

 

   Thread    thread;        // Thread controlling animation.

 

// Method to initialize the applet.

   public void init()

   {  addMouseListener (new ControlAnimation());

      MediaTracker tracker = new MediaTracker(this);

// Instantiate the creature images.

      String[] creatureFiles   = {"img/north.gif", "img/northeast.gif"

            , "img/east.gif", "img/southeast.gif", "img/south.gif"

            , "img/southwest.gif", "img/west.gif", "img/northwest.gif"};

      Image[] creature = new Image[creatureFiles.length];

      for (int d=0; d<creatureFiles.length; d++)

      {  creature[d] = getImage(getCodeBase(), creatureFiles[d]);

         prepareImage(creature[d], this);

         tracker.addImage(creature[d], 0);

      }

// Instantiate the speech images.

         String[] languageFiles   = {"img/a1.gif","img/a2.gif","img/a3.gif"

                  ,"img/a4.gif","img/a5.gif","img/b1.gif","img/b2.gif"

                  ,"img/b3.gif","img/b4.gif"};

      Image[] language= new Image[languageFiles.length];

      for (int l=0; l<languageFiles.length; l++)

      {  language[l] = getImage(getCodeBase(), languageFiles[l]);

         prepareImage(language[l], this);

         tracker.addImage(language[l], 0);

      }

// Prepare the background image.

      String backgroundFile = "img/alpine2.jpg";

            Image  background = getImage(getCodeBase(), backgroundFile);

      prepareImage(background, this);

      tracker.addImage(background, 0);

      try { tracker.waitForAll(); }

      catch (InterruptedException e) {}

 

      bounds = new Rectangle( 0, 0, 2*APPLET_WIDTH, 2*APPLET_HEIGHT);

      focus  = new Rectangle( 0, 0, APPLET_WIDTH, APPLET_HEIGHT);

      direction = SOUTHEAST;

 

      viewBuffer = new Image[BUFFERS];

      view       = new Graphics[BUFFERS];

      for (int b=0; b<BUFFERS; b++)

      {  viewBuffer[b] = createImage(bounds.width, bounds.height);

            view[b]       = viewBuffer[b].getGraphics();

      }

      universe = new Universe(new Dimension(bounds.width,bounds.height),

                                   background, creature, language);

      running  = true;

      time     = 0;

      resize(APPLET_WIDTH, APPLET_HEIGHT);

   }  // End of init.

// Paint the applet.

   public void paint(Graphics page)

   {  int buffer = time % BUFFERS;

      if  (time < 0) time = BUFFERS;

     

      if (time%INTERVAL<FOCUS)

      {  focus.translate(DX[direction], DY[direction]);

        

         while (!focus.intersection(bounds).equals(focus))

         {  focus.translate(-DX[direction], -DY[direction]);

            direction = (int)(Math.random()%DIRECTIONS);

            focus.translate(DX[direction], DY[direction]);

      }  }

      universe.getView(view[buffer], focus, this);
      if (time++ >= BUFFERS)
         page.drawImage(viewBuffer[(buffer+1)%BUFFERS],0, 0,
                            bounds.width,bounds.height,this);

   } 

 

// Method to prevent clear the page before painting.

   public void update(Graphics page)   {  paint(page);  }

// Method to start animation.

   public void start()

   {  if (thread == null) 

      {  thread = new Thread(this);

         thread.start();

   }  }

// Method to stop animation.

   public void stop()  {  thread = null; }

// Actual animation thread runs in this method.

   public void run()

   {      while(thread!=null)

      {  try

         {  thread.sleep(DELAY);

            if (running) repaint();

         }

         catch(InterruptedException e)  {   stop();   }

   }  }

// Action listener to stop and start the animation.

   private class ControlAnimation implements MouseListener

   {  public void mouseClicked( MouseEvent event)

      {  Point point = event.getPoint();

         point.translate(focus.x, focus.y);

         universe.eventHappened(point);

      }

      public void mouseEntered( MouseEvent event) {}

      public void mouseExited( MouseEvent event) {}

      public void mousePressed( MouseEvent event) {running=false;}

      public void mouseReleased( MouseEvent event){running=true;}

   } // End of ControlAnimation Class

}  // End of Animation class.

 

// Class to control what happens in the universe of creatures.

class Universe

{  public final int FIGURES     = 64;

   public final int MIN_FIGURES = 48;

   public final int MAX_FIGURES = 80;

   public final int SPEAKER = 0, SIZE_CHANGER = 1, SPEED_CHANGER = 2;

   public final int SPECIES = 3;

 

   Dimension    bounds;       // Universe bounds.

   Image        background;   // The graphics background.

   ArrayList    creatures;    // Create an array of figures.

 

   public Universe(Dimension b, Image bg, Image[] files, Image[] language)

   {  bounds     = b;

      background = bg;

     

      creatures      = new ArrayList(MAX_FIGURES);

      int figsPerRow = (int)Math.sqrt(FIGURES);

      int width  = bounds.width/figsPerRow;

      int height = bounds.height/figsPerRow;

      int row = 0, column = 0, pixelX, pixelY;

 

      for (int i=0; i<FIGURES; i++)

      {  pixelX = row * width;

         pixelY = column * height;

         creatures.add(createFigure(files, language,
                             new Point(pixelX, pixelY)));

         if (++column == figsPerRow) {column=0; row++;}

      }

   }  // End of Universe constructor.

// Randomly draw the image on different places on the display.

   public void getView(Graphics view, Rectangle r, Applet applet)

   {  Figure figure, newFigure, otherCreature;

      Point     point;

      Dimension size;

 

      view.drawImage(background,-r.x,-r.y,bounds.width,bounds.height,applet);

      for (int creature=0; creature<creatures.size(); creature++)

      {  figure = (Figure)(creatures.get(creature));

         figure.move(bounds, true);

         if (r.intersects(figure.getPosition()))

         {  for (int other = 0; other<creatures.size(); other++)

            {  if (creature!=other)

               {  otherCreature = (Figure)(creatures.get(other));

                  if (figure.intersect(otherCreature))

                  {  figure.move(bounds, false);

                     if (figure.react()) creatures.remove(creature);

            }  }  }

            figure.draw(view, r, applet);

      }  }

// Create a new creature when the population is too low.    

      if (creatures.size() < MIN_FIGURES)

      {  int creature = (int)(Math.random()*creatures.size());

         figure = (Figure)(creatures.get(creature));

         point = figure.getPosition().getLocation();

         size  = figure.getPosition().getSize();

         point.translate(-size.width, -size.height);

         if (point.x >=0 && point.y >=0)

         {  newFigure = (Figure)createFigure(point);

            creatures.add(newFigure);

            newFigure.speak();

      }  }

   }  // End of getView() method.

// Method to create a creature at the mouse click spot on the display.

   public void eventHappened(Point point)

   {  Figure figure = (Figure)createFigure(point);

      for (int creature=0; creature<creatures.size(); creature++)

      {  Figure other = (Figure)creatures.get(creature);

         if (figure.intersect(other))

         {  other.speak();

            return;

      }  }

      if (creatures.size() < MAX_FIGURES)  creatures.add(figure);

   }

 

// Methods to make a new figure.

   public Figure createFigure(Point p) {return createFigure(null,null,p);}

   public Figure createFigure(Image[] images, Image[] language, Point p)

   {  int type = (int)(Math.random()*SPECIES);

      Figure figure = null;

 

      switch (type)

      {  case SPEAKER:      
              figure = (Figure)(new Speaker(images,language,p));

              break;

         case SPEED_CHANGER:
              figure = (Figure)(new SpeedChanger(images,language,p));

              break;

         case SIZE_CHANGER: 
              figure = (Figure)(new SizeChanger(images,language,p));

              break;

      }

      return figure;

   }

}  // End of Universe class.

 

// Class to represent the figures.

abstract class Figure

{  public final static int SPEED = 5;

   public final static int WIDTH  = 50,  HEIGHT = 50;

   public final static int MESSAGE_WIDTH = 100, MESSAGE_HEIGHT = 100;

   public final static int SPEAK_LENGTH  = 5000;

 

   static Image[]   images;    // The images of this figure.

   static Image[]   phrases;   // The images for figure language.

 

   int       direction;      // The current travel direction.

   int       speed;          // Current speed of travel.

   Rectangle position;       // The position of the image in the universe.

 

   int       speakMessage;   // Which message to speak.

   long      speakTime;      // When the speaking started.

 

   int       intersections;  // Count of intersections.

// Constructor.

   public Figure(Image[] images, Image[] phrases, Point p)

   {  if (images!=null)  this.images   = images;  // Figure and phrase images.

      if (phrases!=null) this.phrases  = phrases;

 

      direction     = (int)(Math.random()*Animation.DIRECTIONS);

      speed         = SPEED;

      position      = new Rectangle(p, new Dimension(WIDTH,HEIGHT));

 

      speakMessage  = 0;  // Message to speak.

      speakTime     = 0;  // Initialize when speaking started.

    

      intersections = 0;  // Count the intersections.

   }

// Public Method to determine if two figures intersect.

   public boolean intersect(Figure figure)

   {  if (position.intersects(figure.position))

      {  intersections++;  return true;  }

      return false;

   }

 

// Public Method to get position of figure.

   public Rectangle getPosition() { return position; }

// Method to speak by displaying a message.

      public void speak()           

      {  speakTime = System.currentTimeMillis();

            speakMessage = (int)(Math.random()*phrases.length);

   }

// Method to move the figure.

   public void move(Dimension range, boolean normal)

   {  if (!normal)
         direction = (direction+Animation.DIRECTIONS/2)%Animation.DIRECTIONS;

      move(range);

   }

// Abstract method to determine how a creature reacts

//   upon intersection.

   public abstract boolean react();

// Method to move the figure.

   private void move(Dimension range)

   {  position.x += speed*Animation.DX[direction];

      position.y += speed*Animation.DY[direction];

 

      if (position.x<0)       position.x = 0;

      else if (position.y<0)  position.y = 0;

      else if (position.x>range.width-position.width)

         position.x = range.width-position.width;

      else if (position.y>range.height-position.height)

         position.y = range.height-position.height;

      else return;

// Hit end of range in one of the directions. Change direction.

      direction = (int)(Math.random()*Animation.DIRECTIONS);

   }  // End of move method.

// Method to draw a figure.

   public void draw(Graphics page, Rectangle r, Applet applet)

   {  page.drawImage(images[direction], position.x-r.x, position.y-r.y,

                        position.width, position.height, applet);

      if (System.currentTimeMillis()-speakTime <= SPEAK_LENGTH)

      {  int phraseX      = position.x - MESSAGE_WIDTH/2 - r.x;

         int phraseY      = position.y - MESSAGE_HEIGHT - r.y;

         int phraseWidth  = MESSAGE_WIDTH;

         int phraseHeight = MESSAGE_HEIGHT;

         page.drawImage(phrases[speakMessage], phraseX, phraseY,

                          phraseWidth, phraseHeight, applet);

      }

   }  // End of draw method.

}  // End of Figure class.

// Polymorphic classes with different reactions.

// Creature that changes speed on intersection.

class SpeedChanger extends Figure

{  public static final int MAX_SPEED = 30;

   public static final int MIN_SPEED =  1;

   int speedDelta; 

// Constructor.

   public SpeedChanger(Image[] images, Image[] phrases, Point p)

   {  super(images, phrases, p);

      speedDelta = (int)(Math.random()*2);

      if (speedDelta==0) speedDelta = - 1;

   }

// Method to change the speed of travel.

   public boolean react()  

   {  direction = (direction+1)%Animation.DIRECTIONS;

      speed    += speedDelta;

      if (speed>MAX_SPEED) return true;

      if (speed<MIN_SPEED) return true;

      return false;

   }

}  // End of SpeedChanger class.

 

// Creature that changes size on intersection.

class SizeChanger extends Figure

{  public static final int MIN_SIZE =  10;

   public static final int MAX_SIZE = 100;

   int sizeDelta;

// Constructor.

   public SizeChanger(Image[] images, Image[] phrases, Point p)

   {  super(images, phrases, p);

      sizeDelta = (int)(Math.random()*2);

      if (sizeDelta==0) sizeDelta = - 1;

   }

// Method to change the size creature.

   public boolean react()

   {  direction = (direction+3)%Animation.DIRECTIONS;

      position.width  += sizeDelta;

      position.height += sizeDelta;

      if (position.width>MAX_SIZE) return true;

      if (position.width<MIN_SIZE) return true;

      return false;

   }

}  // End of SizeChanger class.

 

// Creature that speaks on intersection.

class Speaker extends Figure

{  public static final int MAX_INTERSECTIONS = 50;

// Constructor.

   public Speaker(Image[] images, Image[] phrases, Point p)

   {  super(images, phrases, p);  }

// Method to start the speaking.

   public boolean react()

   { speak();

     direction = (int)(Math.random()*Animation.DIRECTIONS);

     if (intersections > MAX_INTERSECTIONS) return true;

     else return false; 

   }  

}  // End of Speaker class.