import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.applet.*;

class Connect4Kernel
{
   public final static int EMPTY         =  0;
   public final static int COMP          =  1;
   public final static int HUMAN         = -1;
   public final static int BORDER        = 99;

   public final static int GAME_NOT_OVER =  0;
   public final static int ONE_HAS_WON   =  1;
   public final static int BOARD_FULL    = -1;

   public final static int UNDECIDED     =  0;
   public final static int PLAYER_WINS   =  1;
   public final static int PLAYER_LOOSES = -1;

   private int             board[]       = new int[156];
   private int             field[]       = new int[13];

   private int             maxDepth;
   private Connect4        c4;

   private int             cnt;
   public  int             Cnt() { return cnt; }
   
   // ------------------------------------------------------------------------
   private boolean _IsEmpty(int column)
   {
      if(field[column]<114) return true; 
      else                  return false;
   }

   // ------------------------------------------------------------------------
   private int _DoMove(int player, int column)
   {
      int f          = field[column];
      board[f]       = player;
      field[column] += 13;
      return f;
   }

   // ------------------------------------------------------------------------
   private int _BestMove(int player, int move[], int depth)
   {
      int         NO_MORE_SEARCH = 10; // final!

      int         res            = PLAYER_LOOSES;
      int         rivals_move[]  = new int[1];
      int         i;
      int         curr_field,
                  curr_column;
      int         value[]        = new int[1];
      int         max_value      = -1;

      for(move[0]=-1, depth--, i=3, value[0]=0 ; i<NO_MORE_SEARCH ; i++, cnt++)
      {
         if(_IsEmpty(i))
         {
            if(move[0] == -1) move[0] = i;

            curr_field = _DoMove(player, curr_column = i);

            switch(GameOver(curr_field, value))
            {
               case ONE_HAS_WON:

                  move[0] = i;
                  i       = NO_MORE_SEARCH;
                  res     = PLAYER_WINS;
                  break;

               case BOARD_FULL:

                  if(res == PLAYER_LOOSES)
                  {
                     move[0] = i;
                     res     = UNDECIDED;
                     i       = NO_MORE_SEARCH; // Da das Brett jetzt voll ist, war dies eh das letzte Feld
                  }
                  break;

               case GAME_NOT_OVER:
               default:

                  if(depth!=0)
                  { 
                     switch(_BestMove(player == HUMAN ? COMP : HUMAN, rivals_move, depth))
                     {
                        case PLAYER_LOOSES:
   
                           if(res!=PLAYER_WINS || value[0]>max_value)
                           {
                              move[0]   = i;
                              res       = PLAYER_WINS;
                              max_value = value[0];
                           }
                           break;
                  
                        case UNDECIDED:
  
                           if(res==PLAYER_LOOSES || (res==UNDECIDED && value[0]>max_value))
                           {
                              move[0]   = i;
                              res       = UNDECIDED;
                              max_value = value[0];
                           }
                           break;

                        case PLAYER_WINS:

                           // not a good move
                           if(res==PLAYER_LOOSES && value[0]>max_value)
                           {
                              move[0]   = i;
                              max_value = value[0];
                           }
                           break;
                     }
                  }
                  else
                  {
                     if(res==PLAYER_LOOSES && value[0]>max_value)
                     {
                        move[0]   = i;
                        res       = UNDECIDED;
                        max_value = value[0];
                     }                
                  }

                  break;
            }

            board[curr_field]  = EMPTY;
            field[curr_column]   -= 13;
         }

         if(depth==maxDepth)
         {
            StringBuffer str = new StringBuffer(30);
            str.append("Analyzing");
            for(int j=3 ; j<i ; j++)
               str.append(".");

            c4.DrawStatus(str.toString());
         }
      }

      return res;
   }

   // ------------------------------------------------------------------------
   private int _Field(int l, int r) { return l*13+r; }

   // ------------------------------------------------------------------------
   private int _Column(int f) { return f%13; }

   // ------------------------------------------------------------------------
   private int _Line(int f) { return f/13; }

   // ------------------------------------------------------------------------
   public Connect4Kernel(Connect4 _c4)
   {
      int i;

      c4 = _c4;

      for(i=0 ; i<156 ; i++)
         if(_Column(i)>2 && _Column(i)<10 && _Line(i)>2 && _Line(i)<9)
            board[i] = EMPTY;
         else
            board[i] = BORDER;

      for(i=0 ; i<13 ; i++)
         field[i] = 3*13+i;
   }

   // -----------------------------------------
   public boolean IsEmpty(int column)
   {
      if(column>=0 && column<7) return _IsEmpty(column+3);
      else                      return false;
   }

   // -----------------------------------------
   public int GameOver(int f, int value[])
   {
      int comp  = board[f];
      int rival;
      int i;
      int u, d, l, r, ul, ur, dl, dr;
      int cnt0,  cnt1,  cnt2,  cnt3;
      int cnt0b, cnt1b, cnt2b, cnt3b;
      int f0a, f0b, f1a, f1b, f2a, f2b, f3a, f3b;
      int f0c, f0d, f1c, f1d, f2c, f2d, f3c, f3d;
      int tmp;

      cnt0  = cnt1  = cnt2  = cnt3  = 1;
      cnt0b = cnt1b = cnt2b = cnt3b = 0;
      u = d = l = r = ul = ur = dl = dr = f;
      f0a = f0b = f0c = f0d = f1a = f1b = f1c = f1d = f2a = f2b = f2c = f2d = f3a = f3b = f3c = f3d = 1;

      if(comp!=EMPTY && comp!=BORDER)
      {
         rival = board[f] == HUMAN ? COMP : HUMAN;;

         for(i=1 ; i<4 ; i++)
         {
            // horizontal
            if(f0c!=0)
            {
               if((tmp = board[++r]) != BORDER)            
                  if(f0a!=0)
                     if(tmp==comp) cnt0++;
                     else
                     {
                        f0a = 0;
                        if(tmp==EMPTY) cnt0b++;
                        else           f0c = 0;
                     }
                  else
                     if(tmp!=rival) cnt0b++;
                     else           f0c = 0;
               else
                  f0c = 0;
            }
            if(f0d!=0)
            {
               if((tmp = board[--l]) != BORDER)            
                  if(f0b!=0)
                     if(tmp==comp) cnt0++;
                     else
                     {
                     f0b = 0;
                        if(tmp==EMPTY) cnt0b++;
                        else           f0d = 0;
                     }
                  else
                     if(tmp!=rival) cnt0b++;
                     else           f0d = 0;
               else
                  f0d = 0;
            }

            // links unten -> rechts oben
            if(f1c!=0)
            {
               if((tmp = board[ur+=14]) != BORDER)            
                  if(f1a!=0)
                     if(tmp==comp) cnt1++;
                     else
                     {
                        f1a = 0;
                        if(tmp==EMPTY) cnt1b++;
                        else           f1c = 0;
                     }
                  else
                     if(tmp!=rival) cnt1b++;
                     else           f1c = 0;
               else
                  f1c = 0;
            }
            if(f1d!=0)
            {
               if((tmp = board[dl-=14]) != BORDER)            
                  if(f1b!=0)
                     if(tmp==comp) cnt1++;
                     else
                     {
                        f1b = 0;
                        if(tmp==EMPTY) cnt1b++;
                        else           f1d = 0;
                     }
                  else
                     if(tmp!=rival) cnt1b++;
                     else           f1d = 0;
               else
                  f1d = 0;
            }

            // links oben -> rechts unten
            if(f2c!=0)
            {
               if((tmp = board[ul+=12]) != BORDER)            
                  if(f2a!=0)
                     if(tmp==comp) cnt2++;
                     else
                     {
                        f2a = 0;
                        if(tmp==EMPTY) cnt2b++;
                        else           f2c = 0;
                     }
                  else
                     if(tmp!=rival) cnt2b++;
                     else           f2c = 0;
               else
                  f2c = 0;
            }
            if(f2d!=0)
            {
               if((tmp = board[dr-=12]) != BORDER)            
                  if(f2b!=0)
                     if(tmp==comp) cnt2++;
                     else
                     {
                        f2b = 0;
                        if(tmp==EMPTY) cnt2b++;
                        else           f2d = 0;
                     }
                  else
                     if(tmp!=rival) cnt2b++;
                     else           f2d = 0;
               else
                  f2d = 0;
            }

            // vertikal
            if(f3c!=0)
            {
               if((tmp = board[u+=13]) != BORDER)            
                  if(f3a!=0)
                     if(tmp==comp) cnt3++;
                     else
                     {
                        f3a = 0;
                        if(tmp==EMPTY) cnt3b++;
                        else           f3c = 0;
                     }
                  else
                     if(tmp!=rival) cnt3b++;
                     else           f3c = 0;
               else
                  f3c = 0;
            }
            if(f3d!=0)
            {
               if((tmp = board[d-=13]) != BORDER)            
                  if(f3b!=0)
                     if(tmp==comp) cnt3++;
                     else
                     {
                        f3b = 0;
                        if(tmp==EMPTY) cnt3b++;
                        else           f3d = 0;
                     }
                  else
                     if(tmp!=rival) cnt3b++;
                     else           f3d = 0;
               else
                  f3d = 0;
            }
         }

         // Wertigkeit des Zuges bestimmen
         value[0] = 0;
         if((cnt0b + cnt0)>3)
            value[0]  = (cnt0-1)*6 + cnt0b;
         if((cnt1b + cnt1)>3)
            value[0] += (cnt1-1)*6 + cnt1b;
         if((cnt2b + cnt2)>3)
            value[0] += (cnt2-1)*6 + cnt2b;
         if((cnt3b + cnt3)>3)
            value[0] += (cnt3-1)*6 + cnt3b;

         if(cnt0>3 || cnt1>3 ||cnt2>3 ||cnt3>3)
            return ONE_HAS_WON;

         for(i=3 ; i<10 ; i++)
            if(field[i]<156)
               return GAME_NOT_OVER;

         return BOARD_FULL;
      }

      return GAME_NOT_OVER;
   }

   // ------------------------------------------------------------------------
   public int DoMove(int player, int column)
   {
      int f = -1;
      if(IsEmpty(column))
         f = _DoMove(player, column+3);
      return f;
   }

   // ------------------------------------------------------------------------
   public int BestMove(int player, int move[], int depth)
   {
      maxDepth = depth-1;
      cnt      = 0;

      int res = _BestMove(player, move, depth);
      move[0] -= 3;

      return res;
   }
 
   // ------------------------------------------------------------------------
   public int Field(int l, int r) 
   {
      return (l+3)*13+r+3;
   }

   // ------------------------------------------------------------------------
   public int Column(int f)  { return _Column(f) -3; }

   // ------------------------------------------------------------------------
   public int Line(int f)  { return _Line(f)-3; }

   // ------------------------------------------------------------------------
   public char F(int l, int r)
   {
      switch(board[Field(l,r)])
      {
         case EMPTY:  return ' ';
         case COMP:   return 'X';
         default:     return '0';
      }
   }

   // ------------------------------------------------------------------------
   public int FieldState(int l, int r)
   {
      return board[Field(l,r)];
   }
};


// ===========================================================================

public class Connect4 extends Applet
{
   // ------------------------------------------------------------------------
   public final static int WAITING_FOR_MOVE  = 0;
   public final static int ANALYZING         = 1;
   public final static int RESTART           = 2;

   public final static int BORDER_WIDTH      = 5;

   // ------------------------------------------------------------------------
   Connect4Kernel c4k;

   int            currField;
   int            level;
   int            bestMove[]           = new int[1];
   int            res                  = Connect4Kernel.UNDECIDED;
   int            value[]              = new int[1];
   int            changeCnt            = 0;
   boolean        levelEverDecreased   = false;
   int            lastCompArrowColumn  = -1;
   int            lastHumanArrowColumn = -1;
   boolean        lastHumanArrowEmpty  = false;

   int            currFontSize         = 0;
   Font           font;
   StringBuffer   currStatus;

   Graphics       g                    = null;
   Panel          panel                = null;
   int            mode                 = -1;

   // ------------------------------------------------------------------------
   public void init()
   {
      c4k                  = new Connect4Kernel(this);

      bestMove[0]          = c4k.Field(0,0);
      value[0]             = 0;

      level                = 4;
      levelEverDecreased   = false;
      changeCnt            = 0;

      lastHumanArrowColumn = -1;
      lastCompArrowColumn  = -1;
      lastHumanArrowEmpty  = true;

      currStatus           = new StringBuffer("Start your game!");

      if(g==null)
         g = getGraphics();

      SetMode(WAITING_FOR_MOVE);
   }

   // ------------------------------------------------------------------------
   public void SetMode(int m) { mode = m; }

   // ------------------------------------------------------------------------
   public boolean IsMode(int m) { return mode==m; }

   // ------------------------------------------------------------------------
   public void DrawStatus(String str)
   {
      Dimension d  = size();
      int       dx = (d.width - 2*BORDER_WIDTH) / 7;
      int       dy = d.height / 8;
      int       i;
      Font      f;

      // clear status display area
      g.setColor(Color.white);
      g.fillRect(BORDER_WIDTH, 7*dy+1, 7*dx, dy-BORDER_WIDTH-1);

      if(currFontSize!=dy)
      {
         currFontSize = dy;
         font = new Font("Dialog", 0, (dy*5)/10);
      }

      g.setFont(font);

      currStatus = new StringBuffer(str);

      g.setColor(Color.black);
      g.drawString(currStatus.toString(), BORDER_WIDTH + 10, 7*dy+(dy*5)/8);

      for(i=4 ; i<9 && i<=level ; i++)
      {
         g.setColor(Color.green);
         g.fillRect(7*dx-BORDER_WIDTH-20, 8*dy-BORDER_WIDTH-(i-3)*4-1, 22, 3);         
      }
   }

   // ------------------------------------------------------------------------
   public void DrawChip(int c, int r)
   {
      Color color;

      Dimension d = size();
      int dx = (d.width - 2*BORDER_WIDTH) / 7;
      int dy = d.height / 8;
      int xk = BORDER_WIDTH + dx*c + dx/8;
      int yk = d.height - (r+2)*dy + dy/8;
      int xs = (dx*3)/4;
      int ys = (dy*3)/4;

      switch(c4k.FieldState(r, c))
      {
         case Connect4Kernel.COMP:
            color = Color.green;
            break;
         case Connect4Kernel.HUMAN:
            color = Color.red;
            break;
         case Connect4Kernel.EMPTY:
         default:
            color = Color.white;
            break;
      }

      g.setColor(color);
      g.fillOval(xk, yk, xs, ys);
//      g.setColor(Color.black);
//      g.drawOval(xk, yk, xs, ys);
   }

   // ------------------------------------------------------------------------
   public void paint(Graphics g)
   {
      Dimension d  = size();
      int       dx = (d.width - 2*BORDER_WIDTH) / 7;
      int       dy = d.height / 8;
      int       r, c;

      // clear entire display area
      g.setColor(Color.white);
      g.fillRect(0, 0, 7*dx+2*BORDER_WIDTH-1, dy*8-1);

      g.setColor(Color.blue.darker().darker());
      g.fillRect(BORDER_WIDTH, dy, dx*7, dy*6);

      for(r = 0 ; r < 6 ; r++)
         for(c = 0 ; c < 7 ; c++)
            DrawChip(c, r);

      DrawStatus(currStatus.toString());

      g.setColor(Color.gray);
      for(r=0 ; r<BORDER_WIDTH ; r++)
      {
         g.drawLine(r, r, r, dy*8-r-1);
         g.drawLine(r+1, r, dx*7+2*BORDER_WIDTH-1, r);
      }
      g.setColor(Color.darkGray);
      for(r=0 ; r<BORDER_WIDTH ; r++)
      {
         g.drawLine(dx*7-r+2*BORDER_WIDTH-1, r+1, dx*7-r+2*BORDER_WIDTH-1, dy*8-r-1);
         g.drawLine(r, dy*8-r-1, dx*7+2*BORDER_WIDTH-1, dy*8-r-1);
      }
   }

   // ------------------------------------------------------------------------
   public int DoMove(int player, int column)
   {
      int curr_field = c4k.DoMove(player, column);

      DrawChip(column, c4k.Line(curr_field));

      if(player==Connect4Kernel.HUMAN)
         lastCompArrowColumn  = -1;
      else
         lastHumanArrowColumn = -1;

      DrawArrows(column, player);

      return curr_field;
   }

   // ------------------------------------------------------------------------
   private void AdaptLevel()
   {
      if(c4k.Cnt()<6000)
      {
         if(++changeCnt == 2)
         {
            level++;
            changeCnt          = 0;
         }
      }
      else if(c4k.Cnt()>40000)
      {
         level--;
         changeCnt = 0;
         levelEverDecreased = true;
      }
      else
         changeCnt = 0;
   }

   // ------------------------------------------------------------------------
   public boolean mouseUp(Event evt, int x, int y)
   {
      int r;

      if(IsMode(RESTART))
      {
         init();
         repaint();
         return true;
      }

      SetMode(ANALYZING);

      game: if(c4k.GameOver(bestMove[0], value)==Connect4Kernel.GAME_NOT_OVER)
      {
         // get column by position of mouse click
         Dimension d = size();
         r = ((x-BORDER_WIDTH) * 7) / d.width;
         if(r<0 || r>6)
            break game;

         if(c4k.IsEmpty(r))
         {
            currField = DoMove(Connect4Kernel.HUMAN, r);

            switch(res = c4k.GameOver(currField, value))
            {
               case Connect4Kernel.BOARD_FULL:

                  DrawStatus("It ends in a tie! (click to restart)");
                  SetMode(RESTART);
                  break;

               case Connect4Kernel.ONE_HAS_WON:

                  DrawStatus("You won! (click to restart)");
                  SetMode(RESTART);
                  break game;

               case Connect4Kernel.GAME_NOT_OVER:
            
                  res = c4k.BestMove(Connect4Kernel.COMP, bestMove, level);
                  currField = DoMove(Connect4Kernel.COMP, bestMove[0]);

                  AdaptLevel();

                  switch(res)
                  {
                     case Connect4Kernel.PLAYER_LOOSES:

                        // DrawStatus("I could loose!");
                        DrawStatus("Your move!");
                        break;
                  
                     case Connect4Kernel.PLAYER_WINS:

                        if(c4k.GameOver(currField, value)!=Connect4Kernel.GAME_NOT_OVER)
                        {
                           DrawStatus("I won! (click to restart)");
                           SetMode(RESTART);
                           break game;
                        }
                        else
                        {
                          if(levelEverDecreased)
                             DrawStatus("Your move!");
                          else
                             DrawStatus("I'm going to win!");
                        }
                        break;

                     case Connect4Kernel.UNDECIDED:
                     default:
                        DrawStatus("Your move!");
                        break;
                  }

                  break;
            }
         }
//         else
//            DrawStatus("Column occupied");
      }

      if(IsMode(ANALYZING))
         SetMode(WAITING_FOR_MOVE);
      return true;
   }

   // ------------------------------------------------------------------------
   void DrawArrow(Graphics g, int dx, int dy, int c, Color color, boolean fill)
   {
      int       xa[] = new int[8];
      int       ya[] = new int[8];

      g.setColor(color);

      xa[0] = BORDER_WIDTH + c*dx+dx/2;      ya[0] = (dy*3)/4;
      xa[1] =                xa[0] + dx/5;   ya[1] = ya[0] - dy/4;
      xa[2] =                xa[0] + dx/10;  ya[2] = ya[1];
      xa[3] =                xa[2];          ya[3] = ya[0] - dy/2;
      xa[4] =                xa[0] - dx/10;  ya[4] = ya[3];
      xa[5] =                xa[4];          ya[5] = ya[1];
      xa[6] =                xa[0] - dx/5;   ya[6] = ya[1];
      xa[7] =                xa[0];          ya[7] = ya[0];

      if(fill)
         g.fillPolygon(xa, ya, 8);
      else
         g.drawPolygon(xa, ya, 8);
   }

   // ------------------------------------------------------------------------
   void DrawArrows(int column, int player)
   { 
      if(column>=0 && column<7)
      {
         boolean empty = c4k.IsEmpty(column);

         Color     color;
         Dimension d      = size(); 
         int       dx     = (d.width - 2*BORDER_WIDTH) / 7;
         int       dy     = d.height / 8;

         // clear arrow display area
         g.setColor(Color.white);
         g.fillRect(BORDER_WIDTH, BORDER_WIDTH, 7*dx, dy-BORDER_WIDTH);

         if(player==Connect4Kernel.HUMAN)
         {
            lastHumanArrowColumn = column;
            lastHumanArrowEmpty  = empty;
         }
         else if(player==Connect4Kernel.COMP)
            lastCompArrowColumn = column;
         else
         {
            lastHumanArrowColumn = -1;
            lastCompArrowColumn  = -1;
         }

         if(lastHumanArrowColumn!=-1)
         {
            if(lastHumanArrowEmpty)
               color = Color.red;
            else
               color = Color.gray;

            DrawArrow(g, dx, dy, lastHumanArrowColumn, color, true);
         }

         if(lastCompArrowColumn!=-1)
         {
            color = Color.green;
            DrawArrow(g, dx, dy, lastCompArrowColumn, color, lastCompArrowColumn!=lastHumanArrowColumn);
         }
      }
   }

   // ------------------------------------------------------------------------
   public boolean mouseMove(Event evt, int x, int y)
   {
      if(IsMode(WAITING_FOR_MOVE))
      {
         Dimension d = size();
         int       c;

         // examine column
         c = ((x-BORDER_WIDTH) * 7) / d.width;

         if(c!=lastHumanArrowColumn)
            DrawArrows(c, Connect4Kernel.HUMAN);
      }

      return true;
   }

   // ------------------------------------------------------------------------
   public String getAppletInfo()
   {
      return "Connect4 - Java (beta) Applet by Sven Wiebus, Dec. 1995";
   }
}

