/***********************************************************************************************
	Klassenavn: cliffie
	Arver: Applet
	Beskrivelse:	Hovedklasse for appletten, som bl.a.:
								håndterer input fra brukeren
								kontrollerer bevegelser/kollisjoner mellom sprites
								styrer biltrafikken v.h.a et køsystem
								avgjør spillets status
								holder orden på nedlasting av bilder
************************************************************************************************/
import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.Math;
import hero;
import car;
import intro;
import hiscoredialog;

public class cliffie extends java.applet.Applet implements Runnable {

	private Thread CliffieThread = null;
	
		
	private final int numberOfIndSteps = 12;
	private boolean loadingIndicator[] = new boolean[numberOfIndSteps];
		
	private hero Cliff;		// Hovedrolleinnhaver - Cliff fra Cheers

	private final int numberOfLives = 4;
	private int CliffLives = numberOfLives;	// Antall liv Cliff har
	private long CliffTime = 0;		 					// Tid i sekunder fra spillet har startet
	private final int CliffGameTime = 302;		// Antall sekunder spillet maksimalt varer
	
	private int score = 0;
	
	private int beerOMeter = 5; 	// Øl-barometer = energi
	private long beerTime; 
	private final int timeBetweenBeers = 15; // tid i sekunder mellom hver slurk (5 tot.)

	private int gameStatus = -1; // -1 = initiering, 0 = introbilde, 1 = spiller, 2 = pause, 
															  //  3 = på Cheers, 4 = mister liv, 5 = game over, 6 = avslutt
															  //  7 = viser hiscores, 8 = vise sitater fra Cliff
	  
		
	private long pauseTime; // brukes når programmet stoppes opp
	
	private final int numberOfSprites = 11; // Tot. antall sprites
	
	private final int numberOfCars = 8;
	private car cars[] = new car[numberOfCars];
	private car carQueue[] = new car[numberOfCars+2];	// En simpel kø-implementasjon for biler
	private int carsInQueue = 0;
	
	private sprite zOrder[] = new sprite[numberOfSprites+2]; // Den rekkefølgen sprite'ene tegnes i
	
	private intro gameIntro;
	
	private Color hiscoreColor;
	private final int maxNumberOfScores = 10;
	private String hiscoreTable[] = new String[maxNumberOfScores];
	private int numberOfScores = 0;
	
	private hiscoredialog hiscoreDialog;
	private boolean hasEnteredHiscore = false;
	private help helpWindow;
	
	// Sitater fra Cliff:
	
	private Color quotesColor;	
	private quotes CliffQuotes;
	private String theQuote;
	private Font quoteFont;

	// Forskjellige bilder:
	
	private Image cityPic = null;
	private Image displayPic = null;
	private Image gameOverPic = null;
	private Image lifesPic = null;
	private Image mugPics[] = new Image[6];  
	private Image mugs = null;
	private Image arrowPic = null;
	private Image hiscorePic = null;
	private Image atCheersPic = null;
	private Image quotePic = null;
					
	private Image offScrImage; // Offscreen-bilde, brukes for å unngå flimmer i tegning til skjerm
	private Graphics offScrGC; // med klipping av grafikk..
	private Graphics unclippedGC; //..og uten
	
	private Polygon dogLimits;	// Grenser for hvor 
	private dog Fido;
	private dog Rufus;
		
	private MediaTracker tracker; // Brukes til å holde orden på nedlasting av grafikk
	private Polygon roadLimits;	// Grenser for hvor Cliff kan gå              
	
	private final int roadPositions[] = {61,88,200,222};	 // de 4 "banene" hvor bilene kan kjøre
	private final double carColors[][] = {{0.5,0.5,1.5},{0.3,0.7,0.1},{1.0,0.5,0.3},{0.2,0.9,0.8},
																				  {0.8,0.3,0.7},{0.8,0.8,0.1},{0.4,0.9,1.5},{0.3,1.3,1.0}};
	
	private Rectangle Cheers;	// Hvor Cheers er plasert

	private final int numberOfMailBoxes = 17;
	private int activeMailBox = 3;
	private final int mailBoxes[][] = {{18,32}, {107,32}, {194,32}, {282,32}, {454,32}, {542,32}, 
																			{11,175}, {39,293}, {39,361}, {176,317}, {279,317}, {434,317}, 
																			{530,317}, {528,176}, {430,176}, {280,176}, {181,176}}; 
	
	// Pil som indikerer aktiv postkasse:
	
	private int arrowMove = 0;
	private boolean arrowUp = true;
	private Random random;
	
	private Font displayFont, commentFont, hiscoreFont;
	
	// Forskjellige lyder:
	
	private AudioClip mailSound = null;
	private AudioClip yipeeSound = null;
	private AudioClip agonySound = null;
	private AudioClip burpSound = null;
	
	// HTML-sider med midi-musikk (brukes i egen frame):
	
	private final String MIDI_intro = "intromusic.htm";
	private final String MIDI_game = "gamemusic.htm";
	private final String MIDI_cheers = "cheersmusic.htm";
	private final String MIDI_nomusic = "nomusic.htm";
	
	private boolean musicRequested = false;
	
	private boolean keyReleased = true; // Om en tast på keyboard'et er blitt sluppet opp
		
	
	/***********************************************************************************************
		Metodenavn: init 
		Beskrivelse: initierer div. ting (henter/deler opp bilder etc.)
	************************************************************************************************/
	public void init(){
		
		setBackground(new Color(255,255,255));
		
		random = new Random();
		tracker = new MediaTracker(this);
		
		hiscoreColor = new Color(61,115,181); // Blå bakgrunnsfarge
		Cheers = new Rectangle(385,33,27,10);
		
		Object anchorpoint = getParent();
				
		while (!(anchorpoint instanceof Frame))
			anchorpoint = ((Component)anchorpoint).getParent();	
		
		hiscoreDialog = new hiscoredialog((Frame)anchorpoint);
		helpWindow = new help("Cliffie Help");
 												
		for (int i=0; i < numberOfIndSteps; i++) loadingIndicator[i] = false;
		
		try {
		   	cityPic = getImage(getCodeBase(), "boston.gif");
		   	displayPic = getImage(getCodeBase(), "display.gif");
		   	atCheersPic = getImage(getCodeBase(), "cliffcheers.jpg");
		   	lifesPic = getImage(getCodeBase(), "lifes.gif");
		   	mugs = getImage(getCodeBase(), "mugs.gif");
		   	arrowPic = getImage(getCodeBase(), "arrow.gif");
		   	hiscorePic = getImage(getCodeBase(), "hiscores.gif");
		   	quotePic = getImage(getCodeBase(), "quotes.gif");
		   	
		   	tracker.addImage(cityPic,0);
		   	tracker.addImage(displayPic,1);
		   	tracker.addImage(atCheersPic,2);
		   	tracker.addImage(lifesPic,3);
		   	tracker.addImage(mugs,4);
		   	tracker.addImage(arrowPic,5);
		   	tracker.addImage(hiscorePic,6);
		   	tracker.addImage(quotePic,7);
		   			   			   	
		   	if (tracker.isErrorAny()) {
				showStatus("Feil ved henting av grafikk!!");
		   		System.out.println("Finner ikke grafikk-fil!..");
		   		System.exit(0);
		   	}

		} catch (Exception e) {e.printStackTrace();}
	
	 	
	 	offScrImage = createImage(size().width+1,size().height*3+1);
		offScrGC = offScrImage.getGraphics();
		offScrGC.clipRect(4,4,596,390);	// Setter en tegne-ramme for selve spillet  
		
		unclippedGC = offScrImage.getGraphics(); // Brukes for tegning utenfor spillrammen
		
		// Polygoner for områder hvor Cliff/hundene har lov til å gå:
		
		roadLimits = strToPolyParse("385,33,385,56,2,56,2,110,65,111,65,200,0,200,0,247,63,247,65,382,600,384,600,56,561,56,561,342,354,342,354,248,561,248,561,200,356,200,356,111,307,111,307,200,107,200,107,248,308,248,308,341,107,338,107,111,561,111,561,56,415,56,415,33,385,33");
		dogLimits = strToPolyParse("563,247,599,247,599,384,65,384,65,250,104,250,105,344,307,344,308,248,354,248,355,344,562,344,563,247");
		
		Cliff = new hero(roadLimits,tracker,8,offScrGC,this);
		
		Rufus = new dog(dogLimits,tracker,9,offScrGC,this);
		Fido = new dog(dogLimits,tracker,9,offScrGC,this);
		
		gameIntro = new intro(800,tracker,10,unclippedGC,this);	// For å styre intro/meny-bilde		
		CliffQuotes = new quotes(this);		
		quotesColor = new Color(252,174,13);

		displayFont = new Font("helvetica", Font.BOLD, 14);
		commentFont = new Font("helvetica", Font.BOLD, 28);
		hiscoreFont = new Font("Courier", Font.PLAIN, 14);
		quoteFont = new Font ("Courier" , Font.BOLD, 16);
		
		
		// Gjør klar hver bil - gir de farge, putter de i bilkøen:
			 	
	 	for (int i=0; i < numberOfCars; i++){
			cars[i] = new car(carColors[i][0],carColors[i][1],carColors[i][2],offScrGC,this);
		}
	  	
		// Stykker opp øl-barometeret (6 forskjellige bilder):
	
		for (int i = 0; i < 6; i++){
			mugPics[i] = createImage(new FilteredImageSource(mugs.getSource(),new CropImageFilter(i*50,0,50,63)) );		 		
			tracker.addImage(mugPics[i],10);		
		}
		
		
		mailSound =  getAudioClip(getCodeBase(),"mail.au");
		yipeeSound = getAudioClip(getCodeBase(),"yipee.au");
		agonySound = getAudioClip(getCodeBase(),"agony.au");
		burpSound = getAudioClip(getCodeBase(),"burp.au");
		
		// For å starte nedlasting (Netscape venter til lyden skal spilles):
			
		mailSound.play(); mailSound.stop(); 
		yipeeSound.play(); yipeeSound.stop();
		agonySound.play(); agonySound.stop();
		burpSound.play(); burpSound.stop();
	
	} // init()..

	
	/***********************************************************************************************
		Metodenavn: pushCarToQueue 
		Beskrivelse: legger en bil på køen etter den har kjørt ferdig sin runde
	************************************************************************************************/
	private void pushCarToQueue(car c){carQueue[carsInQueue++] = c;}
	
	/***********************************************************************************************
		Metodenavn: popCarFromQueue 
		Beskrivelse: tar første bilen i køen ut
	************************************************************************************************/
	private car popCarFromQueue(){
		car temp = carQueue[0];
		for (int i = 1; i < carsInQueue; i++) 	carQueue[i-1] = carQueue[i];
		
		carsInQueue--;
		return temp;
	}	

	
	/***********************************************************************************************
		Metodenavn: zOrderSort
		Beskrivelse: sorterer alle sprite'ene etter Y-posisjon for å skape et realistiskt bilde
	************************************************************************************************/
	public void zOrderSort(){ 
		
		// basert på algoritmen til insettningssortering:
		
		for (int i = 1; i < numberOfSprites; i++){
			sprite tmp = zOrder[i];
			int j;
			
			for (j = i; j > 0 && tmp.getPosY() < zOrder[j-1].getPosY(); j--)
				zOrder[j] = zOrder[j-1];
			zOrder[j] = tmp;  
		}
	}
			

	/***********************************************************************************************
		Metodenavn: readHiscores
		Beskrivelse: henter hiscore-listen fra fil og oppdaterer hiscore-tabellen
	************************************************************************************************/
	public void readHiscores(){
		InputStream s = null;
		String line;
		
		try {
			s = new URL(getCodeBase(), "hiscores.dat").openStream();							
		} catch (Exception e){System.out.println("Finner ikke fil!"); System.exit(1);}
							
		DataInputStream file = new DataInputStream(s);

		numberOfScores = 0;
		
		try {

			while ((line = file.readLine()) != null && numberOfScores <= maxNumberOfScores)
				hiscoreTable[numberOfScores++] = line; 
			 
		} catch (Exception e){System.out.println("Feil ved fil-lesing!"); System.exit(1);}
		
		try {file.close();} catch (IOException e){}
	}
	

	/***********************************************************************************************
		Metodenavn: manageTraffic
		Beskrivelse: tar seg av biltrafikken, bestemmer når biler skal kjøres (bruker en kø)
	************************************************************************************************/
	public void manageTraffic(){
	
		for (int i = 0; i < numberOfCars; i++){
			
			car tempCar;

			// Hvis noen av bilene har kjørt "ferdig" må disse tilbake i køen og nye settes inn:	

			if (cars[i].getStatus() != 0){
				
				if (cars[i].getStatus() == 1 && cars[i].getPosX() > 661){ // Mot høyre
					tempCar = popCarFromQueue();
					tempCar.initDrive(1,-61,cars[i].getPosY(),randomCarSpeed()); 
					cars[i].stop();
					pushCarToQueue(cars[i]);
				}
				else if (cars[i].getStatus() == 2 && cars[i].getPosX() < -61){ // Mot venstre
					tempCar = popCarFromQueue(); 
					tempCar.initDrive(2,661,cars[i].getPosY(),randomCarSpeed()); 
					cars[i].stop();
					pushCarToQueue(cars[i]);
				
				} else cars[i].drive();
			}		
		} // for..

	}	
	

	/***********************************************************************************************
		Metodenavn: writeScoreToFile
		Beskrivelse: forbereder en CGI-URL og sender denne til CGI-script for lagring av hiscore 
	************************************************************************************************/
	public void writeScoreToFile(){
		
		String s = new String(hiscoreDialog.getName());
		System.out.println("Name: "+s);
		int length = s.length();
		
		StringBuffer buffer = new StringBuffer(40);
		String temp = new String();
			
		// Gjør om til lovelige bokstaver for CGI-URL (spes. koder for æ,ø,å): 
		
		for (int i = 0; i < length; i++){
			char c = s.charAt(i);		
			if (c > 44 && c < 125) temp = ""+(char)c;
			else switch (c){
				case 'æ' : temp = "%E6"; break; 
				case 'ø' : temp = "%F8"; break; 
				case 'å' : temp = "%E5"; break; 
				case 'Æ' : temp = "%C6"; break; 
				case 'Ø' : temp = "%D8"; break; 
				case 'Å' : temp = "%C5"; break;
				default: temp = "+";
			}
		
			buffer.append(temp);
		}
		temp = "http://www.iu.hioslo.no/cgi-bin-nilsenr/cliffie.cgi?"+Integer.toString(score)+
					 "&"+buffer.toString();
	
		// Sender ferdigbehandlet link til CGI-frame:
		
		try { 	
			getAppletContext().showDocument(new URL(getCodeBase(),temp),"cgi");
		} catch (MalformedURLException e){ 
			System.out.println("Kunne ikke åpne script!");
		}
	} 

	
	
	/***********************************************************************************************
		Metodenavn: playMusic
		Beskrivelse: spiller MIDI ved å sende HTML-siden med navn midiName til en egen frame, music
	************************************************************************************************/
	public void playMusic(String midiName){
		try { 	
				getAppletContext().showDocument(new URL(getCodeBase(),midiName),"music");
		} catch (MalformedURLException e){ 
			System.out.println("Kunne ikke spille midi!");
		}
	}
	
		
	/***********************************************************************************************
		Metodenavn: doneLoading
		Beskrivelse: returnerer true hvis alle bildene og lydene er ferdig nedlastet
	************************************************************************************************/
	public boolean doneLoading(){
		return (tracker.checkAll(true) && doneLoadingSounds());
	}	

	/***********************************************************************************************
		Metodenavn: doneLoadingSounds
		Beskrivelse: returnerer true hvis alle lydene er ferdig nedlastet
	************************************************************************************************/
	public boolean doneLoadingSounds(){
		return mailSound != null && burpSound != null &&
		       agonySound != null && yipeeSound != null; 
	}
	
	/***********************************************************************************************
		Metodenavn: resetGame
		Beskrivelse: gjør klar for et nytt spill (resetter score, status osv.)
	************************************************************************************************/
	public void resetGame(){

	 	gameStatus = 0;		// Spillet viser intro-bilde
	 	carsInQueue = 0;
	 	beerOMeter = 5;  // Får fullt øl-glass
	 	beerTime = System.currentTimeMillis();
	 	score = 0;
	 	hasEnteredHiscore = false;
	 	
	 	activeMailBox = Math.abs(random.nextInt())%17;
	 		 	
	 	CliffLives = numberOfLives;
	 	Cliff.resetPos();
	 	Cliff.startBlinking();
	 	
	 	for (int i=0; i < numberOfCars; i++){
			cars[i].setStatus(0);
			if (i > 3) pushCarToQueue(cars[i]);
			zOrder[i] = cars[i];
		}
		
		zOrder[numberOfCars] = Fido;
		zOrder[numberOfCars+1] = Rufus;
		zOrder[numberOfCars+2] = Cliff;		
		
		// Setter 4 biler klare til å kjøre:
  	
  	cars[0].initDrive(2,randomCarPosX(),roadPositions[0],randomCarSpeed());
  	cars[1].initDrive(1,randomCarPosX(),roadPositions[1],randomCarSpeed());
  	cars[2].initDrive(2,randomCarPosX(),roadPositions[2],randomCarSpeed());
  	cars[3].initDrive(1,randomCarPosX(),roadPositions[3],randomCarSpeed());
	
		CliffTime = System.currentTimeMillis();
		pauseTime = CliffTime;
	}
	
	
	/***********************************************************************************************
		Metodenavn: randomCarSpeed
		Beskrivelse: returner en tilfeldig fart
	************************************************************************************************/
	public int randomCarSpeed(){return Math.abs(random.nextInt())%7+3;}
	
	
	/***********************************************************************************************
		Metodenavn: randomCarPosX
		Beskrivelse: returnerer en tilfeldig start-posisjon (x-koord.)
	************************************************************************************************/
	public int randomCarPosX(){return Math.abs(random.nextInt())%661-61;}
		

	/***********************************************************************************************
		Metodenavn: stop
		Beskrivelse: stopper appleten
	************************************************************************************************/
	public void stop(){
		if (CliffieThread != null) {
	 		CliffieThread.stop();
			CliffieThread = null;
		}
	}

	/***********************************************************************************************
		Metodenavn: start 
		Beskrivelse: starter appleten
	************************************************************************************************/
	public void start() { 
		if (CliffieThread == null) { 
			CliffieThread = new Thread(this); 
			CliffieThread.start(); 
		} 
	} 

	/***********************************************************************************************
		Metodenavn: finalize 
		Beskrivelse: frigjør Graphics objektenes resursser
	************************************************************************************************/
	public void finalize(){
		offScrGC.dispose();
		unclippedGC.dispose();
	}

	
	/***********************************************************************************************
		Metodenavn: run 
		Beskrivelse: kjører appleten (main-metoden)
	************************************************************************************************/
	public void run(){ 

		long startTime = System.currentTimeMillis();
    
   	while (Thread.currentThread() == CliffieThread){
			
			try	{
				if (gameStatus == 1 || gameStatus == 7 || gameIntro.inTransition())
					CliffieThread.sleep(Math.max(startTime-System.currentTimeMillis(),40));
				else CliffieThread.sleep(300);
			}	catch (InterruptedException e) {}
			
			startTime=System.currentTimeMillis()+35;
   	
  		// Går til riktig metode ved forskjellig gamestatus:
  		
  		switch(gameStatus){
  			case -1: loading(); break;
	  		case 0:	intro(); break;
  			case 1: playing();	break;
  			case 2: pause();	break;
 		 		case 3:	inCheers();	break;
    		case 4:	lifeLost(); break;
    		case 5:	gameOver(); break;
    		case 6: endGame(); break;
  			case 7: hiscores(); break;
  			case 8: quote(); break;
  		}
  		
  		repaint();		  	
  	}
	}  
	
	
	/***********************************************************************************************
		Metodenavn: drawShadowString 
		Beskrivelse: tegner stringen s med skygge (sentrert eller venstrestilt) til g
	************************************************************************************************/
	public void drawShadowString(String s,int x, int y, int w, boolean center, Graphics g){
		Color tempColor = g.getColor();
		
		int l = g.getFontMetrics().stringWidth(s);
	
		// Tegner skyggen:
		g.setColor(Color.darkGray);
		g.drawString(s,x+(center? w/2-l/2 : 0)+1,y+1);  

		// Tegner tekst i satt farge:
		g.setColor(tempColor);
		g.drawString(s,x+(center? w/2-l/2 : 0),y);  
	}
	
	
	/***********************************************************************************************
		Metodenavn: drawCenterString
		Beskrivelse: tegner stringen s sentrert
	************************************************************************************************/
	public void drawCenterString(String s,int x, int y, int w, Graphics g){
		int l = g.getFontMetrics().stringWidth(s);
		g.drawString(s,x+w/2-l/2,y);  
	}

	
	
	/***********************************************************************************************
		Metodenavn: drawQuote
		Beskrivelse: tegner sitat i theQuote til offscreen-bildet v.h.a. uncippedGC
	************************************************************************************************/
	public void drawQuote(){
	
		int l = theQuote.length();
		int lineNb = 1;
		int lineLength = 0;
		char array[]  = theQuote.toCharArray();
		
		unclippedGC.setFont(quoteFont);
		
		for (int i = 0; i < l; i++){ // Skriver ut en og en bokstav
			lineLength++;
			
			if (lineLength > 30 && array[i] == ' '){	
				
				lineNb++;
				lineLength = 0;				
			}
			unclippedGC.setColor(Color.white);
			drawShadowString("" + array[i], 101+12*lineLength, 101 + (lineNb *17), 0, false, unclippedGC); 
		}
		lineNb+=2;
	  drawShadowString("- Cliff Clavin", 450, 100 + (lineNb *17),0,false,unclippedGC);
	}
	
	
	/***********************************************************************************************
		Metodenavn: quote 
		Beskrivelse: skriver ut quote-bildet med tekst 
	************************************************************************************************/
	public void quote(){
		if (gameIntro.inClosingTransition()) return;		
		else if (gameIntro.transitionClosed()) gameStatus = 0;		
		else {
			unclippedGC.setColor(quotesColor);
			unclippedGC.fillRect(0,0,668,400);
			unclippedGC.drawImage(quotePic,198,90,this);
			drawQuote();
		
			drawFrame(0,0,667,399,unclippedGC);
		}
	}
	
	/***********************************************************************************************
		Metodenavn: hiscores
		Beskrivelse: skriver ut hiscore-bildet og hiscores
	************************************************************************************************/
	public void hiscores(){
		
		if (gameIntro.inClosingTransition()) return;		
		else if (gameIntro.transitionClosed()) gameStatus = 0;		
		else {
			unclippedGC.setColor(hiscoreColor);
			unclippedGC.fillRect(0,0,668,400);
			unclippedGC.drawImage(hiscorePic,199,20,this);
			drawFrame(0,0,667,399,unclippedGC);
			
			offScrGC.setFont(hiscoreFont);
			offScrGC.setColor(new Color(Math.abs(random.nextInt())%155+100,Math.abs(random.nextInt())%155+100,
			                            Math.abs(random.nextInt())%155+100)); // Tilfeldige RGB-verdier (blinking)
			offScrGC.drawString(hiscoreTable[0],200,115);

			offScrGC.setColor(Color.white);
			
			for (int i=1; i < numberOfScores; i++)
				offScrGC.drawString(hiscoreTable[i],200,115+18*i);
				
			drawShadowString("Try reload/update if your",0,325,668,true,offScrGC);
			drawShadowString("score is missing..",0,340,668,true,offScrGC);
		}
	}
	
	/***********************************************************************************************
		Metodenavn: pause
		Beskrivelse: tegner pause-tekst
	************************************************************************************************/
	public void pause(){
	
		unclippedGC.setFont(commentFont);
		
		unclippedGC.setColor(Color.white);
		drawShadowString("PAUSE (PRESS ANY KEY TO CONTINUE)",0,230,600,true,unclippedGC);
	}

	/***********************************************************************************************
		Metodenavn: endGame 
		Beskrivelse: tegner tekst som spør om du vil avslutte spillet
	************************************************************************************************/
	public void endGame(){

		unclippedGC.setFont(commentFont);		

		unclippedGC.setColor(Color.white);
		drawShadowString("DO YOU REALLY WANT TO QUIT? (Y/N)",0,230,600,true,unclippedGC);
	}


	/***********************************************************************************************
		Metodenavn: loading
		Beskrivelse: tegner indikator som viser hvor langt grafikk har blitt lastet ned, initere
		             spillet hvis ferdig 
	************************************************************************************************/
	public void loading(){
		unclippedGC.setColor(Color.black);
		unclippedGC.drawString("Loading, please wait..",285,150);
		unclippedGC.drawRect(239,169,numberOfIndSteps*15+6,16);
		
		
		int loadedItems = 0;
		
		for (int i=0; i < numberOfIndSteps; i++){
			if (loadingIndicator[i]) loadedItems++;
			else if (tracker.checkID(i)){
				loadingIndicator[i] = true;
				loadedItems++;
			}
		}
		
		unclippedGC.setColor(Color.blue);
		unclippedGC.fillRect(240,170,loadedItems*15+5,15);		
		
		if (doneLoading()){

  		unclippedGC.drawImage(cityPic,0,400,this);
  		unclippedGC.drawImage(displayPic,600,405,this);
  		drawFrame(0,400,667,399,unclippedGC);	
			resetGame();
			if (gameIntro.musicEnabled()) playMusic(MIDI_intro);
		}
	}

	/***********************************************************************************************
		Metodenavn: intro 
		Beskrivelse: starter intro-musikk
	************************************************************************************************/
	public void intro(){
		if (musicRequested && gameIntro.transitionClosed()){
			playMusic(MIDI_intro);
			musicRequested = false;
		}
	}

	/***********************************************************************************************
		Metodenavn: gameOver
		Beskrivelse: skriver ut "game over", og startet hiscore-dialog hvis spilleren har hiscore
		             (skriver da også til fil v.h.a. CGI-script)
	************************************************************************************************/
	public void gameOver(){		

		unclippedGC.setFont(commentFont);
		
		unclippedGC.drawImage(displayPic,600,5,this);
		unclippedGC.setFont(commentFont);
		
		unclippedGC.setColor(Color.white);
		drawShadowString("GAME OVER",0,224,600,true,unclippedGC);
			
		unclippedGC.setFont(displayFont);
		
		unclippedGC.setColor(Color.white);
				
		if (score <= 100) drawShadowString("Nice try...",0,240,600,true,unclippedGC);
		else if (score > 100 && score <= 200) drawShadowString("Not bad...",0,240,600,true,unclippedGC); 
		else if (score > 200 && score <= 300) drawShadowString("Well done!",0,240,600,true,unclippedGC);
		else if (score > 300 && score <= 400) drawShadowString("Very good!!",0,240,600,true,unclippedGC);
		else drawShadowString("Your're the champ!!!",0,240,600,true,unclippedGC);		
  

  	if (!hasEnteredHiscore){
  		readHiscores();	 // Leser fra fil for å få oppdaterte hiscores
    		
  		// Sjekker om poeng er blant de beste på hiscore-listen:
  		
  		if (score > 0 && (numberOfScores < maxNumberOfScores || score > strToScore(hiscoreTable[numberOfScores-1]))){ 
  			repaint();					// For å skrive ut "game over" før dialogboksen under "fryser" applet'en
  			yipeeSound.play(); // Yipee!!
  			hiscoreDialog.enterHiscore(score);	 // Skriv inn navn til listen
   			writeScoreToFile(); 								 // Kaller opp et CGI-script for lagring på server
   		}  		  		
  		hasEnteredHiscore = true;	// Blir satt selv om poeng ikke er hiscore..
  	}
  }
  
 	/***********************************************************************************************
		Metodenavn: strToScore
		Beskrivelse: konverterer stringen str av typen "  80  Gunnar Hansen" til poengsum, ret. denne
	************************************************************************************************/
	public int strToScore(String str){ 
  	
  	int s = 0;
  	int exp = 1;	// 10,100,1000..
  	int chr;
  	
  	for (int i = 3; i >= 0; i--){
  		chr = (int)str.charAt(i)-48;
			  		
   		if (chr > -1 && chr < 10)
   			s += chr * exp;
  		  		
  		exp *= 10;
  	}
  	return s;
  }

 	/***********************************************************************************************
		Metodenavn: CliffAtCheers
		Beskrivelse: ret. true hvis Cliff er innenfor rektangelet Cheers
	************************************************************************************************/
	public boolean CliffAtCheers() {
		
		Rectangle coll = Cliff.getCollision();
		Rectangle r = new Rectangle(Cliff.getPosX()+coll.x,Cliff.getPosY()+coll.y,coll.width,coll.height);
		
		return r.intersects(Cheers);		
	}
		

 	/***********************************************************************************************
		Metodenavn: inCheers 
		Beskrivelse: øker øl-barometeret og tegner bildet av Norm og Cliff
	************************************************************************************************/
	public void inCheers(){
		
		if ((System.currentTimeMillis() - beerTime) > 300 && beerOMeter < 5){
			beerOMeter++;
			beerTime = System.currentTimeMillis();
		}
	
		offScrGC.drawImage(atCheersPic,195,105,this);
		drawFrame(195,105,215,145,offScrGC);
		

	}
	 
 	/***********************************************************************************************
		Metodenavn: playing
		Beskrivelse: hovedfunk. når spillet er igang
	************************************************************************************************/
	public void playing(){	 	
  	
   	if (musicRequested && gameIntro.transitionOpen()){
			playMusic(MIDI_game);
			musicRequested = false;
		}
  	
  	// Sjekker om Cliff blir overkjørt av biler:
  	  		
  	for (int i = 0; i < numberOfCars; i++)
  		if (cars[i].getStatus() != 0){
  			if (Cliff.collisionDetected(cars[i])){
  				gameStatus = 4;	// mister liv..
  				agonySound.play();
  			}
  			else if (cars[i].inHonkRange(Cliff)) cars[i].honk();
  		}
  	// Sjekker om Cliff blir bitt av en hund:
   	if (Cliff.collisionDetected(Fido) || Cliff.collisionDetected(Rufus)){
   		gameStatus = 4; 
  		agonySound.play();
  	}
  	
	
  	// Sjekker om Cliff er så nærme en hund at han blir bjeffet på:
   	if (Fido.inBarkRange(Cliff)) Fido.bark();
   	if (Rufus.inBarkRange(Cliff)) Rufus.bark(); 
  	
  	
  	// Hvis hundene "krasjer" med hverandre skal den ene skifte retning:
    if (Fido.collisionDetected(Rufus)) Fido.changeCourse(); 
  	
  	// Sjekker om tiden er ute (altså game over):
  	if ((System.currentTimeMillis() - CliffTime) > CliffGameTime*1000){
  		gameStatus = 5;	 // game over
 			pauseTime = System.currentTimeMillis();
  	} 	
 	
  	
  	// Sjekker øl-barometeret:
  	if ((System.currentTimeMillis() - beerTime) > timeBetweenBeers*1000){

  		beerOMeter--;
  		beerTime = System.currentTimeMillis();
		
			if (beerOMeter ==  -1){ // Er det helt tomt?
  			agonySound.play();
  			gameStatus = 4;
  		}
  	}
		
		// Styrer hundene:
  	Rufus.checkMoves();
  	Fido.checkMoves();
  	Rufus.walk();
  	Fido.walk();
  	
  	// Styrer Cliff og trafikken:  	
  	Cliff.walk();
		manageTraffic();
		
		unclippedGC.copyArea(0,400,668,400,0,-400); // Tegner bildet av Boston opp til spilleflate
		
		zOrderSort(); // Sorterer sprite'ene etter Y-posisjon
	
		// Tegner alle spritene:
		
		for (int i = 0; i < numberOfSprites; i++){
			if (zOrder[i] instanceof car){
				if (((car)zOrder[i]).getStatus() != 0) zOrder[i].draw(); 
			} else zOrder[i].draw();
		}
			
		drawArrow(); // Tegner pilen som peker på aktiv postkasse
	}	

 	/***********************************************************************************************
		Metodenavn: drawArrow 
		Beskrivelse: tegner opp pilen som viser aktiv postkasse
	************************************************************************************************/
	public void drawArrow(){
 	
 		arrowMove += (arrowUp? -2 : 2);
 	
 		if (arrowMove < 0){
 			arrowUp = false;
 			arrowMove = 0;
 		} else if (arrowMove > 10){
 			arrowUp = true;
 			arrowMove = 10;
 		}	 
 	
		unclippedGC.drawImage(arrowPic,mailBoxes[activeMailBox][0]+8+arrowMove/2,
		                      mailBoxes[activeMailBox][1]-10-arrowMove, this);
	}
 
 	/***********************************************************************************************
		Metodenavn: lifeLost 
		Beskrivelse: minker antall liv, avgjør om Cliff skal dø
	************************************************************************************************/
 	public void lifeLost(){
 		
 		beerOMeter = 5;
		beerTime = System.currentTimeMillis();
 		
 		CliffLives--;
 		Cliff.resetPos();
 		
 		Cliff.startBlinking(); // Cliff blinker når han har mistet liv
 		 		 		 		
 		if (CliffLives == 0){
 			gameStatus = 5;
 			pauseTime = System.currentTimeMillis();
 		} else gameStatus = 1;
 	}
 
 
 	/***********************************************************************************************
		Metodenavn: drawDisplayInfo 
		Beskrivelse: tegner opp displayet til venstre ved spilling
	************************************************************************************************/
	public void drawDisplayInfo(){

		// Tegner øl-barometeret:
		
		if (beerOMeter > 0) unclippedGC.drawImage(mugPics[5-beerOMeter],608,25,this);
		else unclippedGC.drawImage(mugPics[5],608,25,this);
	
		// Tegner opp cliff-hoder for antall liv:

		for (int i = 0; i < CliffLives; i++)
		unclippedGC.drawImage(lifesPic,632,90+30*i,this);

		// Tegner opp antall poeng:
			
		unclippedGC.setColor(Color.white);
		unclippedGC.setFont(displayFont);
		drawCenterString(""+score,602,250,60,unclippedGC);
		
		// Tegner opp tiden:
		
		int min, sec;
		
		if (gameStatus == 1 || gameStatus == 4){ // Spill og mistet liv
			min = (int)((CliffGameTime*1000-(System.currentTimeMillis()-CliffTime))/60000);
			sec = (int)((CliffGameTime*1000-(System.currentTimeMillis()-CliffTime))/1000)-min*60;
		}	else {
			min = (int)((CliffGameTime*1000-(pauseTime-CliffTime))/60000);
			sec = (int)((CliffGameTime*1000-(pauseTime-CliffTime))/1000)-min*60;
		}
		
		drawCenterString(""+min+":"+(sec < 10? "0" : "")+sec,602,293,60,unclippedGC);				
	}
	
 	/***********************************************************************************************
		Metodenavn: update
		Beskrivelse: tegner diverse ting før paint kalles
	************************************************************************************************/
	public void update(Graphics g){ 
	
		if (doneLoading()){
			if (gameStatus != 0 && gameStatus != 7 && gameStatus != 8) drawDisplayInfo();
			
			if (gameIntro.inTransition() || gameStatus == 0){
				gameIntro.draw();
				drawFrame(0,0,667,399,unclippedGC);
			}
		}
		
		paint(g);
	} 
	
 	/***********************************************************************************************
		Metodenavn: strToPolyParse 
		Beskrivelse: gjør stringen s om til polygon
	************************************************************************************************/
	public Polygon strToPolyParse(String s){
		
		int len = s.length();
		int start = 0;
		int temp = 0;
		int tall = 0;

		boolean addPoly = false;				
		
		Polygon p = new Polygon();
		
		for (int i=0; i <= len; i++)
			if (i == len || s.charAt(i) == ',') {
					tall = Integer.parseInt(s.substring(start,i));
					start = i+1;
					
					if (addPoly) {
						p.addPoint(temp,tall);
						addPoly = false;		
					} else {
						temp = tall; 
						addPoly = true;
					}
					 
			} // if s[i].. 	
			
		return p;
	}

 	/***********************************************************************************************
		Metodenavn: checkMailBoxes 
		Beskrivelse: sjekker om Cliff står foran en postkasse, og om det er den aktive 
	************************************************************************************************/
	private void checkMailBoxes(){
		
		Rectangle coll = Cliff.getCollision();
		Rectangle r = new Rectangle(Cliff.getPosX()+coll.x,Cliff.getPosY()+coll.y,coll.width,coll.height);
		
		if (r.intersects(new Rectangle(mailBoxes[activeMailBox][0],mailBoxes[activeMailBox][1],30,30))){
			
			mailSound.play();
			
			score += 10;
			CliffTime += 5000; // Gir Cliffie 5 sekunder ekstra tid
			
			int mailBox = Math.abs(random.nextInt()) % 17;
			
			while (mailBox == activeMailBox)
				mailBox = Math.abs(random.nextInt()) % 17;
			 			
			activeMailBox = mailBox;
		}
	
	}
	

 	/***********************************************************************************************
		Metodenavn: keyDown
		Beskrivelse: tar seg av tastetrykk og tolker disse
	************************************************************************************************/
	public boolean keyDown(java.awt.Event e,int key){

		Cliff.checkMoves(e); // Sjekker om Cliff skal gå
		if (gameIntro.inTransition()) return true;

		if (keyReleased){
			
			keyReleased = false;
			
			if (gameStatus == 5) { // Game over..
				resetGame();
				if (gameIntro.musicEnabled()) musicRequested = true;
				gameIntro.doTransition();
				return true;
			}
		
			if (key == 'h' || key == 'H' || key == Event.F1) // Vise hjelp..
				if (!helpWindow.isShowing())	helpWindow.show(); 
		


			if (gameStatus == 2){ // Pause, start spill igjen..
				gameStatus = 1; 
				CliffTime += System.currentTimeMillis()-pauseTime;
				beerTime += System.currentTimeMillis()-pauseTime;
				return true;
			}
		
			if (gameStatus == 3){ // På Cheers, start spill igjen..
				gameStatus = 1; 
				beerOMeter = 5;
				Cliff.resetPos();
			 
				CliffTime += System.currentTimeMillis()-pauseTime;
				beerTime = System.currentTimeMillis();
			 
				burpSound.play();
			 			
				if (gameIntro.musicEnabled()) musicRequested = true;
			 
				return true;
			}


			// Avslutt hvis y eller Y er trykket
		
			if (gameStatus == 6){ 
		  	if (key == 'y' || key == 'Y') {
					gameIntro.doTransition();
					resetGame();				
					gameStatus = 0;

					if (gameIntro.musicEnabled()) musicRequested = true;
				}	else gameStatus = 1;	

				CliffTime += System.currentTimeMillis()-pauseTime;
				beerTime += System.currentTimeMillis()-pauseTime;

				return true;
			}	 
		
			// Sjekker om ENTER er trykket, hvis så om Cliff står foran en postkasse eller foran Cheers:
		
			if (key == 13 || key == 10 && gameStatus != 3) {
				if (CliffAtCheers()){
					gameStatus = 3;			
 					beerTime = System.currentTimeMillis();
					pauseTime = System.currentTimeMillis();
					if (gameIntro.musicEnabled()) playMusic(MIDI_cheers);
				}	else checkMailBoxes();
			}
			
			else if (gameStatus == 1 && (key == 'q' || key == 'Q' || key == 27)){ // Avslutt..
				gameStatus = 6;
				pauseTime = System.currentTimeMillis();
			}	
			else if (gameStatus == 1 &&(key == 'p' || key == 'P')){ // Pause..
				gameStatus = 2;
				pauseTime = System.currentTimeMillis();
			}
		}
		return true;
	}
	
 	/***********************************************************************************************
		Metodenavn: keyUp 
		Beskrivelse: Stopper Cliffs gange
	************************************************************************************************/
	public boolean keyUp(java.awt.Event e,int key){
		Cliff.stop();
		keyReleased = true;
		return true;
	}


 	/***********************************************************************************************
		Metodenavn: drawFrame
		Beskrivelse: tegner en grå ramme med skygge til g
	************************************************************************************************/
	public void drawFrame(int x,int y,int w,int h,Graphics g){
		
		g.setColor(Color.lightGray);

		g.fillRect(x,y,5,h);
		g.fillRect(x+w-5,y,5,h);
		g.fillRect(x,y,w,5);
		g.fillRect(x,y+h-5,w,5);

		g.setColor(Color.white);

		g.fillRect(x,y+1,1,h-1);
		g.fillRect(x+w-4,y+4,1,h-8);
		g.fillRect(x+4,y+h-4,w-8,1);
		g.fillRect(x,y,w-1,1);
		
		g.setColor(Color.darkGray);
	
		g.fillRect(x+4,y+4,1,h-8);
		g.fillRect(x+4,y+4,w-8,1);
		
		g.fillRect(x,y+h,w+1,1);
		g.fillRect(x+w,y,1,h);
	}
	
	
 	/***********************************************************************************************
		Metodenavn: paint
		Beskrivelse: tegner offscreen-bildet til skjermen
	************************************************************************************************/
	public void paint(Graphics g) {
			g.drawImage(offScrImage,0,0,this);
 	}

 	/***********************************************************************************************
		Metodenavn: mouseDown 
		Beskrivelse: registerer og behandler musetrykk
	************************************************************************************************/
	public boolean mouseDown(java.awt.Event evt, int x, int y) {
		
		if (gameIntro.inTransition()) return true;
		
		if (gameStatus == 0){	// Intro-bilde
			
			int button = gameIntro.checkButton(x,y);
			
			if (button == gameIntro.BUTTON_help){ // Hjelp-knapp..
				if (!helpWindow.isShowing())	helpWindow.show();	
			}
			
			else if (button == gameIntro.BUTTON_newgame){ // "New game"-knapp
				if (gameIntro.musicEnabled()) musicRequested = true;
				gameIntro.doTransition();
				resetGame();
				gameStatus = 1;
			}
						
			else if (button == gameIntro.BUTTON_music){ // Knapp for musikk av/på
				gameIntro.changeMusic();
				playMusic(gameIntro.musicEnabled()? MIDI_intro : MIDI_nomusic);
			}	
			
			else if (button == gameIntro.BUTTON_hiscores){ // Hiscore-knapp
				readHiscores();
				
				// Fjerner evt. CGI-script fra cgi-framen for å unngå dobbeltlagring:
				
				try { 	
					getAppletContext().showDocument(new URL(getDocumentBase(),"cgiframe.htm"),"cgi");
				} catch (MalformedURLException e){ 
					System.out.println("Kunne ikke åpne fil!");
				}
				
				gameIntro.doTransition();
				gameStatus = 7;	
			}
			
			else if (button == gameIntro.BUTTON_quotes){ // Quotes-knapp
				theQuote = CliffQuotes.getQuote();
				gameIntro.doTransition();
				gameStatus = 8;	
			}
			
			return true;
		}
		
		if (gameStatus == 5){ // Game over, skal tilbake til intro-bildet
			resetGame();
			
			if (gameIntro.musicEnabled()) musicRequested = true;
			gameIntro.doTransition();
			return true;
		}
	
		// Starter overgang til intro-bildet ved hiscoreliste og quotes:
		
		if (gameStatus == 7 || gameStatus == 8){ 
			gameIntro.doTransition();
			return true;
		}
		
		return true;
  }

} // class Cliffie..