import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.*;

public class Reversi extends Applet implements ActionListener, MouseListener, Runnable {
	//UI elementen
	private Button m_btnNewGame, m_btnHelp, m_btnAnim;
	//Beschrijven van de huidige status van het spel
	private int m_nBoardW,m_nBoardH,m_nPlayers;
	private int[][] m_pBoard;
	private int[] m_pScores;
	private int m_nTurn;
	//Bleh! Vector<Integer> ziet er zo veel mooier uit
	//Maar neeeeee.... dat mag niet voor Java 1.5...
	//C++ > Java -_-
	private Vector m_vWinners;
	private boolean m_bShowHelp;
	
	//Constanten
	static final int cStoneSize=24;
	static final int cLineWidth=1;
	static final int cStoneSpacing=1;
	static final int cScoreSpacing=4;
	static final Color cLineColor=Color.BLACK;
	static final Color cHintColor=Color.GRAY;
	static final int cHintSize=16;
	static final Color[] m_pPlayerColors={Color.BLUE,Color.RED,Color.GREEN,Color.ORANGE,Color.PINK,Color.MAGENTA,Color.BLACK,Color.GRAY,Color.DARK_GRAY};
	static final String[] m_pPlayerNames={"blauw","rood","groen","oranje","roze","paars","zwart","grijs","donker grijs"};
	
	//Wat te doen als je reversi bijna alles al heeft?
	//Juist, epileptisch reversi schrijven :)
	//Oftewel, ANIMATIES!
	private double m_dAnimDegree;
	private boolean m_bAnimating;
	
	//AI? tenminste, ff proberen
	private ReversiAI[] m_pAI;
	
	public String getParameter(String name, String def) {
		String retval=getParameter(name);
		if (retval==null)
			retval=def;
		return retval;
	}
	
	public void init() {
		boolean bAllowAnim=Boolean.parseBoolean(this.getParameter("allowanim","true"));
		
		//Construeer de UI
		m_btnNewGame=new Button("nieuw spel");
		m_btnHelp=new Button("help");
		m_btnAnim=new Button("anim");
		
		this.add(m_btnNewGame);
		this.add(m_btnHelp);
		if (bAllowAnim)
			this.add(m_btnAnim);
		
		m_btnNewGame.addActionListener(this);
		m_btnHelp.addActionListener(this);
		m_btnAnim.addActionListener(this);
		this.addMouseListener(this);		

		//Lees paraeters uit de HTML file
		m_nPlayers=Integer.parseInt(this.getParameter("spelers","2"));
		int maxplayers=Math.min(m_pPlayerColors.length,m_pPlayerNames.length);
		//Als -1 spelers, dan maximaal
		if (m_nPlayers==-1) m_nPlayers=maxplayers;
		//Als minder dan 2, dan 2
		if (m_nPlayers<2) m_nPlayers=2;
		//Als er teveel spelers gevraagd zijn, moeten we de gebruiker helaas teleurstellen :(
		if (m_nPlayers>m_pPlayerColors.length) m_nPlayers=maxplayers;
		
		//Volgens specs moet bij twee spelers de breedte en hoogte standaard 6 zijn
		//Bij meerdere spelers niet gedefinieerd, dus ik ga uit van spelers*3 :)
		m_nBoardW=Integer.parseInt(this.getParameter("breedte",Integer.toString(m_nPlayers*3)));
		m_nBoardH=Integer.parseInt(this.getParameter("hoogte",Integer.toString(m_nPlayers*3)));
		//(controle van minimum en maximum verplaatst naar startNewGame)
		
		//AI initialiseren
		m_pAI=new ReversiAI[m_nPlayers];
		int p;
		for (p=0; p<m_nPlayers; p++)
			if (Boolean.parseBoolean(this.getParameter("iscomputer"+Integer.toString(p),"false")) || Boolean.parseBoolean(this.getParameter("allcomputer","false")))
				m_pAI[p]=new ReversiAI(m_nPlayers,p,this);
			else
				m_pAI[p]=null; //Voor de zekerheid
		
		//Start een nieuw spel :)
		startNewGame();
		
		//Nieuwe thread starten voor de animatie :)
		m_bAnimating=bAllowAnim;
		if (bAllowAnim) {
			Thread t=new Thread(this);
			t.start();
		}
	}
	
	public void run() {
		while (true) {
			//Elke stap de draaihoek iets aanpassen :)
			m_dAnimDegree+=0.1;
			//Alsie boven 2pi komt mag je er net zo goed 2pi vanaf trekken
			//Voorkomen dat we doubles overflowen na een tijdje spelen ;)
			while (m_dAnimDegree>Math.PI*2)
				m_dAnimDegree-=Math.PI*2;
			//Als hij aan het animeren is repainten,
			//Blijf maar wel lekker doorlopen in de thread, anders krijg je gezeur met thread herstarten als je aan en uitzet,
			//Mismatch errors, geen threads, teveel threads, etc :)
			// => geen zin in ;)
			if (m_bAnimating)
				this.repaint();
			try {
				Thread.sleep(50);
			} catch (Exception e) {}
		}
	}
	
	//Verander een steen op het bord en houd de score bij
	private void setStone(int x, int y, int player) {
		//Als deze positie eerst van iemand anders was moeten we zijn score eentje lager zetten
		if (m_pBoard[x][y]>0)
			m_pScores[m_pBoard[x][y]-1]--;
		//Als het een speler is, krijgt hij een extra punt
		if (player>=0)
			m_pScores[player]++;
		m_pBoard[x][y]=player+1;
	}
	
	public void actionPerformed(ActionEvent e) {
		//Bij NewGame gewoon nieuw spel starten
		if (e.getSource() == m_btnNewGame) {
			startNewGame();
			this.repaint();
		}
		//bij de Help knop gewoon de help variabele "switchen" (true -> false -> true)
		if (e.getSource() == m_btnHelp) {
			m_bShowHelp=!m_bShowHelp;
			this.repaint();
		}
		//Animation switch :)
		if (e.getSource() == m_btnAnim) {
			m_bAnimating=!m_bAnimating;
			this.repaint();
		}
	}
	
	private void startNewGame() {
		//Controleer minimum en maximum breedte en hoogte
		//Om er zeker van te zijn dat we verderop geen out of bounds errors krijgen :)
		//Minimum moet bij 2 spelers 3 zijn, ik ga uit van spelers + 1, zodat er altijd iig nog een rij/kolom is om dingen neer te zetten.
		if (m_nBoardW<m_nPlayers+1) m_nBoardW=m_nPlayers*3;
		if (m_nBoardH<m_nPlayers+1) m_nBoardH=m_nPlayers*3;
		
		//Initialiseer de arrays
		m_pBoard=new int[m_nBoardW][m_nBoardH];
		m_pScores=new int[m_nPlayers];
		
		//Zet de beginstenen neer
		//Voorbeelden:
		//(bij 2 spelers)
		// 1 2
		// 2 1
		// (bij 3 spelers)
		// 1 2 3
		// 2 3 1
		// 3 1 2
		// (bij 4 spelers)
		// 1 2 3 4
		// 2 3 4 1
		// 3 4 1 2
		// 4 1 2 3
		// etc
		//Dus bij per rij gewoon optellen % players, en elke rij eentje later beginnen :)
		
		//Positie waar het steentje in de linkerbovenhoek van de startpositie moet staan
		int sx=((m_nBoardW-m_nPlayers)/2);
		int sy=((m_nBoardH-m_nPlayers)/2);
		
		//Vul steentjes in
		int x,y;
		for (x=0; x<m_nPlayers; x++)
			for (y=0; y<m_nPlayers; y++)
				setStone(sx + x,sy + y,(x+y)%m_nPlayers);
		
		//Reset de turn
		m_nTurn=0;
		//Er zijn bij een nieuw spel, uiteraard, nog geen winnaars ;)
		m_vWinners=new Vector();
		
		//Geef AI een schop, in geval speler 1 een computer is
		kickAI();
	}
	
	private void drawStone(Graphics g, int x, int y, int player) {
		//Tekenen van een steen, om evt later nog mooiere steentjes te maken dan normale cirkeltjes
		Color color=m_pPlayerColors[player];
		g.setColor(color);
		double offset=Math.abs(Math.sin(m_dAnimDegree+(player*0.5)));
		if (!m_bAnimating)
			offset=1;
		g.fillOval(x+(int)(cStoneSize*(1-offset)*0.5),y,(int)(cStoneSize*offset),cStoneSize);
	}
	
	private void drawHint(Graphics g, int x, int y) {
		//Tekenen van een hint (bij Help), om evt later nog mooiere plaatjes te maken
		g.setColor(cHintColor);
		double offset=Math.abs(Math.sin(m_dAnimDegree-0.5));
		if (!m_bAnimating)
			offset=1;
		int nCurSize=(int)(cHintSize*offset);
		g.drawOval(x+(cStoneSize-nCurSize)/2,y+(cStoneSize-nCurSize)/2,nCurSize,nCurSize);
	}
	
	//Public om het evt uit een hele simpele AI aan te roepen :)
	public boolean isValidMove(int x, int y, int player) {
		//Als er al een steen staat, is het geen geldige move
		if (m_pBoard[x][y]!=0)
			return false;
		int dx,dy,n;
		//Ga voor dx van -1 tot 1 en voor dy van -1 tot 1 met uitzondering van dx= & dy=0
		//Kortom: speur alle richtingen af
		for (dx=-1; dx<=1; dx++) {
			for (dy=-1; dy<=1; dy++) {
				if (dx==0 && dy==0) //de richting (0,0) is een beetje nutteloos ;)
					continue;
				//Als deze richting buiten het bord valt, niet verder kijken
				if (x+dx<0 || y+dy<0 || x+dx>=m_nBoardW || y+dy>=m_nBoardH)
					continue;
				//Als in deze richting meteen al een steen van dezelfde speler staat, is er in die richting geen steen te veroveren
				if (m_pBoard[x+dx][y+dy]==player+1)
					continue;
				//Ga in die richting net zolang zoeken tot je...
				for (n=1; x+n*dx>=0 && y+n*dy>=0 && x+n*dx<m_nBoardW && y+n*dy<m_nBoardH; n++) {
					//Een leeg vlak tegenkomt, want dan kun je geen stenen veroveren
					if (m_pBoard[x+n*dx][y+n*dy]==0)
						break;
					//of je een eigen steen tegenkomt, want dan zijn er stenen te veroveren (en return dan true, omdat de zet dan geldig is)
					if (m_pBoard[x+n*dx][y+n*dy]==player+1)
						return true;
				}
			}
		}
		//Als er geen stenen veroverd kunnen worden, is de zet niet geldig :(
		return false;
	}
	
	private boolean doMove(int x, int y, int player) {
		//Als er al een steen staat mag deze zet niet
		if (m_pBoard[x][y]!=0)
			return false;
		//Houd bij of er stenen veroverd zijn
		boolean bRetval=false;
		//Zelfde riedeltje als validMove, maar dan niet stoppen na een mogelijke move, en de stenen meteen overnemen :)
		int dx,dy,n;
		for (dx=-1; dx<=1; dx++) {
			for (dy=-1; dy<=1; dy++) {
				if (dx==0 && dy==0)
					continue;
				if (x+dx<0 || y+dy<0 || x+dx>=m_nBoardW || y+dy>=m_nBoardH)
					continue;
				if (m_pBoard[x+dx][y+dy]==player+1)
					continue;
				for (n=1; x+n*dx>=0 && y+n*dy>=0 && x+n*dx<m_nBoardW && y+n*dy<m_nBoardH; n++) {
					if (m_pBoard[x+n*dx][y+n*dy]==0)
						break;
					if (m_pBoard[x+n*dx][y+n*dy]==player+1) {
						//Even onthouden dat er stenen zijn veroverd
						bRetval=true;
						//Als er stenen veroverd kunnen worden, weer "teruggaan" en al deze stenen omzetten
						for (; n>0; n--)
							setStone(x+n*dx,y+n*dy,player);
						break;
					}
				}
			}
		}
		//Als er stenen zijn veroverd, was de zet geldig, en kunnen we de steen in kwestie ook nerzetten :)
		if (bRetval)
			setStone(x,y,player);
		return bRetval;
	}
	
	private boolean canMove(int player) {
		//Als een speler geen stenen meer over heeft, kan hij ook geen zetten doen
		if (m_pScores[player]==0)
			return false;
		//Bekijk alle coordinaten of er nog zetten over zijn
		int x,y;
		for (x=0; x<m_nBoardW; x++)
			for (y=0; y<m_nBoardH; y++)
				if (isValidMove(x,y,player))
					return true;
		return false;
	}
	
	public void update(Graphics g) {
		//Double buffering!
		//Maak een tijdelijke buffer aan
		int w=this.getWidth();
		int h=this.getHeight();
		BufferedImage image=new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
		Graphics bufferedg=image.getGraphics();
		//Wis deze buffer
		bufferedg.setColor(Color.WHITE);
		bufferedg.fillRect(0,0,w,h);
		//Teken daar ons veld in
		paint(bufferedg);
		//En teken dit in een keer op het scherm
		g.drawImage(image,0,0,this);
	}
	
	private void fillArrow(Graphics g, int x, int y, int w, int h) {
		//Teken pijltje
		//  /1
		// / 2----3
		//0       |
		// \ 5----4
		//  \6
		int px[]=new int[7];
		int py[]=new int[7];
		px[0]=x;
		py[0]=y+(h/2);
		px[1]=x+(w/4);
		py[1]=y;
		px[2]=x+(w/4);
		py[2]=y+(h/4);
		px[3]=x+w;
		py[3]=y+(h/4);
		px[4]=x+w;
		py[4]=y+(h*3/4);
		px[5]=x+(w/4);
		py[5]=y+(h*3/4);
		px[6]=x+(w/4);
		py[6]=y+h;
		g.fillPolygon(px,py,7);
	}
	
	public void paint(Graphics g) {
		//Bereken totale grootte van veld
		int nTotalWidth=m_nBoardW*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing) + cLineWidth;
		int nTotalHeight=m_nBoardH*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing) + cLineWidth;
		//Bereken waar te starten met tekenen
		int nStartX=(this.getWidth()-nTotalWidth)/2;
		int nStartY=32;
		
		//Teken de score
		int n;
		for (n=0; n<m_nPlayers; n++) {
			drawStone(g,nStartX,nStartY+n*(cStoneSize+cScoreSpacing),n);
			g.setColor(m_pPlayerColors[n]);
			g.drawString("" + m_pScores[n] + " stenen",nStartX+cStoneSize+16,nStartY+n*(cStoneSize+cScoreSpacing)+(cStoneSize/2));
			if (m_nTurn==n) {
				double offset=1-Math.abs(Math.sin(m_dAnimDegree*2));
				if (!m_bAnimating)
					offset=0;
				//fillArrow(g,nStartX+nTotalWidth-(cStoneSize*2),nStartY+n*(cStoneSize+cScoreSpacing)+(int)(cStoneSize*(1-offset)*0.5),cStoneSize*2,(int)(cStoneSize*offset));
				fillArrow(g,nStartX+nTotalWidth-(cStoneSize*2)-(int)(cStoneSize*offset),nStartY+n*(cStoneSize+cScoreSpacing),cStoneSize*2,cStoneSize);
			}
		}
		
		//Wie is er aan zet, of wie heeft er gewonnen?
		if (m_vWinners.isEmpty()) {
			//Wie is er aan zet?
			g.setColor(m_pPlayerColors[m_nTurn]);
			if (m_pAI[m_nTurn]!=null)
				g.drawString(m_pPlayerNames[m_nTurn]+" (computer) bezig met denken",nStartX,nStartY+12+(cStoneSize+cScoreSpacing)*m_nPlayers);
			else
				g.drawString(m_pPlayerNames[m_nTurn]+" aan zet",nStartX,nStartY+12+(cStoneSize+cScoreSpacing)*m_nPlayers);
		} else if (m_vWinners.size()==m_nPlayers) {
			//Iedereen heeft gewonnen = remise
			g.setColor(Color.BLACK);
			g.drawString("Remise!",nStartX,nStartY+12+(cStoneSize+cScoreSpacing)*m_nPlayers);
		} else if (m_vWinners.size()==1) {
			//Een persoon heeft gewonnen (x heeft gewonnen)
			g.setColor(m_pPlayerColors[((Integer)m_vWinners.firstElement()).intValue()]);
			g.drawString(m_pPlayerNames[((Integer)m_vWinners.firstElement()).intValue()]+" heeft gewonnen!",nStartX,nStartY+12+(cStoneSize+cScoreSpacing)*m_nPlayers);
		} else {
			//Meerdere mensen hebben gewonnen (x, y, en z hebben gewonnen!)
			String win="";
			for (n=0; n<m_vWinners.size(); n++) {
				String name=m_pPlayerNames[((Integer)m_vWinners.elementAt(n)).intValue()];
				if (n!=0)
					win+=", ";
				if (n+1==m_vWinners.size())
					win+="en ";
				win+=name;
			}
			win+=" hebben gewonnen!";
			g.setColor(Color.BLACK);
			g.drawString(win,nStartX,nStartY+12+(cStoneSize+cScoreSpacing)*m_nPlayers);
		}
		
		int nBoardX=nStartX;
		int nBoardY=nStartY+(cStoneSize+cScoreSpacing)*m_nPlayers + (12+cScoreSpacing);
		
		//Teken het raster
		int x,y;
		g.setColor(cLineColor);
		for (x=0; x<=m_nBoardW; x++) g.fillRect(nBoardX+(x*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing)),nBoardY,cLineWidth,nTotalHeight);
		for (y=0; y<=m_nBoardH; y++) g.fillRect(nBoardX,nBoardY+(y*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing)),nTotalWidth,cLineWidth);
		
		//Teken de stenen
		for (x=0; x<m_nBoardW; x++) {
			for (y=0; y<m_nBoardH; y++) {
				if (m_pBoard[x][y]>0)
					drawStone(g,nBoardX+cLineWidth+cStoneSpacing+(x*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing)),nBoardY+cLineWidth+cStoneSpacing+(y*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing)),m_pBoard[x][y]-1);
				else if (m_bShowHelp && isValidMove(x,y,m_nTurn))
					drawHint(g,nBoardX+cLineWidth+cStoneSpacing+(x*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing)),nBoardY+cLineWidth+cStoneSpacing+(y*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing)));
			}
		}
	}
	
	 public void mouseClicked(MouseEvent e) {
		 //Als er al een winnaar is, niet reageren op mouseclicks
		 if (!m_vWinners.isEmpty())
			 return;
		 //Als er een computerspeler aan de beurt is, niet reageren op mouseclicks
		 if (m_pAI[m_nTurn]!=null)
			 return;
		//Bereken totale grootte van veld
		int nTotalWidth=m_nBoardW*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing) + cLineWidth;
		//int nTotalHeight=m_nBoardH*(cStoneSize+cLineWidth+cStoneSpacing+cStoneSpacing) + cLineWidth;
		//Bereken waar het veld zich bevind
		int nStartX=(this.getWidth()-nTotalWidth)/2;
		int nStartY=32;
		int nBoardX=nStartX;
		int nBoardY=nStartY+(cStoneSize+cScoreSpacing)*m_nPlayers + (12+cScoreSpacing);
		//Verkrijg coordinaten
		int x=e.getX();
		int y=e.getY();
		//Bereken welk vakje is geklikt
		x-=nBoardX+(cLineWidth/2);
		y-=nBoardY+(cLineWidth/2);
		if(x>=0 && y>=0) {
			x/=cStoneSize+cStoneSpacing+cStoneSpacing+cLineWidth;
			y/=cStoneSize+cStoneSpacing+cStoneSpacing+cLineWidth;
			if (x<m_nBoardW && y<m_nBoardH && doMove(x,y,m_nTurn)) {
				nextTurn();
				this.repaint();
			}
		}
	 }
	 public void mouseEntered(MouseEvent e) {}
	 public void mouseExited(MouseEvent e) {}
	 public void mousePressed(MouseEvent e) {}
	 public void mouseReleased(MouseEvent e) {}
	 
	 public boolean AICallback(int x, int y) {
		 if (!doMove(x,y,m_nTurn))
			 return false;
		 nextTurn();
		 this.repaint();
		 return true;
	 }
	 
	 private void nextTurn() {
		int nSkipped=0;
		do {
			if (nSkipped==m_nPlayers) {
				//We hebben alle spelers geprobeerd, maar geen van allen kon een zet doen,
				//Dus we kiezen een winnaar uit :)
				int nMaxScore=-1;
				int n;
				for (n=0; n<m_nPlayers; n++) {
					if (m_pScores[n]>nMaxScore) {
						nMaxScore=m_pScores[n];
						m_vWinners.clear();
					}
					if (m_pScores[n]==nMaxScore)
						m_vWinners.add(new Integer(n));
				}
				break;
			}
			m_nTurn=(m_nTurn+1)%m_nPlayers;
			nSkipped++;
		} while (!canMove(m_nTurn));
		kickAI();
	 }
	 
	 //Geef de AI een schop aan begin van een beurt
	 //Om te zorgen dattie ook werkelijk iets gaat doen ;)
	 private void kickAI() {
		 if (m_pAI[m_nTurn]!=null) {
			m_pAI[m_nTurn].UpdateBoard(m_pBoard,m_nBoardW,m_nBoardH);
			Thread t=new Thread(m_pAI[m_nTurn]);
			t.start();
		}
	 }
}

