/***********************************************************************************************
        CLIFFIE
/***********************************************************************************************
	
	
	Prosjekt i programmering med Java, våren 1998.
	Laget av Mikkel Sjølie og Robert Nilsen.
		
	Dette er en fil som bestående av alle klassene til appleten Cliffie, og kan ikke kompileres 
	når alt ligger på samme fil.

************************************************************************************************/


/***********************************************************************************************
	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..




/***********************************************************************************************
	Klassenavn: sprite
	Arver: - 
	Beskrivelse:	superklasse for alle sprites								
************************************************************************************************/
import java.awt.*;
import java.applet.*;


public abstract class sprite {
	
	protected int x_pos;							// Objektets x..
	protected int y_pos;							// ..og y-posisjon

	protected MediaTracker tracker;		// Til å kontrollere nedlasting/oppdeling av bilder
	protected Applet parentApplet;			// En peker til hoved-appleten
	protected Graphics offScrGC;				// En peker til buffer-bildet
	
	protected Rectangle collision;		// Objektets kollisjons-rektangel 

	
	/***********************************************************************************************
		Metodenavn: draw
		Beskrivelse: abstakt metode for opptegning av en sprite
	************************************************************************************************/
	public abstract void draw();	
	
	
	/***********************************************************************************************
		Metodenavn: getPosX/getPosY
		Beskrivelse: returnerer spritens x/y-posisjon
	************************************************************************************************/
	public int getPosX(){return x_pos;}
	public int getPosY(){return y_pos;}

	
	/***********************************************************************************************
		Metodenavn: move
		Beskrivelse: beveger spriten til (x,y)-posisjonen
	************************************************************************************************/
	public void move(int x, int y){
		x_pos = x;
		y_pos = y;
	}

	
	/***********************************************************************************************
		Metodenavn: collisionDetected
		Beskrivelse: sjekker om spriten s kolliderer med denne
	************************************************************************************************/
	public boolean collisionDetected(sprite s){ 
		Rectangle r1 = new Rectangle(collision.x+x_pos,collision.y+y_pos,collision.width,collision.height);
		Rectangle r2 = new Rectangle(s.collision.x+s.x_pos,s.collision.y+s.y_pos,s.collision.width,s.collision.height);
 
		return r1.intersects(r2);
	} 

	/***********************************************************************************************
		Metodenavn: getCollision
		Beskrivelse: returnerer kollisjons-rektangelet til spriten
	************************************************************************************************/
	public Rectangle getCollision(){return collision;}

} // class sprite





/***********************************************************************************************
	Klassenavn: hero
	Arver: sprite
	Beskrivelse: kontrollerer Cliffs bevegelser
************************************************************************************************/
import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.Math;
import sprite;

public class hero extends sprite {
      
	private final String rawImageName = "cliffies2.gif";
	private final int totalPics = 12;		// Totalt antall animasjonsbilder
	private final int picWidth = 32;		  // Bredden..
	private final int picHeight = 32; 		// ..og høyden på animasjonsbildene
			
	private Image pics[];					// Animasjonsbilder av clifford
	private Image rawImage;				// Animasjonsbilder uoppstykket
	private int picnr = 4;					// Det aktive animasjonsbildet som skal vises 
	private boolean swingUp = true;		// Om armsvingen skal være opp/ned i animasjon
	private int moveStatus = 0;				
	private boolean resetMoves = true;		// true hvis en forandrer retning uten å slippe opp taster
	private int move = 0;
	
	private final int leap = 4;			// Antall pixels pr. bevegelse i en vilkårlig retning       

	private Polygon moveLimit;				// Begrenser bevegelighet (x/y)
		
	private final int timeBetweenMoves = 50;		//	Tidsintervall mellom hver bevegelse i ms 
	private long timeSinceLastMove;							
	
	private boolean isBlinking = false;
	private int blinks = 0;
	private final int maxBlinks = 20;
	private boolean blinkSwitch = 	true;
	
	
	/***********************************************************************************************
		Metodenavn: hero (konstruktør)
		Beskrivelse: henter bilde og lyd, deler opp grafikken i 12 animasjonsbilder
	************************************************************************************************/
	public hero(Polygon p,MediaTracker mt,int mt_id, Graphics g, Applet applet){
		
		parentApplet = applet;
		offScrGC = g;		
		moveLimit = p;
		
		x_pos = 385;					// X-koordinatet til spriten  
		y_pos = 30;					// Y-koordinatet til spriten
		
		pics = new Image[totalPics];
		tracker = mt;
		
		collision = new Rectangle(6,17,19,13);	
								
		// Henter inn og deler opp bildet i animasjonsbilder:

		try {
		
			// Henter inn:
			rawImage = parentApplet.getImage(parentApplet.getCodeBase(), rawImageName);
		   	tracker.addImage(rawImage, mt_id);
		   		   	
			// Stykker opp:
			for (int i = 0; i < totalPics; i++){
				pics[i] = parentApplet.createImage(new FilteredImageSource(rawImage.getSource(),new CropImageFilter(i*picWidth,0,picWidth,picHeight)) );		 		
				tracker.addImage(pics[i], mt_id);		
			}
		
		} catch (Exception e) {e.printStackTrace();}
		
		timeSinceLastMove = System.currentTimeMillis();
	}
	
	/***********************************************************************************************
		Metodenavn: insideLimits
		Beskrivelse: sjekker om x og y er innenfor spritens kollisjons-polygon
	************************************************************************************************/
	private boolean insideLimits(int x,int y){
		if (moveLimit.inside(x+collision.x, y+collision.y) &&
			  moveLimit.inside(x+collision.x, y+collision.y+collision.height) &&
		    moveLimit.inside(x+collision.x+collision.width, y+collision.y) && 
		    moveLimit.inside(x+collision.x+collision.width, y+collision.y+collision.height)) 
			return true;
		else 	return false;
	}
	
	/***********************************************************************************************
		Metodenavn: checkMoves
		Beskrivelse: bestemmer Cliffs gange utfra keyboard-input
	************************************************************************************************/
	public void checkMoves(Event e){
	
		if (!insideLimits(x_pos,y_pos)){ // I de tilfeller der Cliff går over grensa..
			switch (move){
				case 1: do {x_pos++;} while (!insideLimits(x_pos,y_pos)); break;
				case 2: do {x_pos--;} while (!insideLimits(x_pos,y_pos)); break;
				case 3: do {y_pos++;} while (!insideLimits(x_pos,y_pos)); break;
				case 4: do {y_pos--;} while (!insideLimits(x_pos,y_pos)); break;
			}
		}
		
		move = 0;	// Står stille..
			
		switch (e.key){	// Sjekker om cliff kan gå i ønsket retning  
			case Event.LEFT:	if (insideLimits(x_pos-leap+2,y_pos)) move = 1; break;
			case Event.RIGHT:	if (insideLimits(x_pos+leap-2,y_pos)) move = 2; break;
			case Event.UP:		if (insideLimits(x_pos,y_pos+2-leap)) move = 3; break;
			case Event.DOWN:	if (insideLimits(x_pos,y_pos-2+leap)) move = 4; break;
		}

		if (moveStatus != move && resetMoves == false){
			resetMoves = true;
		}
		moveStatus = move;
	}
	
	/***********************************************************************************************
		Metodenavn: walk
		Beskrivelse: animerer Cliffs gange
	************************************************************************************************/
	public void walk(){
		
		// Returner hvis ingen bevegelse eller ventettid mellom bevegelse ikke er over:
		if (moveStatus == 0 || System.currentTimeMillis() - timeSinceLastMove < timeBetweenMoves) return;	
		
		timeSinceLastMove = System.currentTimeMillis();
		
		if (resetMoves){	// Sjekker om flere piltaster er inne samtidig
			switch (moveStatus){
				case 1:	picnr = 7; break;	// VENSTRE
				case 2:	picnr = 1; break;	// HØYRE
				case 3:	picnr = 10; break;	// OPP
				case 4:	picnr = 4; break;	// NED
			}
			resetMoves = false;
		}

 		if (swingUp == true) picnr++; // Animerer spritens gange
 		else picnr--;
		
		// Sjekker om armene skal svinge fram eller tilbake:
	
		if (picnr % 3 == 0) swingUp = true;
		else if ((picnr+1)% 3 == 0) swingUp = false;
	
		switch (moveStatus){ // Flytter spriten
			case 1:	x_pos -= leap; break;	// VENSTRE
			case 2:	x_pos += leap; break;	// HØYRE
			case 3:	y_pos -= leap; break;	// OPP
			case 4:	y_pos += leap; break;	// NED
		}
	}

	
	/***********************************************************************************************
		Metodenavn: stop
		Beskrivelse: stopper Cliff
	************************************************************************************************/
	public void stop(){
		moveStatus = 0;
		resetMoves = true;
	} 

	/***********************************************************************************************
		Metodenavn: startBlinking
		Beskrivelse: setter Cliff til å blinke 
	************************************************************************************************/
	public void startBlinking(){isBlinking = true;}
	

	/***********************************************************************************************
		Metodenavn: draw
		Beskrivelse: tegner Cliff til applet'ens offscreen-bilde
	************************************************************************************************/
	public void draw(){

		if (!isBlinking){
			offScrGC.drawImage(pics[picnr],x_pos,y_pos,parentApplet);
		} else {
			if (blinkSwitch ^= true) offScrGC.drawImage(pics[picnr],x_pos,y_pos,parentApplet);
			if (++blinks == maxBlinks){
				blinks = 0;
				isBlinking = false;
			}
		}
	}
	
	/***********************************************************************************************
		Metodenavn: resetPos
		Beskrivelse: flytter Cliff til start-posisjon
	************************************************************************************************/
	public void resetPos(){
		move(385,30);
		picnr = 4;		
	}
	

} // Class hero




/***********************************************************************************************
	Klassenavn: car
	Arver: sprite
	Beskrivelse:	kontrollerer delvis en bils bevegelse
								farger en bil i en gitt RGB farge
************************************************************************************************/
import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.Math;
import sprite;

public class car extends sprite {

	private static final int totalPics = 2;			// Totalt antall animasjonsbilder
	private static final int picWidth = 61;			// Bredden..
	private static final int picHeight = 24; 		// ..og høyden på animasjonsbildene

	private Image pics[] = new Image[totalPics];		// Bilder av bilen i to retninger

	// Statisk data for "ufarget" bil:

	private static Image carsUnpainted = null;		
	private static final String rawImageName = "cars.gif";
	private static int carRawData[]=new int[picWidth*picHeight*16];
	private static int carData[]=new int[picWidth*picHeight*16];
	private static int colorTable[]= new int[256];
	private static final int transparentColor = 8421504;
	
	private AudioClip honkSound = null;
	private final int timeBetweenHonks = 700;
	private long timeSinceHonk;

			
	private int picnr = 1;							// Det aktive animasjonsbildet som skal vises 
	private int moveStatus = 0;				

	private int speed = 6;			// Antall pixels pr. bevegelse i en vilkårlig retning       
	
	/***********************************************************************************************
		Metodenavn: car (konstruktør)
		Beskrivelse: setter farge på en bil, henter inn lyd/bilde		
	************************************************************************************************/
		
	public car(double red, double green, double blue, Graphics g, Applet applet){

		parentApplet = applet;
		offScrGC = g;		
		pics = new Image[totalPics];
		tracker = new MediaTracker(parentApplet);
		
		honkSound = parentApplet.getAudioClip(parentApplet.getCodeBase(),"horn.au");
		honkSound.play(); honkSound.stop(); // For å starte nedlasting (Netscape venter til lyden skal spilles)
		
		x_pos = -100;
		y_pos = -100;
		
		collision = new Rectangle(3,6,55,16); // Rektangel for å avgjøre kollisjon med andre spriter

		if (carsUnpainted == null){	// Hvis det statiske bildet ikke er hentet..
			try {
				// Henter inn:
				
				carsUnpainted = parentApplet.getImage(parentApplet.getCodeBase(), rawImageName);
		   	tracker.addImage(carsUnpainted,99);
				tracker.waitForID(99);
		
			} catch (Exception e) {e.printStackTrace();}
	
			// Bruker pixelgrabber for å kunne forandre fargen på hver pixel i bildet:
			
			PixelGrabber pg;
			pg = new PixelGrabber(carsUnpainted,0,0,totalPics*picWidth,picHeight,carRawData,0,totalPics*picWidth);

			try	{pg.grabPixels();	}	catch(InterruptedException e) {}
		}
		
		makeColorTable(red,green,blue);	// lager farger til bilen
		prepareAndPaint();								// lager bil med farge
	}
	
	/***********************************************************************************************
		Metodenavn: honk
		Beskrivelse: lager en tutelyd
	************************************************************************************************/	
	public void honk(){
		if (System.currentTimeMillis() - timeSinceHonk > timeBetweenHonks){
			timeSinceHonk = System.currentTimeMillis();
			honkSound.play();
		}
	}
	
	/***********************************************************************************************
		Metodenavn: inHonkRange
		Beskrivelse: sjekker om en sprite er innenfor bilens tute-rekkevidde
	************************************************************************************************/
	public boolean inHonkRange(sprite s){

		Rectangle r1 = new Rectangle(collision.x+x_pos,collision.y+y_pos,collision.width,collision.height);
		Rectangle r2 = new Rectangle(s.collision.x+s.x_pos,s.collision.y+s.y_pos,s.collision.width,s.collision.height);

		//r1.grow(0,10);	
		
		if (moveStatus == 1) r1.x += 20*speed;
		else if (moveStatus == 2) r1.x -= 20*speed; 
		 
		return r1.intersects(r2);
	} 
	
	
	/***********************************************************************************************
		Metodenavn: initDrive
		Beskrivelse: tilbakestiller bilens bevegelse
	************************************************************************************************/
	public void initDrive(int status, int x,int y, int sp){
		x_pos = x; y_pos = y;
		moveStatus = status;
		
		if (status == 1) picnr = 1;
		else picnr = 0;
		
		speed = sp;  
	}
	
	/***********************************************************************************************
		Metodenavn: drive
		Beskrivelse: beveger bilen i en bestemt retning (hvis mulig)
	************************************************************************************************/
	public void drive(){
		if (moveStatus == 0) return;	// bilen står stille eller er ikke synlig
		
		if (moveStatus == 1) x_pos += speed;				// bilen går mot høyre..
		else if (moveStatus == 2) x_pos -= speed;	// ..eller mot venstre
	}
	
	
	/***********************************************************************************************
		Metodenavn: getStatus
		Beskrivelse: returnerer bilens bevegelses-status
	************************************************************************************************/
	public int getStatus(){return moveStatus;}
	
	/***********************************************************************************************
		Metodenavn: setStatus
		Beskrivelse: setter bilens bevegelses-status
	************************************************************************************************/
	public void setStatus(int s){moveStatus = s;}
	
	/***********************************************************************************************
		Metodenavn: stop
		Beskrivelse: stopper bilen
	************************************************************************************************/
	public void stop(){moveStatus = 0;}
	
	
	/***********************************************************************************************
		Metodenavn: prepareAndPaint
		Beskrivelse: deler opp bilbildet i to separate bilder, og gir bilen en farge
	************************************************************************************************/
	public void prepareAndPaint(){
		
		int i; 
		
		for (i=0; i<totalPics; i++)	{
			pics[i] = parentApplet.createImage(new MemoryImageSource(picWidth,picHeight,carData,i*picWidth,totalPics*picWidth));
			tracker.addImage(pics[i],totalPics);
		}
		
		for (i=0; i<totalPics*picWidth*picHeight; i++){
			if (carRawData[i] != transparentColor) carData[i]=colorTable[carRawData[i]&255];
			else carData[i]=carRawData[i];
		}
		
		for (i=0;i<totalPics;i++) pics[i].flush();
		
		try	{tracker.waitForID(2);	}	catch(InterruptedException e) {}	
	}
	
	
	/***********************************************************************************************
		Metodenavn: draw
		Beskrivelse: tegner bilen til applet'ens offscreen-bilde
	************************************************************************************************/
	public void draw(){
		offScrGC.drawImage(pics[picnr],x_pos,y_pos,parentApplet);
	}
		
	/***********************************************************************************************
		Metodenavn: makeColorTable
		Beskrivelse: lager en farge tabell av 256 farger brukt til farging av biler
	************************************************************************************************/
	public void makeColorTable(double r, double g, double b){
		
		double rt=0,gt=0,bt=0,rd=2.0*r+0.01,gd=2.0*g+0.01,bd=2.0*b+0.01,shift;
		int i=0, j;
		
		for (j=0;j<4;j++){
			while ((rt<255.4) && (gt<255.4) && (bt<255.4) && (i<256)){
				colorTable[i] = 0xff000000 | (((int)rt)<<16) | (((int)gt)<<8) | ((int)bt);
				rt += rd;
				gt += gd;
				bt += bd;
				i++;
			}
			
			if (rt>255.4)	{rd=0; rt=255.1;}
			if (gt>255.4)	{gd=0; gt=255.1;}
			if (bt>255.4)	{bd=0;	bt=255.1;}
			
			shift = 2.0*(r+g+b)/(0.01+rd+gd+bd);
			rd *= shift; gd *= shift; bd *= shift;
		}
	}


}	// class car



/***********************************************************************************************
	Klassenavn: dog
	Arver: sprite
	Beskrivelse:	styrer gangen til en hund
************************************************************************************************/
import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.Math;
import sprite;

public class dog extends sprite {
           
	private final String rawImageName = "doggy.gif";
	private final int totalPics = 12;		// Totalt antall animasjonsbilder
	private final int picWidth = 40;		// Bredden..
	private final int picHeight = 40; 		// ..og høyden på animasjonsbildene
			
	private Image pics[];					// Animasjonsbilder av clifford
	private Image rawImage;				// Animasjonsbilder uoppstykket
	private int picnr = 4;					// Det aktive animasjonsbildet som skal vises 
	private boolean swingUp = true;		// Om armsvingen skal være opp/ned i animasjon
	private int moveStatus = 0;				
	private boolean resetMoves = true;		// true hvis en forandrer retning uten å slippe opp taster
	
	private AudioClip barkSound = null;	// Bjeffe-lyd
	private final int timeBetweenBarks = 800; // Ant. ms bjeffe-lyden varer
	private long timeSinceBark;

	private static Random random;
	
	private int stepsToWalk; // Antall skritt mellom hver retningsforandring (eller stillstand)
	private final int maxStepsToWalk = 100;
	
	private final int leap = 4;			// Antall pixels pr. bevegelse i en vilkårlig retning       

	private Polygon moveLimit;		  		// Begrenser bevegelighet til hunden
		
	private int timeBetweenMoves;		 //	Tidsintervall mellom hver bevegelse i ms 
	private final int maxTimeBetweenMoves = 120;
	private long timeSinceLastMove;							
		
	static {
		random = new Random();
	}
	
	/***********************************************************************************************
		Metodenavn: dog (konstruktør)
		Beskrivelse:	gir hunden en tilfeldig retning/posisjon innenfor bevegelses-polygonet
									henter inn lyd og bilde, og deler bildet i 12 animasjonsbilder
	************************************************************************************************/
	public dog(Polygon p,MediaTracker mt,int mt_id, Graphics g, Applet applet){
		
		parentApplet = applet;
		offScrGC = g;		
		moveLimit = p;
	
		Rectangle r = moveLimit.getBoundingBox();
		
		// Setter hunden et tilfeldig sted innenfor polygonets grenser:
		collision = new Rectangle(13,13,15,15);			
					
		do {
			x_pos = Math.abs(random.nextInt()) % r.width+r.x;
			y_pos = Math.abs(random.nextInt()) % r.height+r.y;
		} while (!insideLimits(x_pos,y_pos));
			
		pics = new Image[totalPics];
		tracker = mt;
		
		barkSound = parentApplet.getAudioClip(parentApplet.getCodeBase(),"bark.au");
		barkSound.play(); barkSound.stop(); // For å starte nedlasting (Netscape venter til lyden skal spilles)

		timeSinceBark = System.currentTimeMillis();	
								
		// Henter inn og deler opp bildet i animasjonsbilder:

		try {
		
			// Henter inn:
			rawImage = parentApplet.getImage(parentApplet.getCodeBase(), rawImageName);
		  tracker.addImage(rawImage, mt_id);
		   		   	
			// Stykker opp:
			for (int i = 0; i < totalPics; i++){
				pics[i] = parentApplet.createImage(new FilteredImageSource(rawImage.getSource(),new CropImageFilter(i*picWidth,0,picWidth,picHeight)) );		 		
				tracker.addImage(pics[i], mt_id);		
			}
		
		} catch (Exception e) {e.printStackTrace();}
		
		timeSinceLastMove = System.currentTimeMillis();
	}
	
	/***********************************************************************************************
		Metodenavn: bark
		Beskrivelse: lager en bjeffelyd
	************************************************************************************************/
	public void bark(){
		if (System.currentTimeMillis() - timeSinceBark > timeBetweenBarks){
			timeSinceBark = System.currentTimeMillis();
			barkSound.play();
		}
	}

	/***********************************************************************************************
		Metodenavn: insideLimits
		Beskrivelse: sjekker om x og y er innenfor spritens kollisjons-polygon
	************************************************************************************************/
	private boolean insideLimits(int x,int y){
		if (moveLimit.inside(x+collision.x, y+collision.y) &&
			  moveLimit.inside(x+collision.x, y+collision.y+collision.height) &&
		    moveLimit.inside(x+collision.x+collision.width, y+collision.y) && 
		    moveLimit.inside(x+collision.x+collision.width, y+collision.y+collision.height)) 
			return true;
		else 	return false;
	}
	
	/***********************************************************************************************
		Metodenavn: checkMoves
		Beskrivelse: bestemmer hundens tilfeldige gange 
	************************************************************************************************/
	public void checkMoves(){
	
		if (!insideLimits(x_pos,y_pos)){ // I de tilfeller der Cliff går over grensen..
			
			switch (moveStatus){
				case 1: do {x_pos++;} while (!insideLimits(x_pos,y_pos)); break;
				case 2: do {x_pos--;} while (!insideLimits(x_pos,y_pos)); break;
				case 3: do {y_pos++;} while (!insideLimits(x_pos,y_pos)); break;
				case 4: do {y_pos--;} while (!insideLimits(x_pos,y_pos)); break;
			}
		}
				
		if (stepsToWalk-- == 0){
			
			if (moveStatus == 0) timeBetweenMoves = Math.abs(random.nextInt()) % maxTimeBetweenMoves+1;
			
			moveStatus = Math.abs(random.nextInt()) % 5;
			
			if (moveStatus != 0)	stepsToWalk = Math.abs(random.nextInt()) % maxStepsToWalk+1;
			else stepsToWalk = Math.abs(random.nextInt()) % maxStepsToWalk/5+1;
		}
				
		switch (moveStatus){	// Sjekker om hunden skal snu eller ikke
			case 1 :	if (!insideLimits(x_pos-leap+2,y_pos)) stepsToWalk = 0; break; // LEFT
			case 2 :	if (!insideLimits(x_pos+leap-2,y_pos)) stepsToWalk = 0; break; // RIGHT
			case 3 :	if (!insideLimits(x_pos,y_pos+2-leap)) stepsToWalk = 0; break; // UP
			case 4 :	if (!insideLimits(x_pos,y_pos-2+leap)) stepsToWalk = 0; break; // DOWN
		}
	}
	

	/***********************************************************************************************
		Metodenavn: walk
		Beskrivelse: animerer hundens gange
	************************************************************************************************/
	public void walk(){
		
		// Returner hvis ingen bevegelse eller ventettid mellom bevegelse ikke er over:
		if (moveStatus == 0 || System.currentTimeMillis() - timeSinceLastMove < timeBetweenMoves) return;	
		
		timeSinceLastMove = System.currentTimeMillis();
		
		switch (moveStatus){
			case 1:	picnr = 7; break;	// VENSTRE
			case 2:	picnr = 1; break;	// HØYRE
			case 3:	picnr = 10; break;	// OPP
			case 4:	picnr = 4; break;	// NED
		}
			

 		if (swingUp == true) picnr++; // Animerer sprite'ns gange
 		else picnr--;
		
		// Sjekker om beina skal svinge fram eller tilbake:
	
		if (picnr % 3 == 0) swingUp = true;
		else if ((picnr+1)% 3 == 0) swingUp = false;
	
		switch (moveStatus){ // Flytter sprite'n
			case 1:	x_pos -= leap; break;	// VENSTRE
			case 2:	x_pos += leap; break;	// HØYRE
			case 3:	y_pos -= leap; break;	// OPP
			case 4:	y_pos += leap; break;	// NED
		}
	}

	/***********************************************************************************************
		Metodenavn: changeCourse
		Beskrivelse: tvinger hunden til skifte retning
	************************************************************************************************/
	public void changeCourse(){stepsToWalk = 0;}
	
	/***********************************************************************************************
		Metodenavn: inBarkRange
		Beskrivelse: sjekker en sprite er innenfor hundens bjefferekkevidde
	************************************************************************************************/
	public boolean inBarkRange(sprite s){

		Rectangle r1 = new Rectangle(collision.x+x_pos,collision.y+y_pos,collision.width,collision.height);
		Rectangle r2 = new Rectangle(s.collision.x+s.x_pos,s.collision.y+s.y_pos,s.collision.width,s.collision.height);

		
		switch (moveStatus){ // Flytter sprite'n
			case 1:	r1.grow(0,18); r1.x -= 60; break;	// VENSTRE
			case 2:	r1.grow(0,18); r1.x += 60; break;  // HØYRE
			case 3:	r1.grow(18,0); r1.y -= 60; break;	// OPP
			case 4:	r1.grow(18,0); r1.y += 60; break;	// NED
		}
 		 
		return r1.intersects(r2);
	} 
	
	
	/***********************************************************************************************
		Metodenavn: draw
		Beskrivelse: tegner hunden til appletens offscreen-bilde
	************************************************************************************************/
	public void draw(){
		offScrGC.drawImage(pics[picnr],x_pos,y_pos,parentApplet);		
	}

} // Class dog


/***********************************************************************************************
	Klassenavn: intro
	Arver: - 
	Beskrivelse: styrer meny-overgangene
************************************************************************************************/
import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.Math;

public class intro {

	private MediaTracker tracker;		// Til å kontrollere nedlasting
	private Applet parentApplet;			// En peker til hoved-appleten
	private Graphics offScrGC;				// En peker til buffer-bildet
		                      
	private final String imageName = "introscreen.gif";
	private final String soundImageName = "sound.gif";
	private final int picWidth = 668;		// Bredden..
	private final int picHeight = 400; 	// ..og høyden på bildet
			
	private final int soundPosX = 71;	 // X og Y for hvor lydmerket
	private final int soundPosY = 194; // skal tegnes
	
	private int offScrDrawY;	// Hvor bildet skal tegnes for bruk av copyArea-funk.
	
	private boolean music = true;
	private Image soundPics[] = new Image[2]; 
	private Image rawSoundImage;
	
	private final int numberOfButtons = 5;
		
	// ID for de Forskjellige knappene:
	
	public final int BUTTON_hiscores = 0;
	public final int BUTTON_music = 1;
	public final int BUTTON_newgame = 2;
	public final int BUTTON_quotes = 3;
	public final int BUTTON_help = 4;	
	
	
	// Status for overgang mellom spill/intro-bilde:
		
	private int transStatus = 0; // 0 = i introstilling, 1 = i spillmodus,  
					         							// 2 = overgang til spill, 3 = overgang fra spill  
	
	private int transX = 0; // Avstanden de to bildehalvdene har fra midten
	private final int transSpeed = 20;
		
	// Rectangler for de forskjellige knappene på bildet:
	private final int buttons[][] = {{12,156,117,29}, {12,189,93,28}, {550,140,118,29},
															      {573,172,96,28}, {597,204,72,29}};
	
	private Image introPic;	// Intro-bildet
	private boolean hasPaintedOnce = false;
		
	
	/***********************************************************************************************
		Metodenavn: intro (konstruktør) 
		Beskrivelse: initierer, henter/kutter opp "music"-ikonet 
	************************************************************************************************/	
	public intro(int y, MediaTracker mt, int mt_id, Graphics g, Applet applet){
		
		parentApplet = applet;
		offScrGC = g;		
		tracker = mt;
		
		offScrDrawY = y;
						
		try {
			// Henter inn:
			rawSoundImage = parentApplet.getImage(parentApplet.getCodeBase(), soundImageName);
			introPic = parentApplet.getImage(parentApplet.getCodeBase(), imageName);
	   	
	   	tracker.addImage(rawSoundImage, mt_id+99);
	   	tracker.addImage(introPic, mt_id);

		} catch (Exception e) {e.printStackTrace();}
	
		// Deler opp bildet i to musikk-ikoner (av/på):
		soundPics[0] = parentApplet.createImage(new FilteredImageSource(rawSoundImage.getSource(),new CropImageFilter(0,0,19,18)) );		 		
		soundPics[1] = parentApplet.createImage(new FilteredImageSource(rawSoundImage.getSource(),new CropImageFilter(19,0,19,18)) );		 		
		
		tracker.addImage(soundPics[0], mt_id+100);
		tracker.addImage(soundPics[1], mt_id+100);
	}
	
	/***********************************************************************************************
		Metodenavn: checkButton 
		Beskrivelse: returnerer ID til knappen som er blitt trykket (-1 hvis ingen)
	************************************************************************************************/	
	public int checkButton(int x,int y){
		int activeButton = -1;
		
		for (int i = 0; i < numberOfButtons; i++)
			if ((new Rectangle(buttons[i][0],buttons[i][1],buttons[i][2],buttons[i][3])).inside(x,y)){
				activeButton = i;
				break;
			} 
	
		return activeButton;	
	}


	/***********************************************************************************************
		Metodenavn: musicEnabled
		Beskrivelse: returnerer true hvis musikken er på
	************************************************************************************************/	
	public boolean musicEnabled(){return music;}
	
	
	/***********************************************************************************************
		Metodenavn: changeMusic
		Beskrivelse: slår av/på musikken
	************************************************************************************************/	
	public void changeMusic(){
		music ^= true;
		offScrGC.drawImage(soundPics[(music ? 1:0)],soundPosX,offScrDrawY+soundPosY,parentApplet); 
	} 
	
	
		
	/***********************************************************************************************
		Metodenavn: inTransition, inClosingTransition, inOpeningTransition, 
		            transitionOpen, transitionClosed 
		Beskrivelse: metoder for å avgjøre om intro-bildet er "åpnet"/"lukket" eller i mellomtilstand
	************************************************************************************************/	
	public boolean inTransition(){return transStatus > 1;}
	public boolean inClosingTransition(){return transStatus == 3;}
	public boolean inOpeningTransition(){return transStatus == 2;}
	public boolean transitionOpen(){return transStatus == 1;}
	public boolean transitionClosed(){return transStatus == 0;}
		
	
	/***********************************************************************************************
		Metodenavn: doTransition
		Beskrivelse: setter igang overgangen fra/til intro-bildet
	************************************************************************************************/	
	public void doTransition(){
		if (transStatus == 0) transStatus = 2;
		else if (transStatus == 1) transStatus = 3;		
	}
	
		
	/***********************************************************************************************
		Metodenavn: draw
		Beskrivelse: tegner introbildet ved bruk av copyArea
	************************************************************************************************/	
	public void draw(){
		
		// Sjekker om bildet har blitt tegnet til offscreen-bildet
		
		if (hasPaintedOnce){
		
			if (transStatus == 0) // Ingen overgang
				offScrGC.copyArea(0,offScrDrawY,picWidth,picHeight,0,-offScrDrawY-1); 
			
			else if (transStatus == 2){ // Overgang til spill  
				transX += transSpeed;
				if (transX >= picWidth/2) transStatus = 1;
				
				offScrGC.copyArea(0,offScrDrawY,picWidth/2,picHeight,-transX,-offScrDrawY-1);
				offScrGC.copyArea(picWidth/2,offScrDrawY,picWidth/2,picHeight,transX,-offScrDrawY-1);
			}
			else if (transStatus == 3){ // Overgang fra spill
				transX -= transSpeed;
				if (transX <= 0) transStatus = 0;
	
				offScrGC.copyArea(0,offScrDrawY,picWidth/2,picHeight,-transX,-offScrDrawY-1);
				offScrGC.copyArea(picWidth/2,offScrDrawY,picWidth/2,picHeight,transX,-offScrDrawY-1);
			}
		
		} else {
			// Kopierer bildet til offscreen-bildet ved første opptegning:
			offScrGC.drawImage(introPic,0,offScrDrawY,parentApplet); 
			hasPaintedOnce = true;														
		}
	}
	

} // Class intro





/***********************************************************************************************
	Klassenavn: help
	Arver: Frame
	Beskrivelse: åpner et hjelp-vindu
************************************************************************************************/
import java.awt.*;
import java.net.URL;
import java.applet.*;

public class help extends Frame {

	private Font fb, fp, fg;
	private int status = 0;
	private Color bgcolor = null;
						  
  /***********************************************************************************************
		Metodenavn: help (konstuktør)
		Beskrivelse: legger til meny-valg og setter vinduets størrelse
	************************************************************************************************/
	public help(String title) {
    
    super(title);
    bgcolor = new Color(0,115,63);
    setBackground(bgcolor);
    setLayout(new GridLayout(1,1));
  
    fb = new Font("Helvetica", Font.BOLD, 12);
		fp = new Font("Helvetica", Font.PLAIN, 12);
		fg = new Font("Helvetica", Font.BOLD, 18);		
	
		MenuBar mb = new MenuBar();
    Menu m = new Menu("Menu");
    m.add(new MenuItem("Help"));
    m.add(new MenuItem("About"));
    m.add(new MenuItem("-"));
    m.add(new MenuItem("Exit"));
    mb.add(m);
    setMenuBar(mb);	    
    status = 1;
    
    move(100,100);
	
		resize(600,450);
	}

  /***********************************************************************************************
		Metodenavn: action
		Beskrivelse: forandrer status når noe har blitt valgt fra menyen
	************************************************************************************************/
	public boolean action(Event evt, Object arg) {
    String label = (String)arg;
    
    if (evt.target instanceof MenuItem) {    	
      if (label.equals("Help")) status = 1;
     	else if (label.equals("About")) status = 2;
     	else if (label.equals("Exit")) {
      	status = 1;
      	hide();
      }
    	repaint();
			return true;
    }
    else return false;		
  } 
    
  /***********************************************************************************************
		Metodenavn: handleEvent
		Beskrivelse: lukker hjelp-vinduet 
	************************************************************************************************/
	public boolean handleEvent(Event evt) {
 		if (evt.id == Event.WINDOW_DESTROY) {
     	status = 1;
     	hide();
  	}
	 return super.handleEvent(evt);
  }
    
  /***********************************************************************************************
		Metodenavn: paint
		Beskrivelse: skriver ut tekst til vinduet avhenging av status
	************************************************************************************************/
	public void paint(Graphics g) {
  	  	  	
  	if (status == 1)
  	{		
  			g.setColor(bgcolor);
  			g.fillRect(0,0,600, 450);
  			g.setColor(Color.white);
  	 		g.setFont(fb);
  	 		g.drawString("The plot:", 20, 100);
  	 		g.setFont(fp);
  	 		g.drawString("In this game you have the great opportunity to be a mailman (more correct: Cliff Clavin from Cheers).", 20, 115);
  	 		g.drawString("You start the game with four lives, a full beer mug, the score of zero points and you got five minutes", 20, 130);
  	 		g.drawString("to complete the round.", 20, 145);
  	  	g.drawString("You maneuver Cliff by using the arrow keys. When you stand in front of the correct mail box (marked", 20, 160);
  	 		g.drawString("with a red arrow), you press \"enter\" to deliver the letter. You have probably already figured out", 20, 175);
  	 		g.drawString("that this is how you collect points in this game. The red arrow will then move to the next mailbox.", 20, 190);
 	  	 	g.drawString("It's not an easy job working as a mailman. You have to look out for cars and dogs. If a car hits you or", 20, 205);
 	  	 	g.drawString("a dog bites you, you will lose a life. It's also important that you make regular visits to the bar", 20, 220);
 	  	 	g.drawString("Cheers to fill up with beer, or else you will lose a life. When you lose a life, Cliff will automatically go", 20, 235);
 	  		g.drawString("to his favorite place to have a beer, and lick his wounds.", 20, 250);
 	  		g.setFont(fb);
  	 		g.drawString("Keys:", 20, 300);	
  	 		g.setFont(fp);
  	 		g.drawString("Arrow keys - Move Cliff in the direction the arrow points.", 20, 315);	
  	 		g.drawString("Enter - Deliver the letter or enter Cheers.", 20, 330);	
  	 		g.drawString("p - Pause the game.", 20, 345);	
  	 		g.drawString("F1 or h - Help.", 20, 360);
  	 		g.drawString("Esc or q - Quit current game.", 20, 375);  	 			 		
  	}
  	else if (status == 2)
  	{  			
  	 		g.setColor(bgcolor);
  			g.fillRect(0,0,600,450);
  			g.setColor(Color.white);
  	 		drawCenterText(fg, 100, "Game developers:", g);	
  	 		drawCenterText(fg, 150, "Robert Nilsen   (Programming/graphics)", g);
  	 		drawCenterText(fg, 200, "Mikkel Sjølie    (Programming/graphics)", g);
  	 		drawCenterText(fg,  300, "Copyright 1998", g); 		 	 		
  	}
  }  
  
  /***********************************************************************************************
		Metodenavn: drawCenterText
		Beskrivelse: sentrerer tekst
	************************************************************************************************/
  public void drawCenterText(Font fo, int y, String s, Graphics gr) {
  
	  int l = getFontMetrics(fo).stringWidth(s);
	  gr.setFont(fo);
  	gr.drawString(s, (size().width - l)/2, y);
  }
  	 
} // class help





/***********************************************************************************************
	Klassenavn: hiscoredialog
	Arver: Dialog
	Beskrivelse: lar deg skrive inn navnet ditt på en hiscore-liste	
************************************************************************************************/
import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.Math;
import cliffie;

public class hiscoredialog extends Dialog {
	
	private TextField name;
	private Label text;
	private int score;
		
	/***********************************************************************************************
		Metodenavn: hiscoredialog (konstruktør) 
		Beskrivelse: setter opp en dialogboks med tekstfelt og knapp  
	************************************************************************************************/
	public hiscoredialog(Frame parent){
			
			super(parent,"Congratulations!",true);
			
			setLayout(new FlowLayout());
			setBackground(Color.white);
			setFont(new Font("Helvetica",Font.PLAIN,12));
						
	
			text = new Label("Enter your name for the hiscore list", Label.CENTER); 
			add(text);
			
			name = new TextField(10);
			add(name);
			
			Button ok = new Button("OK");
			add(ok); 
								
			resize(230,120);
			setResizable(false);
			move(300,200);
	} 
	
	/***********************************************************************************************
		Metodenavn: enterHiscore 
		Beskrivelse: viser dialog for innskriving av navn
	************************************************************************************************/
	public void enterHiscore(int s){
		score = s;
		show();
		name.requestFocus();
	}
	
	/***********************************************************************************************
		Metodenavn: getName 
		Beskrivelse: returnerer navnet fra dialogboksen
	************************************************************************************************/
	public String getName(){return name.getText();}
	
	
	/***********************************************************************************************
		Metodenavn: action  
		Beskrivelse: registrerer og behandler trykk på OK-knapp
	************************************************************************************************/
	public boolean action(Event evt, Object arg){
		if ((String)arg == "OK"){
			hide();
			return true;
		}	else return false;
	}
	
} // class hiscoredialog



/***********************************************************************************************
	Klassenavn: quotes
	Arver: - 
	Beskrivelse:	laster inn og velger ut et sitat
************************************************************************************************/
import java.awt.*;
import java.applet.*;
import java.io.*;
import java.util.*;
import java.net.URL;

public class quotes {
	
	private InputStream quoteFile = null;
	private final int numberOfQuotes = 45;
	private String theQuotes[];
	private String line = null;
	private int counter = 0;
	private int activeQuote;
	private Random rand = null;
	private Applet parentApplet;
	
	/***********************************************************************************************
		Metodenavn: quotes (konstruktør)
		Beskrivelse: henter inn quotes fra fil 
	************************************************************************************************/
	public	quotes(Applet applet) {
				
		parentApplet = applet;
		rand = new Random();

		theQuotes = new String[numberOfQuotes];
				
		try {
			quoteFile = new URL(parentApplet.getCodeBase(), "quotes.txt").openStream();		
		} catch(Exception e) {}
	
		DataInputStream textFile = new DataInputStream(quoteFile);
		
		try {
			
			while ((line = textFile.readLine()) != null) {
				theQuotes[counter] = line;
				counter++;				
			}	
		} catch (IOException e) {
		} finally {
			try{
				textFile.close();
			} catch (IOException e){}
		}	
		
	}	

	/***********************************************************************************************
		Metodenavn: getQuote 
		Beskrivelse: returnerer et tilfeldig sitat
	************************************************************************************************/
	public String getQuote() {
		activeQuote = Math.abs(rand.nextInt()) % numberOfQuotes;
		return theQuotes[activeQuote];
	}  	

} // class quotes






