/* Chain Reaction for X11. A strategy game for 2-4 players.
Copyright (C) 1998 Alistair Cunningham <ac212@cam.ac.uk>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You may have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 
02111-1307, USA. or see http://www.gnu.org/copyleft/gpl.html
*/

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <pwd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <time.h>

#define DEF_WISH_PATH "/usr/bin/wish"

void wprintf(char *, ...);
void draw_screen(void);
void my_exit(void);
int doclick(int,int,int);

int pipe_out[2];
int pipe_in[2];
char *backcol[] = {"#d9d9d9","#ff0000","#0000ff","#00ff00","#ffff00","#ececec","#ff8f8f","#8f8fff","#8fff8f","#ffffaf"};
int v[8][8];
int ov[8][8];
int w[8][8];
int ow[8][8];
int numtiles[5];
int onumtiles[5];
char name[5][34];
int num;
int next_num = 2;
int start_board = 1;
int max[8][8];
int go;
int ogo;
int pf = 0;
int gameover;

int set_sq(int x, int y, int who, int val) {
  int wtemp,c,l;

  wtemp = w[x][y];
  w[x][y] = who;
  v[x][y] = val;

  if (wtemp!=who && who!=0) numtiles[who]++;
  if (wtemp>0 && wtemp!=who) {
    numtiles[wtemp]--;
    if (wtemp!=go && numtiles[wtemp]==0) numtiles[wtemp] = -1;
    c = 0;
    for(l=1;l<=4;l++) {
      if (numtiles[l]==-1) c++;
    }
    if (c>(num-2)) return go;
  }
  return 0;
}

void picknext() {

  go++;
  if (go>num) go=1;
  while (numtiles[go]==-1) {
    go++;
    if (go>num) go=1;
  }

  wprintf(".s1 configure -background %s",backcol[go]);
  wprintf(".s1 configure -text \"%s to move...\"",name[go]);
}

void update_board() {
  int x,y;

  for(y=0;y<8;y++)
    for(x=0;x<8;x++) {
      wprintf(".b.b%d configure -text %d",x+8*y,v[x][y]);
      wprintf(".b.b%d configure -background %s -activebackground %s",x+8*y,backcol[w[x][y]],backcol[w[x][y]+5]);
    }
  for(x=1;x<=4;x++) 
    if (x<=num)
      if (numtiles[x]>-1) wprintf(".c.p.p%d.v configure -text %.2d",x,numtiles[x]);
      else wprintf(".c.p.p%d.v configure -text 00",x);
}

void new_game(void) {
  int i,j,k;
  int num_cols,num_rand;
  char in_buf[5];
  int wt[8][8];

  num = next_num;

  for(i=1;i<=4;i++) {
    if (i>num) {
      wprintf(".c.p.p%d.v configure -text 00",i);
      wprintf(".c.p.p%d.n configure -background %s",i,backcol[0]);
    } else
      wprintf(".c.p.p%d.n configure -background %s",i,backcol[i]);
    numtiles[i] = 0;
    wprintf("puts stdout [string range $playername%d 0 29]",i);
    read(pipe_in[0],name[i],32);
    j = 0;
    while (name[i][j]!='\n') j++;
    name[i][j] = 0;
  }

  wprintf("puts stdout [string range $startcols 0 1]");
  read(pipe_in[0],in_buf,sizeof(in_buf));
  in_buf[4] = 0;
  num_cols = atoi(in_buf);
  if (num_cols<0) num_cols = 0;
  if (num_cols>4) num_cols = 4;

  wprintf("puts stdout [string range $startrand 0 2]");
  read(pipe_in[0],in_buf,sizeof(in_buf));
  in_buf[4] = 0;
  num_rand = atoi(in_buf);
  if (num_rand<0) num_rand = 0;
  if (num_rand>32) num_rand = 32;

  for (j=0;j<8;j++)
    for (i=0;i<8;i++) { 
      ow[i][j] = 0;
      w[i][j] = 0;
      wt[i][j] = 0;
      ov[i][j] = 0;
    }
  
  if (start_board==2) {
    if (num==2) {
      for (j=0;j<8;j++)
	for (i=0;i<num_cols;i++)
	  wt[i][j] = 1;
      for (j=0;j<8;j++)
	for (i=8-num_cols;i<8;i++) 
	  wt[i][j] = 2;
    } else if (num==3) {
      for (j=0;j<num_cols;j++)
	for (i=0;i<num_cols;i++) 
	  wt[i][j] = 1;
      for (j=0;j<num_cols;j++)
	for (i=8-num_cols;i<8;i++) 
	  wt[i][j] = 2;
      for (j=8-num_cols;j<8;j++)
	for (i=0;i<num_cols;i++) 
	  wt[i][j] = 3;
    } else {
      for (j=0;j<num_cols;j++)
	for (i=0;i<num_cols;i++) 
	  wt[i][j] = 1;
      for (j=0;j<num_cols;j++)
	for (i=8-num_cols;i<8;i++) 
	  wt[i][j] = 2;
      for (j=8-num_cols;j<8;j++)
	for (i=0;i<num_cols;i++) 
	  wt[i][j] = 3;
      for (j=8-num_cols;j<8;j++)
	for (i=8-num_cols;i<8;i++) 
	  wt[i][j] = 4;
    }
  }

  if (start_board==3) {
    if (num==3 && num_rand>16) num_rand = 21;
    if (num==4 && num_rand>21) num_rand = 16;
    
    for(i=0;i<num_rand;i++) {
      for(j=1;j<=num;j++) {
	k = rand() % 64;
	while (wt[k%8][k/8]!=0)
	  k = rand() % 64;
	wt[k%8][k/8] = j;
      }
    }
  }
 
  for (j=0;j<8;j++)
    for (i=0;i<8;i++) 
      if (wt[i][j]>0) set_sq(i,j,wt[i][j],(rand() % max[i][j]) + 1);
      else set_sq(i,j,0,0);

  gameover = 0;

  update_board();

  for(i=0;i<3;i++) 
    onumtiles[i] = 0;

  wprintf(".u configure -state disabled");

  if (pf==0) go = num;
  else go = (rand() % num) + 1;
 
  picknext();
}

void undo_move() {
  int x,y;

  wprintf(".u configure -state disabled");
  go = ogo;
  gameover = 0;
  for (y=0;y<8;y++)
    for (x=0;x<8;x++) {
      v[x][y] = ov[x][y];
      w[x][y] = ow[x][y];
    }
  for (x=1;x<=4;x++)
    numtiles[x] = onumtiles[x];

  update_board();
  wprintf(".s1 configure -background %s",backcol[go]);
  wprintf(".s1 configure -text \"%s to move...\"",name[go]);
}

int doclick(int x, int y, int times) {
  int r;

  if (times>1000) return go;
  r = 0;
  if(v[x][y]<max[x][y]) {
    r = set_sq(x,y,go,v[x][y]+1);
    if (r>0) return r;
    else {}
  } else {
    r = set_sq(x,y,0,0);
    if (r>0) return r;
    if (x>0) r = doclick(x-1,y,times+1);
    if (r>0) return r;
    if (x<7) r = doclick(x+1,y,times+1);
    if (r>0) return r;
    if (y>0) r = doclick(x,y-1,times+1);
    if (r>0) return r;
    if (y<7) r = doclick(x,y+1,times+1);
    if (r>0) return r;
  }
  return 0;
}

int clickon(int s) {
  int r,x,y;

  if (w[s%8][s/8]!=0 && w[s%8][s/8]!=go) return 0;

  wprintf(".u configure -state normal");
  ogo = go;
  for (y=0;y<8;y++)
    for (x=0;x<8;x++) {
      ov[x][y] = v[x][y];
      ow[x][y] = w[x][y];
    }
  for (x=1;x<=4;x++)
    onumtiles[x] = numtiles[x];

  wprintf(".s1 configure -text \"Busy...\"");
  r = doclick(s%8,s/8,1);
  update_board();
  if (r==0) {
    picknext();
    return 0;
  }
  else {
    wprintf(".s1 configure -text \"%s wins.\"",name[go]);
    return 1;
  }
}

int main(int argc, char **argv) {
  int pid;
  int quitnow,showhelp,showver;
  int i,j;
  char inbuf[6];
  char *wish;

  wish = DEF_WISH_PATH;
  quitnow = 0;
  showhelp = 0;
  showver = 0;
  for(i=1;i<argc;i++) {
    if (!strcmp(argv[i],"-h") || !strcmp(argv[i],"--help"))
      showhelp = 1;
    else if (!strcmp(argv[i],"-v") || !strcmp(argv[i],"--version")) 
      showver = 1;
    else wish = argv[i];
  }
  if (showhelp == 1) {
    printf("\nUsage: %s [-v] [--version] [-h] [--help] [path to wish]\n",argv[0]);
    printf("For more help, type: man %s\n\n",argv[0]);
    quitnow = 1;
  }
  if (showver == 1) {
    printf("\nChain Reaction 1.0.1\nBy Alistair Cunningham <ac212@cam.ac.uk>\n");
    printf("Released under the GNU general public license\n");
    printf("http://thor.cam.ac.uk/~ac212/xchain/\n\n");
    quitnow = 1;
  }
  if (quitnow == 1) exit(0);

  signal(SIGCHLD,(void *)my_exit);
  srand(time(NULL));
  for(j=0;j<8;j++)
    for(i=0;i<8;i++) {
      max[i][j] = 3;
      if (i==0 || i==7 || j==0 || j==7)
	max[i][j] = 2;
      if ((i==0 || i==7) && (j==0 || j==7))
	max[i][j] = 1;
    }
  
  pipe(pipe_out);
  pipe(pipe_in);
  pid = fork();
  if (pid==-1) {
    perror("Fork");
    exit(1);
  }
  if (pid==0) {

    close(pipe_out[1]);
    close(pipe_in[0]);
    dup2(pipe_out[0],0);
    dup2(pipe_in[1],1);
    close(pipe_out[0]);
    close(pipe_in[1]);
    execl(wish,"wish",NULL);
    fprintf(stderr,"\nCouldn't execute %s\n\n",wish);
    exit(1);
  }
  close(pipe_out[0]);
  close(pipe_in[1]);

  draw_screen();
  new_game();
  
  while (1) {
    read(pipe_in[0],inbuf,5);
    inbuf[4]=0;
    if (!strcmp(inbuf,"newg")) new_game();
    if (!strcmp(inbuf,"undo")) undo_move();
    if (!strcmp(inbuf,"pl02")) next_num = 2;
    if (!strcmp(inbuf,"pl03")) next_num = 3;
    if (!strcmp(inbuf,"pl04")) next_num = 4;
    if (!strcmp(inbuf,"none")) start_board = 1;
    if (!strcmp(inbuf,"cols")) start_board = 2;
    if (!strcmp(inbuf,"rand")) start_board = 3;
    if (!strcmp(inbuf,"pf01")) pf = 0;
    if (!strcmp(inbuf,"pfrn")) pf = 1;
    if (!gameover && !strncmp(inbuf,"zz",2)) gameover = clickon(10*(inbuf[2]-'0')+inbuf[3]-'0');
  }

  return 0;
}

void draw_screen() {
  int i;

  wprintf("wm title . \"Chain Reaction\"");
  wprintf("wm resizable . 0 0");

  wprintf("frame .c");
  wprintf("button .c.q -text Quit -command exit");
  wprintf("pack .c.q -side bottom -fill x");
  wprintf("button .c.n -text \"New game\" -command {puts stdout newg}");
  wprintf("pack .c.n -side bottom -fill x");

  wprintf("frame .c.p -relief sunk -borderwidth 1");
  wprintf("set numplayers %d",next_num);
  wprintf("set playername1 \"%s\"",getpwuid(getuid())->pw_name);
  for (i=2;i<=4;i++)
    wprintf("set playername%d \"Player %d\"",i,i);

  wprintf("label .c.p.l -text Players:");
  wprintf("pack .c.p.l -side top");
  for (i=1;i<=4;i++) {
    wprintf("frame .c.p.p%d",i);
    if (i==1) 
      wprintf("radiobutton .c.p.p%d.r  -value 1 -variable numplayers -text %d: -state disabled",i,i);
    else wprintf("radiobutton .c.p.p%d.r -value %d -variable numplayers -text %d: -state normal -command {puts stdout pl0%d}",i,i,i,i);
    wprintf("pack .c.p.p%d.r -side left",i);
    if (i<3) 
      wprintf("entry .c.p.p%d.n -textvariable playername%d -width 28 -background %s",i,i,backcol[i]);
    else 
      wprintf("entry .c.p.p%d.n -textvariable playername%d -width 28 -background %s",i,i,backcol[0]);    
    wprintf("label .c.p.p%d.v",i);
    wprintf("pack .c.p.p%d.v -side right",i);
    wprintf("pack .c.p.p%d.n -side right",i);
    wprintf("pack .c.p.p%d -side top",i);
  }
  wprintf("pack .c.p -side top -fill both -padx 3 -pady 3");

  wprintf("frame .c.i -relief sunk -borderwidth 1");
  wprintf("set startboard %d",start_board);
  wprintf("label .c.i.l -text \"Start board with:\"");
  wprintf("pack .c.i.l -side top");
  wprintf("frame .c.i.b");
  wprintf("radiobutton .c.i.b.r -value 1 -variable startboard -text None -command {puts stdout none}");
  wprintf("pack .c.i.b.r -side left");
  wprintf("pack .c.i.b -side left");
  wprintf("frame .c.i.c");
  wprintf("radiobutton .c.i.c.r -value 2 -variable startboard -text Cols: -command {puts stdout cols}");
  wprintf("pack .c.i.c.r -side left");
  wprintf("set startcols 4");
  wprintf("entry .c.i.c.e -textvariable startcols -width 3 -justify right");
  wprintf("pack .c.i.c.e -side right");
  wprintf("pack .c.i.c -side left");
  wprintf("frame .c.i.r");
  wprintf("radiobutton .c.i.r.r -value 3 -variable startboard -text \"Random: \" -command {puts stdout rand}");
  wprintf("pack .c.i.r.r -side left");
  wprintf("set startrand 32");
  wprintf("entry .c.i.r.e -textvariable startrand -width 3 -justify right");
  wprintf("pack .c.i.r.e -side right");
  wprintf("pack .c.i.r -side left");
  wprintf("pack .c.i -side top -fill both -padx 3 -pady 3");

  wprintf("frame .c.s -relief sunk -borderwidth 1");
  wprintf("set whosfirst 1");
  wprintf("label .c.s.l -text \"Who goes first:\"");
  wprintf("pack .c.s.l -side top");
  wprintf("radiobutton .c.s.p -value 1 -variable whosfirst -text \"Player 1\" -command {puts stdout pf01}");
  wprintf("pack .c.s.p -side left");
  wprintf("radiobutton .c.s.r -value 2 -variable whosfirst -text \"Random person\" -command {puts stdout pfrn}");
  wprintf("pack .c.s.r -side left");
  wprintf("pack .c.s -side top -fill both -padx 3 -pady 3");

  wprintf("pack .c -side right -fill y");

  wprintf("frame .b -relief sunk -borderwidth 1");
  for(i=0;i<64;i++) {
    wprintf("button .b.b%d -text 0 -command {puts stdout zz%.2d}",i,i,i);
    wprintf("grid .b.b%d -row %d -column %d",i,i/8,i%8);
  }
  wprintf("pack .b -fill both -padx 3 -pady 3");

  wprintf("label .s1 -text \"Who\'s go now\" -relief sunk -borderwidth 1");
  wprintf("pack .s1 -side top -fill x -padx 3 -pady 3");
  wprintf("button .u -text \"Undo last move\" -command {puts stdout undo}");
  wprintf("pack .u -side bottom -fill x");
}

void wprintf(char *fmt, ...) {
  va_list argp;
  char buff[4096];

  va_start(argp, fmt);
  vsprintf(buff, fmt, argp);
  write(pipe_out[1],buff,strlen(buff));
  va_end(argp);
  write(pipe_out[1],"\n",1);
}

void my_exit(void) {

  close(pipe_out[1]);
  close(pipe_in[0]);
  exit(0);
}
