#define HSYNC_MASK 0x01
#define VSYNC_MASK 0x02

#define HFRONTP 12
#define HBACKP 33
#define VBACKP 4

#define ROWS 60
#define COLS 80

#define ALIVE 0x08
#define DEAD 0x00

#define NMASK 0x08
#define AGEMASK 0x07

unsigned char screen[2][60][80];
int scanline;
int df;
int rf;
unsigned char clut[16] = {0xe0, 0xe4, 0xe8, 0xec, 0xf4, 0xf0, 0xf8, 0xfc,
			0x1c, 0x18, 0x10, 0x12, 0x0e, 0x0b, 0x07 ,0x03};
void new_game() {
  int i, j;
  if(rf == df) {
  for(int i=0;i<60;i++) {
    for(int j=0;j<80;j++) {
      if(random(100)>70) {
        screen[rf^1][i][j] = ALIVE;
      } else {
        screen[rf^1][i][j] = DEAD;
      }
    }
  }
  }
  rf ^= 1;
  while(!digitalRead(7)) {
    __asm__("nop\n\t");
  }
}

void setup() {
  randomSeed(analogRead(0));
  df = 0;
  rf = 0;
  new_game();

  TRISECLR = 0xff;
  TRISDCLR = HSYNC_MASK | VSYNC_MASK;
  OC1CON = 0x0000;
  OC1R = 0x083a;
  OC1RS = 0x083a;
  OC1CON = 0x0006;
  PR2 = 0x08A6;

  T4CON = 0x0;
  T5CON = 0x0;
  T4CONSET = 0x0038;            // divide by eight i.e. 1MHz
  TMR4 = 0x0;
  PR4 = 0x4C4B40;               // 10 million i.e. 1Second

  LATDSET = HSYNC_MASK | VSYNC_MASK;
  IFS0CLR = _IFS0_T2IF_MASK | _IFS0_T5IF_MASK;

  // enable the timers and output compare
  T2CONSET = 0x8000;
  T4CONSET = 0x8000;
  OC1CONSET = 0x8000;

  ConfigIntTimer2((T2_INT_ON | T2_INT_PRIOR_3));
  delay(3000);
  mConfigIntCoreTimer((CT_INT_OFF));
}

void update_pixel(int i, int j, int neighbours) {
  	if(screen[rf][i][j] & NMASK) {
	  // alive
	  if((neighbours < (2*NMASK)) || (neighbours > (3*NMASK))) {
	    screen[rf^1][i][j] = DEAD;	// zero age dead pixel
	  } else {
	    if((screen[rf][i][j] & AGEMASK) < AGEMASK) {
    	      screen[rf^1][i][j] = ALIVE | ((screen[rf][i][j] + 1) & AGEMASK);
	    } else {
	      screen[rf^1][i][j] = 0xf;
	    }
	  }
	} else {
	  // dead
	  if((neighbours == (3*NMASK))) {
	    screen[rf^1][i][j] = ALIVE; // zero age alive pixel
	  } else {
	    if((screen[rf][i][j] & AGEMASK) < AGEMASK) {
	      screen[rf^1][i][j] = DEAD | ((screen[rf][i][j] + 1) & AGEMASK);
	    } else {
	      screen[rf^1][i][j] = 0x7;
	    }
	  }
	}
}

void loop() {
  int i, j;
  int neighbours;
  while(!(_IFS0_T5IF_MASK & IFS0)) {
    if(!digitalRead(7)) {
      new_game();
      TMR4 = 0;
    }
  }
  if(df == rf) {
    for(i=1;i<ROWS-1;i++) {
      for(j=1;j<COLS-1;j++) {
        neighbours = screen[rf][i-1][j-1] & NMASK;
        neighbours += screen[rf][i-1][j] & NMASK;
        neighbours += screen[rf][i-1][j+1] & NMASK;
        neighbours += screen[rf][i][j-1] & NMASK;
        neighbours += screen[rf][i][j+1] & NMASK;
        neighbours += screen[rf][i+1][j-1] & NMASK;
        neighbours += screen[rf][i+1][j] & NMASK;
        neighbours += screen[rf][i+1][j+1] & NMASK;

	update_pixel(i,j, neighbours);
      }
    }
    for(i=1;i<COLS-1;i++) {
      neighbours = screen[rf][0][i-1] & NMASK;
      neighbours += screen[rf][0][i+1] & NMASK;
      neighbours += screen[rf][1][i-1] & NMASK;
      neighbours += screen[rf][1][i] & NMASK;
      neighbours += screen[rf][1][i+1] & NMASK;
      neighbours += screen[rf][ROWS-1][i-1] & NMASK;
      neighbours += screen[rf][ROWS-1][i] & NMASK;
      neighbours += screen[rf][ROWS-1][i+1] & NMASK;
      update_pixel(0, i, neighbours);

      neighbours = screen[rf][ROWS-2][i-1] & NMASK;
      neighbours += screen[rf][ROWS-2][i] & NMASK;
      neighbours += screen[rf][ROWS-2][i+1] & NMASK;
      neighbours += screen[rf][ROWS-1][i-1] & NMASK;
      neighbours += screen[rf][ROWS-1][i+1] & NMASK;
      neighbours += screen[rf][0][i-1] & NMASK;
      neighbours += screen[rf][0][i] & NMASK;
      neighbours += screen[rf][0][i+1] & NMASK;
      update_pixel(ROWS-1,i, neighbours);
    }
    for(i=1;i<ROWS-1;i++) {
      neighbours = screen[rf][i-1][0] & NMASK;
      neighbours += screen[rf][i+1][0] & NMASK;
      neighbours += screen[rf][i-1][1] & NMASK;
      neighbours += screen[rf][i][1] & NMASK;
      neighbours += screen[rf][i+1][1] & NMASK;
      neighbours += screen[rf][i-1][COLS-1] & NMASK;
      neighbours += screen[rf][i][COLS-1] & NMASK;
      neighbours += screen[rf][i+1][COLS-1] & NMASK;
      update_pixel(i, 0, neighbours);

      neighbours = screen[rf][i-1][COLS-1] & NMASK;
      neighbours += screen[rf][i+1][COLS-1] & NMASK;
      neighbours += screen[rf][i-1][COLS-2] & NMASK;
      neighbours += screen[rf][i][COLS-2] & NMASK;
      neighbours += screen[rf][i+1][COLS-2] & NMASK;
      neighbours += screen[rf][i-1][0] & NMASK;
      neighbours += screen[rf][i][0] & NMASK;
      neighbours += screen[rf][i+1][0] & NMASK;
      update_pixel(i,COLS-1, neighbours);
    }

    neighbours = screen[rf][1][0] & NMASK;
    neighbours += screen[rf][1][1] & NMASK;
    neighbours += screen[rf][0][1] & NMASK;
    neighbours += screen[rf][ROWS-1][0] & NMASK;
    neighbours += screen[rf][ROWS-1][1] & NMASK;
    neighbours += screen[rf][ROWS-1][COLS-1] & NMASK;
    neighbours += screen[rf][0][COLS-1] & NMASK;
    neighbours += screen[rf][1][COLS-1] & NMASK;
    update_pixel(0,0, neighbours);

    neighbours = screen[rf][1][0] & NMASK;
    neighbours += screen[rf][0][0] & NMASK;
    neighbours += screen[rf][0][COLS-2] & NMASK;
    neighbours += screen[rf][1][COLS-2] & NMASK;
    neighbours += screen[rf][1][COLS-1] & NMASK;
    neighbours += screen[rf][ROWS-1][COLS-1] & NMASK;
    neighbours += screen[rf][ROWS-1][COLS-2] & NMASK;
    neighbours += screen[rf][ROWS-1][0] & NMASK;
    update_pixel(0,COLS-1, neighbours);

    neighbours = screen[rf][ROWS-1][1] & NMASK;
    neighbours += screen[rf][ROWS-2][0] & NMASK;
    neighbours += screen[rf][ROWS-2][1] & NMASK;
    neighbours += screen[rf][ROWS-1][COLS-1] & NMASK;
    neighbours += screen[rf][ROWS-2][COLS-1] & NMASK;
    neighbours += screen[rf][0][COLS-1] & NMASK;
    neighbours += screen[rf][0][0] & NMASK;
    neighbours += screen[rf][0][1] & NMASK;
	update_pixel(ROWS-1,0, neighbours);

    neighbours = screen[rf][ROWS-1][COLS-2] & NMASK;
    neighbours += screen[rf][ROWS-2][COLS-2] & NMASK;
    neighbours += screen[rf][ROWS-2][COLS-1] & NMASK;
    neighbours += screen[rf][ROWS-1][0] & NMASK;
    neighbours += screen[rf][ROWS-2][0] & NMASK;
    neighbours += screen[rf][0][0] & NMASK;
    neighbours += screen[rf][0][COLS-1] & NMASK;
    neighbours += screen[rf][1][COLS-2] & NMASK;
    update_pixel(ROWS-1,COLS-1, neighbours);
    
    rf ^= 1;
  }
  TMR4 = 0;
  IFS0CLR = _IFS0_T5IF_MASK;
}  

// interrupt functions have to be C functions
#ifdef __cplusplus
extern "C" {
#endif

void __ISR(_TIMER_2_VECTOR, IPL3AUTO) scanline_handler(void) {
  int i=0;
  // keep track of how far down the screen we've got
  scanline++;
  if(scanline < 480) {
    // front porch
    for(int i=0;i<HFRONTP;i++) {
      __asm__("nop\n\t");
    }

    // main display
    for(int i=0;i<80;i++) {
      LATE = clut[screen[df][scanline/8][i]];
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
      __asm__("nop\n\t");
    }
  
    // back porch
    LATE = 0;
  } else if(scanline == (480 + VBACKP)) {
    LATDCLR = VSYNC_MASK;
  } else if(scanline == (482 + VBACKP)) {
    LATDSET = VSYNC_MASK;
  } else if(scanline == 526) {
    scanline = -1;
    if(df != rf) {
      df = rf;
    }
  }
  
  // make sure all the timer interrupt flags are clear and ready to trigger
  // again
  IFS0CLR = _IFS0_T2IF_MASK;
}


#ifdef __cplusplus
}
#endif