/* 
 * Remigiusz Jan Andrzej Modrzejewski, http://lrem.net/ <lrem at go2.pl> 
 * Distributed under the GPL, for more details see:  
 * http://lrem.net/zracer.xhtml 
 *  
 * ZRacer - a simple arcade game in ncurses 
 * 
 * ZRacer is a racing game where 1 - 2 players race on a randomly 
 * generated racecourse with split-screen and using the same keyboard. 
 *  
 * Conventions taken: 
 *      - coordinates order is (y, x) 
 *      - (0, 0) is upper left corner of everything 
 *      - as a result, finish line is at line 0 
 *      - car doesn't take up the whole rectangle (for collision checking) 
 *      - when a car crashes, it's window is frozen and no input is taken 
 *      - the track is stored as its ascii-art representation 
 */

#include <curses.h> 
#include <cstdarg> 
#include <cstdio> 
#include <string> 
#include <vector> 
#include <ctime> 
#include <cassert> 
#include <cstdlib>

using namespace std;

// This one defines a random function with results in range 0..1 (double). 
#define drand()((double)rand()/RAND_MAX)

// Various constants 
#define MAX_CAR_SIZE 20 
#define MAX_PLAYERS 2 
#define INF 123456789 
#define KEY_ESC 27 // Missing in ncurses... 
#define RESULTS_COLORS 11 
#define MESSAGE_LENGTH 100

// Action values 
#define ACCELERATE -1 
#define BRAKE 1 
#define LEFT -1 
#define RIGHT 1

// Make passage, but don't exceed available space. 
#define MINIMAL_WIDTH\ 
(min(settings.players*settings.car_size*2.5, (double)settings.race_width-2))

   // Main menu item defines, for convenience. 
#define MENU_QUIT 0 
#define MENU_START 1 
#define MENU_OPTIONS 2

   /* 
    * In-game settings. This needn't really by a struct, but it looks more 
    * readable to access settings by "settings.delay" than "delay". 
    */

   struct _settings 
{ 
   // Basic game delay, is not equal to move time, but is a factor. 
   timespec delay; 
   // The axis of splitscreen. 
   bool vertical_split; 
   // Whether both players race on the same-looking racecourse. 
   bool similar_track; 
   // Or maybe literally the same one? (Collisions possible) 
   bool shared_track; 
   // The sizes of the course, better make it bigger than the car ;) 
   int race_length, race_width; 
   // The minimal width of the road the players can drive. 
   int minimal_width; 
   // The number of participants. 
   int players; 
   // Character with which the cars are drawn. 
   char character; 
   // The size of the car. 
   int car_size; 
   // The distance interval at which the speed of the car changes. 
   int speed_base; 
   // The chance of generating a rock on a given line 
   double rock_chance; 
   // The chance of generating a turning on a given line 
   double turn_chance; 
   // Keys players use to interact with the game. 
   int controls[MAX_PLAYERS][4];

   void reset(void)
   {
      delay.tv_sec = 0;
      // Hundredth of a second * const.
      delay.tv_nsec = 1000000*25;
      similar_track = vertical_split = true;
      shared_track = true;
      race_length = 500;
      // Zero makes these 2 variables adjusted to screen size.                                                 race_width = 0;                                                                                   minimal_width = 0;
      players = 1;
      character = '^';
      car_size = 10;
      speed_base = 5;
      rock_chance = 0.025;
      turn_chance = 0.125;

      // Arrow keys for first player
      controls[0][0]=KEY_UP;
      controls[0][1]=KEY_DOWN;
      controls[0][2]=KEY_LEFT;
      controls[0][3]=KEY_RIGHT;
      // WSAD for second
      controls[1][0]='w';
      controls[1][1]='s';
      controls[1][2]='a';
      controls[1][3]='d';
   }

   void editor(void);
   void _edit_players(void);
   void _edit_length(void);
   void _edit_width(void);
   void _edit_rocks(void);
   void _edit_turns(void);
   void _edit_delay(void);
   void _edit_sharing(void);
} settings;

class car_image 
{ 
   /* 
    * This is where we store the image of the car. 
    * For performance purposes, it is created only once when scaled. 
    */ 
   bool storage [MAX_CAR_SIZE+1][MAX_CAR_SIZE+1]; 
   // And as list of used pixels coords. 
   vector<pair<int, int> > dots; 
   char character; 
   int color, size;

   /* 
    * Internal functions. First one is _clear() - it just sets all 
    * the storage area to false. Second one is line() - takes coords 
    * of 2 points and draws a line between them (or more literally 
    * just marks certain values within storage as true). 
    */ 
   void _clear(void); 
   void _line(int, int, int, int);

   public: 
   /* 
    * Constructor, takes input from the settings. 
    */ 
   car_image(void); 
   /* 
    * This one simply draws the car on the given window. It assumes  
    * that this can clearly be done, so all the checks need to be 
    * done earlier. The parameters it takes are the window and position 
    * of upper left corner of the car. 
    */ 
   void display(WINDOW*, int, int); 
   /* 
    * Draws the explosion of the car. Parameters are windows, position 
    * of upper left corner of the car. Position is relative to the window,  
    * _not_ the track. 
    */ 
   void explode(WINDOW*, int, int); 
   /* 
    * Collision happens only when an obstacle is on a pace taken by the 
    * car. It is possible to have the obstacle between the car's "ribs". 
    * This checks whether given pace relative to *car's position* is 
    * taken by the car. 
    */
   bool collision_check(int, int); 
   /* 
    * These are simple mutators. 
    */ 
   void set_character(char); 
   void set_color(int);

   // And a simple accessor. 
   vector<pair<int, int> > get_dots(void); 
};

class track 
{ 
   /* 
    * These are the most important data for the game. The track should 
    * be generated only once each game, preferably shared between players. 
    */ 
   vector<vector<char> > circuit;

   public: 
   /* 
    * Two constructors. One takes input from the settings, second just  
    * copies another track. 
    */ 
   track(void); 
   track(track*); 
   /* 
    * Takes the number of the top line to display and the window. 
    * Window's height and width are grabbed by getmaxyx(). 
    * There's an assertion that the window is wide enough. 
    */ 
   void display(WINDOW*, int); 
   /* 
    * Tells whether there's an obstacle at a given pace. 
    */ 
   bool taken(int, int);

   void mark(int, int, car_image*); 
   void unmark(int, int, car_image*); 
};

class player_handler 
{ 
   WINDOW* screen; 
   track* course; 
   car_image* car; 
   // last_move is the time value of the previous player's action 
   // (y, x) is the position of the upper left corner of the car 
   // top_line is the top displayed line of the course 
   // commands store what player clicked 
   int last_move, y, x, top_line, command_x, command_y, screen_height; 
   int controls[4];

   public: 
   /* 
    * This constructor prepares the part of the screen for the player. 
    * It decides which part of screen to take, and the color of the car, 
    * taking the settings and the player number. The track is generated 
    * outside it. 
    */ 
   player_handler(int, track*); 
   /* 
    * Player's main loop. Returns true if the player continues to play, 
    * and false if the game ends for him. Also does redraw the window. 
    */ 
   bool tick(int); 
   /* 
    * Knowing the player's key controls, this one does what it's named for. 
    * Only sets the action to perform during next move. 
    */ 
   void parse_input(int); 
   /* 
    * These are used only in shared track races in order to catch player-player 
    * collisions. 
    */ 
   void mark_position(void); 
   void unmark_position(void); 
   /* 
    * Apart from deallocating standard structures, this one has also to 
    * destroy the ncurses window it created, so it needs a separate destructor. 
    */ 
   //~player_handler(void); 
};

class game 
{ 
   int time; 
   player_handler* players[MAX_PLAYERS]; 
   bool alive[MAX_PLAYERS];

   public: 
   /*
    * Constructor does all the fancy things like initializing ncurses, 
    * while destructor brings back normal tty behaviour. This is great, 
    * as it doesn't force me to remember about deinitialization any 
    * time I want to break the game. 
    */ 
   game(void); 
   ~game(void); 
   /* 
    * This is the game's main loop action... 
    * It returns true as long as game continues. 
    */ 
   bool tick(void); 
};

int main_menu (void); 
// This is a wrapper around printw, also accepts arbitrary number of arguments 
void message (char*, ...);

int main (void) 
{ 
   bool keep_asking = true;

   settings.reset();

   while(keep_asking) 
   { 
      switch(main_menu()) 
      { 
         case MENU_START: 
            { 
               game race; 
               while(race.tick()) 
                  nanosleep(&settings.delay, NULL); 
               break; 
            } 
         case MENU_OPTIONS: 
            settings.editor(); 
            break; 
         case MENU_QUIT: 
            keep_asking = false; 
      } 
   }

   // In C a "return 0;" would come here, but this is not C... 
}

/* 
 * This function opens a window with a message disregarding anything else that was 
 * running. Good for displaying error messages, final results and so. Waits for an 
 * ESC pressed before quiting. 
 */ 
void message (char* format_string, ...) 
{ 
   va_list args; 
   // Allocate a buffer for the message. 
   char* final_string = new char[MESSAGE_LENGTH];

   // Fetch the "..." arguments. 
   va_start(args, format_string); 
   // Transform all the arguments into a single string. 
   vsprintf(final_string, format_string, args); 
   // Finalize the work on the "..." arguments. 
   va_end(args);

   // Get screen resolution. 
   int screen_height, screen_width; 
   getmaxyx(stdscr, screen_height, screen_width);

   // Create a window for the message, of it's size  
   WINDOW* message_win = newwin(1, strlen(final_string), 
         // and at the centre of the screen.  
         screen_height/2, screen_width/2 - strlen(final_string)/2);

   // Print the message. 
   wattron(message_win, COLOR_PAIR(RESULTS_COLORS)); 
   wattron(message_win, A_BOLD); 
   waddstr(message_win, final_string); 
   wattroff(message_win, COLOR_PAIR(RESULTS_COLORS)); 
   wattroff(message_win, A_BOLD); 
   wrefresh(message_win);

   // Wait for an ESC. 
   while(getch()!=KEY_ESC) 
      nanosleep(&settings.delay, NULL);

   // Clean up after myself. 
   delwin(message_win); 
}

/* 
 * This class is created solely for the cool trick used in game constructor/destructor. 
 * It's struct just because it doesn't have anything private. 
 * Watch it's ingenuity and simplicity! 
 */ 
struct simple_curses 
{ 
   simple_curses(void) 
   { 
      // Initialize ncurses. This is done separately from initializing for 
      // the game in order to have different options.  
      initscr(); 
      cbreak(); 
      clear(); 
   }

   ~simple_curses(void) 
   { 
      // Fall back to normal options 
      nocbreak(); 
      endwin(); 
   } 
};

int main_menu (void) 
{ 
   // Whole trick is: constructor get's run here, destructor whenever leaving 
   // the function. 
   simple_curses enviroment;

   // Tell them what to do... 
   printw("\t\tWelcome to ZRacer by lRem!\n"); 
   printw("If you like this game look for more at http://lrem.net/\n\n"); 
   printw("\t\t\tMAIN MENU:\n\n"); 
   printw("q) Quit the game.\n"); 
   printw("s) Start a new game.\n"); 
   printw("o) Options.\n\n");

   // And wait for them do it. 
   char pressed = 0; 
   for(;;) 
   { 
      printw("Choose any option: "); 
      refresh(); 
      pressed = getch(); 
      switch(pressed) 
      {
         case 'q': 
            return MENU_QUIT; 
            // No breaks - return already quits the switch. 
         case 's': 
            return MENU_START; 
         case 'o': 
            return MENU_OPTIONS;                                                                                                                                 } 
            // In case user missed the key, print the next request in next line. 
            addch('\n');  
   } 
}

void _settings::editor(void) 
{ 
   simple_curses enviroment;

   printw("\t\tSETTINGS EDITOR\n");                                                                                                                                                                                                  printw("q) Quit the editor\n"); 
   printw("p) Set the number of players\n"); 
   printw("l) Set the length of the racecourse\n"); 
   printw("r) Set the chance to generate a rock\n"); 
   printw("t) Set the chance to generate a turning\n"); 
   printw("s) Set master delay\n"); 
   printw("h) Set track sharing\n"); 
   // This doesn't give nice results... 
   //printw("w) Set the width of the racecourse\n"); 
   char pressed = 0; 
   for(;;) 
   {                                                                                                                                                                                                                printw("Choose any option: "); 
      refresh(); 
      pressed = getch(); 
      switch(pressed) 
      { 
         case 'q': 
            return; 
         case 'p': 
            _edit_players(); 
            break; 
         case 'l':                                                                                                                                _edit_length(); 
                                                                                                                                                  break; 
         case 'r': 
                                                                                                                                                  _edit_rocks(); 
                                                                                                                                                  break; 
         case 't': 
                                                                                                                                                  _edit_turns(); 
                                                                                                                                                  break; 
         case 's': 
                                                                                                                                                  _edit_delay(); 
                                                                                                                                                  break;                                                                                                                                                       case 'h': 
                                                                                                                                                     _edit_sharing(); 
                                                                                                                                                  //case 'w': 
                                                                                                                                                  //      _edit_width(); 
                                                                                                                                                  //      break; 
      } 
   }

}

void _settings::_edit_players(void)                                                                                              { 
   printw("\n\tSelect the number of players (1 - %d, currently %d):", MAX_PLAYERS, players);                                                                        players = 0; 
   // While players outside the possible range. 
   for(; players<1 || MAX_PLAYERS<players;) 
   {
      players = getch() - '0'; 
      addch(' '); 
   } 
   addch('\n'); 
}

void _settings::_edit_length(void) 
{ 
   printw("\n\tSet the length of the track (arbitrary, currently %d):", race_length); 
   race_length = -1; 
   // While players outside the possible range. 
   for(; race_length<0;) 
   { 
      scanw("%d", &race_length); 
   } 
}

void _settings::_edit_width(void) 
{ 
   printw("\n\tSet the width of the track (narrower than terminal, 0 means max, currently %d):", race_width); 
   race_width = -1; 
   // While players outside the possible range. 
   for(; race_width<0;) 
   { 
      scanw("%d", &race_width); 
   } 
}

void _settings::_edit_rocks(void) 
{ 
   printw("\n\tSet the chance of generating a rock (0-1, currently %lf):", rock_chance); 
   rock_chance = -1; 
   for(; rock_chance<0 || 1<rock_chance;) 
   { 
      scanw("%lf", &rock_chance); 
   } 
}

void _settings::_edit_turns(void) 
{ 
   printw("\n\tSet the chance of generating a turn (0-1, currently %lf):", turn_chance); 
   turn_chance = -1; 
   for(; turn_chance<0 || 1<turn_chance;) 
   { 
      scanw("%lf", &turn_chance); 
   } 
}

void _settings::_edit_delay(void) 
{ 
   printw("\n\tSet the master delay (positive, nanosceonds, currently %d):", delay.tv_nsec); 
   delay.tv_nsec = -1; 
   for(; delay.tv_nsec<0;) 
   { 
      scanw("%d", &delay.tv_nsec); 
   } 
}

void _settings::_edit_sharing(void) 
{ 
   printw("\n\tShould the track be Similar, sHared or Different for different palyers? "); 
   char response; 
   for(; response!='s' && response!='h' && response!='d';) 
   {       
      scanw("%c", &response); 
      switch(tolower(response)) 
      { 
         case 's': 
            similar_track = true; 
            shared_track = false; 
            break; 
         case 'h': 
            similar_track = true; 
            shared_track = true; 
            break; 
         case 'd': 
            similar_track = false; 
            shared_track = false; 
            break; 
      } 
   } 
}

bool game::tick(void) 
{       
   bool game_continues = false; 
   time++;

   // For every key waiting in buffer... 
   int pressed_key; 
   while((pressed_key = getch()) != ERR) 
   { 
      if(pressed_key == KEY_ESC) // End the game. 
         for(int i = 0; i<settings.players; i++) 
            alive[i]=false; // By killing all players.

      // Pass the input to each player. 
      for(int i = 0; i<settings.players; i++) 
         players[i]->parse_input(pressed_key); 
   }

   // Checking for player-player collisions is realized by marking each player's position 
   // as an obstacle on the track. 
   if(settings.shared_track) 
      for(int i=0; i<settings.players; i++) 
         if(alive[i]) 
            players[i]->mark_position();

   for(int i=0; i<settings.players; i++) 
      if(alive[i]) 
         if(settings.shared_track) 
         {       
            players[i]->unmark_position(); 
            game_continues = (alive[i] = players[i]->tick(time)) || game_continues; 
            players[i]->mark_position(); 
         } 
         else 
            // If the player dies it sets his alive status to false. 
            // If he lives, then there is a reason to continue the game. 
            game_continues = (alive[i] = players[i]->tick(time)) || game_continues;

   if(settings.shared_track) 
      for(int i=0; i<settings.players; i++) 
         players[i]->unmark_position();


   // Results. 
   if(!game_continues) 
      message("Game finished after %d turns.", time);

   return game_continues; 
}

game::game (void) 
{ 
   // Initialize the RNG
   // We have already a variable called "time", and we need a function of the same name. 
   // In order to circumvent this problem, we use the namespaces. 
   srand(std::time(NULL));

   // Initialize ncurses. 
   initscr(); 
   // Initialize colors (refuse to start without them). 
   assert(has_colors()); 
   start_color(); 
   keypad(stdscr, TRUE); 
   cbreak(); 
   noecho(); 
   nonl(); 
   nodelay(stdscr, true);

   // Color palette. 
   init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK); 
   init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK); 
   init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);                                                                                                                                                        init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK); 
   init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK); 
   init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK); 
   init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK); 
   init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK); 
   init_pair(RESULTS_COLORS, COLOR_YELLOW, COLOR_BLUE);

   // Adjust settings, if needed. 
   if(settings.race_width == 0) 
   {       
      int y, x; 
      getmaxyx(stdscr, y, x); 
      settings.race_width = settings.vertical_split ? x/settings.players : x; 
   }               
   if(settings.minimal_width == 0) 
      settings.minimal_width = (int)(MINIMAL_WIDTH);

   // Prepare players  
   if(settings.shared_track) 
   {               
      track* course = new track(); 
      for(int i = 0; i<settings.players; i++) 
         players[i]=new player_handler(i, course); 
   }       
   else 
      if(settings.similar_track) 
      {               
         track* course = new track(); 
         for(int i = 0; i<settings.players; i++) 
            players[i]=new player_handler(i, course); 
      }       
      else 
      { 
         for(int i = 0; i<settings.players; i++) 
            players[i]=new player_handler(i, new track()); 
      } 
   for(int i = 0; i<settings.players; i++) 
      alive[i]=true;

   // Let the moves begin. 
   time = 0;                                                                                                                                                    }

game::~game (void) 
{                                                                                                                                                                                                                echo(); 
   nl();           
   nocbreak(); 
   nodelay(stdscr, false);  
   endwin();       
}

track::track (void)
{                               
   // Allocate the structures. 
   circuit.resize(settings.race_length);                                                                                                                                                                    for(vector<vector<char> >::iterator it = circuit.begin(); it!=circuit.end(); it++) 
      it->resize(settings.race_width);

   // And create the course! 
   int borders[2]; 
   int borders_directions[2]={0,0};

   // Initially make the road halfway between minimal and maximal possible. 
   assert(settings.minimal_width <= settings.race_width); 
   borders[0] = (settings.race_width - settings.minimal_width)/4; 
   borders[1] = (settings.race_width*3 + settings.minimal_width)/4;

   // And generate the lines. 
   for(int i=settings.race_length-1; 0<=i; i--) 
   { 
      // Put some background. 
      for(int j=0; j<(int)circuit[i].size(); j++)   
         circuit[i][j]=' '; 
      // Distance meter.  
      circuit[i][0]='0'+i%10; 
      // Occasional rock on the track :> 
      if(drand()<settings.rock_chance) 
         circuit[i][rand()%settings.race_width]='*'; 
      // Move the kerbs. 
      borders[0]+=borders_directions[0]; 
      borders[1]+=borders_directions[1]; 
      // Draw the kerbs. 
      switch(borders_directions[0]) 
      {// Different chars, depending on the kerb direction. 
         case 0: 
            circuit[i][borders[0]]='|'; 
            break; 
         case 1: 
            circuit[i][borders[0]]='/'; 
            break; 
         case -1: 
            circuit[i][borders[0]]='\\'; 
      } 
      switch(borders_directions[1]) 
      { 
         case 0: 
            circuit[i][borders[1]]='|'; 
            break; 
         case 1: 
            circuit[i][borders[1]]='/'; 
            break; 
         case -1: 
            circuit[i][borders[1]]='\\'; 
      }


      // Turn the kerbs... 
      int tries = 0; // This is in case it gets to narrow and no space at once (hangs). 
      while( 
            tries++<5 && 
            (drand()<settings.turn_chance || // If RNG wants so, 
             borders[0]+borders_directions[0] == 0 // or no space. 
            )) 
         borders_directions[0] = rand()%3-1; 
      if(borders[1]-borders[0] < settings.minimal_width) // If to narrow, 
         borders_directions[0] = -1; // Make it wider 
      // A sanity check. 
      if(borders[0]+borders_directions[0] == 0)
         borders_directions[0] = 0; 
      // And the second one. 
      tries = 0;
      while( 
            tries++<5 && 
            (drand()<settings.turn_chance || // If RNG wants so, 
             borders[1]+borders_directions[1] == settings.race_width // or no space.     
            )) 
         borders_directions[1]=rand()%3-1; 
      if(borders[1]-borders[0] < settings.minimal_width) 
         borders_directions[1] = 1; 
      if(borders[1]+borders_directions[1] == settings.race_width) 
         borders_directions[1] = 0; 
   } 
}

void track::display(WINDOW* screen, int top_line) 
{ 
   // Get the geometry. 
   int screen_width, screen_height; 
   getmaxyx(screen, screen_height, screen_width);

   // For every visible line... 
   for(int i = top_line; i<top_line+screen_height; i++) 
   {                                                                                                                                                                                                                // Move the cursor at it's beginning... 
      // Position at screen centre. 
      wmove(screen, i-top_line, (screen_width-settings.race_width)/2); 
      // And print all the characters. 
      for(int j=0; j<settings.race_width; j++) 
         waddch(screen, circuit[i][j]); 
   } 
}

bool track::taken(int y, int x) 
{       
   return circuit[y][x]!=' '; 
}

void track::mark(int y, int x, car_image *car) 
{ 
   vector<pair<int, int> > dots = car->get_dots();

   for(unsigned int i=0; i<dots.size(); i++) 
      if(circuit[y+dots[i].first][x+dots[i].second] == ' ') 
         circuit[y+dots[i].first][x+dots[i].second] = settings.character; 
}

void track::unmark(int y, int x, car_image *car) 
{ 
   vector<pair<int, int> > dots = car->get_dots();

   for(unsigned int i=0; i<dots.size(); i++) 
      if(circuit[y+dots[i].first][x+dots[i].second] == settings.character) 
         circuit[y+dots[i].first][x+dots[i].second] = ' ';

}

player_handler::player_handler(int position, track* racecourse) 
{ 
   // Set the sizes for the windows. 
   // Height gets reused and thus is declared within class. 
   int screen_width; 
   getmaxyx(stdscr, screen_height, screen_width); 
   int width = settings.vertical_split? screen_width/settings.players : screen_width; 
   int height = settings.vertical_split? screen_height : screen_height/settings.players;

   // We can't set the track to be wider than the display. 
   assert(settings.race_width<=width);

   // Prepare screen part. 
   if(settings.vertical_split) 
   { 
      screen = newwin(
            height, width,  
            // Take the corresponding vertical stripe. 
            0, width*(settings.players - position - 1) 
            ); 
   }       
   else 
   {       
      screen = newwin( 
            height, width, 
            // Take the corresponding horizontal stripe. 
            height*position, 0 
            ); 
   }

   // Just copy this pointer. 
   course = racecourse;

   // And create an image for yourself 
   car = new car_image();

   // Place the car at a reasonable place. 
   y = settings.race_length - settings.car_size; 
   if(settings.shared_track) 
      x = (settings.race_width - (settings.car_size+1)*(settings.players-2*position))/ 2;                          
   else 
      x = settings.race_width/2;

   // We want the car at the very bottom of the screen. 
   top_line = settings.race_length - height;

   // If not set, the player actually could freeze for a while. 
   last_move = -INF;

   // Copy the controls. 
   memcpy(controls, settings.controls[position], 4*sizeof(int)); 
   // And make sure player doesn't take off. 
   command_y = command_x = 0; 
}

/*player_handler::~player_handler(void) 
  { 
// It's so simple... 
delwin(screen); 
}*/

// Just a simple switched assignment.  
void player_handler::parse_input(int pressed_key)                                                                                { 
   if(pressed_key == controls[0])                                                                                                           command_y=ACCELERATE;                                                                                                                                                                            if(pressed_key == controls[1]) 
      command_y=BRAKE;                                                                                                                                                                                 if(pressed_key == controls[2])                                                                                                           command_x=LEFT; 
   if(pressed_key == controls[3])                                                                                                           command_x=RIGHT;                                                                                                                                                                         }

void player_handler::mark_position(void) 
{ 
   course->mark(y, x, car); 
}

void player_handler::unmark_position(void) 
{                       
   course->unmark(y, x, car); 
}

bool player_handler::tick(int time) 
{       
   bool survive = true;

   // The higher the car on the screen, the faster it moves. 
   if(last_move + (y-top_line)/settings.speed_base < time) 
   { 
      last_move=time; 
      y--;

      // Watch to not segfault here. 
      top_line=max(0, top_line-1);

      // Handle commands 
      y+=command_y; 
      x+=command_x; 
      command_y = command_x = 0;

      // Make sure he doesn't escape from the screen. 
      y = max(top_line, min(top_line + screen_height - settings.car_size, y));

      if(y <= 0) // Plain win 
         return false;

      course->display(screen, top_line); 
      // Check for collisions. 
      for(int i=y; i<y+settings.car_size; i++)  
         for(int j=x; j<x+settings.car_size; j++) 
            if(course->taken(i, j) && car->collision_check(i-y, j-x)) 
               survive=false;

      if(survive) 
         car->display(screen, y-top_line, x); 
      else 
         car->explode(screen, y-top_line, x); 
      wrefresh(screen); 
   }

   return survive; 
}

car_image::car_image(void) 
{ 
   character = settings.character; 
   color = COLOR_YELLOW; 
   size = settings.car_size;

   // The coolest part - drawing the damned thing   
   _clear(); 
   // Original key points were (0,0), (1,4), (2,0), (3,4) and (4,0). 
   // Now we just need to scale them and draw lines between. 
   _line(0, 0, 1*(size-1)/4, 4*(size-1)/4);                                                                          _line(1*(size-1)/4, 4*(size-1)/4, 2*(size-1)/4, 0); 
   _line(2*(size-1)/4, 0, 3*(size-1)/4, 4*(size-1)/4); 
   _line(3*(size-1)/4, 4*(size-1)/4, 4*(size-1)/4, 0);  
}

void car_image::_clear(void) 
{               
   // Simply clear the image 
   for(int i=0; i<size; i++) 
      for(int j=0; j<size; j++) 
         storage[i][j]=false;                                                                                                                         }

void car_image::display(WINDOW* screen, int y, int x) 
{               
   // We don't need to store this anywhere, so it can be set ad-hoc.

   wattron(screen, COLOR_PAIR(color)); 
   wattron(screen, A_BOLD);

   for(int i=0; i<size; i++) 
   { 
      for(int j=0; j<size; j++) 
         if(storage[i][j]) 
            mvwaddch(screen, y+i, x+j, character);

   }

   // Restore the normal color for everything else... 
   wattroff(screen, COLOR_PAIR(color)); 
   wattroff(screen, A_BOLD); 
}

void car_image::explode(WINDOW* screen, int y, int x) 
{// Even in ASCII we can do cool explosions :> 
   for(int i=0; i<size; i++) 
   { 
      for(int j=0; j<size; j++) 
         if(rand()%2) 
         { 
            int color = rand()%7 + 1; 
            wattron(screen, COLOR_PAIR(color)); 
            mvwaddch(screen, y+i, x+j, '*'); 
            wattroff(screen, COLOR_PAIR(color)); 
         }

   }

}

bool car_image::collision_check(int y, int x) 
{ 
   return storage[y][x]; 
}

vector<pair<int, int> > car_image::get_dots(void) 
{ 
   return dots; 
}

/* 
 * This function is the heart of the original task for which this program 
 * was created. It is based on the simple formula, that for a line between 
 * (y1, x1) and (y2, x2) and a given coordinate x, the y coordinate for a 
 * point on a line is y1 + (y2-y1) / ( (x2-x1)/(x-x1) ). 
 */ 
void car_image::_line(int y1, int x1, int y2, int x2) 
{ 
   // It can be given the other way round... 
   if(x2 < x1) 
   { 
      // So just swap the points. 
      int tmp = x1; 
      x1 = x2; 
      x2 = tmp; 
      // And swap the other coordinate. 
      tmp = y1; 
      y1 = y2; 
      y2 = tmp; 
   } 
   for(int i=x1; i<=x2; i++) 
   { 
      int y = y1 + (int)((float) (y2-y1)*(i-x1)/(x2-x1)+0.5); 
      // +0.5 is in order to do real rounding, not just truncation. 
      storage[y][i]=true; 
      dots.push_back(make_pair(y, i));
   } 
}