/* Spiel zu Weihnachten 2000 
 * Frei kopierbar, aber nur vollständig mit diesem Spiel.c und Makefile und 
 * readme.txt. Veränderungen möglich, bei Weitergabe aber immer Original
 * Spiel.c mit beilegen,		
 * Keine Lizenzierungen auf Teile des oder ganzes Programm(s)*/
//***Vereinbarungen****************************
//---Includes, Konstanten----------------------

#include <GL/glut.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

const short MAXFELD=4;		//Maximale Felderanzahl in Spalte, Zeile
				//sollte Geradzahlig sein, 2/4/6/8/10
				//Mit Zwischenräumen: *2+1
const short MAXEBENEN=50;	//Maximal 100 Ebenenstrukturen in Feld
				//0: Game Over-Ebene, 1..48 Normal, 
				//49: Gewinn-Ebene, sollte 3..100
const float FELDEINHEIT=0.5;    //0.5*10=5 GL-Einheiten Hügel-/Baumgröße
				//0.1..1
const short MAXFELDHOEHE=10;	//für vertikale Grössen, wie FELDEINHEIT
				//2..10
const short CHVERLIEREN=10;	//Chance verlieren 1:x,  2..65535
const short ZUMGEWINN=5050;	//div100: gfertig, mod100: kfertig, 101..9999
const short EINFACH=1;		//0/1, bei 1: zeigt besuchte Felder an;

//---Typen- und Klassendefiniton----------------

typedef enum{typlos, baum, huegel} feldtyp;

class Geschenke
  {
	public: 
		Geschenke(int kanzfertig, int ganzfertig);
		~Geschenke();
		void AddAnz(int kplus, int gplus);
		int  Fertig();
		int Punkte();
		void DrawGeschenke();
	private:
		int kanz, ganz, kfertig, gfertig;
  };

class Mann
  {
	public:
		Mann(float neugroesse, float neubreite);
		~Mann();
		void Fahrstuhl(int neuebene);
		void AddPos(int x, int y);
		void NeuRichtung(int winkel);
		void DrawMann();
	private:
		float groesse, breite;
		int   posx, posy, richtung;
  };

class Feld
  {	public:
		Feld(feldtyp neutyp, int neuhoehe, int neudarunter);
		~Feld();
		void     Change(feldtyp neutyp, int neuhoehe,
				 int neudarunter);
		int      Leeren();
                feldtyp  GetTyp();
		void DrawFeld(int x, int y);
	private:
		feldtyp typ;
		int darunter;
		int hoehe;
  };

class Ebene
  {	public:
		Ebene(int neunummer);
		~Ebene();
                int IstDortBaum(int x, int y);
		void TrigFahrstuhl();
		void DrawNummer();
		void DrawEbene();
	private:
		Feld *felder[MAXFELD][MAXFELD];
		int fahrstuhl;
		int nummer;
  };

//---globale Variablen----------------------------

int		aktxwinkel, aktywinkel, aktzwinkel;
float		aktxtrans, aktytrans, aktztrans;
int 		trans=0, winkel=1;
int		aktebene, punkte, ende=0;
GLUquadricObj 	*qobj; 
Ebene		*spielraum[MAXEBENEN-2];
short		raumused[MAXEBENEN-2];
Mann		*weihmann;		
Geschenke	*schenken;
	
//***Methoden der Klassen*************************
//---Klasse Geschenke-----------------------------

Geschenke::Geschenke(int kanzfertig, int ganzfertig)
{
  this->kfertig = kanzfertig;
  this->gfertig = ganzfertig;
  this->kanz = 0;
  this->ganz = 0;
}

Geschenke::~Geschenke()
{
  this->kanz = 0; this->ganz = 0;
  this->kfertig = 0; this->gfertig = 0;
}

void Geschenke::AddAnz(int kplus, int gplus)
{
  if (kplus > 0) this->kanz = this->kanz + kplus;
  if (gplus > 0) this->ganz = this->ganz + gplus;
}

int Geschenke::Fertig()
{
  int erg=0;
  
  if ((this->kanz >= kfertig) && (this->ganz >=gfertig)) erg = 1;
  return erg;
};

int Geschenke::Punkte()
{
  int erg;
  
  erg = this->ganz*100 + this->kanz;
  return erg;
}

void Geschenke::DrawGeschenke()
{
  int i, maxzeile, anzdraw;

  maxzeile = MAXFELD*4;
  glPushMatrix();
  glTranslatef(-MAXFELD*2, MAXFELD*3, MAXFELD*2);
   
  glColor3f(1.0, 1.0, 0.0);
  if (this->gfertig > this->ganz) anzdraw = this->gfertig;
    else anzdraw = this->ganz;
  for(i=1; i<=anzdraw; i++)
    {
	if ((i > this->ganz) && (i <= this->gfertig)) 
	  glColor3f(0.5, 0.5,0.5);
	glTranslatef(FELDEINHEIT, 0, 0);
 	if (((i-1) % maxzeile == 0) && (i > 1) ) 	 
	glTranslatef(-(maxzeile)*FELDEINHEIT, -FELDEINHEIT, 0);
  	glRectf(0, 0, 2*FELDEINHEIT/3, FELDEINHEIT/2);
     }
  glColor3f(0.6, 0.6, 1.0);
  glTranslatef(-(anzdraw % maxzeile)*FELDEINHEIT, -MAXFELDHOEHE/10, 0);
  if (this->gfertig > this->ganz) anzdraw = this->gfertig;
    else anzdraw = this->ganz;
  for(i=1; i<=anzdraw; i++)
    {
	if ((i > this->kanz) && (i <= this->kfertig))
	  glColor3f(0.5, 0.5, 0.5);
	glTranslatef(FELDEINHEIT, 0, 0);
	if ( ((i-1) % maxzeile == 0) && (i > 1) )
	  glTranslatef(-(maxzeile)*FELDEINHEIT, -FELDEINHEIT, 0);
	glRectf(0, 0, FELDEINHEIT/2, FELDEINHEIT/2);
    } 
  glPopMatrix();
}

//---Klasse Mann----------------------------------

Mann::Mann(float neugroesse, float neubreite)
{
  this->groesse = neugroesse;
  this->breite = neubreite;
  this->posx = MAXFELD;
  this->posy = MAXFELD;
};

Mann::~Mann()
{
};

void Mann::Fahrstuhl(int neuebene)
{
  int zufall, altebene;

  if (schenken->Fertig() == 1) neuebene = MAXEBENEN-1;
  if (neuebene > 0)				//Sonst kein Fahrstuhl da
    {
	zufall = rand() % CHVERLIEREN;
	if (schenken->Fertig() == 1) neuebene = MAXEBENEN-1;
	  else if (zufall == 0) neuebene = 0;		//Pech gehabt
 
	if (aktebene == neuebene) 
          if (aktebene+1 < MAXEBENEN) neuebene++;
	    else neuebene--;
        if ((neuebene > 0) && (neuebene < MAXEBENEN-1))
	  {
		spielraum[aktebene-1]->TrigFahrstuhl();	//Vorbereiten
		spielraum[neuebene-1] = new Ebene(neuebene-1);
		spielraum[neuebene-1]->TrigFahrstuhl();

		this->posx = MAXFELD;			//Umschalten
		this->posy = MAXFELD;
		altebene = aktebene;
		aktebene = neuebene;

		spielraum[altebene-1]->TrigFahrstuhl();	//Wiederherstellen
		spielraum[neuebene-1]->TrigFahrstuhl();		 
	  }
	if (neuebene ==  0) { punkte= -1; ende = 1; };
	if (neuebene == MAXEBENEN-1) 
	  { punkte= schenken->Punkte(); ende = 1; };
    }
};

void Mann::AddPos(int x, int y)
{
  int neuposx, neuposy, erg;

  neuposx = this->posx + x;
  neuposy = this->posy + y;
  if ((neuposx <= MAXFELD) && (neuposx >= -MAXFELD))
    {
	erg = spielraum[aktebene-1]->IstDortBaum(neuposx, neuposy);
	if (erg <= -1) this->posx = neuposx;
        if (erg >=  0) schenken->AddAnz(erg/10, erg % 10);
	if (erg <= -2) this->Fahrstuhl( -(erg+2) );
    }
  if ((neuposy <= MAXFELD) && (neuposy >= -MAXFELD))
    {
	erg = spielraum[aktebene-1]->IstDortBaum(neuposx, neuposy);
	if (erg <= -1) this->posy = neuposy;
	if (erg >=  0) schenken->AddAnz(erg/10, erg % 10);
	if (erg <= -2) this->Fahrstuhl( -(erg+2) );
    }
}

void Mann::NeuRichtung(int winkel)
{
  this->richtung = winkel;
}

void Mann::DrawMann()
{
  glPushMatrix();
  glTranslatef(this->posx * 2, 0, this->posy * 2);
  glRotatef(-90, 1, 0, 0);
  glRotatef(richtung, 0, 0, 1);

  glPushMatrix();
  glTranslatef(-(this->breite/2), this->breite/2, 0);
  glColor3f(0.0, 0.0, 0.0);
  gluCylinder(qobj, this->breite/2, this->breite/2, this->groesse/10, 10, 5);
  glTranslatef(this->breite/2*2, 0, 0);
  gluCylinder(qobj, this->breite/2, this->breite/2, this->groesse/10, 10, 5);
  glPopMatrix();

  glPushMatrix();
  glColor3f(1.0, 0.0, 0.0);
  glTranslatef(0, 0, this->groesse/10);
  gluCylinder(qobj, this->breite, this->breite/2, 7.5*(this->groesse)/10,
 		 10,5);
  glPopMatrix();

  glTranslatef(0, 0, 8.5*this->groesse/10);
  glColor3f(1.0, 0.7, 0.7);
  gluSphere(qobj, 1.2*this->groesse/10, 10, 10);
  glTranslatef(0, -this->groesse/20, this->groesse/20);
  glRotatef(45, 1, 0, 0);
  glColor3f(1.0, 0.0, 0.0);
  gluCylinder(qobj, 1.4*this->groesse/10, 0,
		this->groesse/3, 10, 5);   
  glPopMatrix();
}

//---Klasse Feld-----------------------------------------

Feld::Feld(feldtyp neutyp, int neuhoehe, int neudarunter)
{
  this->typ = neutyp;
  this->hoehe = neuhoehe;
  this->darunter = neudarunter;
}

Feld::~Feld()
{
  this->typ = typlos;
  this->hoehe = 0;
  this->darunter = 0;
};

void Feld::Change(feldtyp neutyp, int neuhoehe, int neudarunter)
{
  this->typ = neutyp;
  if (neuhoehe > 0) this->hoehe = neuhoehe;
  this->darunter = neudarunter;
}

int Feld::Leeren()
{
  int erg;

  erg = this->darunter;
  this->darunter = 0;
  if (this->typ == huegel)
    if (this->hoehe <= MAXFELDHOEHE/2)
      erg = 0;			//Huegel zu klein
  return erg;
};

feldtyp Feld::GetTyp()
{
  return this->typ;
}

void Feld::DrawFeld(int x, int y)
{
  glPushMatrix();
  glTranslatef(x*2, 0, y*2);	//y -> z
  glRotatef(-90, 1, 0, 0);
  if (this->typ == baum) 
    if ((this->darunter == 0) && (EINFACH > 0)) glColor3f(0, 0.5, 0);
      else glColor3f(0, 0.3, 0);
  if (this->typ == huegel)
    if ((this->darunter == 0) && (EINFACH > 0)) glColor3f(0.85, 0.85, 1);
     else glColor3f(0.85, 0.85, 0.85);
  if (this->typ != typlos)
    gluCylinder(qobj, 1, 0.5, (this->hoehe - 1)*FELDEINHEIT, 10, 5);
  glTranslatef(0, 0, (this->hoehe - 1)*FELDEINHEIT);
  if (this->typ == huegel) gluSphere(qobj, 0.5, 10, 10);
  if (this->typ == baum) gluCylinder(qobj, 0.5, 0, 2*FELDEINHEIT, 10, 5);
  glPopMatrix();
}

//---Klasse Ebene-------------------------------------

Ebene::Ebene(int neunummer)
{
  int 	  x,y, neuhoehe, anz, r1, r2;
  feldtyp neutyp;
  struct timeval  tv;
  struct timezone tz;

  gettimeofday(&tv, &tz);			//init Zufallsgenerator
  srand(tv.tv_sec % 65536);
  
  for(y=0; y<=MAXFELD-1; y++)
    for(x=0; x<=MAXFELD-1; x++)
      {
	r1 = rand()% 3; r2 = rand() % 2;
	if (r1 == 0) neutyp = huegel;
	        else neutyp = baum;
	r1 = (rand() % MAXFELDHOEHE/2)+1;
	if (neutyp == baum) neuhoehe = r1*2;
	   else if (r2 == 0) neuhoehe = r1;
	          else neuhoehe = MAXFELDHOEHE/2 + r1;
	r1 = (rand() % MAXFELDHOEHE);
	r2 = (rand() % MAXFELDHOEHE);
	anz = (r1*10 + r2);
        if (neutyp == huegel) anz = anz % (MAXEBENEN-1);
	this->felder[x][y] = new Feld(neutyp, neuhoehe, anz);
      };
  this->fahrstuhl=0;  
  this->nummer = neunummer;
  raumused[this->nummer] = 1;
}

Ebene::~Ebene()
{
  int x,y;

  for(y=0; y<=MAXFELD-1; y++)
    for(x=0; x<=MAXFELD-1; x++)
      delete this->felder[x][y];
  raumused[this->nummer] = 0;
}

int Ebene::IstDortBaum(int x, int y)
{
  int feldx, feldy, erg=-1;     //0-99:Baum mit Geschenkanz,
				// -1: leer, -2..-101: Huegel

  if ((abs(x % 2) == 1) && (abs(y % 2) == 1))  
		//sonst kein EbenenFeld dort
    { 
      feldx = (x-1+MAXFELD)/2;   //siehe auch DrawEbene
      feldy = (y-1+MAXFELD)/2;
      if ((feldx >= 0) && (feldx < MAXFELD))
        if ((feldy >= 0) && (feldy < MAXFELD))
          {
		if (this->felder[feldx][feldy]->GetTyp() == baum)
	    	  erg = this->felder[feldx][feldy]->Leeren();
	  	if (this->felder[feldx][feldy]->GetTyp() == huegel)
	   	  erg = -((this->felder[feldx][feldy]->Leeren()) + 2);}	
    }
  fprintf(stderr, "Ende Baum? ");
  return erg;
}

void Ebene::TrigFahrstuhl()
{
  if (this->fahrstuhl == 1) this->fahrstuhl=0;
    else this->fahrstuhl=1;
}

void Ebene::DrawNummer()
{
  short leftzif, rightzif, bild, i;
  char  zif_zu_lcd[9];  // Bit-Pos:  0  
  			//	    1 2
			//	     3		
			//	    4 5
			//	     6
  
  zif_zu_lcd[0]=0x77;			//0111 0111		
  zif_zu_lcd[1]=0x24;			//0010 0100
  zif_zu_lcd[2]=0x5D;			//0110 1011
  zif_zu_lcd[3]=0x6D;			//0110 1101
  zif_zu_lcd[4]=0x2E;			//0010 1110
  zif_zu_lcd[5]=0x6B;			//0101 1101
  zif_zu_lcd[6]=0x7B;			//0111 1011
  zif_zu_lcd[7]=0x25;			//0010 0101
  zif_zu_lcd[8]=0x7F;			//0111 1111
  zif_zu_lcd[9]=0x6F;			//0110 1111

  glPushMatrix();
  glTranslatef(MAXFELD*2, 3*MAXFELD, 0);

  leftzif=((aktebene-1) % 100)/10;
  rightzif=(aktebene-1) % 10;
  glColor3f(1.0, 0.0, 1.0);
  for(i=0; i<=1; i++)
    { 
	if (i == 0) bild = zif_zu_lcd[leftzif];
	  else bild = zif_zu_lcd[rightzif];
	if (bild % 2)  
	  glRectf(0, 0, 5*FELDEINHEIT/2, -FELDEINHEIT/2);	//Bit 0
	bild = bild / 2;
	if (bild % 2) 
	  glRectf(0, 0, FELDEINHEIT/2, -2*FELDEINHEIT);	//Bit 1
	bild = bild / 2;
	if (bild % 2)
	  glRectf(2*FELDEINHEIT, 0,  
		5*FELDEINHEIT/2, -2*FELDEINHEIT);	//Bit 2
	bild = bild / 2;
	if (bild % 2) 
	  glRectf(0, -2*FELDEINHEIT, 
		5*FELDEINHEIT/2, -5*FELDEINHEIT/2);	//Bit 3 
	bild = bild / 2;
	if (bild % 2)
	  glRectf(0, -2*FELDEINHEIT, 
		FELDEINHEIT/2, -9*FELDEINHEIT/2);	//Bit 4
	bild = bild / 2;
	if (bild % 2) 
	  glRectf(2*FELDEINHEIT, -2*FELDEINHEIT, 
		5*FELDEINHEIT/2, -9*FELDEINHEIT/2);	//Bit 5
	bild = bild / 2;
	if (bild % 2) 
	  glRectf(0, -4*FELDEINHEIT, 
		5*FELDEINHEIT/2, -9*FELDEINHEIT/2);	//Bit 6
	glTranslatef(3*FELDEINHEIT, 0, 0);
    }
  glPopMatrix();
}

void Ebene::DrawEbene()
{
  int x,y, s;

  s=MAXFELD*2+1;
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  glBegin(GL_POLYGON);
    glColor3f(1.0, 1.0, 1.0);
    glVertex3f(-s, 0, s);
    glVertex3f(s, 0, s);
    glVertex3f(s, 0, -s);
    glVertex3f(-s, 0, -s);
    glVertex3f(-s, 0, s);
  glEnd();
  this->DrawNummer();
  if (this->fahrstuhl == 0)
    for(y=0; y<=MAXFELD-1; y++)
      for(x=0; x<=MAXFELD-1; x++)
        this->felder[x][y]->DrawFeld(x*2+1-MAXFELD, y*2+1-MAXFELD);
  schenken->DrawGeschenke();
}

//***Funktionen, OpenGL-Grundlagen***********************

void initVars()
{
  aktxwinkel = 0; aktywinkel = 0; aktzwinkel = 0;
  aktxtrans = 0; aktytrans = 0; aktztrans = 0;
  aktebene = 1; punkte = 0; ende = 0;
  spielraum[aktebene-1] = new Ebene(aktebene-1);
  weihmann = new Mann(3, 0.6);
  schenken = new Geschenke(ZUMGEWINN/100, ZUMGEWINN % 100);
};

//---Darstellungsfunktion, greift auf die Klassen zu-------

void display(void)
{  
  int i;  

  glEnable(GL_DEPTH_TEST);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glPushMatrix();
  glTranslatef(aktxtrans/10, aktytrans/10, aktztrans/10);
  glRotatef(aktxwinkel, 1, 0, 0);
  glRotatef(aktywinkel, 0, 1, 0);
  glRotatef(aktzwinkel, 0, 0, 1);

  if (punkte == 0) spielraum[aktebene-1]->DrawEbene();
  weihmann->DrawMann();
  if (ende == 1) 
    { 
	fprintf(stdout, "%d \n", punkte);
        for(i=0; i <= MAXEBENEN-2; i++)
	  if ( raumused[i] > 0 ) delete spielraum[i];
	delete weihmann;
	delete schenken;
        exit(0);
    }
  
  glFlush();
  glPopMatrix();
}

//---Einstellungen für Darstellung-----------------------------

void init (void)
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60, 1, 1, 10*MAXFELD);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(0, MAXFELD*7/2, MAXFELD*6, 0, 0, 0, 0, 1, 0);
}

//---Tastatur-Eingabeverarbeitung-----------------------------

static void specialkey(int key, int x, int y)
{
  short xl, yl, xr, yr, xu, yu, xd, yd, wl, wr, wu, wd;

  if ((aktywinkel <= 45) || (aktywinkel > 315))
    { xl=-1; yl=0; xr=1; yr=0; xu=0; yu=-1; xd=0; yd=1;
 	wl=90; wr=270; wu=0; wd=180;};
  if ((aktywinkel > 45) && (aktywinkel <= 135)) 
    {xl=0; yl=-1; xr=0; yr=1; xu=1; yu=0; xd=-1; yd=0;
	wl=0; wr=180; wu=270; wd=90; };
  if ((aktywinkel > 135) && (aktywinkel <= 225)) 
    {xl=1; yl=0; xr=-1; yr=0; xu=0; yu=1; xd=0; yd=-1;
	wl=270; wr=90; wu=180; wd=0; };
  if ((aktywinkel > 225) && (aktywinkel <= 315))
    {xl=0; yl=1; xr=0; yr=-1; xu=-1; yu=0; xd=1; yd=0;
	wl=180; wr=0; wu=90; wd=270; }; 
  
  switch (key) {
     case GLUT_KEY_LEFT  : weihmann->AddPos(xl, yl); 
			     weihmann->NeuRichtung(wl); break;
     case GLUT_KEY_RIGHT : weihmann->AddPos(xr, yr); 
			     weihmann->NeuRichtung(wr); break;
     case GLUT_KEY_UP    : weihmann->AddPos(xu, yu);
			     weihmann->NeuRichtung(wu); break;
     case GLUT_KEY_DOWN  : weihmann->AddPos(xd, yd);
			     weihmann->NeuRichtung(wd); break;
     }
   glutPostRedisplay();
}

static void key(unsigned char k, int x, int y)
{
  switch (k) {
     case 'v' : trans=1; winkel=0; break;
     case 'd' : winkel=1; trans=0; break;
     case 27  : ende=1; break;
     }
  if (winkel)
   switch (k) {
     case 'x' : aktxwinkel = (aktxwinkel + 30) % 360; break;
     case 'X' : aktxwinkel = (aktxwinkel - 30) % 360; break;
     case 'y' : aktywinkel = (aktywinkel + 30) % 360; break;
     case 'Y' : aktywinkel = (aktywinkel - 30) % 360; break;
     case 'z' : aktzwinkel = (aktzwinkel + 30) % 360; break;
     case 'Z' : aktzwinkel = (aktzwinkel - 30) % 360; break;     
     case 'i' : aktxwinkel = 0; aktywinkel = 0; aktzwinkel = 0; break;
    }
  if (trans)
   switch (k) {
     case 'x' : aktxtrans = (aktxtrans + 1) ; break;
     case 'X' : aktxtrans = (aktxtrans - 1) ; break;
     case 'y' : aktytrans = (aktytrans + 1) ; break;
     case 'Y' : aktytrans = (aktytrans - 1) ; break;
     case 'z' : aktztrans = (aktztrans + 1) ; break;
     case 'Z' : aktztrans = (aktztrans - 1) ; break;     
     case 'i' : aktxtrans = 0; aktytrans = 0; aktztrans = 0; break;
    }
  glutPostRedisplay();
}

//---Hauptprogramm---------------------------------------------

int main(int argc, char** argv)
{
   initVars();

   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);
   glutInitWindowSize (400, 400);
   glutInitWindowPosition (100, 100);
   glutCreateWindow ("spiel");
   init();
   glutDisplayFunc(display);
   glutKeyboardFunc(key);
   glutSpecialFunc(specialkey);
   qobj = gluNewQuadric();
   glutMainLoop();
   gluDeleteQuadric(qobj);

   return 0;   
}

