// game.cpp: Beispielspieler für Asteroids
// Matthias Fuchs
// Martin Schmidt: Punkteerkennung, Highscore schreiben, Tests
// Joe Merten: Zustandserkennung, Lebenerkennung
// Original: Harald Bögeholz / c't

#include "game.h"
#include "output.h"

#include "console.h"
#include <ctype.h>
#include <iostream>

#ifdef _DEBUG
#include <stdio.h>
#endif

#if defined(WINDOWS) || defined(_WIN32)
#else
// Linux-Version; Mac ???
#include <unistd.h>  // usleep
#include <SDL.h>	  // SDL_GetTicks
#define Sleep(x) usleep(x*1000)
#endif

#ifdef __GNUC__
//#define __int64 long long
#define UI64(x) x##ULL
#define I64(x) x##LL
#else
#define UI64(x) x##ui64
#define I64(x) x##i64
#endif

// Fiese globale Variablen
Stats stats;

static unsigned __int32 GetTimer (void)
{
#if defined(WINDOWS) || defined(_WIN32)
	unsigned __int64 counter;
	static unsigned __int64 frequence = UI64(0);
	if (frequence == UI64(0))
		QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&frequence));
	QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&counter));
	return static_cast<unsigned __int32> (counter * 1000 / frequence);
#else
	return SDL_GetTicks();
#endif
}

Game::Game(Connection &con, InterfacePlayer *pl)
:  connection(con),
	player(pl),
	lastframepoints(0),
	lastnbigasteroid(0),
	lastnmidasteroid(0),
	lastnsmallasteroid(0),
	lastsaucersize(0),
	oldgamestate(GameStatus::unknown),
	shipwaspresent (false),
	inhyperspace (false),
	shiplost (false),
	lastlifes (0)
{}

GameStatus::State Game::GetState (void) const
{
	return oldgamestate;
}

void Game::SetPlayer(InterfacePlayer *pl)
{
	player = pl;
}


#if 0
void Game::Start(void)
{

}

void Game::Abort(void)
{

}
#endif


void Game::Run(unsigned int numofframes)
{
	if (player == NULL)
		return;

	FramePacket frame;
	KeysPacket keys;
	GameStatus state;

	// Initialwerte setzen
	lastframepoints = 0;
	lastnbigasteroid = 0;
	lastnmidasteroid = 0;
	lastnsmallasteroid = 0;
	lastsaucersize = 0;
	oldgamestate = GameStatus::unknown;
	shipwaspresent = false;
	inhyperspace = false;
	shiplost = false;
	lastlifes = 0;
	
	// Winkelbyte-Tabelle laden
	initialize_winkelbyte();

	// Warten auf Startbildschirm
	frame.ping = 1;
	keys.clear();
	while (state.state < GameStatus::standby)
	{
		keys.ping = frame.ping;		 // Verluste egal
		connection.SendPacket(keys);
		connection.ReceivePacket(frame);
		InterpretScreen(frame, state);

		Sleep (50);
	}

	// Spiel starten, falls Startbildschirm
	__int8 prevframe = frame.frameno;
	int lostframes, pingtime;
	state.t = 0;
	player->Prepare (state);
	keys.ping = frame.ping-1;
	if (state.state == GameStatus::standby)
		keys.start(true);
		
	// Initialisieren	
	stats.Start();

	lastframepoints = 0;
	state.frameno = frame.frameno;
	if (state.frameno + numofframes < state.frameno)	// Überlaufdetektion
		numofframes = ~0 - state.frameno;
	for
	(
		unsigned int t = 1, endframe = numofframes + state.frameno;
		state.state < GameStatus::gameover && state.frameno < endframe;
		++t
	)
	{
		unsigned int startTime = GetTimer();

		++keys.ping;
		connection.SendPacket(keys);
		connection.ReceivePacket(frame);

		// jedes gesendete Päckchen erhält eine individuelle Nummer zur Latenzmessung
		lostframes = 0; pingtime = 0;
		++state.frameno;
		if (frame.frameno != ++prevframe || frame.ping != keys.ping)
		{
			int ping = keys.ping - frame.ping;
			if (ping < 0)
				ping += 256;
			int lf = frame.frameno - prevframe;
			if (lf < 0)
				lf += 256;
			prevframe = frame.frameno;
			state.frameno += lf;
			
			lostframes = lf;
			pingtime = ping;
		}
		
		if (lostframes > 0)
			OutputInfo2("Latenz %d. %d Frames verloren.", pingtime, lostframes);
	
		try {
			InterpretScreen(frame, state);
		} catch (...) {
			printf("Fehler in InterpreteScreen\n");
		}

		for (int i = 0; i < state.nasteroids; i++)
			state.asteroids[i].IntegrationScheme(lostframes);
		for (int i = 0; i < state.nshots; i++)
			state.shots[i].IntegrationScheme(lostframes);
		for (int i = 0; i < state.nexplosions; i++)
			state.explosions[i].IntegrationScheme(lostframes);	
		if (state.ship.IsPresent())	state.ship.IntegrationScheme(lostframes);
		if (state.saucer.IsPresent()) state.saucer.IntegrationScheme(lostframes);
		
		for (int i = 0; i < state.m_objects.count(); i++)
			state.m_objects[i]->IntegrationScheme(lostframes);
		for (int i = 0; i < state.m_shots.count(); i++)
			state.m_shots[i]->IntegrationScheme(lostframes);
			
		//printf("m_objects count: %d ", state.m_objects.count());	

		state.t = t;
		keys.clear();	// alle Tasten loslassen

		try {
			player->MakeTurn (state, keys);
		} catch (...) {
			printf("Fehler in MakeTurn\n");
		}

		inhyperspace = keys.hyperspace();

		// Nicht öfters als 125 Mal in der Sekunde kommunizieren (kein busy wait)
		// Standardmäßig ARTIFICTAL_LATENCY = 8 (= 1000 / 125)
		// Mame erzeugt 60 Frames in der Sekunde
		unsigned int endTime = GetTimer();
		if (endTime - startTime < ARTIFICIAL_LATENCY)
			Sleep(ARTIFICIAL_LATENCY - (endTime - startTime));
	}

	// falls Spiel durch Anzahl Frames abgebrochen wurde, auf Spielende warten
	if (state.state < GameStatus::gameover)
	{
		player->Abort (state);

		// Warteschleife, Spieler kann Selbstzerstörung einleiten
		while (state.state < GameStatus::gameover)
		{
			unsigned int startTime = GetTimer();

			keys.ping = frame.ping;		 // Verluste egal
			connection.SendPacket(keys);
			connection.ReceivePacket(frame);
			InterpretScreen(frame, state);

			keys.clear();	// alle Tasten loslassen
			player->MakeTurn (state, keys);

			// Nicht öfters als 125 Mal in der Sekunde kommunizieren (kein busy wait)
			// Mame erzeugt 60 Frames in der Sekunde
			unsigned int endTime = GetTimer();
			if (endTime - startTime < ARTIFICIAL_LATENCY)
				Sleep(ARTIFICIAL_LATENCY - (endTime - startTime));
		}
	}

	// Spiel ist aus
	player->GameOver (state);

	// Warten auf Highscore-Bildschirm (bei standby dann aber abbrechen)
	keys.clear();
	while (state.state < GameStatus::highscore
		&& state.state > GameStatus::standby)
	{
		keys.ping = frame.ping;		 // Verluste egal
		connection.SendPacket(keys);
		connection.ReceivePacket(frame);
		InterpretScreen(frame, state);

		Sleep (50);
	}

	// Highscore schreiben
	if (state.state == GameStatus::highscore)
		WriteHighscore (player->GetToken());

	// Warten bis anderer Bildschirm als Highscore
	keys.clear();
	while (state.state == GameStatus::highscore)
	{
		keys.ping = frame.ping;		 // Verluste egal
		connection.SendPacket(keys);
		connection.ReceivePacket(frame);
		InterpretScreen(frame, state);

		Sleep (50);
	}
}

// nur für Debugzwecke
#ifdef _DEBUG
#define ADDDIGIT(d) \
	scorestr[numofdigits] = '0' + d, ++numofdigits, points = points*10 + d
#else
	#define ADDDIGIT(d) ++numofdigits, points = points*10 + d
#endif

#define ADDCHAR(d) textstr[numofchars++] = d


void Game::InterpretScreen(const FramePacket &packet, GameStatus& game)
{
#ifdef _DEBUG
	char scorestr[16];
	const GameStatus lastgame = game;	// vorherige Werte merken zum Vergleich
#endif

	const unsigned __int8 *vector_ram = packet.vectorram;
	int dx, dy, vx, vy, vz, vs;
/*	int sf; */
	int v1x = 0;
	int v1y = 0;
	int shipdetect = 0;
	unsigned int points = 0;
	int numofdigits = 0;	 // < 0 bedeutet fertig
	char textstr[64];
	int numofchars = 0;
	
	int digit_read_mode = 0;	// 1: Score, 2: Highscore
	
	unsigned int _startTime = GetTimer();

	game.clear();
	if (oldgamestate == GameStatus::standby)
	{  // Sonderbehandlung: "STARTKNOEPFE" blinkt, daher mal da und mal nicht
		game.state = GameStatus::standby;
	}

	/* Bemerkung:
		unsigned __int16 value = vector_ram[i] | (vector_ram[i+1] << 8);
		statt
		unsigned __int16 value = reinterpret_cast<unsigned __int16*>(vector_ram)[i>>1];
		damit unabhängig von der Darstellung des Prozessors
		(Little- oder Big-Endian).
	*/
	{
		unsigned __int16 value = vector_ram[0] | (vector_ram[1] << 8);
		if (value != 0xe001 && value != 0xe201)
			return; // sollte nicht vorkommen; erster Befehl ist immer ein JMPL
	}

	// Objekte des aktuellen Frames
	ObjectManager objectsNew;
	ObjectManager objectsShotNew;
	Asteroid* astr;
	Saucer* scr;
	Shot* sht;

	for (unsigned int pc = 2; pc < sizeof packet.vectorram;)
	{		
		unsigned __int16 value = vector_ram[pc] | (vector_ram[pc+1] << 8);
 		int op = value >> 12;
 
		switch (op)
		{
			case 0xa: // LABS
			{
				vy = value & 0x3ff;
				unsigned __int16 value2 = vector_ram[pc+2] | (vector_ram[pc+3] << 8);
				vx = value2 & 0x3ff;
				vs = value2 >> 12;
				
				// score								
				if (digit_read_mode == 1) {
					 digit_read_mode = 0;
				}
				if (vx == 100 && vy == 876 && vs == 1) {
					//std::cout << "reading score" << std::endl;
					points = 0;
					numofdigits = 0;
					digit_read_mode = 1;					
				}
				
				break;
			}
			case 0xb: // HALT
				goto end;
			case 0xc: // JSRL
			{
				switch (value & 0xfff)
				{
					case 0x880:	 // Explosion groß (3)
							game.explosions[game.nexplosions++].set(vx, vy, 3, vs);
							break;
					case 0x896:	 // Explosion (2)
							game.explosions[game.nexplosions++].set(vx, vy, 2, vs);
					break;
					case 0x8B5:	 // Explosion (1)
							game.explosions[game.nexplosions++].set(vx, vy, 1, vs);
							break;
					case 0x8D0:	 // Explosion klein (0)					
							game.explosions[game.nexplosions++].set(vx, vy, 0, vs);
					break;
					case 0x8f3:	 // Asteroid Typ 1
						astr = new Asteroid(vx, vy, 1, vs);
						objectsNew.Add((GameObject*) astr);
						
						game.asteroids[game.nasteroids++].set(vx, vy, 1, vs);						
												
						if (vs == 0)
							++game.nbigasteroid;
						else if (vs == 15)
							++game.nmidasteroid;
						else
							++game.nsmallasteroid;
						break;
					case 0x8ff:	 // Asteroid Typ 2
						astr = new Asteroid(vx, vy, 2, vs);
						objectsNew.Add((GameObject*) astr);
						
						game.asteroids[game.nasteroids++].set(vx, vy, 2, vs);
						
						if (vs == 0)
							++game.nbigasteroid;
						else if (vs == 15)
							++game.nmidasteroid;
						else
							++game.nsmallasteroid;
						break;
					case 0x90d:	 // Asteroid Typ 3
						astr = new Asteroid(vx, vy, 3, vs);
						objectsNew.Add((GameObject*) astr);
						
						game.asteroids[game.nasteroids++].set(vx, vy, 3, vs);						
						
						if (vs == 0)
							++game.nbigasteroid;
						else if (vs == 15)
							++game.nmidasteroid;
						else
							++game.nsmallasteroid;
						break;
					case 0x91a:	 // Asteroid Typ 4
						astr = new Asteroid(vx, vy, 4, vs);
						objectsNew.Add((GameObject*) astr);
						
						game.asteroids[game.nasteroids++].set(vx, vy, 4, vs);		
						
						if (vs == 0)
							++game.nbigasteroid;
						else if (vs == 15)
							++game.nmidasteroid;
						else
							++game.nsmallasteroid;
						break;
					case 0x929:	 // Ufo										
						game.saucer.set (vx, vy, vs);
						break;
					// Lebenerkennung
					case 0xa6d:
						++game.lifes;
						break;
					// PUNKTEerkennung
					case 0xadd:	 // Ziffer 0 oder O
						ADDCHAR ('O');
						if (numofdigits >= 0)
				  		if (digit_read_mode == 1) {
						  	ADDDIGIT(0);  
							}			  
						break;
					case 0xb2e:	 // Ziffer 1
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
						  	ADDDIGIT(1);
							}
						break;
					case 0xb32:	 // Ziffer 2
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
							ADDDIGIT(2);
							}
						break;
					case 0xb3a:	 // Ziffer 3
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
							ADDDIGIT(3);
							}
						break;
					case 0xb41:	 // Ziffer 4
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
							ADDDIGIT(4);
							}
						break;
					case 0xb48:	 // Ziffer 5
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
							ADDDIGIT(5);
							}
						break;
					case 0xb4f:	 // Ziffer 6
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
						  	ADDDIGIT(6);
							}
						break;
					case 0xb56:	 // Ziffer 7
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
							ADDDIGIT(7);
							}
						break;
					case 0xb5b:	 // Ziffer 8
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
							ADDDIGIT(8);
							}
						break;
					case 0xb63:	 // Ziffer 9
						if (numofdigits >= 0)
							if (digit_read_mode == 1) {
							ADDDIGIT(9);
							}
						break;
					//PUNKTEerkennung ende
					case 0xb2c:	 // Leerzeichen (Punkte- oder Zustanderkennung)
					{
						unsigned __int16 value2 =
							vector_ram[pc+2] | (vector_ram[pc+3] << 8);
						if (numofdigits > 0 && (value2 & 0xfff) == 0xb2c)
						{  // 2 Leerzeichen hintereinander --> Ende mit der Punktezahl
#ifdef _DEBUG
							scorestr[numofdigits] = '\0';
#endif
							numofdigits = -numofdigits;
						}
						else if (numofchars > 0)
						{  // Leerzeichen beenden Wort --> Vergleiche für Zustandserkennung
							textstr[numofchars] = '\0';
							if (strcmp (textstr, "STARTKNOEPFE") == 0)
								game.state = GameStatus::standby;
							else if (strcmp (textstr, "SPIELER") == 0)
								game.state = GameStatus::prepare;
							else if (strcmp (textstr, "SPIELENDE") == 0)
								game.state = GameStatus::gameover;
							else if (strcmp (textstr, "BUCHSTABENWAHL") == 0)
								game.state = GameStatus::highscore;
								// "ERGEBNIS" wird auch auf der Startseite angezeigt,
								// wenn bereits ein highscore existiert
								// Zeilenumbrüche existieren nicht, daher "BESTENBITTE"
						}
						numofchars = 0;		// wegen 0 = O
						break;
					}

					// Worterkennung für Zustände
					case 0xA78: ADDCHAR ('A'); break;
					case 0xA80: ADDCHAR ('B'); break;
					case 0xA8D: ADDCHAR ('C'); break;
					case 0xA93: ADDCHAR ('D'); break;
					case 0xA9B: ADDCHAR ('E'); break;
					case 0xAA3: ADDCHAR ('F'); break;
					case 0xAAA: ADDCHAR ('G'); break;
					case 0xAB3: ADDCHAR ('H'); break;
					case 0xABA: ADDCHAR ('I'); break;
					case 0xAC1: ADDCHAR ('J'); break;
					case 0xAC7: ADDCHAR ('K'); break;
					case 0xACD: ADDCHAR ('L'); break;
					case 0xAD2: ADDCHAR ('M'); break;
					case 0xAD8: ADDCHAR ('N'); break;
//					case 0xADD: ADDCHAR ('O'); break;		 siehe Ziffer 0
					case 0xAE3: ADDCHAR ('P'); break;
					case 0xAEA: ADDCHAR ('Q'); break;
					case 0xAF3: ADDCHAR ('R'); break;
					case 0xAFB: ADDCHAR ('S'); break;
					case 0xB02: ADDCHAR ('T'); break;
					case 0xB08: ADDCHAR ('U'); break;
					case 0xB0e: ADDCHAR ('V'); break;
					case 0xB13: ADDCHAR ('W'); break;
					case 0xB1a: ADDCHAR ('X'); break;
					case 0xB1f: ADDCHAR ('Y'); break;
					case 0xB26: ADDCHAR ('Z'); break;
					// Worterkennung für Zustände ende
				}
				break;
			}
			case 0xd: // RTSL
				goto end;
			case 0xe: // JMPL
				/*
				pc = (value & 0xfff) << 1;
				break;
				*/
				goto end;
			case 0xf: // SVEC
				/*
				dy = value & 0x300;
				if ((value & 0x400) != 0)
					dy = -dy;
				dx = (value & 3) << 8;
				if ((value & 4) != 0)
					dx = -dx;
				sf = (((value & 8) >> 2) | ((value & 0x800) >> 11)) + 2;
				vz = (value & 0xf0) >> 4;
				*/
				break;
			default:
			{
				unsigned __int16 value2 = vector_ram[pc+2] | (vector_ram[pc+3] << 8);
				dy = value & 0x3ff;
				if ((value & 0x400) != 0)
					dy = -dy;
				dx = value2 & 0x3ff;
				if ((value2 & 0x400) != 0)
					dx = -dx;
				/* sf = op;  ??? */
				vz = value2 >> 12;
				if (dx == 0 && dy == 0 && vz == 15)
				{
					sht = new Shot(vx, vy);
					objectsShotNew.Add((GameObject*) sht);
					
					game.shots[game.nshots++].set(vx, vy);
				}

				if ((op == 1 || op == 2) && vz == 0)
					shiplost = true;
				// Vermutung: op == 1 oder op == 2 und vz == 0 sind die Schiffsexplosionen
				if (op == 6 && vz == 12 && dx != 0 && dy != 0)
				{
					switch (shipdetect)
					{
						case 0:
							v1x = dx;
							v1y = dy;
							++shipdetect;
							break;
						case 1:
							game.state = GameStatus::playing;
							inhyperspace = false;
							shiplost = false;
							game.ship.set(vx, vy, v1x - dx, v1y - dy);
							++shipdetect;
								// hat dann Wert == 2 ...
							break;
					}
				}
				else if (shipdetect == 1)
					shipdetect = 0;

				break;
			}
		}
		
		if (op <= 0xa)
			pc+=2;
		if (op != 0xe) // JMPL
			pc+=2;
						
		//std::cout << textstr << std::endl;			
	}
	
end:

	// Zustandskorrektur
	if (game.state == GameStatus::unknown && !game.ship.present
		&& oldgamestate == GameStatus::playing)
		game.state = GameStatus::playing;
		// wird nur bei aktiven Schiff sonst gesetzt
#ifdef _DEBUG
	if (oldgamestate != GameStatus::highscore
		&& game.state < oldgamestate)
		OutputWarning ("Zustandsreihenfolge offenbar falsch.");
#endif

#if 0
	// reinnehmen, wenn shiplost funktioniert
	if (!shiplost && !game.ship.present)
		inhyperspace = true;
#endif

	// Events bestimmen
	if (game.state == GameStatus::playing || oldgamestate == GameStatus::playing)
	{
		if (game.state == GameStatus::playing && oldgamestate != GameStatus::prepare)
		{  // gelesene Punkt übernehmen (beim Wechsel wäre es falsch)
#ifdef _DEBUG
			unsigned int oldscore = game.score;
#endif
			if (lastframepoints > points)
				game.score += 100000;
			game.score += points - lastframepoints;
#ifdef _DEBUG
/*
			if (oldscore != game.score)
			{
				OutputDebug2 ("alter Frame %u, aktuell %u; alter Score %u, aktueller Score %u",
					lastframepoints, points, oldscore, game.score);
			}
*/
#endif
			lastframepoints = points;
		}
		if ((!game.ship.present && shipwaspresent && !inhyperspace)
			|| lastlifes > game.lifes)
			game.events = (GameStatus::Event) (game.events | GameStatus::ship_lost);
		if (game.nbigasteroid > lastnbigasteroid && lastnbigasteroid <= 2)
		{
			game.events = (GameStatus::Event) (game.events |
				(game.level == 0 ? GameStatus::gamestart : GameStatus::nextlevel));
			++game.level;
			// Level ist geraten und falsch, wenn mitten im Spiel startet
		}
		if (lastnbigasteroid > game.nbigasteroid
			|| lastnmidasteroid > game.nmidasteroid
			|| lastnsmallasteroid > game.nsmallasteroid)
		{
			game.events = (GameStatus::Event) (game.events
				| GameStatus::asteroid_destroyed);
			// Zerstörung eines Asteroiden führt zu zwei neue der kleineren Größe
			// Unter beachtung aller Zahlen könnte man ein Gleichungsystem lösen,
			// das als Lösung die Zahl der zerstörten Asteroiden jeder Kategorie hat
		}
		if (lastsaucersize > 0 && lastsaucersize != game.saucer.size)
		{
			game.events = (GameStatus::Event) (game.events
				| GameStatus::saucer_destroyed);
		}
		// Alternative: Punktestanderhöhung betrachten und zerlegen
		// numerisch anspruchsvoll, da mehrere Objekte gleichzeitig möglich
		// Aber nicht richtig: Ufo kann mit Asteroiden zusammenstossen, keine Punkte
		if (!game.ship.present && shipwaspresent && inhyperspace)
		{
			game.events = (GameStatus::Event) (game.events
				| GameStatus::ship_inhyperspace);
		}

		if (lastlifes < game.lifes)
		{
			game.events = (GameStatus::Event) (game.events
				| GameStatus::newlife);
			// neues Leben gibt es offenbar mit jedem 10000er Schritt
		}
	}
	if (oldgamestate != game.state)
	{
		game.events = (GameStatus::Event) (game.events
			| GameStatus::state_changed);
	}
	// aktuelle Daten speichern
	lastnbigasteroid = game.nbigasteroid;
	lastnmidasteroid = game.nmidasteroid;
	lastnsmallasteroid = game.nsmallasteroid;
	lastsaucersize = game.saucer.size;
	oldgamestate = game.state;
	shipwaspresent = game.ship.present;
	lastlifes = game.lifes;

#ifdef _DEBUG
	if (game.events & GameStatus::state_changed)
		OutputDebug (ToString(game.state));
	if (game.events & ~GameStatus::state_changed)
		OutputDebug (ToString((GameStatus::Event)(game.events & ~GameStatus::state_changed)));
	if ((game.events & (GameStatus::newlife | GameStatus::ship_lost | GameStatus::nextlevel)) ||
		((game.events & GameStatus::state_changed) && (game.state == GameStatus::gameover)))
	{
		OutputDebug2 ("Score: %u, Leben: %u, Level: %u",
			game.score, game.lifes, game.level);
	}
#endif
	
	
	// Objekte in m_objects aktualisieren
	try {
		/*
		int cnt;
		
		cnt = game.m_objects.count();
		printf("ALT:   %d asts-------------------------------------------\n", cnt);	
		for (int i = 0; i < cnt; i++)
		{
			GameObject *ast = game.m_objects[i];
			printf("Typ: %d, x(%.0f, %.0f), v(%.2f, %.2f), tag: %d, age: %d\n", ast->objecttype, ast->pos.x, ast->pos.y, ast->vel.x, ast->vel.y, ast->tag, ast->age);
		}
		
		cnt = objectsNew.count();
		printf("NEU:   %d asts--------------------------------------------------\n", cnt);	
		for (int i = 0; i < cnt; i++)
		{
			GameObject *ast = objectsNew[i];
			printf("Typ: %d, x(%.0f, %.0f), v(%.2f, %.2f), tag: %d, age: %d\n", ast->objecttype, ast->pos.x, ast->pos.y, ast->vel.x, ast->vel.y, ast->tag, ast->age);
		}
		*/		
		
		game.Update(&game.m_objects, &objectsNew);
		game.Update(&game.m_shots, &objectsShotNew);
		
		/*
		cnt = game.m_objects.count();
		printf("NACH update:  %d asts-------------------------------------------\n", cnt);	
		for (int i = 0; i < cnt; i++)
		{
			GameObject *ast = game.m_objects[i];
			printf("Typ: %d, x(%.0f, %.0f), v(%.2f, %.2f), tag: %d, age: %d\n", ast->objecttype, ast->pos.x, ast->pos.y, ast->vel.x, ast->vel.y, ast->tag, ast->age);
		}
		
		printf("\n");
		*/
		
	} catch (...) {
		printf("Fehler in ObjectManager::Update");
	}
	//printf(" update_end\n");
	
	unsigned int _endTime = GetTimer();
		
	// Schalter für Stats
	if (STATS_ON) stats.on(); 
	stats.UpdateStats(_endTime - _startTime, points);

}


bool Game::InterpretHighscoreScreen
(const FramePacket &packet, int pos, char* curchar)
{
	const unsigned __int8 *vector_ram = packet.vectorram;
	char textstr[64];
	int numofchars = 0;
	bool highscore = false;

	/* Bemerkung:
		unsigned __int16 value = vector_ram[i] | (vector_ram[i+1] << 8);
		statt
		unsigned __int16 value = reinterpret_cast<unsigned __int16*>(vector_ram)[i>>1];
		damit unabhängig von der Darstellung des Prozessors
		(Little- oder Big-Endian).
	*/
	{
		unsigned __int16 value = vector_ram[0] | (vector_ram[1] << 8);
		if (value != 0xe001 && value != 0xe201)
			return false; // sollte nicht vorkommen; erster Befehl ist immer ein JMPL
	}

	for (unsigned int pc = 2; pc < sizeof packet.vectorram;)
	{
		unsigned __int16 value = vector_ram[pc] | (vector_ram[pc+1] << 8);
		int op = value >> 12;
		switch (op)
		{
			case 0xb: // HALT
				goto end;
			case 0xc: // JSRL
			{
				switch (value & 0xfff)
				{
					case 0xb2c:	 // Leerzeichen 
					{
						if (numofchars > 0)
						{  // Leerzeichen beenden Wort --> Vergleiche für Zustandserkennung
							textstr[numofchars] = '\0';
							if (strcmp (textstr, "BUCHSTABENWAHL") == 0)
								highscore = true;
								// "ERGEBNIS" wird auch auf der Startseite angezeigt,
								// wenn bereits ein highscore existiert
								// Zeilenumbrüche existieren nicht, daher "BESTENBITTE"
							if (strncmp (textstr, "DRUECKEN", 8) == 0
								&& strcmp (textstr, "DRUECKENWENN") != 0)
							{  // Die Eingabezeichen schliessen direkt an das Wort
								// "DRUECKEN" an, aber nicht das Vorkommen "DRUECKENWENN"
								textstr[numofchars++] = ' ';
								if (numofchars >= pos+8)
									goto end;
								break;		// Zeichen nicht löschen
							}
							// OutputDebug (textstr);
							numofchars = 0;
						}
						break;
					}

					case 0xA78: ADDCHAR ('A'); break;
					case 0xA80: ADDCHAR ('B'); break;
					case 0xA8D: ADDCHAR ('C'); break;
					case 0xA93: ADDCHAR ('D'); break;
					case 0xA9B: ADDCHAR ('E'); break;
					case 0xAA3: ADDCHAR ('F'); break;
					case 0xAAA: ADDCHAR ('G'); break;
					case 0xAB3: ADDCHAR ('H'); break;
					case 0xABA: ADDCHAR ('I'); break;
					case 0xAC1: ADDCHAR ('J'); break;
					case 0xAC7: ADDCHAR ('K'); break;
					case 0xACD: ADDCHAR ('L'); break;
					case 0xAD2: ADDCHAR ('M'); break;
					case 0xAD8: ADDCHAR ('N'); break;
					case 0xADD: ADDCHAR ('O'); break;
					case 0xAE3: ADDCHAR ('P'); break;
					case 0xAEA: ADDCHAR ('Q'); break;
					case 0xAF3: ADDCHAR ('R'); break;
					case 0xAFB: ADDCHAR ('S'); break;
					case 0xB02: ADDCHAR ('T'); break;
					case 0xB08: ADDCHAR ('U'); break;
					case 0xB0e: ADDCHAR ('V'); break;
					case 0xB13: ADDCHAR ('W'); break;
					case 0xB1a: ADDCHAR ('X'); break;
					case 0xB1f: ADDCHAR ('Y'); break;
					case 0xB26: ADDCHAR ('Z'); break;
				}
				break;
			}
			case 0xd: // RTSL
				goto end;
			case 0xe: // JMPL
				/*
				pc = (value & 0xfff) << 1;
				break;
				*/
				goto end;
		}
		if (op <= 0xa)
			pc+=2;
		if (op != 0xe) // JMPL
			pc+=2;
	}

end:
	*curchar = numofchars > pos+8 ? textstr[pos+8] : '?';
	return highscore;
}


#define HIGHSCORESLEEPTIME 100

inline int chardistance (char chr1, char chr2)
{
	if (chr1 == ' ')
		chr1 = '@';	 // == 'A' - 1
	if (chr2 == ' ')
		chr2 = '@';	 // == 'A' - 1

	if (chr2 >= chr1)
	{
		return (chr2 - chr1 < 27 + chr1 - chr2)
			?  chr2 - chr1
			:  -(27 + chr1 - chr2);
	}

	// inverse
	return (chr1 - chr2 < 27 + chr2 - chr1)
		?  -(chr1 - chr2)
		:  27 + chr2 - chr1;
}



void Game::WriteHighscore(const char *str)
{
	FramePacket frame;
	KeysPacket keys;
	keys.clear();
	for (unsigned int i = 0; i < 3 && str[i] != '\0'; ++i) // Hauptschleife, geht die Buchstaben durch
	{
		char cur='A';
		char chr = toupper(str[i]);
		if (chr < 'A' || chr > 'Z')
			chr = ' ';

		// Buchstaben auslesen, der angezeigt wird
		connection.ReceivePacket(frame);
		while (1)
		{
			if (!InterpretHighscoreScreen(frame, i, &cur))
				return;	  // Bildschirm hat gewechselt, kein Highscore mehr

			// passende Tasten drücken
			if (cur==chr)
			{  // richtiges Zeichen liegt vor, festmachen
				keys.clear();
				keys.hyperspace(true);
				keys.ping = frame.ping-1;
				connection.SendPacket(keys);
				connection.ReceivePacket(frame);
				Sleep (HIGHSCORESLEEPTIME);	 // mind. 2 Frames warten
				keys.clear();
				keys.ping = frame.ping-1;
				connection.SendPacket(keys);
				connection.ReceivePacket(frame);
				Sleep (HIGHSCORESLEEPTIME);	 // mind. 2 Frames warten
				break;	// raus aus while
			}
			else
			{  // noch nicht das richtige Zeichen ...
				keys.clear();
				int direction = chardistance (chr, cur);
				if (direction < 0)
					keys.left(true);
				else
					keys.right(true);

				keys.ping = frame.ping-1;
				connection.SendPacket(keys);
				connection.ReceivePacket(frame);
				Sleep (HIGHSCORESLEEPTIME);	 // mind. 2 Frames warten

				keys.clear();
				keys.ping = frame.ping-1;
				connection.SendPacket(keys);
				connection.ReceivePacket(frame);
				Sleep (HIGHSCORESLEEPTIME);	 // mind. 2 Frames warten
			}
		}  // while
	}  // for
}

Stats::Stats() {
	setrgb(2); // matrix rules!!
	m_lastMsgLength = 0;
	Reset();
	m_active = false;
}

void Stats::Reset() {
	m_numShots = 0;
	m_numHits = 0;
	m_numSmallSaucers = 0;
	m_numLargeSaucers = 0;
	m_numSmallAsteroids = 0;
	m_numMediumAsteroids = 0;
	m_numLargeAsteroids = 0;
	m_numFrames = 0;
	m_lastScore = 0;
	m_score = 0;
	m_pointsPerSec = 0;
	m_startTimer = 0;
	m_scoreDiff = 0;
	m_numFramesPerSec = 0;
	
	m_numScoreAvgUpdates = 0;
			
	m_scoreTimer = 0;
	m_startTimer = 0;		
}

void Stats::Start() {
	m_startTimer = GetTimer();
}

void Stats::IncShots() {
	m_numShots++;
}

void Stats::IncSmallAsteroids() {
	m_numSmallAsteroids++;
}	

void Stats::IncMediumAsteroids() {
	m_numMediumAsteroids++;
}

void Stats::IncLargeAsteroids() {
	m_numLargeAsteroids++;
}

void Stats::IncSmallSaucers() {
	m_numSmallSaucers++;
}

void Stats::IncLargeSaucers() {
	m_numLargeSaucers++;
}

void Stats::UpdateLocks(float dist, float angle) {
	m_numLocks++;
	
}

void Stats::UpdateMakeTurnPerformance(unsigned __int32 ticks) {
	maketurnTimes.totalTime += ticks;
	if (maketurnTimes.maxTime < ticks)
		maketurnTimes.maxTime = ticks;
	maketurnTimes.avgTime = (float) maketurnTimes.totalTime / (float) m_numFrames;
}

void Stats::UpdateStats(unsigned __int32 ticks, int score) {
	
	unsigned __int32 currentTimer = GetTimer();
	
	m_numFrames++;
	
	m_numFramesPerSec = (m_numFrames / (float)(currentTimer - m_startTimer + 0.00001f)) * 1000;
	
	frameTimes.totalTime += ticks;
	frameTimes.avgTime = (float) frameTimes.totalTime / (float) m_numFrames;
	
	if (m_score != score) // score hat sich geändert -> Treffer
		m_numHits++;
	
	m_score = score;		
			
	if (currentTimer - m_scoreTimer >= 1000) {	// sekundentakt
		m_numScoreAvgUpdates++;
		unsigned __int32 diff = m_score - m_lastScore;
		m_scoreDiff += diff;
		m_pointsPerSec = (float) m_scoreDiff / m_numScoreAvgUpdates;
		m_scoreTimer = currentTimer;
		m_lastScore = m_score;
	}
	 
	if (frameTimes.maxTime < ticks)
		frameTimes.maxTime = ticks;			
	
	if (m_numFrames % STATS_REFRESH_RATE == 0)
		PrintStats();				
}

void Stats::PrintLine(int y, const char* label, const char* a, const char* b) {
	
	if (label)
		consoleWrite(y, 0, label);

	if (a)		
		consoleWrite(y, OUTPUT_COL_WIDTH, a);
	
	if (b)
		consoleWrite(y, 2 * OUTPUT_COL_WIDTH, b);
		
	consoleWrite("\r\n");	
} 

void Stats::PrintLine(int y, const char* label, const char* a, const char* b, const char* c) {
	if (label)
		consoleWrite(y, 0, label);
		
	if (a)
		consoleWrite(y, OUTPUT_COL_WIDTH, a);
		
	if (b)
		consoleWrite(y, 2 * OUTPUT_COL_WIDTH, b);
	
	if (c)	
		consoleWrite(y, 3 * OUTPUT_COL_WIDTH, c);
		
	consoleWrite("\r\n");
}

void Stats::PrintStats() {
	if (!m_active)
		return;
	
	std::stringstream msg;
	char value1[20];
	char value2[20];
	char value3[20];
	int lenv1;
	int lenv2;
	int lenv3;
	
	clrscr();
	
	int line = 2;

	// UGLY
	//msg << "---------- Stats ----------\r\n";
	consoleWrite(0, 0, "---------- Stats ----------");		
	PrintLine(line++, NULL, "Total", "Per sec");
	
	lenv1 = sprintf(value1, "%d", m_numFrames);
	lenv2 = sprintf(value2, "%f", m_numFramesPerSec);
	PrintLine(line++, "Frames", value1, value2);
	//msg << "score: " << m_score << " - points per sec: " << m_pointsPerSec << "\r\n";
	lenv1 = sprintf(value1, "%d", m_score);
	lenv2 = sprintf(value2, "%f", m_pointsPerSec);
	PrintLine(line++, "Score", value1, value2);
	
	line++;
	PrintLine(line++, NULL, "Avg", "Max", "Total");
	lenv1 = sprintf(value1, "%f", frameTimes.avgTime);
	lenv2 = sprintf(value2, "%d", frameTimes.maxTime);
	lenv3 = sprintf(value3, "%d", frameTimes.totalTime);	
	PrintLine(line++, "Intprt", value1, value2, value3);
	
	lenv1 = sprintf(value1, "%f", maketurnTimes.avgTime);
	lenv2 = sprintf(value2, "%d", maketurnTimes.maxTime);
	lenv3 = sprintf(value3, "%d", maketurnTimes.totalTime);	
	PrintLine(line++, "MkTurn", value1, value2, value3);
	
	line++;
	PrintLine(line++, NULL, "Total", "Hits", "Ratio");
	lenv1 = sprintf(value1, "%d", m_numShots);
	lenv2 = sprintf(value2, "%d", m_numHits);
	lenv3 = sprintf(value3, "%f", (float)m_numHits / ((float)m_numShots / 100.0f));
	PrintLine(line++, "Shots", value1, value2, value3);
	line++;
	PrintLine(line++, NULL, "Small", "Medium", "Large");
	lenv1 = sprintf(value1, "%d", m_numSmallAsteroids);
	lenv2 = sprintf(value2, "%d", m_numMediumAsteroids);
	lenv3 = sprintf(value3, "%d", m_numLargeAsteroids);
	PrintLine(line++, "Asteroids", value1, value2, value3);
	
	lenv1 = sprintf(value1, "%d", m_numSmallSaucers);
	lenv3 = sprintf(value3, "%d", m_numLargeSaucers);
	PrintLine(line++, "Saucer", value1, "--", value3);	
	
	line++;

	m_lastMsgLength = 0;
	m_lines = line;
	locate(line, 0);
}

Stats& Stats::operator<<(const char* str) {
	consoleWrite(str);	
	return (*this);
}

Stats& Stats::operator<<(const float f) {
	msgBuffer.str("");	
	msgBuffer.clear();
	msgBuffer << f;
	consoleWrite(msgBuffer.str().c_str());	
	return (*this);
}

Stats& Stats::operator<<(const int i) {
	msgBuffer.str("");	
	msgBuffer.clear();
	msgBuffer << i;
	consoleWrite(msgBuffer.str().c_str());	
	return (*this);
}

void Stats::off() {
	m_active = false;
}

void Stats::on() {
	m_active = true;
}


unsigned __int32 Stats::StatsGetTimer() {
	return GetTimer();
}

