
import javax.swing.*;

import java.awt.*;
import java.awt.event.*;

public class DragMouse { 

	JFrame frame;
	MySplashDrawPanel splashdrawPanel;
	MyDrawPanel drawPanel;
	JPanel splashPanel;
	
	int level = 1;	
	static final int MAX_LEVEL = 5;

	int speed = 100;    // "resolution" of level timer in milliseconds
	int total_time = 0;
	int cumulative_total_time = 0;
	int best_total_time = 100000000;
	int worst_total_time = 0;
	double average_total_time;
	
	int circle_radius = 20;
	int circle_x;
	int circle_y;

	boolean grabbed = false;
	int grabbed_dx;
	int grabbed_dy;
	
	int window_width = 300;
	int window_height = 300;

	int target_index;
	int target_sidelength = 20;
	int[] target_x = { 5, window_width - target_sidelength - 10, window_width - target_sidelength - 10, 5 };
	int[] target_y = { 5, 5, window_height - target_sidelength - 40, window_height - target_sidelength - 40 } ;
	
//----------------------------------------------------------
	
	public static void main (String[] args) 
	{
		DragMouse dm = new DragMouse();
	}

//----------------------------------------------------------
	
	DragMouse() 
	{
		frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		drawPanel = new MyDrawPanel();
		drawPanel.addMouseListener(new CircleMouseListener());
		drawPanel.addMouseMotionListener(new CircleMouseMotionListener());
		
		splashdrawPanel = new MySplashDrawPanel();
		splashdrawPanel.addMouseListener(new SplashMouseListener());
		
		// start on splash screen...
		frame.getContentPane().add(BorderLayout.CENTER, splashdrawPanel);
		
		frame.setSize(window_width, window_height);  
		frame.setVisible(true);
	}
	
//----------------------------------------------------------

	// put circle in a random spot inside window
	
	void set_random_circle_location()
	{
		circle_x = 5 + (int) (Math.random() * (drawPanel.getWidth() - 10));
		circle_y = 5 + (int) (Math.random() * (drawPanel.getHeight() - 10));
	}

//----------------------------------------------------------
	
	// is (x, y) inside the target square?
	
	boolean inside_target(int x, int y) 
	{
		return (x >= target_x[target_index] && x < (target_x[target_index] + target_sidelength) && y >= target_y[target_index] && y < (target_y[target_index] + target_sidelength));
	}
	
//----------------------------------------------------------

	// move to next level, updating circle and target locations and resetting timer
	
	void goto_next_level() 
	{
		if (level == MAX_LEVEL) {
			
			frame.getContentPane().remove(drawPanel);
			frame.getContentPane().add(BorderLayout.CENTER, splashdrawPanel);
			frame.getContentPane().validate();
			
			frame.repaint();
		
		}
		
		level++;
		
		if (total_time < best_total_time)
			best_total_time = total_time;
		if (total_time > worst_total_time)
			worst_total_time = total_time;
		
		cumulative_total_time += total_time;
		total_time = 0;
		
		set_random_circle_location();
		
		int old_target_index = target_index;
		do {
			target_index = (int) (Math.random() * target_x.length);
		} while (target_index == old_target_index);
	}
	
//----------------------------------------------------------

	// handles timer updates (sole purpose is to increment time and redraw)
	
	class TimerListener implements ActionListener {
		
		public void actionPerformed(ActionEvent e) 
		{
			frame.repaint();
			
			// this much time has elapsed
			total_time += speed;
		}

	}
	
//----------------------------------------------------------

	// handles mouse motions in game's drawing canvas (not splash screen)
	
	class CircleMouseMotionListener implements MouseMotionListener {

		public void mouseMoved(MouseEvent event) { }
		
		public void mouseDragged(MouseEvent event) 
		{

			if (grabbed) {
				
				int cx = event.getPoint().x - grabbed_dx;
				int cy = event.getPoint().y - grabbed_dy;
				
				// don't allow circle to be dragged outside of window
				// this is slightly wrong because of title bar height, frame width...
				
				if (cx >= 0 && cx < window_width && cy >= 0 && cy < window_height) {
					circle_x = cx;
					circle_y = cy;
					frame.repaint();
				}
			}
		}
	}

//----------------------------------------------------------

	// handles mouse clicks in game's drawing canvas (not splash screen) 
	
	class CircleMouseListener implements MouseListener {

		public void mouseClicked(MouseEvent event) { }
		public void mouseExited(MouseEvent event) { }
		public void mouseEntered(MouseEvent event) { }

		public void mousePressed(MouseEvent event) 
		{
			double x = event.getPoint().x;
			double y = event.getPoint().y;
			double cx = circle_x;
			double cy = circle_y;
			double r = circle_radius;

			// is click inside circle?

			if (Math.sqrt((x - cx)*(x - cx) + (y - cy)*(y - cy)) <= r) {
				grabbed = true;
				grabbed_dx = event.getPoint().x - circle_x;
				grabbed_dy = event.getPoint().y - circle_y;
				frame.repaint();
			}	
		}

		public void mouseReleased(MouseEvent event) 
		{
			if (grabbed && inside_target(event.getPoint().x, event.getPoint().y)) 
				goto_next_level();
			
			grabbed = false;
			frame.repaint();
		}
	} 

//----------------------------------------------------------

	// what to draw DURING the game
	
	class MyDrawPanel extends JPanel {

		public void paintComponent(Graphics g) 
		{	
			// clear background
			
			g.setColor(Color.BLACK);
			g.fillRect(0, 0, this.getWidth(), this.getHeight());

			// set circle color
			
			if (grabbed)
				g.setColor(Color.GREEN);
			else
				g.setColor(Color.RED);
			
			// draw circle

			g.fillOval(circle_x - circle_radius, circle_y - circle_radius, 2 * circle_radius, 2 * circle_radius);
			
			// draw target

			g.setColor(Color.BLUE);
			g.fillRect(target_x[target_index], target_y[target_index], target_sidelength, target_sidelength);
					
			// draw level 
			
			g.setColor(Color.WHITE);
			g.drawString("Level: " + (new Integer(level)).toString() + " / " + (new Integer(MAX_LEVEL)).toString(), getWidth()/2 - 80, 15);
			
			// draw current time

			g.drawString("Time: " + (new Integer(total_time)).toString(), getWidth()/2 + 20, 15);
			
			if (level > 1) {
				g.drawString("Best: " + (new Integer(best_total_time)).toString(), getWidth()/2 + 20, 30);
			}
		}
	}
	
//----------------------------------------------------------

	// this catches the click on the splash screen to start the game
	
	class SplashMouseListener implements MouseListener {

		public void mouseClicked(MouseEvent event) { }
		public void mouseExited(MouseEvent event) { }
		public void mouseEntered(MouseEvent event) { }
		public void mousePressed(MouseEvent event) { }
		

		public void mouseReleased(MouseEvent event) 
		{
			// only handle if we are at the beginning
			
			if (level > 1)
				return;
			
			frame.getContentPane().remove(splashdrawPanel);
			frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
			frame.getContentPane().validate();
			
			target_index = (int) (Math.random() * target_x.length);
			
			set_random_circle_location();
			
			Timer timer = new Timer(speed, new TimerListener());
			timer.start(); 
			
			frame.repaint();
		}
	} 

//----------------------------------------------------------

	// what to draw BEFORE the game
	
	class MySplashDrawPanel extends JPanel {

		public void paintComponent(Graphics g) 
		{	
			// clear background
			
			g.setColor(Color.BLACK);
			g.fillRect(0, 0, this.getWidth(), this.getHeight());

			// draw title 
			
			g.setColor(Color.WHITE);
			if (level == 1) {
				g.drawString("Prepare for...DragMouse!", getWidth()/2 - 80, getHeight()/2 - 40);
				g.drawString("Click to play", getWidth()/2 - 40, getHeight()/2 + 20);
			}
			else {
				g.drawString("Congratulations!!!", getWidth()/2 - 60, getHeight()/2 - 40);
				g.drawString("Best time: " + (new Integer(best_total_time)).toString(), getWidth()/2 - 60, getHeight()/2 - 10);
				g.drawString("Worst time: " + (new Integer(worst_total_time)).toString(), getWidth()/2 - 60, getHeight()/2 + 10);
				average_total_time = (double) cumulative_total_time / (double) MAX_LEVEL;
				g.drawString("Average time: " + (new Double(average_total_time)).toString(), getWidth()/2 - 60, getHeight()/2 + 30);
			}
		}
	}
}


