#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 0x1D
#define DEAD 0x6c

#define NMASK 0x01

unsigned char screen[2][60][80];
int scanline;
int df;
int rf;

void setup() {
  randomSeed(analogRead(0));
  for(int i=0;i<60;i++) {
    for(int j=0;j<80;j++) {
      if(random(100)>62) {
        screen[0][i][j] = ALIVE;
      } else {
        screen[0][i][j] = DEAD;
      }
    }
  }

  TRISECLR = 0xff;
  TRISDCLR = HSYNC_MASK | VSYNC_MASK;
  df = 0;
  rf = 0;
  OC1CON = 0x0000;
  OC1R = 0x083a;
  OC1RS = 0x083a;
  OC1CON = 0x0006;
  PR2 = 0x08A6;
  LATDSET = HSYNC_MASK | VSYNC_MASK;
  IFS0CLR = _IFS0_T2IF_MASK | _IFS0_T3IF_MASK | _IFS0_T4IF_MASK | _IFS0_T5IF_MASK;

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

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

void loop() {
  int i, j;
  int neighbours;
  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;

        if((neighbours < 2) || (neighbours > 3)) {
          screen[rf^1][i][j] = DEAD;
        } else if(neighbours == 3) {
          screen[rf^1][i][j] = ALIVE;
        } else {
          screen[rf^1][i][j] = screen[rf][i][j];
        }
      }
    }
    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;
      if((neighbours < 2) || (neighbours > 3)) {
        screen[rf^1][0][i] = DEAD;
      } else if(neighbours == 3) {
        screen[rf^1][0][i] = ALIVE;
      } else {
        screen[rf^1][0][i] = screen[rf][0][i];
      }

      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;
      if((neighbours < 2) || (neighbours > 3)) {
        screen[rf^1][ROWS-1][i] = DEAD;
      } else if(neighbours == 3) {
        screen[rf^1][ROWS-1][i] = ALIVE;
      } else {
        screen[rf^1][ROWS-1][i] = screen[rf][ROWS-1][i];
      }
    }
    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;
      if((neighbours < 2) || (neighbours > 3)) {
        screen[rf^1][i][0] = DEAD;
      } else if(neighbours == 3) {
        screen[rf^1][i][0] = ALIVE;
      } else {
        screen[rf^1][i][0] = screen[rf][i][0];
      }

      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;
      if((neighbours < 2) || (neighbours > 3)) {
        screen[rf^1][i][COLS-1] = DEAD;
      } else if(neighbours == 3) {
        screen[rf^1][i][COLS-1] = ALIVE;
      } else {
        screen[rf^1][i][COLS-1] = screen[rf][i][COLS-1];
      }
    }

    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;
    if((neighbours < 2) || (neighbours > 3)) {
      screen[rf^1][0][0] = DEAD;
    } else if(neighbours == 3) {
      screen[rf^1][0][0] = ALIVE;
    } else {
      screen[rf^1][0][0] = screen[rf][0][0];
    }

    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;
    if((neighbours < 2) || (neighbours > 3)) {
      screen[rf^1][0][COLS-1] = DEAD;
    } else if(neighbours == 3) {
      screen[rf^1][0][COLS-1] = ALIVE;
    } else {
      screen[rf^1][0][COLS-1] = screen[rf][0][COLS-1];
    }

    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;
    if((neighbours < 2) || (neighbours > 3)) {
      screen[rf^1][ROWS-1][0] = DEAD;
    } else if(neighbours == 3) {
      screen[rf^1][ROWS-1][0] = ALIVE;
    } else {
      screen[rf^1][ROWS-1][0] = screen[rf][ROWS-1][0];
    }

    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;
    if((neighbours < 2) || (neighbours > 3)) {
      screen[rf^1][ROWS-1][COLS-1] = DEAD;
    } else if(neighbours == 3) {
      screen[rf^1][ROWS-1][COLS-1] = ALIVE;
    } else {
      screen[rf^1][ROWS-1][COLS-1] = screen[rf][ROWS-1][COLS-1];
    }
    
    rf ^= 1;
  }
}  

// 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 = 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