#include <stdlib.h>
#include <time.h>
#include <allegro.h>

#define GAMEW 78
#define GAMEH 56

#define NUMFIGS 4
#define NUMNODES 4
#define NUMHELPERS 8

struct FIG {
 bool active;
 int x,y;
 int chasetimer;
 int bcol;
};

struct NODE {
 int x,y;
 int phase,timeleft;
};

struct HELPER {
 bool active;
 int x,y;
 int bcol;
};

struct QUEUEENTRY {
 unsigned char x,y;
};

static volatile int truetime=0;
static int logictime=0;

static BITMAP *textbar;
static BITMAP *gamearea;

static const char *gameover;
static int score;

static int px,py;
static FIG figs[NUMFIGS];
static NODE nodes[NUMNODES];
static HELPER helpers[NUMHELPERS];

static QUEUEENTRY queue[GAMEW*GAMEH];
static int queuestart,queueend;
static bool surrounded;

static void timerproc() {
 truetime++;
}
END_OF_STATIC_FUNCTION(timerproc);

static void init() {
 textbar=create_bitmap(320,8);
 gamearea=create_bitmap(GAMEW,GAMEH);
 clear_bitmap(gamearea);
 gameover=NULL;
 score=0;
 px=GAMEW/2; py=GAMEH/2;
 figs[0].x=GAMEW/4; figs[0].y=GAMEH/2;
 figs[1].x=GAMEW*3/4; figs[1].y=GAMEH/2;
 figs[2].x=GAMEW/2; figs[2].y=GAMEH/4;
 figs[3].x=GAMEW/2; figs[3].y=GAMEH*3/4;
 nodes[0].x=GAMEW/4; nodes[0].y=GAMEH/4;
 nodes[1].x=GAMEW*3/4; nodes[1].y=GAMEH/4;
 nodes[2].x=GAMEW/4; nodes[2].y=GAMEH*3/4;
 nodes[3].x=GAMEW*3/4; nodes[3].y=GAMEH*3/4;
 putpixel(gamearea,px,py,6);
 for (int i=0; i<NUMFIGS; i++) {
  figs[i].active=true; figs[i].chasetimer=0;
  putpixel(gamearea,figs[i].x,figs[i].y,5);
 }
 for (int i=0; i<NUMNODES; i++) {
  nodes[i].phase=0;
  nodes[i].timeleft=200+(rand()&0x7FFFFFFF)%200;
  putpixel(gamearea,nodes[i].x,nodes[i].y,0);
 }
 for (int i=0; i<NUMHELPERS; i++) helpers[i].active=false;
}

static void trymoveplayer(int newx, int newy) {
 int c=getpixel(gamearea,newx,newy);
 if (c<0 || c==5) return;
 for (int i=0; i<NUMFIGS; i++) {
  if (figs[i].active && figs[i].x==newx && figs[i].y==newy) return;
 }
 for (int i=0; i<NUMHELPERS; i++) {
  if (helpers[i].active && helpers[i].x==newx && helpers[i].y==newy) return;
 }
 px=newx; py=newy;
 if (c!=2) putpixel(gamearea,px,py,6);
}

static int getfigtemptation(int newx, int newy) {
 int c=getpixel(gamearea,newx,newy);
 if (c<0) return 0;
 if (c==2 || c==6) return 1;
 return 2;
}

static int gethelpertemptation(int newx, int newy) {
 int c=getpixel(gamearea,newx,newy);
 if (c<0) return 0;
 if (newx==px && newy==py) return 0;
 return 1;
}

static void makehelper(int x, int y) {
 for (int i=0; i<NUMHELPERS; i++) {
  if (helpers[i].active) continue;
  helpers[i].active=true;
  helpers[i].x=x; helpers[i].y=y;
  return;
 }
}

static void killfigandhelper(int i, int k) {
 int numfigsleft=0;
 for (int j=0; j<NUMFIGS; j++) {
  if (figs[j].active) numfigsleft++;
 }
 if (numfigsleft>=2) figs[i].active=false;
 helpers[k].active=false;
}

static void makefig(int x, int y) {
 for (int i=0; i<NUMFIGS; i++) {
  if (figs[i].active) continue;
  figs[i].active=true;
  figs[i].x=x; figs[i].y=y;
  return;
 }
}

static void checksurround(int x, int y) {
 int c=getpixel(gamearea,x,y);
 if (c==0) surrounded=false;
}

static void checkputqueue(int x, int y, int oldc, int newc) {
 int c=getpixel(gamearea,x,y);
 if (c==oldc) {
  putpixel(gamearea,x,y,newc);
  queue[queueend].x=x; queue[queueend].y=y;
  queueend++;
  checksurround(x-1,y);
  checksurround(x+1,y);
  checksurround(x,y-1);
  checksurround(x,y+1);
 }
}

static void fill(int x, int y, int oldc, int newc) {
 queuestart=queueend=0;
 checkputqueue(x,y,oldc,newc);
 while (queuestart<queueend) {
  checkputqueue(queue[queuestart].x-1,queue[queuestart].y,oldc,newc);
  checkputqueue(queue[queuestart].x+1,queue[queuestart].y,oldc,newc);
  checkputqueue(queue[queuestart].x,queue[queuestart].y-1,oldc,newc);
  checkputqueue(queue[queuestart].x,queue[queuestart].y+1,oldc,newc);
  queuestart++;
 }
}

static void scantrappedfigs() {
 //Scan gamearea looking for figs
 for (int y=0; y<GAMEH; y++) {
  for (int x=0; x<GAMEW; x++) {
   int c=getpixel(gamearea,x,y);
   if (c==5) {
    //Fill in special colour 255 to mark it as done,
    //and as we go, remember if it's surrounded
    surrounded=true;
    fill(x,y,5,255);
    //If it's surrounded, fill it green and award points
    if (surrounded) {
     fill(x,y,255,2);
     score+=queueend*(10+queueend);
    }
   }
  }
 }
 //Reset all the 255's back to 5
 for (int y=0; y<GAMEH; y++) {
  for (int x=0; x<GAMEW; x++) {
   int c=getpixel(gamearea,x,y);
   if (c==255) putpixel(gamearea,x,y,5);
  }
 }
}

static bool update() {
 while (keypressed()) {
  int k=readkey();
  if (k>>8==KEY_ESC) return true;
 }
 //Update the player
 if (!gameover) {
  int xmove=0; if (key[KEY_LEFT]) xmove--; if (key[KEY_RIGHT]) xmove++;
  int ymove=0; if (key[KEY_UP  ]) ymove--; if (key[KEY_DOWN ]) ymove++;
  if (xmove<0) trymoveplayer(px-1,py);
  else if (xmove>0) trymoveplayer(px+1,py);
  else if (ymove<0) trymoveplayer(px,py-1);
  else if (ymove>0) trymoveplayer(px,py+1);
 }
 //Update the helpers
 for (int i=0; i<NUMHELPERS; i++) {
  if (helpers[i].active) {
   int lp=gethelpertemptation(helpers[i].x-1,helpers[i].y);
   int rp=lp+gethelpertemptation(helpers[i].x+1,helpers[i].y);
   int up=rp+gethelpertemptation(helpers[i].x,helpers[i].y-1);
   int dp=up+gethelpertemptation(helpers[i].x,helpers[i].y+1);
   if (dp>0) {
    int dir=(rand()&0x7FFFFFFF)%dp;
    if (dir<lp) helpers[i].x--;
    else if (dir<rp) helpers[i].x++;
    else if (dir<up) helpers[i].y--;
    else helpers[i].y++;
   }
   int c=getpixel(gamearea,helpers[i].x,helpers[i].y);
   if (c==5 || c==3 || c==4) putpixel(gamearea,helpers[i].x,helpers[i].y,0);
   for (int j=0; j<NUMFIGS; j++) {
    if (figs[j].active && figs[j].x==helpers[i].x && figs[j].y==helpers[i].y) {
     killfigandhelper(j,i);
    }
   }
  }
 }
 //Update the figs
 for (int i=0; i<NUMFIGS; i++) {
  if (figs[i].active) {
   if (figs[i].chasetimer>0 && !gameover) {
    //Only move half the time while chasing!
    if ((figs[i].chasetimer&1)==0) {
     int dx=px-figs[i].x, dy=py-figs[i].y;
     if (dx>dy && dx>-dy) figs[i].x++;
     else if (dx<dy && dx<-dy) figs[i].x--;
     else if (dy>0) figs[i].y++;
     else figs[i].y--;
    }
   }
   else {
    int lp=getfigtemptation(figs[i].x-1,figs[i].y);
    int rp=lp+getfigtemptation(figs[i].x+1,figs[i].y);
    int up=rp+getfigtemptation(figs[i].x,figs[i].y-1);
    int dp=up+getfigtemptation(figs[i].x,figs[i].y+1);
    if (dp>0) {
     int dir=(rand()&0x7FFFFFFF)%dp;
     if (dir<lp) figs[i].x--;
     else if (dir<rp) figs[i].x++;
     else if (dir<up) figs[i].y--;
     else figs[i].y++;
    }
   }
   if (figs[i].chasetimer>0) figs[i].chasetimer--;
   int c=getpixel(gamearea,figs[i].x,figs[i].y);
   if (c!=2) putpixel(gamearea,figs[i].x,figs[i].y,5);
   if (figs[i].x==px && figs[i].y==py) gameover="Figged!";
   for (int j=0; j<NUMHELPERS; j++) {
    if (helpers[j].active && helpers[j].x==figs[i].x && helpers[j].y==figs[i].y) {
     killfigandhelper(i,j);
    }
   }
  }
 }
 //Update the nodes
 for (int i=0; i<NUMNODES; i++) {
  //Player touching node?
  if (!gameover && nodes[i].x==px && nodes[i].y==py) {
   if (nodes[i].phase==1) makehelper(nodes[i].x,nodes[i].y);
   if (nodes[i].phase==2) scantrappedfigs();
   nodes[i].phase=0; nodes[i].timeleft=200+(rand()&0x7FFFFFFF)%200;
   continue;
  }
  //Fig touching node?
  bool done=false;
  for (int j=0; j<NUMFIGS; j++) {
   if (figs[j].active && figs[j].x==nodes[i].x && figs[j].y==nodes[i].y) {
    if (nodes[i].phase==1) makefig(nodes[i].x,nodes[i].y);
    if (nodes[i].phase==2) figs[j].chasetimer=120;
    nodes[i].phase=0; nodes[i].timeleft=200+(rand()&0x7FFFFFFF)%200;
    done=true; break;
   }
  }
  if (done) continue;
  //Helper touching node?
  for (int j=0; j<NUMHELPERS; j++) {
   if (helpers[j].active && helpers[j].x==nodes[i].x && helpers[j].y==nodes[i].y) {
    if (nodes[i].phase==1) makehelper(nodes[i].x,nodes[i].y);
    if (nodes[i].phase==2) makehelper(nodes[i].x,nodes[i].y);
    nodes[i].phase=0; nodes[i].timeleft=200+(rand()&0x7FFFFFFF)%200;
    done=true; break;
   }
  }
  if (done) continue;
  //Neither - node can evolve
  nodes[i].timeleft--;
  if (nodes[i].timeleft==0) {
   nodes[i].phase=(nodes[i].phase+1)%3;
   nodes[i].timeleft=200+(rand()&0x7FFFFFFF)%200;
   putpixel(gamearea,nodes[i].x,nodes[i].y,0);
  }
  if (nodes[i].phase==1) putpixel(gamearea,nodes[i].x,nodes[i].y,4);
  if (nodes[i].phase==2) putpixel(gamearea,nodes[i].x,nodes[i].y,logictime%20>=10?4:3);
 }
 return false;
}

static void draw() {
 acquire_screen();
 //Draw text bar
 clear_bitmap(textbar);
 textout(textbar,font,gameover?gameover:"Prunes",0,0,gameover?1:7);
 textprintf_right(textbar,font,320,0,7,"Score: %d",score);
 stretch_blit(textbar,screen,0,0,320,8,0,0,640,16);
 //Draw border
 rect(screen,0,16,639,479,7);
 rect(screen,1,17,638,478,7);
 rect(screen,2,18,637,477,7);
 rect(screen,3,19,636,476,7);
 rect(screen,4,20,635,475,0);
 rect(screen,5,21,634,474,0);
 rect(screen,6,22,633,473,0);
 rect(screen,7,23,632,472,0);
 //Draw game
 int pcol=0;
 if (!gameover) {pcol=getpixel(gamearea,px,py); putpixel(gamearea,px,py,7);}
 for (int i=0; i<NUMHELPERS; i++) {
  if (helpers[i].active) {
   helpers[i].bcol=getpixel(gamearea,helpers[i].x,helpers[i].y);
   putpixel(gamearea,helpers[i].x,helpers[i].y,4);
  }
 }
 for (int i=0; i<NUMFIGS; i++) {
  if (figs[i].active) {
   figs[i].bcol=getpixel(gamearea,figs[i].x,figs[i].y);
   putpixel(gamearea,figs[i].x,figs[i].y,1);
  }
 }
 stretch_blit(gamearea,screen,0,0,GAMEW,GAMEH,8,24,GAMEW*8,GAMEH*8);
 for (int i=NUMFIGS-1; i>=0; i--) {
  if (figs[i].active) putpixel(gamearea,figs[i].x,figs[i].y,figs[i].bcol);
 }
 for (int i=NUMHELPERS-1; i>=0; i--) {
  if (helpers[i].active) putpixel(gamearea,helpers[i].x,helpers[i].y,helpers[i].bcol);
 }
 if (!gameover) putpixel(gamearea,px,py,pcol);
 release_screen();
}

static void shutdown() {
 destroy_bitmap(gamearea);
 destroy_bitmap(textbar);
}

int main(int argc, char **argv) {
 srand(time(NULL));
 LOCK_VARIABLE(truetime);
 LOCK_FUNCTION(timerproc);
 allegro_init();
 install_keyboard();
 install_timer();
 install_int_ex(&timerproc,BPS_TO_TIMER(20));
 set_gfx_mode(GFX_AUTODETECT,640,480,0,0);
 RGB pal[8];
 for (int i=0; i<8; i++) {
  pal[i].r=(i&1?63:0);
  pal[i].g=(i&2?63:0);
  pal[i].b=(i&4?63:0);
 }
 set_palette_range(pal,0,7,true);
 init();
 bool exit=false;
 while (!exit) {
  draw();
  while (logictime>=truetime) rest(1);
  while (!exit && logictime<truetime) {exit=update(); logictime++;}
 }
 shutdown();
 set_gfx_mode(GFX_TEXT,0,0,0,0);
 return 0;
}
END_OF_MAIN();


