/* A fairly hacked-up version of sokoban, for CP/M.
 * ported 95/7/28 by Russell Marks
 *
 * It compiles ok (with the usual warnings :-)) under Hitech C.
 * All the source files were basically cat'ted together to make compliation
 * less awkward.
 *
 * You'll need to patch the 'clear' and 'move' routines with your
 * machine/terminal's clear screen and cursor move codes. (They're currently
 * set for ZCN.) For very simple terminals, you may even be able to patch
 * the binary directly and not bother recompiling.
 *
 * This version is for VT100 compatible terminals, patched by
 * Anna Christina Nass <acn@acn.wtf>
 */

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

#define refresh()
#define printw printf


void clear()
{
printf("%c[2J%c[H",27,27);
}


void move(int y,int x)
{
printf("%c[%d;%dH",27,y+1,x+1);
}

short readscreen();


/**/
/* OBJECT: this typedef is used for internal and external representation */
/*         of objects                                                    */
/**/
typedef struct {
   char obj_intern;	/* internal representation of the object */
   char obj_display1;	/* first  display char for the object		 */
   char obj_display2;	/* second display char for the object		 */
   short invers;	/* if set to 1 the object will be shown invers */
} OBJECT;

/**/
/* You can now alter the definitions below.
/* Attention: Do not alter `obj_intern'. This would cause an error */
/*            when reading the screenfiles                         */
/**/
static OBJECT 
   player = 	 { '@', '*', '*', 0 },
   playerstore = { '+', '*', '*', 1 },
   store = 	 { '.', '.', '.', 0 },
   packet = 	 { '$', '[', ']', 0 },
   save = 	 { '*', '<', '>', 1 },
   ground = 	 { ' ', ' ', ' ', 0 },
   wall = 	 { '#', '#', '#', 1 };

/*************************************************************************
********************** DO NOT CHANGE BELOW THIS LINE *********************
*************************************************************************/
#define MAXROW		20
#define MAXCOL		40

typedef struct {
   short x, y;
} POS;

#define E_FOPENSCREEN	1
#define E_PLAYPOS1	2
#define E_ILLCHAR	3
#define E_PLAYPOS2	4
#define E_TOMUCHROWS	5
#define E_TOMUCHCOLS	6
#define E_ENDGAME	7
#define E_NOUSER	9
#define E_FOPENSAVE	10
#define E_WRITESAVE	11
#define E_STATSAVE	12
#define E_READSAVE	13
#define E_ALTERSAVE	14
#define E_SAVED		15
#define E_TOMUCHSE	16
#define E_FOPENSCORE	17
#define E_READSCORE	18
#define E_WRITESCORE	19
#define E_USAGE		20
#define E_ILLPASSWORD	21
#define E_LEVELTOOHIGH	22
#define E_NOSUPER	23
#define E_NOSAVEFILE	24

/* defining the types of move */
#define MOVE 		1
#define PUSH 		2
#define SAVE 		3
#define UNSAVE 		4
#define STOREMOVE 	5
#define STOREPUSH 	6

/* defines for control characters */
#define CNTL_L		'\014'
#define CNTL_K		'\013'
#define CNTL_H		'\010'
#define CNTL_J		'\012'
#define CNTL_R		'\022'
#define CNTL_U		'\025'

static POS   tpos1,		   /* testpos1: 1 pos. over/under/left/right */
             tpos2,		   /* testpos2: 2 pos.  "                    */
             lastppos,		   /* the last player position (for undo)    */
             last_p1, last_p2; /* last test positions (for undo)         */
static char lppc, ltp1c, ltp2c;    /* the char for the above pos. (for undo) */
static char action, lastaction;

/** For the temporary save **/
static char  tmp_map[MAXROW+1][MAXCOL+1];
static short tmp_pushes, tmp_moves, tmp_savepack;
static POS   tmp_ppos;

short scoring = 1;
short level, packets, savepack, moves, pushes, rows, cols;
short scorelevel, scoremoves, scorepushes;
char  map[MAXROW+1][MAXCOL+1];
POS   ppos;
char  username[]="Player", *prgname;


short play() {

   short c;
   short ret;
   short undolock = 1;		/* locked for undo */

   showscreen();
   tmpsave();
   ret = 0;
   while( ret == 0) {
      c=getch();
      switch(c) { case '8': c = 'k'; break;
                  case '2': c = 'j'; break;
                  case '4': c = 'h'; break;
		  case '6': c = 'l'; break;
		  case '5': c = 'u'; break;
                  case 'E'&0x3f: c='k'; break;
                  case 'S'&0x3f: c='h'; break;
                  case 'D'&0x3f: c='l'; break;
                  case 'X'&0x3f: c='j'; break;

                  default: break; };

      switch(c) {
	 case 'q':    /* quit the game 					*/
	              ret = E_ENDGAME; 
	              break;
	 case CNTL_R: /* refresh the screen 				*/
		      clear();
		      showscreen();
		      break;
	 case 'c':    /* temporary save					*/
         case 's':
		      tmpsave();
		      break;
	 case CNTL_U: /* reset to temporary save 			*/
         case 'r':
		      tmpreset();
		      undolock = 1;
		      showscreen();
		      break;
	 case 'U':    /* undo this level 				*/
		      moves = pushes = 0;
		      if( (ret = readscreen()) == 0) {
		         showscreen();
			 undolock = 1;
		      }
		      break;
	 case 'u':    /* undo last move 				*/
		      if( ! undolock) {
		         undomove();
		         undolock = 1;
		      }
		      break;

    	 case 'k':    /* up 						*/
	 case 'K':    /* run up 					*/
	 case CNTL_K: /* run up, stop before object 			*/
	 case 'j':    /* down 						*/
	 case 'J':    /* run down 					*/
	 case CNTL_J: /* run down, stop before object 			*/
	 case 'l':    /* right 						*/
	 case 'L':    /* run right 					*/
	 case CNTL_L: /* run right, stop before object 			*/
	 case 'h':    /* left 						*/
	 case 'H':    /* run left 					*/
	 case CNTL_H: /* run left, stop before object 			*/
		      do {
		         if( (action = testmove( c)) != 0) {
			    lastaction = action;
		            lastppos.x = ppos.x; lastppos.y = ppos.y;
		            lppc = map[ppos.x][ppos.y];
		            last_p1.x = tpos1.x; last_p1.y = tpos1.y; 
		            ltp1c = map[tpos1.x][tpos1.y];
		            last_p2.x = tpos2.x; last_p2.y = tpos2.y; 
		            ltp2c = map[tpos2.x][tpos2.y];
		            domove( lastaction); 
		            undolock = 0;
		         }
		      } while( (action != 0) && (! islower( c))
			      && (packets != savepack));
		      break;
            default:    helpmessage(); break;
      }
      if( (ret == 0) && (packets == savepack)) {
	 scorelevel = level;
	 scoremoves = moves;
	 scorepushes = pushes;
	 break;
      }
   }
   return( ret);
}

testmove( action)
register short action;
{
   register short ret;
   register char  tc;
   register short stop_at_object;

   if( (stop_at_object = iscntrl( action))) action = action + 'A' - 1;
   action = (isupper( action)) ? tolower( action) : action;
   if( (action == 'k') || (action == 'j')) {
      tpos1.x = (action == 'k') ? ppos.x-1 : ppos.x+1;
      tpos2.x = (action == 'k') ? ppos.x-2 : ppos.x+2;
      tpos1.y = tpos2.y = ppos.y;
   }
   else {
      tpos1.y = (action == 'h') ? ppos.y-1 : ppos.y+1;
      tpos2.y = (action == 'h') ? ppos.y-2 : ppos.y+2;
      tpos1.x = tpos2.x = ppos.x;
   }
   tc = map[tpos1.x][tpos1.y];
   if( (tc == packet.obj_intern) || (tc == save.obj_intern)) {
      if( ! stop_at_object) {
         if( map[tpos2.x][tpos2.y] == ground.obj_intern)
            ret = (tc == save.obj_intern) ? UNSAVE : PUSH;
         else if( map[tpos2.x][tpos2.y] == store.obj_intern)
            ret = (tc == save.obj_intern) ? STOREPUSH : SAVE;
         else ret = 0;
      }
      else ret = 0;
   }
   else if( tc == ground.obj_intern)
      ret = MOVE;
   else if( tc == store.obj_intern)
      ret = STOREMOVE;
   else ret = 0;
   return( ret);
}

domove( moveaction) 
register short moveaction;
{
   map[ppos.x][ppos.y] = (map[ppos.x][ppos.y] == player.obj_intern) 
			       ? ground.obj_intern 
			       : store.obj_intern;
   switch( moveaction) {
      case MOVE:      map[tpos1.x][tpos1.y] = player.obj_intern; 	break;
      case STOREMOVE: map[tpos1.x][tpos1.y] = playerstore.obj_intern; 	break;
      case PUSH:      map[tpos2.x][tpos2.y] = map[tpos1.x][tpos1.y];
		      map[tpos1.x][tpos1.y] = player.obj_intern;	
		      pushes++;						break;
      case UNSAVE:    map[tpos2.x][tpos2.y] = packet.obj_intern;
		      map[tpos1.x][tpos1.y] = playerstore.obj_intern;		
		      pushes++; savepack--;			 	break;
      case SAVE:      map[tpos2.x][tpos2.y] = save.obj_intern;
		      map[tpos1.x][tpos1.y] = player.obj_intern;			
		      savepack++; pushes++;				break;
      case STOREPUSH: map[tpos2.x][tpos2.y] = save.obj_intern;
		      map[tpos1.x][tpos1.y] = playerstore.obj_intern;		
		      pushes++;						break;
   }
   moves++;
   dispmoves(); disppushes(); dispsave();
   mapchar( map[ppos.x][ppos.y], ppos.x, ppos.y);
   mapchar( map[tpos1.x][tpos1.y], tpos1.x, tpos1.y);
   mapchar( map[tpos2.x][tpos2.y], tpos2.x, tpos2.y);
   move( MAXROW+1, 0);
   refresh();
   ppos.x = tpos1.x; ppos.y = tpos1.y;
}

undomove() {

   map[lastppos.x][lastppos.y] = lppc;
   map[last_p1.x][last_p1.y] = ltp1c;
   map[last_p2.x][last_p2.y] = ltp2c;
   ppos.x = lastppos.x; ppos.y = lastppos.y;
   switch( lastaction) {
      case MOVE:      moves--;				break;
      case STOREMOVE: moves--;				break;
      case PUSH:      moves--; pushes--;		break;
      case UNSAVE:    moves--; pushes--; savepack++;	break;
      case SAVE:      moves--; pushes--; savepack--;	break;
      case STOREPUSH: moves--; pushes--;		break;
   }
   dispmoves(); disppushes(); dispsave();
   mapchar( map[ppos.x][ppos.y], ppos.x, ppos.y);
   mapchar( map[last_p1.x][last_p1.y], last_p1.x, last_p1.y);
   mapchar( map[last_p2.x][last_p2.y], last_p2.x, last_p2.y);
   move( MAXROW+1, 0);
   refresh();
}

tmpsave() {

   register short i, j;

   for( i = 0; i < rows; i++) for( j = 0; j < cols; j++)
      tmp_map[i][j] = map[i][j];
   tmp_pushes = pushes;
   tmp_moves = moves;
   tmp_savepack = savepack;
   tmp_ppos.x = ppos.x; tmp_ppos.y = ppos.y;
}

tmpreset() {

   register short i, j;

   for( i = 0; i < rows; i++) for( j = 0; j < cols; j++)
      map[i][j] = tmp_map[i][j];
   pushes = tmp_pushes;
   moves = tmp_moves;
   savepack = tmp_savepack;
   ppos.x = tmp_ppos.x; ppos.y = tmp_ppos.y;
}

short readscreen() {

   FILE *screen;
   short j, c, f, ret = 0;

   if( (screen = fopen( "soklevls.dat", "r")) == NULL)
      ret = E_FOPENSCREEN;
   else {
      if(level>1) {
         for(f=1;f<level;f++) {
            while((c=getc(screen))!=12 && c!=EOF) ;
            getc(screen);	/* get the \n after */
            }
         }

      packets = savepack = rows = j = cols  = 0;
      ppos.x = -1; ppos.y = -1;
      while( (ret == 0) && ((c = getc( screen)) != 12) && c!=EOF) {
         if( c == '\n') {
	    map[rows++][j] = '\0';
	    if( rows > MAXROW) 
	       ret = E_TOMUCHROWS;
	    else {
	       if( j > cols) cols = j;
	       j = 0;
	    }
	 }
	 else if( (c == player.obj_intern) || (c == playerstore.obj_intern)) {
	    if( ppos.x != -1) 
	       ret = E_PLAYPOS1;
	    else { 
	       ppos.x = rows; ppos.y = j;
	       map[rows][j++] = c;
	       if( j > MAXCOL) ret = E_TOMUCHCOLS;
	    }
	 }
	 else if( (c == save.obj_intern) || (c == packet.obj_intern) ||
		  (c == wall.obj_intern) || (c == store.obj_intern) ||
		  (c == ground.obj_intern)) {
	    if( c == save.obj_intern)   { savepack++; packets++; }
	    if( c == packet.obj_intern) packets++;
	    map[rows][j++] = c;
	    if( j > MAXCOL) ret = E_TOMUCHCOLS;
	 }
	 else ret = E_ILLCHAR;
      }
      fclose( screen);
      if( (ret == 0) && (ppos.x == -1)) ret = E_PLAYPOS2;
   }
   return( ret);
}

showscreen() {

   register short i, j;

   clear();
   for( i = 0; i < rows; i++)
      for( j = 0; map[i][j] != '\0'; j++)
         mapchar( map[i][j], i, j);
   move( MAXROW, 0);
   printw( "Level:      Packets:      Saved:      Moves:       Pushes:");
   displevel();
   disppackets();
   dispsave();
   dispmoves();
   disppushes();
   move( MAXROW+2,0);
   refresh();
}

mapchar( c, i, j) 
register char c; 
register short i, j;
{
   OBJECT *obj, *get_obj_adr();
   register short offset_row = 0;	/*(MAXROW - rows) / 2;*/
   register short offset_col = MAXCOL - cols;

   obj = get_obj_adr( c);

/*   if( obj->invers) standout();*/
   move( i + offset_row, 2*j + offset_col); 
   printw( "%c%c", obj ->obj_display1, obj ->obj_display2);
/*   if( obj->invers) standend();*/
}

OBJECT *get_obj_adr( c)
register char c;
{
   register OBJECT *ret;

   if(      c == player.obj_intern)		ret = &player;
   else if( c == playerstore.obj_intern)	ret = &playerstore;
   else if( c == store.obj_intern)		ret = &store;
   else if( c == save.obj_intern)		ret = &save;
   else if( c == packet.obj_intern)		ret = &packet;
   else if( c == wall.obj_intern)		ret = &wall;
   else if( c == ground.obj_intern)		ret = &ground;
   else                                         ret = &ground;

   return( ret);
}


displevel() { 
   move( MAXROW, 7); printw( "%3d", level); 
}
   
disppackets() { 
   move( MAXROW, 21); printw( "%3d", packets); 
}
   
dispsave() { 
   move( MAXROW, 33); printw( "%3d", savepack); 
}
   
dispmoves() { 
   move( MAXROW, 45); printw( "%5d", moves); 
}
      
disppushes() { 
   move( MAXROW, 59); printw( "%5d", pushes); 
}

static short optrestore = 0, 
	     optlevel = 1; 

helpmessage() {
#if 0
   move( MAXROW+2, 0); 
   printw( "Press ? for help.");
   refresh();
   sleep( 1);
   move( MAXROW+2, 0); deleteln();
   refresh(); 
#endif
}

showhelp() {
}


main( argc, argv) 
short argc; 
char *argv[];
{
   short ret;

   scorelevel = 0;
   moves = pushes = packets = savepack = 0;
   if( (prgname = strrchr( argv[0], '/')) == NULL)
      prgname = argv[0];
   else prgname++;
   
   {
      if( (ret = checkcmdline( argc, argv)) == 0) {
         level=optlevel;
      }
   }
   ret = gameloop();
   errmess( ret);
}

checkcmdline( argc, argv) 
short argc; 
char *argv[];
{
   short ret = 0;

    if(argc==2) {
	 if( (optlevel = atoi(argv[1])) == 0)
	    ret = E_USAGE;
      }
   return( ret);
}

gameloop() {

   short ret = 0;

/*   initscr(); cbreak(); noecho();*/
   if( ! optrestore) ret = readscreen();
   while( ret == 0) {
      if( (ret = play()) == 0) {
         level++;
         moves = pushes = packets = savepack = 0;
         ret = readscreen();
      }
   }
   clear(); refresh(); 
/*   nocbreak(); echo(); endwin();*/
   return( ret);
}

char *message[] = {
   "illegal error number",
   "cannot open screen file",
   "more than one player position in screen file",
   "illegal char in screen file",
   "no player position in screenfile",
   "too much rows in screen file",
   "too much columns in screenfile",
   "quit the game",
   NULL,			/* errmessage deleted */
   "cannot get your username",
   "cannot open savefile",
   "error writing to savefile",
   "cannot stat savefile",
   "error reading savefile",
   "cannot restore, your savefile has been altered",
   "game saved",
   "too much users in score table",
   "cannot open score file",
   "error reading scorefile",
   "error writing scorefile",
   "illegal command line syntax",
   "illegal password",
   "level number too big in command line",
   "only superuser is allowed to make a new score table",
   "cannot find file to restore"
};

errmess( ret) 
register short ret;
{
   if( ret != E_ENDGAME) {
      fprintf( stderr, "%s: ", prgname);
      switch( ret) {
         case E_FOPENSCREEN: case E_PLAYPOS1:   case E_ILLCHAR: 
	 case E_PLAYPOS2:    case E_TOMUCHROWS: case E_TOMUCHCOLS: 
	 case E_ENDGAME:     case E_NOUSER:      
	 case E_FOPENSAVE:   case E_WRITESAVE:  case E_STATSAVE:    
	 case E_READSAVE:    case E_ALTERSAVE:  case E_SAVED:       
	 case E_TOMUCHSE:    case E_FOPENSCORE: case E_READSCORE: 
	 case E_WRITESCORE:  case E_USAGE:	case E_ILLPASSWORD:
	 case E_LEVELTOOHIGH: case E_NOSUPER:	case E_NOSAVEFILE:
			     fprintf( stderr, "%s\n", message[ret]);
                             break;
         default:            fprintf( stderr, "%s\n", message[0]);
                             break;
      }
      if( ret == E_USAGE) usage();
   }
}

usage() {
   fprintf( stderr, "Usage: %s [start_level]", prgname);
}

