//    4 in a row
//    Copyright (C) 1996  August Hoerandl (hoerandl@elina.htlw1.ac.at)
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.applet.*;
import java.lang.*;

class constant {
    final static int XMAX = 7; // feldgroesse
    final static int YMAX = 6;
    final static int SIZE = XMAX*YMAX + 1;  // anzahl der felder + 1

    final static int black = 0;
    final static int white = 1;
    final static int empty = 2;
    
    final static int MINBEW = -30000;
    final static int MAXBEW =  30000;

    final static int BEW4   = 7000;

    static table thetable;

    constant()
    { 
	thetable = new table();
    }
}


class zug {
    private int  num;
    private int f;

    zug() {};
    zug(int x, int y, int who)
    {
	num = y*constant.XMAX+x;
        f = who;
    }
    int getcol() { return f; }
    int getnum() { return num; }
}


class zuggen {
    field f;
    int c;
    int slot;
    
    zuggen(field b, int col)
    {
        f = b;
        c =  col;
	slot = -1;
	next();
    }

    zug get()
    {
        return new zug(slot, f.getheight(slot), c);
    }

    boolean avail()
    {
        return (slot < constant.XMAX);
    }

    void next()
    {
	for(;;) {
            slot++;
            if (! avail())
                return;
            if (f.getheight(slot) != constant.YMAX)
                return;
	}
    }
}



class field {
    private int bew;
    private int count;
    private int  data[];
    private int  height[];

    field()
    {
	data = new int[constant.SIZE]; 
	height = new int[constant.XMAX];

	for(int i=0; i < constant.SIZE; i++)
		data[i] = constant.empty;
	for(int i=0; i < constant.XMAX; i++)
		height[i] = 0;
	bew = 0;
	count = 0;
    }

    field(field f)
    {
	data = new int[constant.SIZE]; 
	height = new int[constant.XMAX];

	for(int i=0; i < constant.SIZE; i++)
                data[i] = f.data[i];
	for(int i=0; i < constant.XMAX; i++)
                height[i] = f.height[i];
        bew = f.bew;
        count = f.count;
    }


    int get(zug z)
    {
	return data[z.getnum()];
    }

    int get(int n)
    {
	return data[n];
    }

    int getcount()
    {
        return count;
    }

    int getheight(int x)
    {
	return height[x];
    }
    int getbew()
    {
        return bew;
    };

    void exec(zug z)
    {
	count ++;
	data[z.getnum()] = z.getcol();
	height[z.getnum() % constant.XMAX] ++;
	// add up points
	if (z.getcol() == constant.white)
	    bew += constant.thetable.bewerte(z, this);
	else
	    bew -= constant.thetable.bewerte(z, this);
    }

    boolean gameover()
    {
	return (count >= constant.SIZE-1 ||
		bew > constant.BEW4/2 ||
		bew < -constant.BEW4/2);
    }
}


class tableentry {
    static int dummy = constant.SIZE-1;
    int r[]; // rechts 4 nachbarn + ende-entry
    int ru[];
    int u[];
    int lu[];
    int l[];
    int ld[];
    int d[];
    int rd[];

    tableentry(int pos)
    {
	r  = new int[5]; // rechts 4 nachbarn + ende-entry
	ru = new int[5];
	u  = new int[5];
	lu = new int[5];
	l  = new int[5];
	ld = new int[5];
	d  = new int[5];
	rd = new int[5];

        init(pos);
    }

    int calc(int xpos, int ypos)
    {
	if (xpos >= 0 && xpos < constant.XMAX &&
            ypos >= 0 && ypos < constant.YMAX) {
	        zug tmp = new zug(xpos,ypos, constant.empty);
		return tmp.getnum();
	} else
		return dummy;
    }

    void init(int pos)
    {
	int xpos = pos % constant.XMAX;
	int ypos = pos / constant.XMAX;
        zug tmp = new zug(xpos,ypos, constant.empty);
	//if (tmp.getnum() != pos)
	//	exit(2);
	for(int i=0; i < 5; i++)
		r[i]=ru[i]=u[i]=lu[i]=l[i]=ld[i]=d[i]=rd[i]=dummy;
	for(int i=1; i < 5; i++) {
		r[i-1] = calc(xpos+i, ypos   );
		ru[i-1]= calc(xpos+i, ypos+i );
		u[i-1] = calc(xpos  , ypos+i );
		lu[i-1]= calc(xpos-i, ypos+i );
		l[i-1] = calc(xpos-i, ypos   );
		ld[i-1]= calc(xpos-i, ypos-i );
		d[i-1] = calc(xpos  , ypos-i );
		rd[i-1]= calc(xpos+i, ypos-i );
	}	r[4]=ru[4]=u[4]=lu[4]=l[4]=ld[4]=d[4]=rd[4]=dummy;
    }

   int bew1(int n1, int n2, int nextpos1, int nextpos2, field f)
   {
	// n1        anzahl in richtung 1
	// n2        anzahl gegenrichtung
	// nextpos1  naechste pos in richtung 1
	// nextpos2  naechste pos in gegenrichtung

	// punkte:  anzahl nachbarn
	//          eine seite frei
	//          andere seite frei
	int points[][][] = {
		{ {  0, 1 },    // 0 nachbarn, eine seite bes     bb, bf
		  {  1, 3 } }, // 0 nachbarn, eine seite frei    fb, ff

		{ {  5, 10 },   // 1 nachbar
		  { 10, 25 } },

		{ {  10, 80 },   // 2 nachbarn
		  {  80, 500 } }
	};

	int sum = n1+n2;
	if (sum >= 3)
		return constant.BEW4;

	return points[sum]
		     [(nextpos1 != dummy && f.get(nextpos1) == constant.empty) ? 1 : 0]
		     [(nextpos2 != dummy && f.get(nextpos2) == constant.empty) ? 1 : 0];
    }

    int bewerte(int col, field f)
    {
	int nr, nru, nu, nlu, nl, nld, nd, nrd; // anzahl col in eine richtung

	for(nr=0; f.get(r[nr]) == col; nr++)
		;
	for(nru=0; f.get(ru[nru]) == col; nru++)
		;
	for(nu=0; f.get(u[nu]) == col; nu++)
		;
	for(nlu=0; f.get(lu[nlu]) == col; nlu++)
		;
	for(nl=0; f.get(l[nl]) == col; nl++)
		;
	for(nld=0; f.get(ld[nld]) == col; nld++)
		;
	for(nd=0; f.get(d[nd]) == col; nd++)
		;
	for(nrd=0; f.get(rd[nrd]) == col; nrd++)
		;

	return  bew1(nr , nl , r[nr],   l[nl],   f) +
		bew1(nu,  nd,  u[nu],   d[nd],   f) +
		bew1(nru, nld, ru[nru], ld[nld], f) +
		bew1(nrd, nlu, rd[nrd], lu[nlu], f);
    }
}

class table {
   tableentry  tab[];

   table()
   {
	tab = new tableentry[constant.SIZE];
	for(int i=0; i < constant.SIZE; i++)
            tab[i] = new tableentry(i);
   }

   int bewerte(zug last, field f)
   {
	return tab[last.getnum()].bewerte(last.getcol(), f);
   }
}

class zug_val {
    private zug _z;
    private int _val;

    zug_val(zug z, int val)
    {
        _z = z;
        _val = val;
    }

    zug getzug()
    { return _z; }

    int getval()
    { return _val; }
}

public
class fourrow extends Applet {
 
   final int XMAX = constant.XMAX;  
   final int YMAX = constant.YMAX;  
   
   private field brett;
   private constant c;
   private AudioClip audio;
   private boolean domove;
   private zug lastmove;

   /**
     * Initialize the applet. Resize and load images.
     */
    public void init() {
        audio = getAudioClip(getCodeBase(), "oops.au");
    }

    public fourrow()
    {
        c = new constant();
   	brett = new field();
        domove = false;
        lastmove = new zug(XMAX, YMAX, constant.empty);
    }

    /**
     * The user has clicked in the applet. Figure out where
     * and see if a legal move is possible. If it is a legal
     * move, respond with a legal move (if possible).
     */
    public boolean mouseDown(Event evt, int x, int y) {
        if (brett.gameover()) {
            audio.play();
            brett = new field();
            repaint();
        } else {
            if (! (evt.shiftDown() && brett.getcount()==0)) {
                Dimension d = size();
                int xoff = d.width / XMAX;
                int spalte = x / xoff;
                int zeile = brett.getheight(spalte);
                if (zeile < constant.YMAX) {
                    lastmove = new zug(spalte, zeile, constant.white);
                    brett.exec(lastmove);
                    if (brett.gameover())
                        audio.play();
                } else
                    audio.play();
            }
            repaint();
            domove = true;
        }
        return true;
    }

    void move() {
        showStatus("Thinking ...");
        if (brett.gameover()) {
            audio.play();
            brett = new field();
            repaint();
        } else {
            //setCursor(Frame.WAIT_CURSOR);

            zug_val zv = teste(brett,
                               constant.black,
                               3 + brett.getcount()/8,
                               constant.MINBEW);
            lastmove = zv.getzug();
            brett.exec(lastmove);
            if (brett.gameover())
                 audio.play();

            //setCursor(Frame.DEFAULT_CURSOR);
            repaint();
        }
        if (! brett.gameover())
            showStatus("Enter Move ...");
        else
            showStatus("Game over - click to restart");
    }

    /**
     * Paint it.
     */
    public void paint(Graphics g) {
	Dimension d = size();
	int xoff = d.width / XMAX;
	int yoff = d.height / YMAX;
        int xm = xoff / 8;
	int ym = yoff / 8;

	g.setColor(Color.blue);
	//g.fillOval(0, 0, d.width, d.height);

        g.setColor(Color.black);
	for(int i = 1; i < XMAX; i++)
	   g.drawLine(i*xoff, 0, i*xoff, d.height);
	for(int i = 1; i < YMAX; i++)
	   g.drawLine(0, i*yoff, d.width, i*yoff);

	for(int i = 0; i < XMAX; i++) {
           for(int j=0; j < YMAX; j++) {
                g.setColor(Color.black);
		g.drawOval(i*xoff+xm, j*yoff+ym, xoff-2*xm, yoff-2*ym);
                zug tmp = new zug(i, YMAX-1-j, constant.empty);
                int m = tmp.getnum() == lastmove.getnum() ? 2 : 0;
		switch (brett.get(tmp)) {
		   case constant.white:
                    	g.setColor(Color.yellow);
                        g.fillOval(i*xoff+xm+m, j*yoff+ym+m, xoff-2*(xm+m), yoff-2*(ym+m));
			break;                 
		   case constant.black:
                    	g.setColor(Color.red);
                        g.fillOval(i*xoff+xm+m, j*yoff+ym+m, xoff-2*(xm+m), yoff-2*(ym+m));
			break;                 
		   default:
                    	//g.setColor(Color.white);
                        //g.fillOval(i*xoff+xm+m, j*yoff+ym+m, xoff-2*(xm+m), yoff-2*(ym+m));
			break;                 
	
		}
	    }
	}

        if (domove) {
            domove = false;
            //g.setCursor(Frame.WAIT_CURSOR);
            move();
        } else if (brett.getcount() == 0)
	    showStatus("Click on a column to play (shift-click for computer to move first)");
    }

    zug_val teste(field f, int who, int tiefe, int minmaxval)
    {
        boolean minmax;
        int other;
        int val;

        zug best = new zug(0,0, constant.empty);

        if (who == constant.black) {
                minmax = false; /* suche minimum */
                val = constant.MAXBEW;
                other = constant.white;
	} else {
                minmax = true;  /* suche maximum */
                val = constant.MINBEW;
                other = constant.black;
	}
	int t1 = tiefe-1;
        for(zuggen zg = new zuggen(f, who); zg.avail(); zg.next() ) {
                field neu = new field(f);
		neu.exec(zg.get());
                int bew = 0;

                if (tiefe <= 0) {
                    int tmp = (constant.XMAX+constant.SIZE-neu.getcount())/(constant.SIZE/10);
                    bew = neu.getbew() + (int)(Math.random()*tmp);
                } else if (neu.gameover()) {
		    if (who == constant.white)
			bew = constant.BEW4 - neu.getcount();
		    else
			bew = -constant.BEW4 + neu.getcount();
                } else {
                    zug_val b = teste(neu, other, t1, val);
                    bew =b.getval();
                }
		if ( (bew > val) == minmax) {
			val = bew;
			best = zg.get();
			if ( ( val > minmaxval) == minmax )
				break;
		}
	}
        return new zug_val(best, val);
    }
}


