jan 2016-06-16
index.raw.html
<canvas id="blocks"></canvas>
<script src="../../js/jquery-2.2.4.min.js"></script>
<script src="gol.js"></script>
<script>
    setTimeout(init, 200);
</script>
<div class="sign">jan 2016-06-16</div>
gol.coffee
init = ->
  scale = 5;
  canvas = document.getElementById("blocks")
  $(canvas).css({opacity: 0})
  [canvas.width, canvas.height] = [$(canvas).width() / scale,
                                   $(canvas).height() / scale]
  fontsize = Math.min(canvas.width, canvas.height) * 0.8

  ctx = canvas.getContext("2d")
  ctx.fillStyle = "#000000"
  ctx.font = "#{fontsize}px flourish"
  txt = "rg"
  txtwidth = ctx.measureText(txt).width
  ctx.fillText(txt, canvas.width / 2 - txtwidth / 2, fontsize * 0.7)
  $(canvas).fadeTo(950, 1)
  setTimeout(golRun, 1000)


golRun = ->
  golStep()
  setTimeout(golRun, 50)


adjacent = [
  [ 0,  1],
  [ 1,  1],
  [ 1,  0],
  [ 1, -1],
  [ 0, -1],
  [-1, -1],
  [-1,  0],
  [-1,  1],
]

class Cell
  constructor: (isAlive, neighbours) ->
    @neighbours = if neighbours? then neighbours else 0
    @isAlive = isAlive? && isAlive

  step: ->
    @isAlive = @neighbours is 3 or @isAlive and @neighbours is 2


class Pos
  constructor: (@x, @y) ->

  repr: -> "[#{@x},#{@y}]"

  add: ([ox, oy]) -> new Pos(@x + ox, @y + oy)

  index: (size) ->
    4 * (@wrap(@x, size.x) + @wrap(@y, size.y) * size.x) + 3

  wrap: (v, max) -> (v + max) % max

  @all: (size) ->
    [].concat (new Pos(x, y) for x in [0..size.x] for y in [0..size.y])...

class Field
  constructor: (curr, next) ->
    @size = new Pos(curr.width, curr.height)
    @data = curr.data
    @next = next?.data or curr.data[..]

  getPixelAt: (pos) -> 0 != @data[pos.index(@size)]

  setPixelAt: (pos, cell) ->
    @next[pos.index(@size)] = if cell.isAlive then 255 else 0

  cellAt: (pos) ->
    new Cell(@getPixelAt(pos),
      adjacent.reduce ((sum, a) =>
        sum + +@getPixelAt(pos.add(a))), 0)

  step: ->
    for pos in Pos.all(@size)
      cell = @cellAt(pos)
      cell.step()
      @setPixelAt(pos, cell)
    true

golStep = ->
    @canvas = document.getElementById("blocks");
    @ctx = @canvas.getContext("2d");
    @curr = @ctx.getImageData(0, 0, @canvas.width, @canvas.height);
    @next = @ctx.createImageData(@curr.width, @curr.height)
    @field = new Field(@curr, @next)
    @field.step()
    @ctx.putImageData(@next, 0, 0)



root = exports ? window
root.Cell = Cell
root.Pos = Pos
root.Field = Field
root.init = init
gol.spec.coffee
should = require "should";
{Cell, Pos, Field} = require "./gol";

describe "A cell", ->

  it 'should be dead initially', ->
    (new Cell).isAlive.should.be.false()

describe "A dead cell", ->

  it 'should know its neighbours', ->
    cell = new Cell(false, 0)
    cell.neighbours.should.equal 0
    cell = new Cell(false, 1)
    cell.neighbours.should.equal 1

  it 'with no neighbours should die', ->
    cell = new Cell(false)
    cell.step()
    cell.isAlive.should.be.false()

  it 'with three neighbours should live', ->
    cell = new Cell(false, 3)
    cell.step()
    cell.isAlive.should.be.true()

describe "A live cell", ->

  it 'should be alive', ->
    cell = new Cell(true)
    cell.isAlive.should.be.true()

  it 'with no neighbours should die', ->
    cell = new Cell(true)
    cell.isAlive.should.be.true()
    cell.step()
    cell.isAlive.should.be.false()

  it 'with three neighbours should live', ->
    cell = new Cell(true, 3)
    cell.isAlive.should.be.true()
    cell.step()
    cell.isAlive.should.be.true()

describe "A field wraps some imgdata and", ->
  [w, h] = [7, 5]
  sample =
    width: w
    height: h
    data: 0 for [0 .. w * h * 4]

  [tx, ty] = [1, 3]
  tpos = new Pos(tx, ty)
  sample.data[4 * (tx + ty * w) + 3] = 1
  sample.data[4 * (tx+2 + ty * w) + 3] = 1

  beforeEach -> @field = new Field(sample)


  it 'should know its width and height', ->
    @field.size.repr().should.equal(new Pos(w, h).repr())

  it 'should know how to map coordinates to data', ->
    @field.getPixelAt(tpos).should.be.true()
    @field.getPixelAt(tpos.add([1, 0])).should.be.false()

  it 'should create a live cell for a pixel that is set', ->
    @field.cellAt(tpos).isAlive.should.be.true()

  it 'should create a dead cell for a pixel that is cleared', ->
    @field.cellAt(tpos.add([1, 0])).isAlive.should.be.false()

  it 'should wrap around when reading pixels off canvas', ->
    @field.getPixelAt(tpos.add([@field.width, 0])).should.be.true()

  it 'should count neighbours for a cell', ->
    @field.cellAt(tpos.add([1, 0])).neighbours.should.equal(2)

  it 'should step to next generation', ->
    (""+@field.next).should.equal(""+sample.data)
    @field.step()
    (""+@field.next).should.not.equal(""+sample.data)
: