/*-
  worms.cpp -- A primitive game example of wiggly worms moving about

  (c) 2013 pmateti@wright.edu 

  The file worms12-doc.html has a detailed commentary/tutorial that
  goes with this file.  These two files are given to students asking
  them to critique it and improve it.  E.g., make it (more) object
  oriented.

  Build it on Linux: 'g++ -o worms worms.cpp -lncurses'
  Run   it: './worms 3'

  Build it on Windows: Visual Studio 2012 or later and Public Domain
  Curses (http://pdcurses.sourceforge.net/).  These mods were contributed by
  salva.3@wright.edu 2013.
*/

/* TBD 
   signals ^C
*/

#include 
#include 
#include 

#ifdef _WIN32
//c++11 headers
#include 
#include 
#else
#include 
#endif

#define random(x) 	(rand() % x)
#define isDigit(x)	('0' <= x && x <= '9')
#define min(a, b) 	((a) < (b)? (a) : (b))
#define ESC		'\033'	// the ESCape char
#define CARROT		'.'	// a carrot is shown as a dot

const char * say[] = {		// worms carry these strings
  "do-not-optimize-too-soon*",
    "If it does not have to be correct, making the program efficient is easy.",
    "90% of code executes only 10% of the time!",
    "Is there a program, longer than say 1000 lines, that is correct?",
    "OS is not bug-free, the compiler is not bug-free, so is my program!",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    "98765432109876543210*",
    "Edsger-Dijkstra*",		// legendary programmers ...
    "C-A-R-Hoare*",
    "Donald-Knuth*",
    "Richard-Stallman*",
    "Linux-Torvalds*",
};
#define Nsay sizeof(say)/sizeof(char *) // #sayings we have

typedef struct {
  int onec;			// one character
  int attr;			// its color
} ASOG;				// A Square On the Ground

typedef enum {
  NORTH, NORTHEAST, EAST, SE, S, SW, W, NW
} DIRECTION;			// as numbers: 0 .. 7

typedef enum {
  VEGETARIAN, CANNIBAL, SCISSORHEAD
} WORM_KIND;			// 0, 1, 2

typedef struct {
  int color;
  int capacity;			// stomach size
  int foodValue;
} WORM_INFO;

typedef enum {
  EATEN, DEAD, ALIVE
} LIFE;

typedef struct {
  int x, y;			// coordinates of the segment
  char c;			// the letter it carries
} SEGMENT;

#define MAXsegs	 100		// max # segments per worm
typedef struct {
  WORM_KIND type;		// once set, type does not change
  int direction;		// its (head's) direction
  int nsegs;			// # of segs in this worm
  int stomach;			// food value
  LIFE status;			// EATEN, DEAD, or ALIVE
  SEGMENT body[MAXsegs];	// body parts
} WORM;
#define headSeg(wp)	(wp->body + wp->nsegs)
#define xOf(wp)		(headSeg(wp)->x)
#define yOf(wp)		(headSeg(wp)->y)

#define MAXworms 100		// max # worms in our program
WORM worm[MAXworms];		// vars for the worms
int hxWorms;			// high water mark of the worm[]
int xworms[3];			// counts of different worms
#define nVegetarians	xworms[0]
#define nCannibals	xworms[1]
#define nScissors	xworms[2]
#define	nAllWorms	(nVegetarians + nCannibals + nScissors)

#define MAXrow 100
#define MAXcol 100
ASOG passive[MAXrow * MAXcol];	// for carrots and dead worms
ASOG screen[MAXrow * MAXcol];	// what gets displayed
int asogRows, asogCols;		// the actual size of 'worm area'
#define	asog(ptr, row, col) (ptr + row*asogCols + col)

#ifdef _WIN32
#include 
#else
#include 
#endif
#define mvCursor(y, x) move(y, x)
#define putChar(c) addch(c)
#define putString(s) addstr(s)
#define getChar() getch()
#define screenFlush() refresh()
#define screenClear() clear()
#define EOLN "\n"		// End Of LiNe string

void endCurses()
{
  if (!isendwin())
    endwin();
}

void startCurses()
{
  initscr();			// ncurses init
  cbreak();			// unbuffered getChar
  noecho();			// no echoing of keys pressed
  //  intrflush(stdscr, 0); // TBD
  nodelay(stdscr, TRUE);	// get a char *if* available
  atexit(endCurses);
  start_color();
  use_default_colors();
  init_pair(1, COLOR_RED, -1);
  init_pair(2, COLOR_GREEN, -1);
  init_pair(3, COLOR_BLUE, -1);
  getmaxyx(stdscr, asogRows, asogCols);
  if (asogCols > MAXcol) asogCols = MAXcol;
  if (asogRows > MAXrow) asogRows = MAXrow;
  asogRows -= 6;	 // 6 lines for msgs, asogRows needs to be > 0
}

int getOneChar()
{
  nodelay(stdscr, FALSE);	// wait until a char is typed
  int c =  getChar();
  nodelay(stdscr, TRUE);
  return c;
}

// ncurses code ends here

void showScreen()
{
  ASOG * sp = screen;
  for (int y = 0; y < asogRows; y++) {
    mvCursor(y, 0);
    for (int x = 0; x < asogCols; x++, sp++)
      putChar(sp->onec | sp->attr);
  }
  screenFlush();
}

char msg[1024];

void showMsg(int y)
{
  msg[1023] = '\0';
  mvCursor(y, 0);
  putString(msg);
  screenFlush();
}

void sprinkleCarrots()
{
  for(ASOG *p = asog(passive, asogRows, 1); p-- > passive;) {
    p->attr = WA_DIM;
    p->onec = CARROT;
  }
}

WORM_INFO worm_info[] = {	// indexed by WORM_KIND
  {COLOR_PAIR(1), 3, 3},	// from 
  {COLOR_PAIR(2), 4, 5},
  {COLOR_PAIR(3), 5, 4}
};

/* Show a single worm.  pre: wp != 0.  post: It is drawn on passive[]
   if it is dead.  If alive, it is drawn on screen[].  If eaten, not
   drawn. ; */

void showOneWorm(WORM * wp)
{
  if (wp->status != EATEN) {
    ASOG *f = (wp->status == ALIVE ? screen : passive);
    SEGMENT *bp, *hp = headSeg(wp);
    for (bp = wp->body + 1; bp <= hp; bp++) {
      ASOG *g = asog(f, bp->y, bp->x);
      g->attr = worm_info[wp->type].color;
      g->onec = bp->c;
    }
  }
}

/* Display the carrots, the dead worms, and the current positions of
   one (pre: wp != 0) or all (pre: wp == 0) worms.  globals: passive[]
   read-only.  pre: none; post: ... ; */

void showWormsAndCarrots(WORM * wp)
{
  ASOG *sp = screen, *se = asog(screen, asogRows, 1);
  for (ASOG *pp = passive; sp < se; )
    *sp++ = *pp++;		// copy the carrots + dead worms
  WORM * headWorm = worm + (wp? 1 : hxWorms);
  for (WORM * wp = worm; wp < headWorm; )
    showOneWorm(wp++);
  showScreen();
}

/* It is hungry if its tummy is at least 25% empty.  pre: wp != 0;
   post: Return 1 if worm wp is hungry; otherwise return 0.  */

int isHungry(WORM * wp)
{
  int m = wp->stomach;
  int n = wp->nsegs * worm_info[wp->type].capacity;
  return (wp->status == ALIVE && 4*m < 3*n);
}

/* pre: wp != 0 */

void gotEaten(WORM * wp)
{
  if (wp->status == ALIVE) {
    wp->status = EATEN;
    xworms[wp->type] --;
  }
}

/* pre: wp->status == ALIVE; post: if stomach is 0 or less, mark it
   dead, and send its body to the cemetary. */

void checkStomach(WORM * wp)
{
  if (wp->stomach <= 0) {
    wp->status = DEAD;
    xworms[wp->type] --;
    showOneWorm(wp);		// mv the body to passive
  }
}

/* Eat a carrot, if available.  pre: wp->status == ALIVE; post:
   ...; */

void eatACarrot(WORM * wp)
{
  ASOG *sg = asog(passive, yOf(wp), xOf(wp));
  if (CARROT == sg->onec) {
    wp->stomach += 2;		// food value of one carrot
    sg->onec = ' ';
  }
}

/* Check if any segment of worm wp is at (col, row).  Return this
   segment's number.  Recall that body[0] is just an eraser-blank, not
   really a part of the worm.  pre: wp != 0; post: ...; */

int isAt(WORM * wp, int row, int col)
{
  SEGMENT *bp = headSeg(wp);
  for (int sn = wp->nsegs; sn > 0; sn--, bp--)
  {
    if ((bp->y == row) && (bp->x == col))
	{
      return sn;
	}
  }
  return 0; //this is important as not all compileres return 0 by default
}

/* The following two vars should be 'out' parameters (var params), but
   for ease of understanding, let us declare these as globals. */

WORM *victimWormp;
int victimSegNum;

/* Find a worm that is on the same square as "my" head.  pre: me != 0;
   post: victimSegNum > 0 => victimWormp != 0 and victimWormp's
   victimSegNum segment is at the same square as the head of me.  */

void setVictimWorm(WORM * me)
{
  WORM *headWorm = worm + hxWorms, *wp;
  victimWormp = 0;
  victimSegNum = 0;
  for (wp = worm; wp < headWorm; wp++) {
    if (wp != me && wp->status == ALIVE) {
		victimSegNum = isAt(wp, yOf(me), xOf(me));
      if (victimSegNum > 0) {
	victimWormp = wp;
	return;			// found the victim
      }
    }
  }
}

/* We have a new worm about to join.  Find a slot for it in our worm[]
   array.  pre: 0 <= hxWorms <= MAXworms; post: Return a ptr to a slot
   for the new work, or 0, if we do not succeed in finding a slot.  */

WORM * findSlot()
{
  WORM * zp = worm + hxWorms;
  if (hxWorms < MAXworms) {
    hxWorms++;
    return zp;
  }
  /* see if there are any dead/eaten worms */
  for (WORM * wp = worm; wp < zp; wp++)
    if (wp->status != ALIVE)
      return wp;
  return 0;
}

/* Copy the body segments.  pre: vp != 0 && 0 < vsn <= vp->nsegs;
   post: old body[vsn+1..end] should become new body[1..new-length];
   body[0] remains as the eraser-blank */

void shiftBodyDown(WORM * vp, int vsn)
{
  SEGMENT *dp = vp->body + 1;	    // dp destination, sp source
  SEGMENT *hp = headSeg(vp);
  for (SEGMENT *sp = vp->body + vsn + 1; sp <= hp; )
    *dp++ = *sp++;
  int tsegs = vp->nsegs;
  vp->nsegs -= vsn;
  vp->stomach = vp->stomach / tsegs * vp->nsegs;
  checkStomach(vp);
}

/* A scissor-head or cannibal hit the victim at segment number vsn.
   Slice the victim into two.  pre: vp != 0 && 0 < vsn <= vp->nsegs;
   post: The first one with vp->body[1..vsn-1] and the second one with
   vp->body[vsn+1..vp->nsegs] as its new body.  */

WORM * sliceTheVictim(WORM * vp, int vsn)
{
  int tsegs = vp->nsegs;
  WORM *wp = findSlot();
  if (wp == 0)			// could not find a slot
    return vp;

  xworms[vp->type]++;

  *wp = *vp;			// bottom-half
  wp->nsegs = vsn - 1;
  wp->stomach = vp->stomach / tsegs * wp->nsegs;
  checkStomach(wp);

  shiftBodyDown(vp, vsn);	// the top-half
  return wp;
}

/* Eat whatever you find at the wp's head position.  If wp is a
   cannibal or scissor-head it will also eat a carrot, if available.
   Update the stomach contents.  pre: isHungry(wp) && wp->status ==
   ALIVE; post: ...; */

void eat(WORM * wp)
{
  if (wp->type != VEGETARIAN) {
    setVictimWorm(wp);
    if (victimWormp != 0) {
      wp->stomach += worm_info[victimWormp->type].foodValue;
      WORM * zp = sliceTheVictim(victimWormp, victimSegNum);
      if (wp->type == CANNIBAL && zp->status == ALIVE) {
	wp->stomach += zp->nsegs * worm_info[zp->type].foodValue;
	gotEaten(zp);
      }
    }
  }
  eatACarrot(wp);
}

// dx/dy changes, indexed by DIRECTION; (+0 just to line up)
const int dxa[] = { +0, +1, +1, +1, +0, -1, -1, -1 };
const int dya[] = { -1, -1, +0, +1, +1, +1, +0, -1 };

const int nextTurn[16] = {	// experiment with other values
  0, 0, 0, 0,  0, 0, 0, 0,	// think: why 16?
  1, 1, 1, 7,  7, 7, 2, 6
};

/* One moment in the life of a worm: crawl by one step using one unit
   of food, may change direction, eat if hungry, mark as dead if
   stomach is now empty.  */

void live(WORM * wp)
{
  SEGMENT *hp = headSeg(wp);
  for (SEGMENT * bp = wp->body; bp < hp; bp++) // < not <=
    bp->x = (bp + 1)->x, bp->y = (bp + 1)->y;  // crawl

  int dir = wp->direction = (wp->direction + nextTurn[random(16)]) % 8;
  hp->y += dya[dir];
  hp->x += dxa[dir];
  if (hp->y < 0) hp->y = asogRows-1;
  if (hp->x < 0) hp->x = asogCols-1;
  if (hp->y >= asogRows) hp->y = 0;
  if (hp->x >= asogCols) hp->x = 0;

  wp->stomach--;
  if (isHungry(wp))
    eat(wp);
  checkStomach(wp);
}

/* Create a worm.  It should be just long enough to carry the phrase
   sy[].  It is going to crawl out of a random hole.  pre: s != 0;
   post: ...; */

void createWorm(WORM_KIND type, const char * sy)
{
  WORM *wp = findSlot();
  if (wp == 0)
    return;			// no room for a new worm

  int n = wp->nsegs = min(strlen(sy), MAXsegs - 1);
  wp->stomach = n * worm_info[type].capacity;
  wp->direction = NORTH;
  wp->status = ALIVE;
  wp->type = type;

  SEGMENT * bp = wp->body;
  int yy = bp->y = random(asogRows);	// birth place of this worm
  int xx = bp->x = random(asogCols);
  bp->c = ' ';				// works as an "eraser"

  SEGMENT * hp = headSeg(wp);
  for (bp++; bp <= hp; bp++) {
    bp->c = *sy++;		// store the phrase s[]
    bp->x = xx;			// worm hangs down in the z-axis
    bp->y = yy;
  }
  xworms[type] ++;
}

/* parameters for the 'graphical' (such as it is) display */
int slowness;			// slowness number
int paused = 0;			// == 1 iff paused
int idelay = 10;		// delay increment
int tdelay;			// total delay yet to do

int userKeyPress(int c)
{
  switch (c) {
  case '+': slowness -= min(slowness, 100); break;
  case '-': slowness += 100; break;
  case 'f': slowness = 0; break;
  case 's': /* for you todo: highlight a worm */ break;
  case 'k': /* for you todo: kill the highlighted worm */ break;
  case 'w': createWorm((WORM_KIND)random(3), say[random(Nsay)]); break;
  case ' ': paused ^= 1; break;
  }
  return c;
}

/* User can control the speed, etc.  The total delay required, tdelay,
   is doled out in increments of idelay so that keyboard interaction
   is more responsive.  pre: none; post: returns ESC or '\0';; */

int userControl()
{
  sprintf
    (msg, 
     "SPC %s, ESC terminates, k kills-, w creates-, s shows-a-worm" EOLN
     "%2d Vegetarians,%2d Cannibals,%2d Scissor-heads,%2d hi-water-mark" EOLN
     "%04d slowness, - increases, + reduces, f full-speed" EOLN,
     (paused? "resumes" : "pauses "), 
     nVegetarians, nCannibals, nScissors, hxWorms, slowness);
  showMsg(asogRows + 1);
  for (tdelay = slowness+1; tdelay > 0; tdelay -= idelay) {
    char c = userKeyPress(getChar()); // no-delay
    if (c == ESC) return ESC;
    if (paused) tdelay += idelay;
    if (idelay > 0)
	{
#ifdef _WIN32
		std::this_thread::sleep_for(std::chrono::microseconds(1000 * idelay));
#else
		usleep(1000 * idelay);
#endif
	}
  }
  return '\0';
}

int main(int argc, char * argv[])
{
  slowness = 10*(argc > 1? argv[1][0] - '0' : 1);
  if (slowness < 0) slowness = 0;
  srand(time(0));		// pseudo-random number generator seed
  for (int ch = 0; ch != ESC; ) {
    startCurses();
    sprinkleCarrots();
    hxWorms = nVegetarians = nCannibals = nScissors = 0;
    for (int i = random(6) + 3; i > 0; i--)
      createWorm((WORM_KIND) random(3), say[random(Nsay)]);

    while (ch != ESC && nAllWorms > 0) {
      for (WORM * wp = worm; wp < worm + hxWorms; wp++) // hxWorms may +-
	if (wp->status == ALIVE)
	  live(wp);
      showWormsAndCarrots(0);
      ch = userControl();
    }

    sprintf(msg, "press ESC to terminate, or any other key to re-run"
	    EOLN  EOLN  EOLN  EOLN);
    showMsg(asogRows + 1);
    ch = getOneChar();
    endCurses();
  }
  return 0;
}

/* -eof- */