/* Conway's Game of Life
 * http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
 */

PIX = 16;
WIDTH = width() / PIX;
HEIGHT = height() / PIX;
ESC = 27;
SPACE = 32;
CLEAR = 67;

myGrid = createEmptyGrid();

function draw(grid, showGridLines, flushAtEnd)
  cls();
  if showGridLines then
    color(20, 80, 20);
    for y = 1 to HEIGHT do
      line(0, y * PIX, width(), y * PIX);
    done;
    for x = 1 to WIDTH do
      line(x * PIX, 0, x * PIX, height());
    done;
  endif;
  for y = 1 to HEIGHT do
    for x = 1 to WIDTH do
      if grid[x, y] then
        color(150, 255, 150);
        fillRect((x - 1) * PIX, (y - 1) * PIX, PIX, PIX);
        color(50, 255, 50);
        rect((x - 1) * PIX, (y - 1) * PIX, PIX, PIX);
      endif;
    done;
  done;
  if flushAtEnd then
    flush();
  endif;
endfunc;

function createRandomGrid(fill)
  for y = 1 to HEIGHT do
    for x = 1 to WIDTH do
      if random() <= fill then
        local grid[x, y] = TRUE;
      else
        local grid[x, y] = FALSE;
      endif;
    done;
  done;
  return grid;
endfunc;

function createEmptyGrid()
  for y = 1 to HEIGHT do
    for x = 1 to WIDTH do
      local grid[x, y] = FALSE;
    done;
  done;
  return grid;
endfunc;

function createGosperGliderGunGrid()
  // http://en.wikipedia.org/wiki/Gun_(cellular_automaton)
  local grid = createEmptyGrid();
  grid[2, 6] = TRUE;
  grid[2, 7] = TRUE;
  grid[3, 6] = TRUE;
  grid[3, 7] = TRUE;
  
  grid[12, 6] = TRUE;
  grid[12, 7] = TRUE;
  grid[12, 8] = TRUE;
  grid[13, 5] = TRUE;
  grid[13, 9] = TRUE;
  grid[14, 4] = TRUE;
  grid[14, 10] = TRUE;
  grid[15, 4] = TRUE;
  grid[15, 10] = TRUE;
  grid[16, 7] = TRUE;
  grid[17, 5] = TRUE;
  grid[17, 9] = TRUE;
  grid[18, 6] = TRUE;
  grid[18, 7] = TRUE;
  grid[18, 8] = TRUE;
  grid[19, 7] = TRUE;
  
  grid[22, 6] = TRUE;
  grid[22, 5] = TRUE;
  grid[22, 4] = TRUE;
  grid[23, 6] = TRUE;
  grid[23, 5] = TRUE;
  grid[23, 4] = TRUE;
  grid[24, 3] = TRUE;
  grid[24, 7] = TRUE;
  grid[26, 3] = TRUE;
  grid[26, 7] = TRUE;
  grid[26, 2] = TRUE;
  grid[26, 8] = TRUE;
  
  grid[36, 4] = TRUE;
  grid[36, 5] = TRUE;
  grid[37, 4] = TRUE;
  grid[37, 5] = TRUE;
  
  return grid;
endfunc;

function getAt(grid, x, y)
  if x >= 1 and x <= WIDTH and y >= 1 and y <= HEIGHT then
    if grid[x, y] then
      return 1;
    else
      return 0;
    endif;
  endif;
  return 0;
endfunc;

function countNeighbours(grid, x, y)
  local count = 0;
  local count = count + getAt(grid, x - 1, y - 1);
  local count = count + getAt(grid, x, y - 1);
  local count = count + getAt(grid, x + 1, y - 1);
  local count = count + getAt(grid, x - 1, y);
  local count = count + getAt(grid, x + 1, y);
  local count = count + getAt(grid, x - 1, y + 1);
  local count = count + getAt(grid, x, y + 1);
  local count = count + getAt(grid, x + 1, y + 1);
  return count;
endfunc;

function applyConwayRules(grid)
  for y = 1 to HEIGHT do
    for x = 1 to WIDTH do
      numNeighbours = countNeighbours(grid, x, y);
      local newGrid[x, y] = FALSE;
      if grid[x, y] and numNeighbours >= 2 and numNeighbours <= 3 then
        local newGrid[x, y] = TRUE;
      elseif not grid[x, y] and numNeighbours == 3 then
        local newGrid[x, y] = TRUE;
      endif;
    done;
  done;
  return newGrid;
endfunc;

function menu()
  cls();
  println("\n\n");
  color(255, 150, 150);
  println("      Conway's Game of Life");
  println("      ---------------------\n");
  color(150, 150, 255);
  println("      Please select one of:\n");
  color(150, 255, 150);
  println("      0. Exit");
  println("      1. Random layout, 16% fill");
  println("      2. Random layout, 25% fill");
  println("      3. Random layout, 50% fill");
  println("      4. Random layout, 75% fill");
  println("      5. Gosper Glider Gun");
  println("      6. Edit layout");
  color(150, 150, 255);
  println("\n      During animation, press Esc to return to menu.");
  flush();
endfunc;

function animate(grid)
  while not keyPressed(ESC) do
    t = time();
    draw(grid, FALSETRUE);
    grid = applyConwayRules(grid);
    wait(150 - (time() - t));
  done;
endfunc;

function edit()
  finished = FALSE;
  while not finished do
    draw(myGrid, TRUEFALSE);
    color(255, 255, 255);
    println("Press Esc to return to menu, C to clear, or Space to start animation.");
    flush();
    if mouseButtonPressed(1) then
      while mouseButtonPressed(1) do
        // wait for user to release button.
        wait(10);
      done;
      x = 1 + mouseX() / PIX;
      y = 1 + mouseY() / PIX;
      myGrid[x, y] = not myGrid[x, y];
    endif;
    if keyPressed(ESC) then
      finished = TRUE;
    elseif keyPressed(CLEAR) then
      myGrid = createEmptyGrid();
    elseif keyPressed(SPACE) then
      finished = TRUE;
      animate(myGrid);
    endif;
    wait(50);
  done;
endfunc;

autoFlush(FALSE);
while TRUE do
  menu();
  c = readChar();
  if c == '0' then
    cls();
    flush();
    exit();
  elseif c == '1' then
    animate(createRandomGrid(0.1667));
  elseif c == '2' then
    animate(createRandomGrid(0.25));
  elseif c == '3' then
    animate(createRandomGrid(0.5));
  elseif c == '4' then
    animate(createRandomGrid(0.75));
  elseif c == '5' then
    animate(createGosperGliderGunGrid());
  elseif c == '6' then
    edit();
  endif;
done;