//&>/dev/null;x="${0%.*}";[ ! "$x" -ot "$0" ]||(rm -f "$x";cc -lncurses -o "$x" "$0")&&"$x" $*;exit
// ^^ this up there makes the file executable ^^

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <ncurses.h>
#include <getopt.h>

/* levels have fixed, hardcoded dimensions */
#define LEVEL_HEIGHT 9
#define LEVEL_WIDTH 16

char *levels[] = { "################" /* 0 */
                   "#@##        x#H#"
                   "#          x ###"
                   "#       ##x    #"
                   "#   ## x      ##"
                   "## x  x     x  #"
                   "# x      x## x #"
                   "#     ##x     x#"
                   "################",

                   " #  # # #   # ##" /* 1 */
                   "#   x         @#"
                   "   #x  #x   x   "
                   "# # x     x  # #"
                   "      #   x   # "
                   "#    #H#  x    #"
                   "   #  # #   #xx#"
                   "#             # "
                   " #  #        #  ",

                   "################" /* 2 */
                   "#           x#@#"
                   "#   ##      ##H#"
                   "#   #x     x   #"
                   "# x     x##   x#"
                   "# #x  x  x#  x##"
                   "# ##x #x  x x###"
                   "#x    ##x      #"
                   "################",

                   "################" /* 3 */
                   "#            #H#"
                   "#     #        #"
                   "##x#x x#x#x#x#x#"
                   "# # #x x# # # ##"
                   "##x#x#x x#x#x#x#"
                   "#     #        #"
                   "#   #       #@ #"
                   "################",

                   " ############## " /* 4 */
                   "#@  #   #  #   #"
                   "#  #x # x  x # #"
                   "##      #  #   #"
                   "#x #x#        ##"
                   "##     # x #   #"
                   "#x#  # # #   #H#"
                   "#   #    x#  #x#"
                   " ############## ",

                   "    ############" /* 5 */
                   "   #   x   #x x#"
                   "  #    x    # ##"
                   " #     x       #"
                   "#@     x       #"
                   "##     x  #   ##"
                   "#      x    #  #"
                   "#H #   x  ##x  #"
                   "################",

                   "################" /* 6 */
                   "#              #"
                   " ## ###  #x ##x#"
                   " #x  #x # # # # "
                   "  #  #  ### ##  "
                   " ##  #  #x# #x# "
                   "#              #"
                   "#  @#x    H  #x#"
                   "################",

                   "############### " /* 7 */
                   "#  x##        ##"
                   "#  #x  ## x    #"
                   "#  x## # #x    #"
                   "##     ##  #x# #"
                   "## #       x#x #"
                   "#xHx#   x  #@# #"
                   "##             #"
                   " ###############",

                   "  # ########### " /* 8 */
                   " #x#x      #  @#"
                   "#x x#    x    # "
                   " # #  x##  x#  #"
                   "#  #x #xHx    x#"
                   "#     x##     # "
                   "#x#x         #  "
                   "#           #   "
                   "############    ",

                   "    ########### " /* 9 */
                   "#### x         #"
                   "#   H ###x x# x#"
                   "# x   #x #x   # "
                   "#     #  x  # x#"
                   "#x#x  # x#  #@# "
                   " #x   ###  ###  "
                   "#         # # # "
                   " #########  #  #",

                   "################" /* 10 */
                   "#      #      @#"
                   "#  #xx     xx ##"
                   "##   x   ##   x#"
                   "#x    #x#xx  ###"
                   "## ##    ##    #"
                   "#x  x# x    H x#"
                   "##x###    #   ##"
                   " ## ########### ",

                   "##     ##  #### " /* 11 */
                   "#@#####x ### x##"
                   "#    xx     x  #"
                   "# ##  ##x  #x# #"
                   "# # x ###x ##  #"
                   "# ##  ##   #H# #"
                   "#     x        #"
                   "#        x     #"
                   "################",

                   " ############## " /* 12 */
                   "# @#        x ##"
                   "# #   #x   x## #"
                   "#       x    # #"
                   "# x          #x#"
                   "# #      x     #"
                   "##      x x  #x#"
                   "#H  #    x # # #"
                   " ############## ",

                   "################" /* 13 */
                   "#x#x        x#x#"
                   "# x#@      ##  #"
                   "# H        x   #"
                   "#        x#    #"
                   "#      x       #"
                   "# x#        #  #"
                   "#x#x        x#x#"
                   "################",

                   " ###### ####### " /* 14 */
                   "#     x#     x #"
                   "# # x #  #   x #"
                   "#  @#   #xx #x #"
                   " # #   # x  H# #"
                   "#x       #  #x #"
                   " #      x     # "
                   "#x            x#"
                   " ############## ",

                   "################" /* 15 */
                   "## H#x x      x#"
                   "#x @x#x       ##"
                   "## ###    x   ##"
                   "##     x#x#    #"
                   "#xx       x#x  #"
                   "## x    ####x  #"
                   "##x#  #        #"
                   "################",

                   "################" /* 16 */
                   "# x#        #@ #"
                   "# #  x#xx#x  # #"
                   "#    #x##x# x  #"
                   "# x#       x#  #"
                   "#  x#x    x#   #"
                   "# #  # ##x#  # #"
                   "#    x #x   H  #"
                   "################",

                   "################" /* 17 */
                   "# x    x  H#   #"
                   "#  #x#x   #x   #"
                   "#   #x#    #x  #"
                   "#   x  #   x#x #"
                   "# #x#   # x#   #"
                   "# x#x # x #    #"
                   "#x#@  #     #  #"
                   "################",

                   "################" /* 18 */
                   "#x   ##     ##x#"
                   "# #  #      #x #"
                   "# x#   x##  x  #"
                   "# #    #x      #"
                   "#    #   x#    #"
                   "# ## x# ##x  #H#"
                   "# x# #x     ##@#"
                   "################",

                   "################" /* 19 */
                   "#   x#x        #"
                   "##x  x# ##x   ##"
                   "# # # x   #  # #"
                   "# H #  ##  # @x#"
                   "# #  #   x # # #"
                   "##   x## #x  x##"
                   "#        x#x   #"
                   "################",

                   "################" /* 20 */
                   "# ###     x   ##"
                   "#   #       # ##"
                   "#   ##x      x #"
                   "#  x    x   x ##"
                   "#    #    ###x #"
                   "#  x x @ H x xx#"
                   "################"
                   "                ",

                   "################" /* 21 */
                   "#x#  #x# #x  # #"
                   "#    #         #"
                   "#x   #  #x  x  #"
                   "## #x      x ###"
                   "# x #   ###x   #"
                   "#     #@#H  x  #"
                   "################"
                   "                ",

                   " ############## " /* 22 */
                   "# #  #x# #x  # #"
                   "#    x  #      #"
                   "##   #   x #x  #"
                   "#  #x  #  xx x #"
                   "##x #   ## x   #"
                   "#     #@#H  x  #"
                   " ############## "
                   "                ",

                   "################" /* 23 */
                   "#     #       ##"
                   "# ##x x    ##x##"
                   "# #x     x#  ###"
                   "# xx  x# ##    #"
                   "# #x x #    ## #"
                   "# ##   @#H###xx#"
                   "################"
                   "                ",

                   "################" /* 24 */
                   "#            # #"
                   "# x ##x   x    #"
                   "#   #x  x  ##  #"
                   "# x    ##  #x  #"
                   "# #x   x#    x #"
                   "# ##x #@ H     #"
                   "################"
                   "                " };

enum {                          /* color names for ncurses */
     color_BALL,
     color_CUBE,
     color_VOID,
     color_GIFT,
     color_WALL,
     color_BALL_SELECTED,
     color_CUBE_SELECTED,
};

typedef enum {                  /* characters used to design levels */
     WALL = '#',
     BALL = '@',
     CUBE = 'H',
     VOID = ' ',
     GIFT = 'x'
} cell;

typedef enum {                  /* and now a nasty trick to slightly simplify key handling */
     UP    = KEY_UP,            /* we'll use ncurses codes to define directions */
     DOWN  = KEY_DOWN,
     LEFT  = KEY_LEFT,
     RIGHT = KEY_RIGHT
} direction;

struct options {                /* store how the game is started (controlled by --arguments) */
     int black_and_white;
     int starting_level;
};

struct state {                  /* current state of the game at an instant T */
     char board[ LEVEL_HEIGHT * LEVEL_WIDTH ];
     char moving;
     int  distance_travelled;
     int  level;
     int  nb_levels;
};

/* Count how many gifts are present in the current level
 */
int count_gifts( struct state *s )
{
     int i, n = 0;
     for( i = 0 ; i < LEVEL_HEIGHT * LEVEL_WIDTH ; i++ ) {
          if ( s->board[ i ] == GIFT ) {
               n++;
          }
     }
     return n;
}

/* Write the position of actor into pos
 */
void get_pos( struct state *s, cell actor, int* pos )
{
     int p;

     p = (int)( strchr( s->board, actor ) - s->board );

     pos[ 1 ] = p / LEVEL_WIDTH; /* y */
     pos[ 0 ] = p - ( pos[ 1 ] * LEVEL_WIDTH ); /* x */
}

/* Returns the content of cell( x, y ) of the current level
 */
char get_cell( struct state *s, int x, int y )
{
     return s->board[ y * LEVEL_WIDTH + x ];
}

/* Set cell( x, y ) of the current level to value
 */
void set_cell( struct state *s, int x, int y, cell value )
{
     s->board[ y * LEVEL_WIDTH + x ] = value;
}

/* Load level nb from levels as the current level
 */
void load_level( struct state *s, char* levels[], int nb )
{
     strncpy( s->board, levels[ nb ], LEVEL_HEIGHT * LEVEL_WIDTH );
     s->level = nb;
     s->moving = BALL;
     s->distance_travelled = 0;
}

/* Swicth the currently moving actor between BALL and CUBE
 */
void switch_actor( struct state *s )
{
     s->moving = (s->moving == BALL) ? CUBE : BALL;
}

/* Returns a 'boolean' indicating if the current level is finished (no gifts left)
 */
int won_or_not( struct state *s )
{
     return( count_gifts( s ) == 0 );
}

/* display the current level (with Ncurses)
 */
void display_level( struct state *s )
{
     int i, j;
     
     for( i = 0 ; i < LEVEL_HEIGHT ; i++ ) {
          for( j = 0 ; j < LEVEL_WIDTH ; j++ ) {
               switch( get_cell( s, j, i ) ) {
                    case WALL:
                         attron( COLOR_PAIR( color_WALL ));
                         mvprintw( i+1, j*2, "##" );
                         attroff( COLOR_PAIR( color_WALL ));
                         break;
                    case VOID:
                         attron( COLOR_PAIR( color_VOID ));
                         mvprintw( i+1, j*2, "  " );
                         attroff( COLOR_PAIR( color_VOID ));
                         break;
                    case BALL:
                         if ( s->moving == BALL ) {
                              attron( A_BOLD );
                              attron( COLOR_PAIR( color_BALL_SELECTED ));
                         }
                         else {
                              attron( COLOR_PAIR( color_BALL ));
                         }
                         mvprintw( i+1, j*2, "()" );
                         if ( s->moving == BALL ) {
                              attroff( A_BOLD );
                         }
                         break;
                    case CUBE:
                         if ( s->moving == CUBE ) {
                              attron( A_BOLD );
                              attron( COLOR_PAIR( color_BALL_SELECTED ));
                         }
                         else {
                              attron( COLOR_PAIR( color_BALL ));
                         }
                         mvprintw( i+1, j*2, "[]" );
                         if ( s->moving == CUBE ) {
                              attroff( A_BOLD );
                              attroff( COLOR_PAIR( color_CUBE_SELECTED ));
                         }
                         else {
                              attroff( COLOR_PAIR( color_CUBE ));
                         }
                         break;
                    case GIFT:
                         attron( COLOR_PAIR( color_GIFT ));
                         mvprintw( i+1, j*2, "<>" );
                         attroff( COLOR_PAIR( color_GIFT ));
                         break;
                    default: break;     /* ignore newlines */
               }
          }
     }

     mvprintw( 1, 35, "level %i of %i", s->level + 1, s->nb_levels );
     mvprintw( 2, 35, "%i gifts left", count_gifts( s ) );
     mvprintw( 3, 35, "%i meter%s traveled", s->distance_travelled, ( s->distance_travelled > 1 ) ? "s" : "" );

     refresh();
}

/* Move the current actor towards direction in the current level,
   eating gifts on the way if the moving actor is the ball
*/
void make_a_move( struct state *s, direction where )
{
     int motion[ 2 ] = { 0, 0 };
     int item_coord[ 2 ];
     get_pos( s, s->moving, item_coord ); /* get the coordinates of the moving actor */

     /* Setup the motion vector according to direction.*/
     switch( where ) {
          case UP:
               motion[ 1 ]--;
               break;
          case DOWN:
               motion[ 1 ]++;
               break;
          case LEFT:
               motion[ 0 ]--;
               break;
          case RIGHT:
               motion[ 0 ]++;
               break;
          default: break;
     }

     /* Calculating arrival coordinates */
     while (                      /* Hairy conditions ahead */
          /* target cell is within level's boundaries */
          ( ( item_coord[ 0 ] + motion[ 0 ] >= 0 ) && ( item_coord[ 0 ] + motion[ 0 ] < LEVEL_WIDTH ) ) &&
          ( ( item_coord[ 1 ] + motion[ 1 ] >= 0 ) && ( item_coord[ 1 ] + motion[ 1 ] < LEVEL_HEIGHT ) ) &&
          /* and target cell is empty */
          ( get_cell( s, item_coord[ 0 ] + motion[ 0 ], item_coord[ 1 ] + motion[ 1 ] ) == VOID )
          /* or, the ball will eat gifts so we can move it on one */
          || ( s->moving == BALL && ( get_cell( s, item_coord[ 0 ] + motion[ 0 ], item_coord[ 1 ] + motion[ 1 ] ) == GIFT ) )
          )
     {
          set_cell( s, item_coord[ 0 ], item_coord[ 1 ], VOID ); /* void the origin cell */

          item_coord[ 0 ] += motion[ 0 ];           /* move coordinate */
          item_coord[ 1 ] += motion[ 1 ];           /* to those of target cells */
          
          set_cell( s, item_coord[ 0 ], item_coord[ 1 ], s->moving ); /* move actor into target cell */

          /*
            We could update the display here, but in practice it all happen so fast
            that the move isn't noticeable. So it's commented.
            (maybe on a very slow machine it adds beauty?...)
          */
          /* display_level( s ); */

          s->distance_travelled++;                /* increment distance_travelled */
     }
}

/* Parse the --arguments, if any, to populate options
 */
int parse_args( int argc, char* argv[], struct options *o, struct state *s )
{
     int option_index;
     char c = '?';
  
     char* optstring = "l:bhv";
     static struct option long_options[] = {
          {"black-and-white" , no_argument,       NULL, 'b'},
          {"level"           , required_argument, NULL, 'l'},
          {"help"            , no_argument,       NULL, 'h'},
          {0, 0, 0, 0}
     };
  
     char* help_text = "star [options]\n"
          "\t-h --help :\n\t\t what you are reading\n"
          "\t-b --black-and-white :\n\t\t don't use colors (ncurses)\n"
          "\t-l<n> --level=<n> :\n\t\t wrap to level n\n";

     while(c != EOF) {
          c = getopt_long(argc, argv, optstring, long_options, &option_index);
    
          switch(c) {
               case 'h' :
                    printf( "%s\n", help_text );
                    exit( EXIT_SUCCESS );
               case 'b' :
                    o->black_and_white = 1;
                    break;
               case 'l' :
                    o->starting_level = atoi( optarg ) - 1;
                    if ( o->starting_level > s->nb_levels ) {
                         o->starting_level = s->nb_levels - 1;
                    } else {
                         if ( o->starting_level < 0 ) {
                              o->starting_level = 0;
                         }
                    }
                    break;
               case '?' :
               case ':' :
                    exit( EXIT_SUCCESS );
               default : break;
          }
     }

     if (optind < argc) {
          fprintf( stderr, "Invalid arguments :\n" );
          while (optind < argc) {
               fprintf( stderr, "%s\n", argv[optind++] );
          }
     }
  
     return optind;
}

/* Entry point of the program.

   Read --arguments.
   Initialize Ncurses.
   Load the first level.
   Enter the loop where it read the keys to play or 'q' or 'Q' to quit.
*/
int main( int argc, char* argv[] )
{
     int key, over = 0;
     struct state *s = malloc( sizeof( struct state ) );
     struct options *o = malloc( sizeof( struct options ) );

     if ( s == NULL || o == NULL ) {
          exit( EXIT_FAILURE );               /* Guys, we're out of memory */
     }
          
     /* trick to count how many levels we have */
     s->nb_levels = sizeof( levels ) / sizeof( levels[ 0 ] );
   
     o->starting_level = 0;
     o->black_and_white = 0;
   
     parse_args( argc, argv, o, s ); /* not caring about return value here, it's treated inside the function */
   
     /* ncurses */
     WINDOW *w_main = initscr(  ); /* why can't stdscr be used transparently? */
     cbreak();
     noecho();
     nonl();
     intrflush( w_main, FALSE );
     keypad( w_main, TRUE );
     if ( ( ! o->black_and_white ) && has_colors(  ) == TRUE ) {
          start_color(  );
          init_pair( color_CUBE,          COLOR_RED,    COLOR_BLACK  );
          init_pair( color_BALL,          COLOR_BLUE,   COLOR_BLACK  );
          init_pair( color_GIFT,          COLOR_YELLOW, COLOR_BLACK  );
          init_pair( color_WALL,          COLOR_WHITE,  COLOR_WHITE  );
          init_pair( color_VOID,          COLOR_BLACK,  COLOR_BLACK  );
          init_pair( color_CUBE_SELECTED, COLOR_RED,    COLOR_YELLOW );
          init_pair( color_BALL_SELECTED, COLOR_BLUE,   COLOR_YELLOW );
     }

     /* load the first level to start the loop in a correct state */
     load_level( s, levels, o->starting_level );
   
     do {
          if ( won_or_not( s ) ) {
               if ( s->level < s->nb_levels - 1 ) {
                    load_level( s, levels, s->level + 1 );
               }
               else {
                    over = 1;
               }
          }

          if ( over == 0 ) {
               display_level( s );
               key = getch();
               switch( key ) {
                    case KEY_UP:
                    case KEY_DOWN:
                    case KEY_LEFT:
                    case KEY_RIGHT:
                         make_a_move( s, key ); /* see definition of direction up above */
                         break;
                    case ' ':
                         switch_actor( s );
                         break;
                    case 'n':
                         if ( s->level < s->nb_levels - 1 ) {
                              load_level( s, levels, s->level + 1 );
                         }
                         break;
                    case 'p':
                         if ( s->level > 0 ) {
                              load_level( s, levels, s->level - 1 );
                         }
                         break;
                    case 'r':
                         load_level( s, levels, s->level );
                         break;
                    default:
                         break;
               }
          }
          else {
               key = 'q';
          }
     } while( ( s->level < s->nb_levels ) && (( key != 'q' ) && ( key != 'Q' )) );

     free( s );
     free( o );

     /* Clean ncurses' mess */
     echo();
     nocbreak();
     endwin();
     nl();

     if ( over == 1 ) {
          printf( "################################\n" );
          printf( "##                            ##\n" );
          printf( "## You've finished the whole  ##\n" );
          printf( "##  game!                     ##\n" );
          printf( "##                            ##\n" );
          printf( "## Now it's your turn to      ##\n" );
          printf( "##  contribute new levels ;)  ##\n" );
          printf( "##                            ##\n" );
          printf( "################################\n" );
     }

     return 0;
}