// MineSwp.java

// this is a Java implementation of the MineSweeper game

// roo is used to get the #seconds since midnight,
// and to display information when a game is over.

// the TestRoo program uses the Java Native Interface (JNI)
// to communicate with the roo.dll. The corresponding
// roo.java program provides the native wrappers for the
// roo.dll's Java entry points.

import java.awt.*;

import java.awt.event.*;

import java.awt.image.*;

import java.io.*;

import java.util.*;

import roo; // bring in the roo native code wrapper -- roo.class

class MineSwp extends Frame implements ActionListener 
{
	static
	{
		// load the roo dynamic link library

		// you can uncomment the following and use an absolute path if necessary

		// String sRooDllFileName = "g:\\r4\\roo.dll";

		// normally, the roo.dll is placed in the Java startup directory

		String sRooDllFileName = System.getProperty( "user.dir" ) + "\\roo.dll";

		System.load( sRooDllFileName );
	}

	// this is the program's main routine

	static public void main( String[] args )
	{
		// prepare 'roo' interaction environment

		r = new roo();

		// set program caption for unexpected messages

		r.Initialize( "MineSweeper" );

		// prepare instance of associated MineSwp.roo class

		sRooMineSwpInstanceLocator = r.CreateRooClass( "MineSwp", null );

		if( sRooMineSwpInstanceLocator.length() == 0 )
		{
			System.out.println( "Could not create class: MineSwp.roo" );
			bRunning = false;
			r.TerminateRoo();
			System.exit( 1 );
			return;
		}

		// activate MineSwp class instance -- the principal class of this program

		new MineSwp();
	}

	// MineSwp() is the constructor of the  MineSwp class -- the principal class of this program 

    MineSwp()
    {
		super( "MineSweeper" );
			 
		f = this;

		setLayout(null);

		setVisible(false);

		PrepareGame( false );

		PrepareNewGameButton();

		PrepareRemainingCountLabel();

		PrepareTicker();

	  	setFont( new Font( "Monospaced", Font.BOLD, 16 ) );

		btnNewGame.addActionListener( this );

		this.addWindowListener( new WindowEventHandler() );

		setVisible(true);

		pack();

		show();

		setSize( 360, 396 );
	}

	// process action events:
	//  new game button click
	//  game cell button click

	public void actionPerformed( ActionEvent evt )
	{
		if( evt.getSource() == btnNewGame )

			PerformNewGameProcessing();

		else if( ! bGameOver )

			ProcessCellButtonClick( evt );
	}

	// add mines

	void AddMines()
	{
		// get mine indices from MineSwp.roo

		int nArguments = 3;

		String[] aArguments = new String[ nArguments ];

		aArguments[ 0 ] = Integer.toString( nRows );
		aArguments[ 1 ] = Integer.toString( nColumns );
		aArguments[ 2 ] = Integer.toString( nMines );

		String sLabels =
			r.InvokeRooClassMethod(
				sRooMineSwpInstanceLocator,
				"GetMines",
				aArguments );

		// mine indices are in a string, separated by spaces

		StringTokenizer parser = new StringTokenizer( sLabels, " " );
		
		try
		{
			for( int i=0; parser.hasMoreTokens(); i++ )
			{
				int nBombIndex = Integer.parseInt( parser.nextToken(), 10 ); // 0-origin

				int nRow = nBombIndex / nColumns;
				int nCol = nBombIndex % nColumns;

				cells[ nRow ][ nCol ] = 9; // mines have a cell value of 9

				// add 1 to all adjacent cells

				for( int iRow = nRow - 1; iRow < min( nRow+2, nRows ); iRow++ )

				  for( int iCol = nCol - 1; iCol < min( nCol+2, nColumns ); iCol++ )

					IncrementAdjacentMines( iRow, iCol );
			}
		}
		catch ( NoSuchElementException e )
		{
		}
	}

	// the #seconds since midnight is acquired from the roo time() built-in function

	int GetTime()
	{
		return Integer.parseInt( r.ExecuteRooFunction(	"Time",	aTimeArguments ), 10 );
	}

	// increment #mines adjacent to a specific cell

	void IncrementAdjacentMines( int nRow, int nCol )
	{
		// ensure the row and column values are within bounds

		if( nRow >= 0 && nCol >= 0 )
			cells[ nRow ][ nCol ] ++ ;
	}

	int min( int a, int b )
	{
		return a < b ? a : b;
	}

	void PrepareButtonsAndUnderlyingLabels( boolean bNewGame )
	{
		for( int i=0; i < nRows * nColumns; i++ )
		{
			int nRow = i / nColumns;
			int nCol = i % nColumns;

			Button b =
				bNewGame ? (Button) aButton.elementAt( i )
				: new Button( "" );

			b.setName( Integer.toString( i ) );

			Integer n = Integer.valueOf( b.getName() );

			int nAdjacentMines = min( cells[ nRow ] [ nCol ], 9 );

			Label l =
				bNewGame ? (Label) aLabel.elementAt( i )
				: new Label( Integer.toString( nAdjacentMines ) );

			l.setForeground( Color.decode( textColor[ nAdjacentMines ] ) );

			l.setName( Integer.toString( nAdjacentMines ) );

			l.setText( Integer.toString( nAdjacentMines ) );

			if( ! bNewGame )
			{
				b.setBounds( 12 + ( nCol * 21), 24 + ( nRow * 21), 20, 20 );

				l.setAlignment( Label.CENTER );

				l.setVisible( false );

				l.setLocation( b.getLocation() );

				l.setSize( b.getSize() );

				b.addActionListener( this );

				add( b );

				aButton.addElement( b ); // remember every cell button

				f.add( l );

				aLabel.addElement( l ); // remember every cell label
			}

			bGameOver = false; // => ready to go !
		}
	}

	void PrepareGame( boolean bNewGame )
	{
		nCellsRevealed = 0;

		cells = new int[ nRows ] [ nColumns ]; // reset all cells to 0

		AddMines();

		PrepareButtonsAndUnderlyingLabels( bNewGame );
	}

	void PerformNewGameProcessing()
	{
		setVisible(false);

		for( int i=0; i < 256; i++ )
		{
			((Button) aButton.elementAt( i )).setVisible( true );

			((Label) aLabel.elementAt( i )).setVisible( false );
		}

		PrepareGame( true );

		setVisible( true );

		nStartTime = 0;

		bGameOver = false;

		nCellsRevealed = 0;

		lblRemaining.setText( "" + Integer.toString( 256 - nMines ) );

		lblTicker.Reset();

		System.gc(); // release resources
	}

	void ProcessCellButtonClick( ActionEvent evt )
	{
		Button b = (Button) evt.getSource();

		Integer n = Integer.valueOf( b.getName() );

		int nCell = n.intValue();

		Label l = (Label) aLabel.elementAt( nCell );

		int nBombs = Integer.valueOf( l.getName() ).intValue();

		if( nBombs > 8 )
			PerformMineHitProcessing( b );

		// a cell was clicked that is not a mine

		else
			PerformNonMineHitProcessing( nCell );

		b.setVisible( false );
	}

	void PerformGameWonProcessing()
	{
		lblRemaining.setText( "! WIN !" );

		bGameOver = true;

		int nTime = GetTime();

		int nArguments = 1;

		String[] aArguments = new String[ nArguments ];

		aArguments[ 0 ] = "You win !!^" + Integer.toString( nTime - nStartTime );

		String sLabels = r.InvokeRooClassMethod( sRooMineSwpInstanceLocator, "ShowMessage", aArguments );

		btnNewGame.requestFocus();
	}

	void PerformMineHitProcessing( Button b )
	{
		cv.setVisible( true );

		cv.setLocation( b.getLocation() );

		cv.setSize( b.getSize() );

		f.add( cv );

		cv.repaint();

		bGameOver = true;

		int nArguments = 1;

		String[] aArguments = new String[ nArguments ];

		aArguments[ 0 ] = "Boom !!";

		String sLabels = r.InvokeRooClassMethod( sRooMineSwpInstanceLocator, "ShowMessage", aArguments );

		btnNewGame.requestFocus();
	}

	void PerformNonMineHitProcessing( int nCell )
	{
		if( 0 == nCellsRevealed )
			nStartTime = GetTime();

		int nRow = nCell / nColumns;
		int nCol = nCell % nColumns;

		Sweep( nRow, nCol );

		int nRemaining = ( 256 - nMines ) - nCellsRevealed;

		if( nRemaining == 0 )
			PerformGameWonProcessing();

		else
			lblRemaining.setText( Integer.toString( nRemaining ) );
	}

	void PrepareNewGameButton()
	{
		btnNewGame.setEnabled( true );

		btnNewGame.setBounds( 8 + ( ( 11 * 21) / 2 ), 24 + ( 16 * 21), 112, 26 );

	  	btnNewGame.setFont( new Font( "SansSerif", Font.BOLD, 16 ) );

		add( btnNewGame );
	}

	void PrepareRemainingCountLabel()
	{
		lblRemaining.setText( "" + Integer.toString( 256 - nMines ) );

		lblRemaining.setBounds( 32 + ( 15 * 21) - 60, 24 + ( 16 * 21), 60, 26 );

		lblRemaining.setVisible( true );

		lblRemaining.setForeground( Color.red );

		lblRemaining.setBackground( Color.black );

	  	lblRemaining.setFont( new Font( "SansSerif", Font.BOLD, 18 ) );

		lblRemaining.setAlignment( Label.CENTER );

		add( lblRemaining );
	}

	void PrepareTicker()
	{
		lblTicker.setText( "0" );

		lblTicker.setBounds( 12, 24 + ( 16 * 21), 60, 26 );

		lblTicker.setVisible( true );

		lblTicker.setForeground( Color.red );

		lblTicker.setBackground( Color.black );

	  	lblTicker.setFont( new Font( "SansSerif", Font.BOLD, 18 ) );

		lblTicker.setAlignment( Label.CENTER );

		add( lblTicker );
	}

	// the Sweep method reveals adjacent cells,
	// around a cell that was clicked.

	// when a cell with a label of 0 is clicked,
	// all adjacent cells, that are not mines, are revealed.

	// the Sweep method is recursive.

	void Sweep( int row, int col )
	{
		if( row < 0 | row >= nRows
		 |  col < 0 | col >= nColumns )
			return;

		int n = col + ( nColumns * ( row ) );

		Label l = (Label) aLabel.elementAt( n );

		if( l.getName() == "" ) // dejaVu
			return;

		int nBombs = Integer.valueOf( l.getName() ).intValue();

		if( nBombs >= 9 ) // don't show mines when sweeping
			return;

		Button b = (Button) aButton.elementAt( n );

		b.setVisible( false );
 
		l.setVisible( true );

		l.setName( "" ); // avoid endless recursion

		nCellsRevealed++;

		// recurse

		if( nBombs == 0 )
		//  nBombs == 1  ) // fast sweep option -- but it's too fast !
		{
			Sweep( row - 1, col - 1 );  // previous row
			Sweep( row - 1, col );    
			Sweep( row - 1, col + 1 );

			Sweep( row, col - 1 );      // this row
			Sweep( row, col + 1 );     

			Sweep( row + 1, col - 1 );  // next row
			Sweep( row + 1, col );     
			Sweep( row + 1, col + 1 );
		}
	}

	// the ImageCanvas embedded class is used to draw the bomb image

	class ImageCanvas extends Component
	{
		Image _image;

		ImageCanvas( Image image )
		{
			_image = image;
			prepareImage( _image, this );
		}

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

		public void update( Graphics g )
		{
			g.drawImage( _image, 0, 0, null );
		}
	}

	// the Ticker embedded class updates the second ticker

	class Ticker extends Label implements Runnable
	{
		Ticker()
		{
			(new Thread( this) ).start();
		}

		public void Reset()
		{
			setText( "0" );
			repaint();
		}

		public void run()
		{
			while( bRunning )
			{
				try
				{
					Thread.sleep( 500 );

					if( ! bGameOver && nStartTime != 0 )
					{
						setText( "" + Integer.toString( GetTime() - nStartTime ) );
						repaint();
					}
				}
				catch( Exception e )
				{
					e.printStackTrace();
				}
			}
		}
	}

	// the WindowEventHandler embedded class processes a system close window request

	class WindowEventHandler extends WindowAdapter
	{
		public void windowClosing( WindowEvent evt )
		{
			bGameOver = true;
			bRunning = false;
			f.remove( lblTicker );
			r.RemoveRooClassInstance( sRooMineSwpInstanceLocator );
			r.TerminateRoo();
			dispose();
			System.exit( 0 );
		}
	}

	// various class variables:

	int nRows = 16;

	int nColumns = 16;

	int nMines = 40;

	int[][] cells = new int[ nRows ] [ nColumns ]; // holds mines, and #adjacent mines

	Vector aButton = new Vector(); // buttons to hit

	Vector aLabel = new Vector(); // underlying labels

	static roo r; // roo object interface

	static String sRooMineSwpInstanceLocator; // roo instance locator

	// timer values:

	static boolean bRunning = true;

	boolean bGameOver = true;

	int nCellsRevealed = 0;

	int nStartTime = 0;

	// the frame object

	Frame f;

	// an image canvas is used to display the bomb image

	String sBombFileName = System.getProperty( "user.dir" ) + "\\bomb.gif";

	ImageCanvas cv = new ImageCanvas( Toolkit.getDefaultToolkit().getImage( sBombFileName ) );

	// buttons & labels at bottom of display

	Button btnNewGame = new Button( "New Game" );

	Label lblRemaining = new Label( "#Remaining", Label.CENTER );

	Ticker lblTicker = new Ticker();

	// text colors for underlying labels

	String[] textColor =
	{
		"#F5DEB3", //   wheat ,
		"#0000FF", //  blue ,
		"#008000", //  green ,
		"#FF0000", //  red ,
		"#9400D3", //  darkviolet ,
		"#8B0000", //  darkred ,
		"#00008B", //  darkblue ,
		"#000000", //  black ,
		"#A52A2A", //  brown ,
		"#000000",
		"#000000"
	};

	// time built-in function arguments

	String[] aTimeArguments = { "SecondsSinceMidnight" };
}
