
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.net.URL;
import java.util.*;

/**
 * applet Demineur
 *
 * @author Frederic MAIRE
 * @version 1.0
 *
 */
public class Demineur extends Applet 
    implements KeyListener, MouseListener
{
    private URL codeBase;
    private MediaTracker mediaTracker;
    private Rectangle linkInterjeux;
    Locale locale;
    ResourceBundle bundle;
    String s;

    private Image bufImg;
    private Graphics bufG;

    private static Random rnd = new Random();

    private Image imgNumbers;
    private Image imgCover;
    private Image imgIcon;

    private int maxLevel = 0;

    private int nbBombs;
    private int nbUncovered;
    private int nbFlag;
    private int explX, explY;    
    private long startTime;

    private int bW; // board width in cells
    private int bH; // board height in cells
    private byte[][] board;
    private byte[][] cover; 
    private int offX; // board offset
    private int offY; 
    private int sW; // image square size
    private int sH;
    private int xMenu; // position of information area
    private int yMenu; 

    static final int cBOMB = -1;
    static final int cFREE = 0;
    static final int cHIDE = 1;
    static final int cFLAG = 2;
    static final int cUNKN = 3;
    static final int cBOMB0 = 4;
    static final int cBOMB1 = 5;
    static final int cBOMB2 = 6;

    private Rectangle recBoard;

    private int bestScore = 0;
    private int score = 0;
    private int level = -1;

    private boolean isFirst = true;
    private boolean isPlaying = false;
    private boolean isWin = false; // if !isPlaying : true==win false==lost
    private boolean isSoundOn = false;
    private boolean isHelpShowing = false;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    private void delay(long millis) {
	try {
	    Thread.sleep(millis);
        } catch(Exception e) {
	    return;
        }
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    private void goInterjeux()
    {
        try
        {
            getAppletContext().showDocument(new URL("http://www.interjeux.net/php/refout.php3?name=Demineur&url=" + getDocumentBase()), "_blank");
        }
        catch(Exception exception)
        {
            return;
        }
    }

    private Rectangle drawLink(Graphics g, String s1, int i, int j)
    {
        FontMetrics fontmetrics = g.getFontMetrics();
        Rectangle rectangle = fontmetrics.getStringBounds(s1, g).getBounds();
        rectangle.setLocation(i, j - (int)rectangle.getHeight());
        g.setColor(new Color(0x00000000));
        g.drawString(s1, i, j);
        int k = i + fontmetrics.stringWidth(s1);
        int l = (j + fontmetrics.getDescent()) - 1;
        g.drawLine(i, l, k, l);
        return rectangle;
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 

    private boolean isAllCleared() {
	return (nbUncovered == nbBombs);
    }
    
    private void reveal() {
    }

    private void spreadFree( int xx, int yy) {
	if ((xx>=0) && (xx<bW) && (yy>=0) && (yy<bH)) {
	    if (cover[xx][yy] == cHIDE) {
		cover[xx][yy] = cFREE;
		nbUncovered--;
	    }
	    if (board[xx][yy] == 0) {
		board[xx][yy] = -2;
		spreadFree(xx-1,yy-1);
		spreadFree(xx-1,yy);
		spreadFree(xx-1,yy+1);
		spreadFree(xx,yy-1);
		spreadFree(xx,yy+1);
		spreadFree(xx+1,yy-1);
		spreadFree(xx+1,yy);
		spreadFree(xx+1,yy+1);
	    }
	}
    }

    private void clearSpread() {
	for (int i=0; i<bW; i++)
	    for (int j=0; j<bH; j++)
		if (board[i][j]==-2) board[i][j] = 0;
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 
	
    private void initGame() {
	board = new byte[bW][bH];
	cover = new byte[bW][bH];

	for (int i=0; i<bW; i++) {
	    for (int j=0; j<bH; j++) {
		board[i][j] = 0;
		cover[i][j] = cHIDE;
	    }
	}
	bestScore = 0;
    }
    
    private void nextLevel() {	
	level++; 
	if (level>maxLevel) {
	    level--;
	    winGame();
	} else {
	    nbUncovered = bW * bH;
	    nbFlag = 0;
	    // the board
	    for (int i=0; i<board.length; i++) {
		for (int j=0; j<board[i].length; j++) {
		    board[i][j] = 0;
		    cover[i][j] = cHIDE;
		}
	    }
	    // drop bombs
	    int xx, yy, ii, jj;
	    boolean drop;
	    for (int b=0; b<nbBombs; b++) {
		drop = false;
		while (!drop) {
		    xx = (byte)(rnd.nextInt(bW));
		    yy = (byte)(rnd.nextInt(bH));
		    if (board[xx][yy] >= 0) {
			drop = true;
			board[xx][yy] = cBOMB;
			// calculate danger indicator
			for (ii=xx-1; ii<xx+2; ii++) {
			    for (jj=yy-1; jj<yy+2; jj++) {
				if ((ii>=0) && (ii<bW) && (jj>=0) && (jj<bH)) 
				    if (board[ii][jj]>=0) 
					board[ii][jj]++;
			    }
			}
		    }
		}
	    }
	    repaint();
	    startTime = (new Date()).getTime();
	}
    }
    
    private void newGame() {
	isFirst = false;
	score = 0;
	level = -1;
	nextLevel();
	isPlaying = true;	
	repaint();
    }

    private void endGame() {
	isPlaying = false;
	if (score>bestScore) bestScore = score;
	repaint();
    }

    private void winGame() {	
	long endTime = (new Date()).getTime();
	score = (nbBombs*10000)/(bW*bH) - (int)(endTime-startTime)/1000;
	isWin = true;
	endGame();
    }
    private void lostGame(int xx, int yy) {
	explX = xx; explY = yy;
	isWin = false;
	endGame();
    }

    private void playMove( int xx, int yy) {
	if (cover[xx][yy] != cFLAG) {
	    doMove(xx,yy);
	}
	repaint(); 
    }
    private void doMove( int xx, int yy) {
	cover[xx][yy] = cFREE;
	nbUncovered--;
	repaint();
	if (board[xx][yy] == cBOMB) {
	    lostGame(xx,yy);
	} else {
	    if (board[xx][yy] == 0) {
		spreadFree(xx,yy);
		clearSpread();
		repaint();
	    }
	    if (isAllCleared()) {
		nextLevel();
	    }
	}
    }
    private void doInvalidMove( int xx, int yy) {
	repaint(); // have to clear selected
    }

    private void doInfo( int xx, int yy) {
	switch (cover[xx][yy]) {
	case cHIDE : cover[xx][yy] = cFLAG; nbFlag++; break;
	case cFLAG : cover[xx][yy] = cUNKN; nbFlag--; break;
	case cUNKN : cover[xx][yy] = cHIDE; break;
	}
	if (isAllCleared()) {
	    nextLevel();
	}
	repaint();
    }

    private boolean isMoveValid( int xx, int yy) {
	return true;
    }

    private boolean isMovePossible() {
	return true;
    }
    
    // -----------------------------------------------------

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    // MouseListener
    
    public void mouseClicked(MouseEvent mouseevent)
    {
        if(linkInterjeux.contains(mouseevent.getPoint())) {
            goInterjeux();
	} else {
	    if (isPlaying) {
		if (recBoard.contains(mouseevent.getPoint())) {
		    int xx = (mouseevent.getX() - offX) / sW;
		    int yy = (mouseevent.getY() - offY) / sH;
		    switch (mouseevent.getButton()) {
		    case MouseEvent.BUTTON1:
			playMove(xx,yy);
			break;
		    case MouseEvent.BUTTON3:
			doInfo(xx,yy);
			break;
		    }
		}
	    }
	}
    }

    public void mouseEntered(MouseEvent mouseevent)
    {
    }

    public void mouseExited(MouseEvent mouseevent)
    {
    }

    public void mousePressed(MouseEvent mouseevent)
    {
    }

    public void mouseReleased(MouseEvent mouseevent)
    {
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    // KeyListener
    
    public void keyTyped(KeyEvent keyevent)
    {
    }

    public void keyPressed(KeyEvent keyevent)
    {
        if(isPlaying)
            switch(keyevent.getKeyCode())
            {
            case KeyEvent.VK_S:
                isSoundOn = !isSoundOn;
                break;
            case KeyEvent.VK_H:
                isHelpShowing = !isHelpShowing;
		repaint(); 
                break;
            }
        else
            switch(keyevent.getKeyCode())
            {
            case KeyEvent.VK_S:
                isSoundOn = !isSoundOn;
                break;
            case KeyEvent.VK_H:
                isHelpShowing = !isHelpShowing;
		repaint(); 
                break;
	    case KeyEvent.VK_G:
	    case KeyEvent.VK_SPACE:
                newGame();
                break;
            }
    }

    public void keyReleased(KeyEvent keyevent)
    {
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    // Component

    private void drawImgIdx( Graphics g, Image img, int x, int y, int idx) {
	g.drawImage( img, 
		     x*sW+offX, y*sH+offY, x*sW+offX+sW, y*sH+offY+sH, 
		     idx*sW, 0, idx*sW+sW, sH,
		     this);
    }

    private void showHelp(Graphics g)
    {
	int ww = Integer.parseInt(bundle.getString("HelpWidth"));
	int hh = Integer.parseInt(bundle.getString("HelpHeight"));
	int num = Integer.parseInt(bundle.getString("HelpLines"));
	int xx = (getBounds().width-ww)/2;
	int yy = (getBounds().height-hh-offY)/2+offY;
	g.setColor(new Color(0xE052ADDE, true));
	g.fillRoundRect(xx, yy, ww, hh, 10,10);
	g.setColor(new Color(0xF0FFFFFF, true));
	for (int i=0; i<num; i++) {
	    s = bundle.getString("HelpMsg"+i+".str");
	    g.drawString(s, xx+10, yy+16+i*16);
	}
    }

    public void paint(Graphics g)
    {
	update(g);
    }

    public void update(Graphics g)
    {
	Rectangle bb = getBounds();        
	bufG.setColor(new Color(0x00C6C6C6));
	bufG.fillRect(bb.x,bb.y,bb.width,bb.height);
	bufG.setColor(new Color(0x00808080));
	bufG.fillRect(recBoard.x,recBoard.y,recBoard.width+1,recBoard.height+1);

	// title
	bufG.setColor(new Color(0x00005AE7));
	bufG.fillRect(0,0,bb.width,imgIcon.getHeight(this));
	bufG.drawImage( imgIcon, 0, 0, null);
	bufG.setColor(new Color(0x00FFFFFF));
	s = bundle.getString("Name.str");
	bufG.drawString(s, imgIcon.getWidth(this)*3/2, 15);

	// the board
	if (!isPlaying && isWin) { 
	    for (int i=0; i<bW; i++) 
		for (int j=0; j<bH; j++) 
		    drawImgIdx( bufG, imgNumbers, i, j, board[i][j]+1);
	} else {
	    for (int i=0; i<bW; i++) {
		for (int j=0; j<bH; j++) {
		    if (cover[i][j] == cFREE) {
			drawImgIdx( bufG, imgNumbers, i, j, board[i][j]+1);
		    } else {
			if (isPlaying) {
			    drawImgIdx( bufG, imgCover, i, j, cover[i][j]);
			} else {
			    if (cover[i][j] == cFLAG)
				drawImgIdx( bufG, imgCover, i, j, (board[i][j]==cBOMB)?cBOMB0:cBOMB1);
			    else
				if (board[i][j] == cBOMB) 
				    drawImgIdx( bufG, imgCover, i, j, cBOMB0);
				else 
				    drawImgIdx( bufG, imgCover, i, j, cHIDE);
			}
		    }
		}		
	    }
	}
	// explode bomb
	if (!isPlaying && !isWin && !isFirst) {
	    bufG.drawImage( imgCover, 
			    explX*sW+offX, explY*sH+offY, explX*sW+offX+sW, explY*sH+offY+sH, 
			    cBOMB2*sW, 0, cBOMB2*sW+sW, sH,
			    this );
	}
	// the menu
        bufG.setColor(new Color(0x00000000));
	s = bundle.getString("Score.str");
	bufG.drawString(s + score, xMenu, yMenu);
	s = bundle.getString("BestScore.str");
	bufG.drawString(s + bestScore, xMenu+100, yMenu);
	s = bundle.getString("Bombs.str");
	bufG.drawString(s + (nbBombs-nbFlag), xMenu+220, yMenu);

	if (!isPlaying) {
	    bufG.setColor(new Color(0x00F00000));
	    s = bundle.getString("StartMsg.str");
	    bufG.drawString(s, xMenu, bb.height-8);

	    //bufG.setColor(new Color(0xA0000000, true));
	    //bufG.fillRect(offX, offY, bW*sW, bH*sH);
	}
	if (isHelpShowing) {
	    showHelp(bufG);
	}

	linkInterjeux = drawLink(bufG, "(c) www.interJEUX.net", bb.width-130, bb.height-8);
	// double buffer
        g.drawImage(bufImg, 0, 0, this);
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    // Applet

    public String getAppletInfo() {
	return "Copyright (c) 2003 Frederic MAIRE \nhttp://www.interJEUX.net";
    }

    public void init() {
	String v;
	codeBase = getCodeBase();
	mediaTracker = new MediaTracker(this);

        String as[] = new String[3];
        as[0] = getParameter("language");
        as[1] = getParameter("country");
        as[2] = getParameter("variant");
        if(as[0] == null)
            as[0] = "fr";
        if(as[1] == null)
            as[1] = "";
        if(as[2] == null)
            locale = new Locale(as[0], as[1]);
        else
            locale = new Locale(as[0], as[1], as[2]);
        bundle = ResourceBundle.getBundle("Demineur", locale);
        requestFocus();

	bufImg = createImage(getWidth(), getHeight());
	bufG = bufImg.getGraphics();

	nbBombs = Integer.parseInt(getParameter("bombs"));
	bW = Integer.parseInt(getParameter("board_width"));
	bH = Integer.parseInt(getParameter("board_height"));
	offX = 8;
	offY = 8+32;

	imgNumbers = getImage(codeBase, "numbers.gif");
	mediaTracker.addImage(imgNumbers, 1);
	imgCover = getImage(codeBase, "cover.gif");
	mediaTracker.addImage(imgCover, 2);
	imgIcon = getImage(codeBase, "icon.gif");
	mediaTracker.addImage(imgIcon, 3);

        try {
            mediaTracker.waitForAll();
        } catch(InterruptedException e) {
            System.err.println("Error while loading images." + e);
        }
	sH = sW = imgNumbers.getHeight(this);
	recBoard = new Rectangle (offX, offY, sW*bW, sH*bH);
	xMenu = 24;
	yMenu = offY-8;

	initGame();

        addKeyListener(this);
        addMouseListener(this);
	repaint();
    }

    public void start() {
    }

    public void stop() {
    }

    public void destroy() {
	bufImg.flush();
	imgNumbers.flush();
	imgCover.flush();
    }
}
