/**
*===========================================================================
* CSCI298c-java<br>
* Programmer: Ping Wang<br>
* Lab 4: Tank.java is to show Producer/Consumer and monitor concept for 
*        the threads.<br> 
* Description:<br>
*   There are 4 classes in this project.<br>
*      Tank:     As a interface for game control and applet will show up in 
*                the screen.<br>  
*      Producer: Put new enemy position to monitor.<br>
*      Consumer: Get the enemy position from the monitor, put new tank position
*                into the monitor.<br>  
*      Monitor: Provide synchronized methods, put and get methods.<br> 
*============================================================================
*/

import java.awt.*;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.event.*;

/**
*===========================================================================
* Class Name:
*   Tank <hr>
* Purpose:
*   This is an interface for applet<hr>   
* Import: 
*   None<hr>
* Export: 
*   Web page shows the applet and animation. 
*
*============================================================================
*/

public class Tank extends Applet implements Runnable
{
   // Locations for tank and enemy
   private Point tankPoint, enemyPoint; 
   private Point tankStartingPoint, enemyStartingPoint;
   // Audit sound
   private AudioClip beginSound, bgSound, fireSound, explodeSound;
   // Thread declaration
   private volatile Thread tank = null; 
   private volatile Thread producer, consumer;
   // Object declaration
   private Monitor monitor;
   // Image objects
   private Image tankImage, enemyImage, enemyImage2;
   // Flags for this program
   private boolean b_start = false, b_fire = false, b_stop = false;
   // Panel objects used to contain button and graphics
   private Panel canvas, buttonPanel;

   /**   
   *Method: init()<br>   
   *Purpose:    
   *   Initialization, and load into images and audio sounds.<br>   
   *Pre-Condition:   
   *   None<br>   
   *Post-Condition:    
   *  Initialization is done   
   */      
   public void init()
   {    
      // Set layout format
      setLayout(new BorderLayout(10, 5));
      canvas = new Panel();
      add(canvas, "Center");

      buttonPanel = new Panel();
      // Add buttons onto buttonPanel
      addButton(buttonPanel, "Start",
         new ActionListener()
         {
            /**
            *Method: actionPerformed()<br>
            *Purpose: 
            *   Method generated by Button Event<br>
            *Pre-Condition:
            *   None<br>
            *Post-Condition: 
            *   Event method is performaed
            */
            public void actionPerformed(ActionEvent e)
            {
               if (b_start == false)
               {
                  start();           // Start this thread
                  producer.start();  // Start producer thread
                  consumer.start();  // Start consumer thread
                  b_start = true;
                  b_stop = false;
               }
            }
         });
      addButton(buttonPanel, "Stop",
         new ActionListener()
         {
               /**
               *Method: actionPerformed()<br>
               *Purpose: 
               *   Method generated by Button Event<br>
               *Pre-Condition:
               *   None<br>
               *Post-Condition: 
               *   Event method is performaed
               */

            public void actionPerformed(ActionEvent e)
            {              
               if (b_stop == false)
               {
                  b_stop = true;
                  b_start = false;
                  stop();          // Stop all threads
               }  
            }
         });
      add(buttonPanel, "South");

      // Get image files
      tankImage = getImage(getCodeBase(), "tankImage.gif");
      enemyImage = getImage(getCodeBase(), "enemyImage.gif");
      enemyImage2 = getImage(getCodeBase(), "enemyImage2.gif");
      // Get sounds files
      beginSound = getAudioClip(getCodeBase(), "beginSound.au");
      bgSound = getAudioClip(getCodeBase(), "bgSound.au");
      fireSound = getAudioClip(getCodeBase(), "fireSound.au");
      explodeSound = getAudioClip(getCodeBase(), "explodeSound.au");
   }

   /**   
   *Method: addButton()<br>   
   *Purpose:    
   *   Add buttons onto comtainer<br>   
   *Pre-Condition:   
   *   Panel p, String s, ActionListener a<br>   
   *Post-Condition:    
   *   Button is added   
   */   
   public void addButton(Panel p, String s, ActionListener a)
   {
      Button b = new Button(s); // Create new Button object
      p.add(b);                 // Add button to corresponding container
      b.addActionListener(a);  // Registration of Listener
   }

   /**   
   *Method: pause()<br>   
   *Purpose:    
   *   Set Thread sleep time<br>   
   *Pre-Condition:   
   *   int time<br>   
   *Post-Condition:    
   *   Thread sleep time is set   
   */      
   public void pause(int time)
   {
      try{
         tank.sleep(time);        // Thread sleep for some time
      }catch(InterruptedException e){}
   }

   /**   
   *Method: stop()<br>   
   *Purpose:    
   *   Stop Threads<br>   
   *Pre-Condition:   
   *   None<br>   
   *Post-Condition:    
   *   Threads are stop  
   */   
   public void stop()
   {
      tank = null;       // Stop tank thread
      producer = null;   // Stop producer thread
      consumer = null;   // Stop consumer thread
      bgSound.stop();    // Stop playing background sound
   }

   /**   
   *Method: run()<br>   
   *Purpose:    
   *   Run Threads<br>   
   *Pre-Condition:   
   *   None<br>   
   *Post-Condition:   
   *   Threads run   
   */   
      
   public void run()
   {
      Graphics g;

      bgSound.loop();     // Play background sound

      while (b_start == false)
      {
         // Display blink sentence to prompt user to click button
         g = canvas.getGraphics();
         g.drawString("Click the start button to begin", 225, 225);
         pause(1500);
         g.clearRect(200, 200, 300, 40);
         pause(500);
      }
      // Play beginning sound
      beginSound.play();
      g = canvas.getGraphics();
      g.clearRect(0, 0, 600, 600);

      while(tank != null)
      {
         g.drawRect(0, 0, 590, 450);
         try
         {
            // Draw tank image
            drawTank(tankImage, monitor.getTankPoint()); 
            // Draw enemy image  
            drawEnemy(enemyImage, monitor.getEnemyPoint());
            tank.sleep(100);

            if (monitor.getFireStatus())
            {
               fire(); // Handle fire
               tank.sleep(1000);
               // Set fire status back to false
               monitor.setFireStatus(false);
               // Produce random location for the enemy
               int temp_x = (int)(Math.random()*500+50);
               int temp_y = 10;
               Point enemyStartingPoint = new Point(temp_x, temp_y);
               monitor.setEnemyPoint(enemyStartingPoint);
            }

            g = canvas.getGraphics();
            // Clean this enmey image before new one being drawn
            g.clearRect(monitor.getEnemyPoint().x, 
                  monitor.getEnemyPoint().y-20, 40, 80);
            // Clean this tank image before new one being drawn
            g.clearRect(monitor.getTankPoint().x-20,
                  monitor.getTankPoint().y, 90, 50);
             
         }catch(Exception e)
         {
            e.printStackTrace(); // Print out any stack traces
         }
      }
   }

   /**   
   *Method: start()<br>   
   *Purpose:    
   *   Thread start.  Draw the scence.<br>    
   *Pre-Condition:   
   *   None<br>   
   *Post-Condition:    
   *   Thread run   
   */   
   public void start()
   { 
      Point tempPoint;
      // Produce random starting position of enemy 
      int temp_x = (int)(Math.random() * 500 + 50);
      int temp_y = 10;
      tempPoint = new Point(temp_x, temp_y);
      enemyStartingPoint = tempPoint;
      // Produce starting position of tank
      tankStartingPoint = new Point(250, 400);

      if (tank == null)
      {
         monitor = new Monitor(); // Create new Monitor object
         tank = new Thread(this); // Create new Tank thread
         monitor.setEnemyPoint(enemyStartingPoint);
         monitor.setTankPoint(tankStartingPoint);
         producer = new Producer(monitor); // Create new Producer thread
         consumer = new Consumer(monitor); // Create new Consumer thread
         tank.start();
      }
   }
   /** Method Name: drawTank()<br>    
   *   Purpose: draw graphics<br>   
   *   Pre-Condition: Graphics g, Image aTank, Point aPoint.<br>   
   *   Post-Condition: Graphics is painted   
   */   
   public void drawTank(Image aTank, Point aPoint)
   {
      Image tankImage = aTank;
      Point tankPoint = aPoint;
      Graphics g = canvas.getGraphics();
      // Draw input tankImage
      g.drawImage(tankImage, tankPoint.x, tankPoint.y, this);
   }

   /** Method Name: drawEnemy()<br>    
   *   Purpose: draw graphics<br>   
   *   Pre-Condition: Graphics g, Image aEnemy, Point aPoint.<br>   
   *   Post-Condition: Graphics is painted   
   */      
   public void drawEnemy(Image aEnemy, Point aPoint)
   {
      Image enemyImage = aEnemy;
      Point enemyPoint = aPoint;
      Graphics g = canvas.getGraphics();
      // Draw input enemyImage
      g.drawImage(enemyImage, enemyPoint.x, enemyPoint.y, this);
   }

   /** Method Name: drawBullet()<br>   
   *   Purpose: draw bullets shooting and explosion<br>   
   *   Pre-Condition: Point aTankPoint<br>   
   *   Post-Condition: Bullets and explosion graphics is drawnk   
   */   
   public void drawBullet(Point aTankPoint)
   {
      int temp_tank_x = aTankPoint.x;
      int temp_tank_y = aTankPoint.y;
      int temp_enemy_x, temp_enemy_y;
      Graphics g = canvas.getGraphics();

      do
      {
         // draw bullets
         g.fillOval(temp_tank_x + 25, temp_tank_y - 5, 4, 4);
         g.fillOval(temp_tank_x + 25, temp_tank_y - 30, 6, 6);
         g.fillOval(temp_tank_x + 25, temp_tank_y - 60, 8, 8);
         pause(50);
         // clear bullets of last loop
         g = canvas.getGraphics();
         g.clearRect(temp_tank_x + 25, temp_tank_y - 60, 10, 60);
         temp_tank_y = temp_tank_y - 30;

         // get coordinates of enemy tank
         temp_enemy_x = monitor.getEnemyPoint().x;
         temp_enemy_y = monitor.getEnemyPoint().y;
      }while((temp_tank_y - 60) > monitor.getEnemyPoint().y);

      // clean bullets from screen
      g = canvas.getGraphics();
      g.clearRect(temp_tank_x + 25, temp_tank_y - 30, 10,
         temp_tank_y - temp_enemy_y);
      // draw explosion around the enemy
      explode(temp_enemy_x, temp_enemy_y);
   }

   /** Method Name: fire()<br>   
   *   Description: Handle fire sound playing and call drawBullet() method.<br>   
   *   Pre-Condition:    
   *     None.<br>   
   *   Post-Condition:    
   *     Fire sound is played three times, bullets and explosion drawn.   
   */   
   public void fire()
   {
      // loop for playing fire sound
      for(int i = 0; i < 3; i++)
      {
         // play fire sound three times 
         fireSound.play();
      }
      // draw bullets
      drawBullet(monitor.getTankPoint());
   }

   //The method below was modified from the explodeSequnce method presented 
   //in Greg Murray's past year 298java program.
   /** Method Name: explode()<br>   
   *   Description: Handle explosion sequence.<br>   
   *   Pre-Condition: int x, int y.<br>   
   *   Post-Condition: Bullets and explosion graphics is drawn   
   */   
   public void explode(int x, int y)
   {
      int destroy_x = x + 15;
      int destroy_y = y;
      int counter, temp1, temp2;
      Graphics g = canvas.getGraphics();
      pause(100);
      explodeSound.play();
      for (counter = 0; counter < 20; counter++)
      {
         // Produce random numbers
         temp1 = (int)(Math.random()*counter*4);
         temp2 = (int)(Math.random()*counter*4);

         // draw fragments of explosion ;
         g.setColor(Color.red);
         g.fillRect(destroy_x + temp1, destroy_y + temp2, counter/3, counter/3);
         pause(45);
         g.fillRect(destroy_x + temp1, destroy_y - temp2, counter/3, counter/3);
         pause(30);
         g.fillRect(destroy_x - temp1, destroy_y + temp2, counter/3, counter/3);
         pause(15);
         g.fillRect(destroy_x - temp1, destroy_y - temp2, counter/3, counter/3);
      }

      pause(1000);
      g.clearRect(destroy_x - 100, destroy_y - 100, 200, 200);
   }

   /** Method Name: paint()<br>   
   *   Description: Do nothing<br>   
   *   Pre-Condition: None<br>   
   *   Post-Condition: None   
   */   
   public void paint(Graphics g)
   {
   }
} // end of Tank class

//******************************************************************************


/**
*===========================================================================
* Class Name:
*   Producer<hr>
* Purpose:
*   This is a producer to produce enemy in the screen.  It provides new enemy 
*   locations<hr>
* Import: 
*   None<hr>
* Export: 
*   Enemy information is produced
*
*============================================================================
*/

class Producer extends Thread
{
   private Monitor monitor;     // create monitor object
   private Point enemyPoint;    // enemy information

   /**   
   *Method: Producer()<br>   
   *Purpose:    
   *   constructor<br>   
   *Pre-Condition:   
   *   Monitor aMonitor<br>   
   *Post-Condition:    
   *  Initialization is done   
   */     
   public Producer(Monitor aMonitor)  
   {                                  
      monitor = aMonitor;     
   }

   /**   
   *Method: run()<br>   
   *Purpose:    
   *   Producer thread runs<br>   
   *Pre-Condition:   
   *   None<br>   
   *Post-Condition:    
   *   Thread runs   
   */     
   public void run()
   {
      int temp_x;
      int temp_y;

      while(this != null)
      {
         try
         {
            enemyPoint = monitor.getEnemyPoint(); // Get enemy position
            temp_x = enemyPoint.x;
            temp_y = enemyPoint.y;
            temp_y++;
            enemyPoint = new Point(temp_x, temp_y);
            // Set new enemy position
            monitor.setEnemyPoint(enemyPoint);   
            // This thread sleep for some time
            this.sleep((int)(Math.random() * 50));
         }catch(InterruptedException e){}
      }
   }
} // end of Produder class

//******************************************************************************
/**
*===========================================================================
* Class Name:
*   Consumer<hr>
* Purpose:
*   This is a consumer to retrive enemy information from monitor . Then,
*   put new tank location into monitor.<hr>
* Import: 
*   None<hr>
* Export: 
*   Enemy information are retrived and tank location is modified. 
*
*============================================================================
*/
class Consumer extends Thread
{
   private Monitor monitor;              // create monitor object
   private Point enemyPoint, tankPoint;  // enemy information
      
   /**   
   *Method: Consumer()<br>   
   *Purpose:    
   *   Constructor<br>   
   *Pre-Condition:   
   *   None<br>                                  
   *Post-Condition:    
   *  Initialization is done   
   */      
   public Consumer(Monitor aMonitor)
   {
      // Create Monitor object
      monitor = aMonitor;
   }

   /**   
   *Method: run()<br>   
   *Purpose:    
   *   Consumer thread runs<br>   
   *Pre-Condition:   
   *   None<br>   
   *Post-Condition:    
   *   Thread runs   
   */      
   public void run()
   {
      int temp_enemy_x;
      int temp_enemy_y;
      int temp_tank_x;
      int temp_tank_y;

      while(this != null)
      {
         try
         {
            enemyPoint = monitor.getEnemyPoint(); // Get enemy position
            temp_enemy_x = enemyPoint.x;

            tankPoint = monitor.getTankPoint();   // Get tank position
            temp_tank_x = tankPoint.x;
            temp_tank_y = tankPoint.y;

            if (temp_tank_x < temp_enemy_x - 13)
            {
               temp_tank_x++;
            }
            else if (temp_tank_x > temp_enemy_x - 13)
            {
               temp_tank_x--;
            }
            else
            { 
               monitor.setFireStatus(true);
            }

            tankPoint = new Point(temp_tank_x, temp_tank_y);
            monitor.setTankPoint(tankPoint);   // Set new tank position

            // Thread sleeps sometime
            this.sleep((int)(Math.random()*30));
         }catch(InterruptedException e){}
      }
   }
} // end of Consumer class

//******************************************************************************

/**
*===========================================================================
* Class Name:
*   Monitor<hr>
* Purpose:
*   This is a monitor to provided synchronized methods for producer and
*   consumer.<hr>  
* Import: 
*   None<hr>
* Export: 
*   Locations of enemy and tank images. 
*
*============================================================================
*/

class Monitor
{
   // Enemy and tank position information
   private Point enemyPoint;
   private Point tankPoint;
   private boolean b_fire = false;

   /**   
   *Method: setFireStatus()<br>   
   *Purpose:    
   *   Set new value of boolean variable<br>   
   *Pre-Condition:   
   *   boolean bFire<br>                     
   *Post-Condition:    
   *   boolean variable is set with a value   
   */      
   public synchronized void setFireStatus(boolean bFire)
   {
      b_fire = bFire;
      notifyAll();
   }

   /**   
   *Method: getFireStatus()<br>   
   *Purpose:    
   *   Get value of boolean variable<br>   
   *Pre-Condition:   
   *   None<br>                     
   *Post-Condition:    
   *   Value of boolean variable b_fire is returned   
   */      
   public synchronized boolean getFireStatus()
   {
      notifyAll();
      return b_fire;
   }

   /**   
   *Method: setEnemyPoint()<br>   
   *Purpose:    
   *   Set new position value of enemy<br>   
   *Pre-Condition:   
   *   Point aPoint<br>                     
   *Post-Condition:    
   *   enemy postion variable is set with a new value   
   */      
   public synchronized void setEnemyPoint(Point aPoint)
   {
      enemyPoint = aPoint;
      notifyAll();
   }

   /**   
   *Method: setTankPoint()<br>   
   *Purpose:    
   *   Set new position value of Tank<br>   
   *Pre-Condition:   
   *   Point aPoint<br>                     
   *Post-Condition:    
   *   tank postion variable is set with a new value   
   */         
   public synchronized void setTankPoint(Point aPoint)
   {
      tankPoint = aPoint;
      notifyAll();
   }

   /**   
   *Method: getEnemyPoint()<br>   
   *Purpose:    
   *   Get position value of enemy<br>   
   *Pre-Condition:   
   *   None<br>                     
   *Post-Condition:    
   *   Value of position variable enemyPoint is returned   
   */      
   public synchronized Point getEnemyPoint()
   {
      notifyAll();
      return enemyPoint;
   }

   /**   
   *Method: getTankPoint()<br>   
   *Purpose:    
   *   Get position value of tank<br>   
   *Pre-Condition:   
   *   None<br>                     
   *Post-Condition:    
   *   Value of position variable tankPoint is returned   
   */      
   public synchronized Point getTankPoint()
   {
      notifyAll();
      return tankPoint;
   }
} // end of Monitor class

