// moonlander.c
// by Sven Bachmann 2004 - 2006
//
// Befehl zum Kompilieren:
// gcc moonlander.c -lncurses -o moonlander
//
// 2004-xx-xx: Sven Bachmann
// * Moonlander erstellt
//
// 2006-07-02: Sven Bachmann
// * Update: - Text komplett auf deutsch
//           - Statuszeile kann nicht mehr Zeilenende ueberschreiben
//           - Standardrahmen von ncurses werden benutzt

#include <ncurses.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>


#define MIN_THRUST		0		// Minimaler Schub (in m/s)
#define	MAX_THRUST		12		// Maximaler Schub (in m/s)
#define MAX_HEIGHT		50000		// Maximale Hoehe (in m)
#define MIN_HEIGHT		0		// Minimale Hoehe (in m) (- Aufschlag)
#define MAX_LANDING_SPEED	10		// Maximale Landegeschwindigkeit (in m/s)
#define MAX_FUEL		10000		// Maximale Tankbefuellung (in l)
#define TIME_UNIT		1000000		// Zeiteinheit setzen
#define FUEL_UNIT		100		// Treibstoffverbrauch bei maximalem Schub (in l)
#define MOON_GRAVITY		1.63		// Mondanziehungskraft


void fnInit_screen ( void );	// Funktion fuer die Bildschirminitialisierung
void fnPrint_stats ( void );	// Funktion zum Ausgeben der Statistik
void fnPrint_lander ( void );	// Funktion zum Ausgeben des landenden Raumschiffes
void fnPrint_thrust ( int );	// Funktion zum Ausgeben des Schubes
void fnRemove_lander ( void );	// Funktion zum Entfernen des Raumschiffes
void fnPrint_landed ( void );	// Funktion zum Ausgeben des gelandeten Raumschiffes
void fnPrint_crash ( void );	// Funktion zum Ausgeben des zerschellten Raumschiffes


WINDOW *pWspace;		// Fenster fuer den Weltraum
WINDOW *pWstats;		// Fenster fuer die Statistiken
WINDOW *pWthrust;		// Fenster fuer die Schubanzeige


int iLandery, iLanderx;		// Koordinaten fuer das Raumschiff
int iOldy, iOldx;		// Koordinaten wo das Raumschiff zuletzt war


float fHeight = MAX_HEIGHT;	// Standardhoehe setzen (in m)
float fSpeed = 1000;		// Standardgeschwindigkeit setzen (in m/s)
float fFuel = MAX_FUEL;		// Standardfuellstand des Tankes (in l)
float fThrust = 0;		// Standardschub deaktivieren (in m/s)
float fTime_unit = 0.5;		// Zeiteinheit (in s)
unsigned long ulSleep_time = 0;


bool bAutoland = FALSE;		// automatisches Landen standardmaessig deaktivieren



/* main
    Funktion:	Steuerung des Programmablaufes
    Param.Ein.:	-
    Param.Aus.:	Rueckgabecode fuer das BS
*/

int main( void ) {
    int iKeyboard_input = -1;

    fnInit_screen();

    while ( iKeyboard_input == -1 )
        iKeyboard_input = getch();

    while ( iKeyboard_input != KEY_END ) {
        switch ( iKeyboard_input ) {
            case KEY_RIGHT:
            iLanderx++;
            break;
            case KEY_LEFT:
            iLanderx--;
            break;
            case KEY_UP:
            fThrust++;
            break;
            case KEY_DOWN:
            fThrust--;
            break;
            case KEY_F( 1 ) : fThrust = MAX_THRUST;
            break;
            case KEY_F( 2 ) : fThrust = MIN_THRUST;
            break;
            case KEY_F( 3 ) : bAutoland = !bAutoland;
            break;
            case KEY_F( 4 ) : fFuel = MAX_FUEL;
            break;
            case KEY_NPAGE:
            fTime_unit -= 0.01;
            break;
            case KEY_PPAGE:
            fTime_unit += 0.01;
            break;
        }

        flushinp();

        ulSleep_time = TIME_UNIT * 0.1;
        usleep( ulSleep_time );

        // automatisches Landen funktioniert nur bei Zeiteinheit=0.5
        // einigermassen zufriedenstellend, wobei die Simulation
        // gleich damit (F3) gestartet werden muss, da das Raumschiff
        // sonst eine zu hohe Fallgeschwindigkeit bekommt

        if ( bAutoland ) {
            if ( fSpeed > MAX_LANDING_SPEED ) {
                fThrust = MOON_GRAVITY + MAX_LANDING_SPEED - 0.01;
            } else {
                fThrust = 0;
            }
        }

        if ( fThrust > MAX_THRUST )
            fThrust = MAX_THRUST;
        if ( fThrust < MIN_THRUST )
            fThrust = MIN_THRUST;

        if ( fFuel < FUEL_UNIT )
            fThrust = MAX_THRUST / ( FUEL_UNIT / fFuel );

        fFuel -= ( FUEL_UNIT / MAX_THRUST * fThrust ) * fTime_unit;
        fSpeed -= ( fThrust ) * fTime_unit;
        fSpeed += ( MOON_GRAVITY ) * fTime_unit;
        fHeight -= ( fSpeed ) * fTime_unit;

        iLandery = 1 + ( LINES - 9 ) - ( ( LINES - 9 ) / ( MAX_HEIGHT / fHeight ) );
        if ( iLandery < 1 )
            iLandery = 1;
        if ( iLanderx < 1 )
            iLanderx = 1;
        else if ( iLanderx > ( COLS - 18 ) )
            iLanderx = COLS - 18;

        iKeyboard_input = getch();

        if ( fHeight > MAX_HEIGHT ) {
            fHeight = MAX_HEIGHT;
            fSpeed = 0;
        }

        if ( fHeight < MIN_HEIGHT )
            fHeight = MIN_HEIGHT;

        fnPrint_stats();
        fnPrint_lander();
        fnPrint_thrust( fFuel / 100 );

        if ( fHeight <= 0 ) {
            wmove( pWspace, 5, ( ( COLS - 20 ) / 2 ) - 3 );

            if ( fSpeed > MAX_LANDING_SPEED )
                fnPrint_crash();
            else
                fnPrint_landed();

            wrefresh( pWspace );

            while ( iKeyboard_input != KEY_END )
                iKeyboard_input = getch();
            break;
        }
    }

    endwin();

    return 0;
}


/* fnInit_screen
    Funktion:   Initialisierung des Bildschirms
    Param.Ein.: -
    Param.Aus.: -
*/

void fnInit_screen() {
    initscr();
    cbreak();
    keypad( stdscr, true );
    nodelay( stdscr, true );
    noecho();
    curs_set( 0 );
    refresh();

    pWstats = newwin( 3, 0, LINES - 3, 0 );
    pWspace = newwin( LINES - 3, COLS - 10, 0, 0 );
    pWthrust = newwin( LINES - 3, 10, 0, COLS - 10 );

    box( pWstats, 0, 0 );
    box( pWspace, 0, 0 );
    box( pWthrust, 0, 0 );

    wrefresh( pWstats );
    wrefresh( pWthrust );

    wmove ( pWspace, 1, 2 );
    wprintw ( pWspace, "LINKS  - links" );
    wmove ( pWspace, 2, 2 );
    wprintw ( pWspace, "RECHTS - rechts" );
    wmove ( pWspace, 3, 2 );
    wprintw ( pWspace, "OBEN   - Gegenschub erhoehen" );
    wmove ( pWspace, 4, 2 );
    wprintw ( pWspace, "UNTEN  - Gegenschub verringern" );
    wmove ( pWspace, 5, 2 );
    wprintw ( pWspace, "F1     - voller Gegenschub" );
    wmove ( pWspace, 6, 2 );
    wprintw ( pWspace, "F2     - kein Gegenschub" );
    wmove ( pWspace, 7, 2 );
    wprintw ( pWspace, "F3     - Automatische Landung (an/aus)" );
    wmove ( pWspace, 8, 2 );
    wprintw ( pWspace, "F4     - Cheat: Treibstoff" );
    wmove ( pWspace, 9, 2 );
    wprintw ( pWspace, "ENDE   - Simulation verlassen" );
    wrefresh( pWspace );

    iOldy = iLandery = 1;
    iOldx = iLanderx = ( ( COLS - 20 ) / 2 ) - 6;

    fnPrint_lander();
    fnPrint_stats();
    fnPrint_thrust( 100 );
}


/* fnPrint_stats
    Funktion:	Ausgabe der Statistik am unteren Rand
    Param.Ein.: -
    Param.Aus.: -
*/

void fnPrint_stats( void ) {
    const char sPrint[] = "%sHoehe: %.2f - Geschw.: %.2f - Tank: %.2f - Schub: %.2f - ZE: %.2f";
    char sBuffer[ COLS - 3 ];
    int iCnt;

    snprintf( sBuffer, COLS - 4, sPrint,
              ( bAutoland ) ? "<Autolandung> " : "", fHeight, fSpeed, fFuel, fThrust, fTime_unit );

    for ( iCnt = strlen( sBuffer ); iCnt < ( sizeof( sBuffer ) - 1 ); iCnt++ ) {
        sBuffer[ iCnt ] = ' ';
    }
    sBuffer[ iCnt ] = 0;

    mvwprintw( pWstats, 1, 2, sBuffer );
    wrefresh( pWstats );
}


/* fnPrint_lander
    Funktion:	Ausgabe des Raumschiffs waehrend des Landens
    Param.Ein.:	-
    Param.Aus.: -
*/

void fnPrint_lander( void ) {
    fnRemove_lander();

    wmove ( pWspace, iLandery, iLanderx );
    wprintw( pWspace, "  ooo" );
    wmove ( pWspace, iLandery + 1, iLanderx );
    wprintw( pWspace, " o   o" );
    wmove ( pWspace, iLandery + 2, iLanderx );
    wprintw( pWspace, "ooooooo" );
    wmove ( pWspace, iLandery + 3, iLanderx );
    wprintw( pWspace, " U U U" );

    wrefresh( pWspace );
}


/* fnPrint_thrust
    Funktion:	Ausgabe des Treibstoffbalkens
    Param.Ein.:	Treibstoff in Prozent
    Param.Aus.:	-
*/

void fnPrint_thrust ( int iPercent ) {
    int iTempA = 0;
    int iTempB;

    for ( ; iTempA < ( LINES - 5 ); iTempA++ ) {
        wmove( pWthrust, 1 + iTempA, 1 );
        for ( iTempB = 0; iTempB < 8; iTempB++ )
            if ( iTempA >= ( LINES - 5 ) - ( ( LINES - 5 ) * iPercent / 100 ) )
                wprintw( pWthrust, "x" );
            else
                wprintw( pWthrust, " " );
    }

    wrefresh( pWthrust );
}


/* fnRemove_lander
    Funktion:	Loeschen des Raumschiffs an alter Position
    Param.Ein.:	-
    Param.Aus.:	-
*/

void fnRemove_lander ( void ) {
    int iTemp = 0;

    for ( ; iTemp < 4; iTemp++ ) {
        wmove ( pWspace, iOldy++, iOldx );
        wprintw( pWspace, "       " );
    }

    iOldy = iLandery;
    iOldx = iLanderx;
}


/* fnPrint_landed
    Funktion:	Ausgabe des gelandeten Raumschiffes
    Param.Ein.:	-
    Param.Aus.:	-
*/

void fnPrint_landed ( void ) {
    fnRemove_lander();

    wmove ( pWspace, iLandery - 2, iLanderx );
    wprintw( pWspace, "   |\\" );
    wmove ( pWspace, iLandery - 1, iLanderx );
    wprintw( pWspace, "   |/" );
    wmove ( pWspace, iLandery, iLanderx );
    wprintw( pWspace, "  ooo" );
    wmove ( pWspace, iLandery + 1, iLanderx );
    wprintw( pWspace, " o   o" );
    wmove ( pWspace, iLandery + 2, iLanderx );
    wprintw( pWspace, "ooooooo" );
    wmove ( pWspace, iLandery + 3, iLanderx );
    wprintw( pWspace, " U U U" );

    wrefresh( pWspace );
}


/* fnPrint_crash
    Funktion:	Ausgabe des zerschellten Raumschiffes
    Param.Ein.:	-
    Param.Aus.:	-
*/

void fnPrint_crash ( void ) {
    fnRemove_lander();

    wmove ( pWspace, iLandery + 2, iLanderx );
    wprintw( pWspace, "   oU" );
    wmove ( pWspace, iLandery + 3, iLanderx );
    wprintw( pWspace, "o ooUoUoo" );

    wrefresh( pWspace );
}


