//************************************************************************
// You are free to copy this source and use it in any way you deem fit
//************************************************************************
// 3D Mine Sweeper
// (I hope I am first to think of this) 
//
// Program: MineSweeper
// Programmer: H K Gupta 		Date: Someday in March 1998
//************************************************************************

import java.awt.*;
import java.applet.Applet;
import java.lang.Math;
import java.util.Date;

public class MineSweeper extends Applet  implements Runnable{

	Panel		pLeft;
	  Label[]	lHridayesh;
	Panel		pRight;
	  Label		lMinesRemaining;
	  Label		lTime;
	  Button	bNewGame;
	  Label		l3D;
	  Label		lMine;
	  Label		lSweeper;
	  Button	bBeginner;
	  Button	bIntermediate;
	  Button	bExpert;
	  Label		lPlayerType;
	  Button	bHelp;

	int	playerType = 3; // 2 beginner, 3 intermediate, 4 expert

	MineSweeperDisplay pMain;

	boolean 	firstTime = true;
	Thread		runThread = null;
	boolean		pauseInEffect = false;

	public void init() {
        }

	public	void start() {
		if(firstTime) {
			setUpDisplay();
			firstTime = false;
		}
		if (runThread == null) {
			if(!pauseInEffect) {
            			runThread = new Thread(this, "MineSweeper");
            			runThread.start();
			}
        	}
	}

	private boolean imagesLoaded = false;
	public	void run() {
		if(!imagesLoaded) {
			try { // Wait for icons file to load
				pMain.tracker.waitForAll();
			} catch (InterruptedException e) {}
			try { // Wait for mineSweeper image file to load
				pMain.mineSweeperTracker.waitForAll();
			} catch (InterruptedException e) {}
			imagesLoaded = true;
		}
		while (Thread.currentThread() == runThread) {
			try {
                		Thread.sleep(500);
            		} catch (InterruptedException e){}
			pMain.timer();
        	}
	}

	public	void stop() {
        	runThread = null;
	}

	void setUpDisplay() {

		setLayout(new BorderLayout());

		pLeft = new Panel();
			pLeft.setLayout(new GridLayout(0, 1));
			add("West", pLeft);
			lHridayesh = new Label[9];
			for(int i = 0; i < 9; i++) {
				lHridayesh[i] = new Label("HRIDAYESH".substring(i, i+ 1), Label.CENTER);
				lHridayesh[i].setFont(new Font("TimesRoman",Font.BOLD,36));
				lHridayesh[i].setForeground(Color.red);
				pLeft.add(lHridayesh[i]);
			}		
		
		pRight = new Panel();
			pRight.setLayout(new GridLayout(0, 1));
			add("East", pRight);
			lMinesRemaining = addLabel(pRight, "");
			lTime		= addLabel(pRight, "");
			lTime.setFont(new Font("TimesRoman",Font.BOLD,18));
			bNewGame	= addButton(pRight, "New Game");
			l3D		= addLabel(pRight, "3-D");
			lMine		= addLabel(pRight, "MINE");
			lSweeper	= addLabel(pRight, "SWEEPER");
			bBeginner 	= addButton(pRight, "Beginner");
			bIntermediate 	= addButton(pRight, "Intermediate");
			bExpert 	= addButton(pRight, "Expert");
			lPlayerType 	= addLabel(pRight, "InterMediate");
			bHelp		= addButton(pRight, "HELP!!");

		pMain = new MineSweeperDisplay(this, 
				getImage(getCodeBase(), "HridayeshGlobeArrows.gif"),
				getImage(getCodeBase(), "MineSweeper.gif"));
		add("Center", pMain);
        	validate();
	}

	Button	addButton(Panel p, String s) {
		Button b = new Button(s);
		p.add(b);
		return b;
	}

	Label	addLabel(Panel p, String s) {
		Label l = new Label(s, Label.CENTER);
		p.add(l);
		return l;
	}

    	public 	boolean action(Event e, Object arg) {
        	Object target = e.target; 

		if(target == bNewGame) 		{ pMain.startNewGame();	return true; }

		if(target == bBeginner) 	{ setPlayerType(2);	return true; }
		if(target == bIntermediate) 	{ setPlayerType(3);	return true; }
		if(target == bExpert) 		{ setPlayerType(4);	return true; }

		if(target == bHelp)		{ processHelp();	return true; }

	       	return false;
    	}

	private void setPlayerType(int type) {
		playerType = type;
		if(type == 2) lPlayerType.setText("Beginner");
		if(type == 3) lPlayerType.setText("Intermediate");
		if(type == 4) lPlayerType.setText("Expert");
		pMain.setLevel(type);
	}

	void	processHelp() {
		HridayeshHelp window = null;
                try {
                    window = new HridayeshHelp();
                } catch (Exception e) {
                    bHelp.setLabel("Error");
                    bHelp.disable();
                    return;
                }
                window.setTitle("Mine Sweeper Help");

window.setText(
  "*********                     3D Mine Sweeper                          ***********"
+ "\n\n"
+ "\n         We are sure that you do not want help on the rules of the game"
+ "\n          Anybody who uses computers is supposed to know the rules of"
+ "\n          this great game as they are part of your primary education"
+ "\n\n       However here is a brief summary of the the game for those who did"
+ "\n                  not pay attention in their classroom"
+ "\n           1. Use left button to expose clear areas where you think there"
+ "\n              are no mines. (If there is a mine you are doomed)\n"
+ "\n           2. Use right button to toggle between a mine, a question mark"
+ "\n              and an unexposed position"
+ "\n\n"
+ "\n             and here we will try to explain some of the extra features"
+ "\n\n"
+ "\nInstead of laying out the field in two dimensions as others do, we have gone 3D."
+ "\nThe mine field is overlaid on a globe which you can rotate using the arrows"
+ "\nThis will help you in uncovering the hidden parts of the globe"
+ "\n\n"
+ "\n                   We hope that the above help is sufficient"
+ "\n\n"
+ "\nIN CASE YOU ARE STILL CONFUSED, WE ARE IN THE PROCESS OF SETTING A 1-900 NUMBER TO"
+ "\nPROVIDE HAND HOLDING AND A SHOULDER TO CRY ON. ($3.99 per minute charges will apply) "
);

                window.pack();
                window.show();
	}
}

class MineSweeperDisplay extends HridayeshGlobe {

	private MineSweeper parent;
	private boolean gameOver = true;
	private boolean gameOnBoard = false;
	private boolean mineExploded = false;
	private long startTime;

		MediaTracker 	mineSweeperTracker;
	private Image		mineSweeperImages;

	public	MineSweeperDisplay(MineSweeper parent, Image iconImages, Image mineSweeperImages) {
		super(3, iconImages);
		this.parent = parent;

		this.mineSweeperImages = mineSweeperImages;
		mineSweeperTracker = new MediaTracker(this);
		mineSweeperTracker.addImage(this.mineSweeperImages, 0);
	}

	public	void setLevel(int level) {
		waitRotate = 100;
		gameOnBoard = true;
		setGlobeLevel(level);
	}

	public	void startNewGame() {
		setLevel(globeLevel);
	}

	private int waitRotate = 0;
	public	void timer() {
		if(gameOver) {
			if(waitRotate > 0) { // wait for sometime before autorotate
				waitRotate--;
				return;
			}
			else {
				rotate();
				return;
			}
		}
		waitRotate = 10;
		Date dateForTimer = new Date();
		int elapsedTime = (int)((dateForTimer.getTime() - startTime) / 1000);
		parent.lTime.setText(" " + elapsedTime);
	}

	private boolean[]	isMine;
	private int[]		selectionType; // 0 not selected, 1 open as clear, 2 as mine 3 as Question
	private int[]		neighbourMines;
	private int[]		drawnType; // to optimize and remember the last drawn mine type

	void	globeInit(int maxPoints) { // override the base class method
		isMine 		= new boolean[maxPoints];
		selectionType 	= new int[maxPoints];
		neighbourMines 	= new int[maxPoints];
		drawnType	= new int[maxPoints];
	}

	boolean globeMouseAllowed() { // override the base class method
		if(gameOver) return false;
		return true;
	}

	void	globeJustRotated() {
		for(int i = 0; i < nPoints; i++) drawnType[i] = -1;
	}
		
	void	globeCreate() { // override the base class method
		fillMines();
		Date dateForTimer = new Date();
		startTime = dateForTimer.getTime();
		if(gameOnBoard) {
			gameOver = false;
			waitRotate = 10;
		}
		else { // for demo purposes
			for(int i = 0; i < nPoints; i++) {
				if(isMine[i]) {
					selectionType[i] = 2;
				}
				else {
					selectionType[i] = 1;
				}
			}
		}
	}

	void globeDrawLine(Graphics o, int i, int j) {
		if((drawnType[i] == selectionType[i]) && (drawnType[j] == selectionType[j])) return;
		o.setColor(Color.blue);
		if((selectionType[i] == 0) || (selectionType[i] == 3) ||
			(selectionType[j] == 0) || (selectionType[j] == 3))
			o.setColor(Color.red);
		o.drawLine(intX[i], intY[i], intX[j], intY[j]);
	}

	private int minesRemaining;
	private int actualMines;

	private void showMinesRemaining() {
		parent.lMinesRemaining.setText("Mines Left " + minesRemaining);
	}

	private void fillMines() {
		minesRemaining = nPoints / 5;
		actualMines = minesRemaining;
		showMinesRemaining();
		for(int i = 0; i < nPoints; i++) {
			isMine[i] = false;
			selectionType[i] = 0;
			neighbourMines[i] = 0;
			drawnType[i] = -1;
		}
		for(int i = 0; i < minesRemaining; i++) { // create minesRemaining mines
			int j = (int)(Math.random() * (nPoints - i)); // fill j'th free spot
			int k = 0;
			while(j >= 0) {
				if(!isMine[k]) j--;
				k++;
			}
			isMine[k - 1] = true;
		}

		// count neighbouring mines
		for(int i = 0; i < nPoints; i++) {
			for(int j = 0; j < 6; j++) {
				if(pntNeighbour[i][j] != -1) {
					if(isMine[pntNeighbour[i][j]]) neighbourMines[i]++;
				}
			}
		}
	}

	void	globeInitDisplayPoint(Graphics o, int point) { // override the base class method
		if(selectionType[point] == drawnType[point]) return;
		if(drawnType[point] == -2) {
			o.setColor(Color.white);
			o.fillRect(intX[point] - 6, intY[point] - 6, 12, 12);
			drawnType[point] = -1;
		}
	}

	void	globeDisplayPoint(Graphics o, int point) { // override the base class method
		if(selectionType[point] == drawnType[point]) return;
		for(int j = 0; j < 6; j++) {
			int k = pntNeighbour[point][j];
			if(k >= 0) {
				if(intZ[k] >= 0) displayPoint(o, k);
			}	
		}
		displayPoint(o, point);
	}

	private void displayPoint(Graphics o, int point) {
		if(!imagesCopied) { 
			imagesCopy(); 
			imagesCopied = true;
		} 
		int	imageNumber = 0;
		switch (selectionType[point]) {
			case 0:	drawnType[point] = 0;
				return;
			case 1: if (neighbourMines[point] <= 0) {
					o.setColor(Color.white);
					o.fillOval(intX[point] - 3, intY[point] - 3, 6, 6);
					drawnType[point] = selectionType[point];
					return;
				}
				imageNumber = neighbourMines[point] - 1;
				break;
			case 2:	imageNumber = 5;	break;
			case 3: imageNumber = 9;	break;
			case 4: imageNumber = 8;	break;
			case 5: imageNumber = 6;	break;
			case 6:	imageNumber = 7;	break;
		}
		o.drawImage(images[imageNumber], intX[point] - 5, intY[point] - 5, this);
		drawnType[point] = selectionType[point];
	}

	private Image[]	images;
	private boolean imagesCopied = false;
	private int	noOfImages = 10;

	private Image[]	messageImages;
	private int	noOfMessageImages = 3;

	private	void imagesCopy() {
		Graphics imageG;
		images = new Image[noOfImages];
		for(int i = 0; i < noOfImages; i++) {
			images[i] = createImage(10, 10);
			imageG = images[i].getGraphics();
			imageG.drawImage(mineSweeperImages, - ((10 + 2) * i), -1, this);
		}
		messageImages = new Image[noOfMessageImages];
		for(int i = 0; i < noOfMessageImages; i++) {
			messageImages[i] = createImage(200, 18);
			imageG = messageImages[i].getGraphics();
			imageG.drawImage(mineSweeperImages, 0, - (12 + (18 + 2) * i), this);
		}
	}

	void	globePointSelected(int point, int mask) { // override the base class method
		if(gameOver || (selectionType[point] == 1)) return;
		if(mask != 0) { // right button is pressed
			switch (selectionType[point]) {
				case 0: selectionType[point] = 2; 
					drawnType[point] = -1;
					minesRemaining--;
					if(isMine[point]) actualMines--;
					break;
				case 2: selectionType[point] = 3; 
					drawnType[point] = -1;
					minesRemaining++;
					if(isMine[point]) actualMines++;
					break;
				case 3: selectionType[point] = 0; 
					drawnType[point] = -2;
					break;
				default: return;
			}
			if((actualMines == 0) && (minesRemaining == 0)) {
				gameOver = true;
				mineExploded = false;
				waitRotate = 0;
			}
			showMinesRemaining();
			repaint();
			return;
		}
		if(isMine[point]) {
			selectionType[point] = 4;
			openAllMines();
			mineExploded = true;
			gameOver = true;
		}
		else {
			if(selectionType[point] != 0) return;
			if(neighbourMines[point] == 0) {
				selectionType[point] = 1;
				while(openClear()) {}; // open all clear mines in neighbourhood 
			}
			else {
				selectionType[point] = 1;
			}
		}
		repaint();
	}

	private boolean openClear() { // open the neighbour of an opened clear area
		for (int i = 0; i < nPoints; i++) {
			if((!isMine[i]) && (selectionType[i] != 2)) {
				if(selectionType[i] != 1) {
					for(int k = 0; k < 6; k++) {
						if(pntNeighbour[i][k] != -1) {
							if(neighbourMines[pntNeighbour[i][k]] == 0) {
								if(selectionType[pntNeighbour[i][k]] == 1) {
									selectionType[i] = 1;
									return true;
								}
							}
						}
					}
				}
			}
		}
		return false;
	}

	private void openAllMines() {
		for(int i = 0; i < nPoints; i++) {
			if(isMine[i]) {
				if(selectionType[i] != 4) { // not the error point
					if(selectionType[i] != 2) selectionType[i] = 6;
				}
			}
			else {
				if(selectionType[i] == 2) {
					selectionType[i] = 5; // was not a mine
				}
			}
		}
	}

	private Color[] rainbow = {	Color.red, Color.yellow, Color.black, Color.green,
					Color.blue, Color.orange, Color.darkGray, Color.magenta};
	private int[] rainbowX = { 150, 106,    0, -106, -150, -106,    0,  106};
	private int[] rainbowY = { 0,   106,  150,  106,    0, -106, -150, -106}; 

	private int rainbowIndex = 0;

	void	globeFinalUpdate(Graphics o, int xCenter, int yCenter) { // override the base class method
		if(waitRotate > 0) return;
		int messageNumber; 
		if(!gameOnBoard) { 
			messageNumber = 0;
		}
		else {
			if(mineExploded) {
				messageNumber = 2;
			}
			else {
				messageNumber = 1;
			}
		}
		o.drawImage(messageImages[messageNumber], xCenter - 100, yCenter - 9, this);
		rainbowIndex++;
		if(rainbowIndex >= 8) rainbowIndex -= 8;
		for(int i = 0; i < 8; i++) {
			int c = i + rainbowIndex;
			if(c >= 8) c -= 8;
			o.setColor(rainbow[c]);
			o.fillOval(xCenter + rainbowX[i] - 10, yCenter + rainbowY[i] - 10, 20, 20);
		}
	}

}
