// Connect4Demo.java
// NKU Java for Teachers Workshop, 2003
// -------------------------------------------------------------------
// A rough version of a computer-vs-user Connect4 game.
// This is Friday's "final" version.
//
// Organized via interfaces to allow independent
// board representations, computer move selection, and GUI presentation.
// -------------------------------------------------------------------


import javax.swing.* ;
import java.awt.* ;
import java.awt.event.* ;
import java.util.* ;



public class GuiConnect4Demo
// Launch the Connect4 game.
{
    public static void main( String[] args )
    {
        IConnect4 c4             = new KevinsConnect4() ;
        IConnect4Thinker thinker = new LazyThinker() ;
        
        Connect4Frame cf= new Connect4Frame( c4, thinker ) ;
        cf.show() ;
    }
}


////////////////////////////////////////


interface IConnect4
// A Connect4 board, for use in a Connect4 game program.
{
    // inspectors
    int getNumRows() ;
    int getNumColumns() ;
    int getSpot( int iRow, int iColumn ) ;
    boolean wins( int iColor ) ;
    boolean boardFull() ;
    IConnect4 copy() ;
    
    // mutators
    boolean drop( int iColor, int iCol ) ;

    // diagnostics
    void dump() ;
    
    // named constants
    int EMPTY=0, RED=1, BLUE=2 ;
}


////////////////////////////////////////////////////////////////////////////////


interface IConnect4Thinker
// An interface to the "AI" used to select a move.
{
    int makeMove( IConnect4 gameboard, int iColor ) ;
}


////////////////////////////////////////////////////////////////////////////////

class LazyThinker implements IConnect4Thinker
// Picks a Connect4 move by picking a random open column.
{
    Random _rand ;  // generator for random moves
    
    public LazyThinker()
    {
        _rand= new Random() ;
    }
        
    public int makeMove( IConnect4 gameboard, int iColor )
    // Decide which column to drop the token in. This version: pick randomly.
    {
        assert( ! gameboard.boardFull() ) ;
        
        int iColumn ;
        boolean ok ;
        do
        {
            // Keep trying until an open column is found.
            iColumn= _rand.nextInt( gameboard.getNumColumns() ) ;
            ok= gameboard.drop( iColor, iColumn ) ;
        } 
        while ( ! ok ) ;
        
        return iColumn ;
    }
}


////////////////////////////////////////////////////////////////////////////////


class Connect4Panel extends JPanel implements MouseListener
{
    static final int USERCOLOR     = IConnect4.RED ;
    static final int COMPUTERCOLOR = IConnect4.BLUE ;
    
    IConnect4        _gameboard ;        // holds the state of the game
    int              _tokenDiameter ;    // diameter of image of colored tokens
    boolean          _usersTurn ;        // is it the user's turn? 
    boolean          _gameOver ;         // is the game over?
    IConnect4Thinker _thinker ;          // computer's move generator
    
    Connect4Panel( IConnect4 gameboard, IConnect4Thinker thinker, int tokenDiameter )
    {
        super() ;
        
        _gameboard= gameboard ;
        _tokenDiameter= tokenDiameter ;
        _usersTurn= true ; // user moves first (in this version)
        _gameOver= false ;
        _thinker= thinker ;
        
        addMouseListener( this ) ;
    }
    

    public void paintComponent( Graphics g )
    // Redraw the grid of tokens on the board.
    {
        super.paintComponent( g ) ;
        
        int nRows= _gameboard.getNumRows() ;
        int nCols= _gameboard.getNumColumns() ;
        int yBottom= nRows*_tokenDiameter   ; // y coordinate of bottom row
        
        for ( int iRow=0 ; iRow < nRows ; ++iRow )
            for ( int iCol=0 ; iCol < nCols ; ++iCol )
            {
                // Figure out where to draw token, then draw it.
                int x=  iCol*_tokenDiameter ;
                int y= yBottom - iRow*_tokenDiameter  ; 
                drawToken( g, x, y, _gameboard.getSpot( iRow, iCol ) ) ;
            }
            
        // Print a message informing user of the state of game play.
        printStateMessage( g ) ;    
    }
    
    
    void printStateMessage( Graphics g )
    // Print state message on console. 
    {
        g.setColor( Color.BLACK ) ;
        int y= _tokenDiameter / 2 ;
        
        if ( _gameboard.wins( USERCOLOR ) )
            g.drawString( "User wins!", 0, y ) ;
        else if ( _gameboard.wins( COMPUTERCOLOR ) )
            g.drawString( "Computer wins!", 0, y ) ;
        else if ( _gameboard.boardFull() )
            g.drawString( "Cat's game!", 0, y ) ;
        else if ( _usersTurn )
            g.drawString( "Click on column to drop your token...", 0, y ) ;
        else  
            g.drawString( "Click anywhere to see computer's move...", 0, y ) ;
    }
    
    
    
    void drawToken( Graphics g, int x, int y, int iColor )
    // Draw a colored token (or empty circle) at (x,y) position on board.
    {
        switch ( iColor )
        {
            case IConnect4.EMPTY: g.setColor( Color.GRAY ) ; break ;
            case IConnect4.RED:   g.setColor( Color.RED ) ;  break ;
            case IConnect4.BLUE:  g.setColor( Color.BLUE ) ; break ;
        }
        g.fillOval( x , y ,  _tokenDiameter, _tokenDiameter ) ;
    }
    
    
    // Mouse listener methods
    public void mouseClicked( MouseEvent e ) {} 
    public void mouseEntered( MouseEvent e ) {} 
    public void mouseExited( MouseEvent e ) {} 
    public void mousePressed( MouseEvent e ) {} 
    public void mouseReleased( MouseEvent e ) 
    // Used to make a move.
    {
        if ( _gameOver )
            return ; // ignore clicks if game is over.
            
        if ( _usersTurn )
        {
            // This click is users move: a selection of a column to drop a token.
            int x= e.getX() ;  // x-coordinate of mouse click
            int iCol= x / _tokenDiameter ;
            boolean ok=  _gameboard.drop( USERCOLOR, iCol ) ;
            if ( ok )
                _usersTurn= false ;
        }
        else
        {
            // This click is the user's invitation for the computer to move.
            int iCol= _thinker.makeMove( _gameboard.copy(), COMPUTERCOLOR ) ;
            _gameboard.drop( COMPUTERCOLOR, iCol ) ;
            _usersTurn= true ;
        }
        
        // Did this last move mean the game was over?
        _gameOver= _gameboard.wins( COMPUTERCOLOR ) || _gameboard.wins( USERCOLOR ) 
                  || _gameboard.boardFull() ;
        
        repaint() ;
    }
}

////////////////////////////////////////////////////////////////////////


class Connect4Frame extends JFrame
{
    static final int TOKENDIAMETER=  50 ; // pixels 

    
    Connect4Frame( IConnect4 gameboard, IConnect4Thinker thinker )
    // Construct a frame containing a Connect4 panel using the given gameboard and thinker.
    {
        super( "Connect4!" ) ;  // title bar
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ) ;
        
        // Base frame size on dimensions of game board.
        int width=  gameboard.getNumColumns()*TOKENDIAMETER +  10;
        int height= gameboard.getNumRows()*TOKENDIAMETER    + 100 ;
        setSize( width, height ) ;

        // Create a panel to draw the tokens on and add it to this frame.        
        JPanel _panel= new Connect4Panel( gameboard, thinker, TOKENDIAMETER ) ;
        getContentPane().add( _panel ) ;
    }
}

////////////////////////////////////////////////////////////////////////


class KevinsConnect4 implements IConnect4
{
    private final int  _nRows, _nCols ; // dimensions never change once object is constructed
    
    private int[][]    _grid ;          // columns are numbered 0,1,2.. up from bottom
    private int        _tokenCount ;    // how many tokens have been dropped so far?
   
   
    public KevinsConnect4( int nRows, int nCols ) 
    // Construct a Connect4 board, nRows-by-nCols.
    {
        assert nRows > 4 ;
        assert nCols > 4 ;
        
        _grid= new int[nRows][nCols] ;
        _nRows= nRows ;
        _nCols= nCols ;
        
        for ( int i=0 ; i < _nRows ; ++i )
            for ( int j=0 ; j < _nCols ; ++j )
                _grid[i][j]= EMPTY ;
                
        _tokenCount= 0 ;
     }
     
     public KevinsConnect4()
     // Construct a Connect4 board of default size.
     {
        this( 6, 7 ) ;
     }
     
     
     public IConnect4 copy()
     // Return a copy of this object.
     {
        // Construct new object.
        KevinsConnect4 c4= new KevinsConnect4( _nRows, _nCols ) ;
        
        // Copy fields.
        c4._tokenCount= _tokenCount ;
        for ( int i=0 ; i < _nRows ; ++i )
            for ( int j=0 ; j < _nCols ; ++j )
                c4._grid[i][j]= _grid[i][j] ;
                
        return c4 ;
     }
    
    
     public int getNumRows()     { return _nRows ; }
    
     public int getNumColumns()  { return _nCols ; }
    
     public int getSpot( int iRow, int iCol )  { return _grid[iRow][iCol] ; } 
    
    
     public boolean wins( int iColor ) 
     // Has the player iColor won the game (four tokens in a row)? 
     {
        for ( int iRow=0 ; iRow < _nRows ; ++iRow )
        {
        	// Check if there is a run starting in iRow.
        	for ( int iCol=0 ; iCol < _nCols ; ++iCol )
        	{
                // Upward diagonal run?
        		if ( iCol < _nCols-3  &&  iRow < _nRows-3 )
        		{
        		   if ( _findRunFrom( iColor, iCol, iRow, 1, 1 ) )
        		       return true ;
        	    }
        	    // Horizontal run?
        	    if ( iCol < _nCols-3 )
        	    {
                   if ( _findRunFrom( iColor, iCol, iRow, 1, 0 ) )
                       return true ;
                }
                // Vertical run?
                if ( iRow < _nRows-3 )
                {
                   if ( _findRunFrom( iColor, iCol, iRow, 0, 1 ) )
                       return true ;
                }
                // Downward diagonal run?
                if ( iCol < _nCols-3  &&  iRow >= 3 )
                {
                   if ( _findRunFrom( iColor, iCol, iRow, 1, -1 ) )
                       return true ;
                }
            }
        }
        return false ;
    }
    
    
    private boolean _findRunFrom( int iColor, int iCol, int iRow, int colStep, int rowStep )
    // There is a 4-in-a-row starting from (iRow,iCol), moving in given step. 
    {
    	for ( int i=0 ; i < 4 ; i++ )
  	        if ( _grid[iRow+i*rowStep][iCol+i*colStep] != iColor )
   	            return false ;
    	            
    	// Found 4-in-a-row! 
    	return true ;
    }
	       
           
    public boolean boardFull()
    // Are all the slots on the board are occupied by tokens?
    {
        return _tokenCount == _nRows * _nCols ;
    }

    
    public boolean drop( int iColor, int iCol )  
    // Drop a token of given color in given column. Returns true on success.
    {
        // Rule out full and out-of-range cases.
        if ( boardFull() ||  iCol < 0  || iCol >= _nCols )
           return false ; 
        
        // Move upward in this column until empty spot is found.
        for ( int iRow=0 ; iRow < _nRows ; ++iRow )
        {
            if ( _grid[iRow][iCol] == EMPTY )
            {
                _grid[iRow][iCol]= iColor ;
                _tokenCount++ ;
                return true ; // successfully dropped token
            }
        }
        // column was full
        return false ; 
    }
    
    
    public void dump()
    // Print out a crude representation of the game board on the console.
    {
        for ( int iRow= _nRows-1 ; iRow >=0 ; --iRow )
        {
            System.out.print( " |") ;
            for ( int iCol= 0 ; iCol < _nCols ; ++iCol ) 
            {
                char ch ; // character to indicate presence of checker on grid
                switch( _grid[iRow][iCol] )
                {
                    case EMPTY : ch= '.' ; break ;
                    case RED   : ch= 'r' ; break ;
                    case BLUE  : ch= 'B' ; break ;
                    default    : ch= '?' ; break ;
                } 
                System.out.print( " " + ch ) ;
            }
            System.out.println( "|" ) ; 
        }
        
        // Print out some handy numbers
        System.out.print( "  " ) ;
        for ( int iCol= 0 ; iCol < _nCols ; ++iCol ) 
            System.out.print( " " + iCol ) ;
        System.out.println( "\n") ;
    }
        
}


