Skip to content

Instantly share code, notes, and snippets.

@kiko
Forked from manveru/goban.coffee
Created June 23, 2010 05:14
Show Gist options
  • Save kiko/449535 to your computer and use it in GitHub Desktop.
Save kiko/449535 to your computer and use it in GitHub Desktop.
p: (obj) ->
if console and console.debug
console.debug obj
Array::index: (member) ->
for element, index in this
if element is member
return index
null
Array::include: (member) ->
@index(member) isnt null
class Group
constructor: (goban, color, members) ->
@goban: goban
@color: color
@members: members
push: (stone) ->
@members.push stone
include: (stone) ->
if stone.color isnt @color
return false
for other, idx in @members
if other.equal stone
return true
false
remove: (stone) ->
for other, idx in @members
if other.equal stone
delete @members[idx]
return true
join: (group) ->
for member in @members
unless group.include member
group.push member
member.group: group
suicide: ->
for member in @members
stone: new Stone(@goban, member.x, member.y, 'empty')
@goban.board[stone.x][stone.y]: stone
stone.commit()
freedoms: ->
seen: []
@members.reduce (acc, stone) ->
for neighbour in stone.neighbours()
unless seen.include neighbour
if neighbour.color is 'empty'
acc += 1
seen.push neighbour
acc
, 0
class Stone
constructor: (goban, x, y, color) ->
[@goban, @x, @y, @color]: [goban, x, y, color]
@group: new Group(@goban, color, [this])
# temporarily add all adjacent groups of same color
for neighbour in @neighbours()
if neighbour.color is color
for member in neighbour.group.members
unless @group.include member
@group.push member
# placement was successful, merge groups and kill adjacent groups of the
# other color with 0 freedoms.
commit: ->
for member in @group
member.group: @group
@goban.history.push this
lithocide: ->
for neighbour in @neighbours()
if neighbour.color isnt @color
group: neighbour.group
if group.freedoms() <= 0
group.suicide()
alive: ->
p @freedoms()
@freedoms() > 0
equal: (stone) ->
@x is stone.x &&
@y is stone.y &&
@color is stone.color
group: (sofar) ->
sofar ?= new Group(@goban, @color, [this])
for neighbour in @neighbours()
if !sofar.include neighbours
sofar.push neighbour
neighbour.group sofar
sofar
neighbours: ->
stones: []
coords: [
[@x, @y - 1],
[@x, @y + 1],
[@x - 1, @y],
[@x + 1, @y]
]
for [x, y] in coords
if stone: @goban.at(x, y)
stones.push stone
stones
freedoms: ->
@group.freedoms()
class Goban
constructor: (jCanvas) ->
@jCanvas: jCanvas
@canvas: jCanvas[0]
@ctx: @canvas.getContext '2d'
@grid: parseInt @canvas.className.slice(0, 1), 10
@groups: {
'black': new Group(this, 'black', [])
'white': new Group(this, 'white', [])
'empty': new Group(this, 'empty', [])
}
maxX: $(document).width()
maxY: $(document).height()
# dimensions, assume it's rectangular
@top: 0
@left: 0
if maxX > maxY:
@bottom: maxY
@right: maxY
else
@bottom: maxX
@right: maxX
@color: 'black'
@history: []
@board: {}
for x in [1..@grid]
@board[x]: {}
for y in [1..@grid]
@board[x][y]: new Stone(this, x, y, 'empty')
redraw: ->
@canvas.width: @right
@canvas.height: @bottom
ctx: @ctx
# the board
# vertical lines
@tenRadius: @bottom * 0.01
@stoneRadius: @bottom * 0.04
@gridLeft: @left + (@stoneRadius + 1)
@gridRight: @right - (@stoneRadius + 1)
@gridTop: @top + (@stoneRadius + 1)
@gridBottom: @bottom - (@stoneRadius + 1)
for i in [@gridLeft..@gridRight] by (@gridRight - @gridLeft) / (@grid - 1)
ctx.beginPath()
ctx.moveTo i, @gridTop
ctx.lineTo i, @gridBottom
ctx.closePath()
ctx.stroke()
# horizontal lines
for i in [@gridTop..@gridBottom] by (@gridBottom - @gridTop) / (@grid - 1)
ctx.beginPath()
ctx.moveTo @gridLeft, i
ctx.lineTo @gridRight, i
ctx.closePath()
ctx.stroke()
for x, ys of @board
for y, stone of ys
if stone.color is 'black'
@drawCircle x, y, @stoneRadius, '#000000'
else if stone.color is 'white'
@drawCircle x, y, @stoneRadius, '#ffffff'
else if (x is '3' || x is '7') && (y is '3' || y is '7')
@drawCircle x, y, @tenRadius, '#000000'
drawCircle: (x, y, radius, color) ->
ctx: @ctx
ctx.beginPath()
ctx.fillStyle: color
x: @gridLeft + ((x - 1) * (@gridRight - @gridLeft) / (@grid - 1))
y: @gridTop + ((y - 1) * (@gridBottom - @gridTop) / (@grid - 1))
ctx.arc x, y, radius, 0, 2 * Math.PI, true
ctx.closePath()
ctx.fill()
ctx.stroke()
onClick: (e) ->
offset: @jCanvas.offset()
# x/y coordinates relative to the grid
canvasX: e.pageX - offset.left
canvasY: e.pageY - offset.top
# look for the x/y vector on the board
x: Math.floor(canvasX / ((@gridRight - @gridLeft) / (@grid - 1))) + 1
y: Math.floor(canvasY / ((@gridBottom - @gridTop) / (@grid - 1))) + 1
if @placeStone(x, y, @color)
if @color is 'white'
@color: 'black'
else
@color: 'white'
placeStone: (x, y, color) ->
original: @at x, y
stone: new Stone(this, x, y, color)
# try killing adjacent stones without freedoms.
@board[x][y]: stone
stone.lithocide()
unless stone.alive()
@board[x][y]: original
return false
stone.commit()
@redraw()
true
at: (x, y) ->
if ys: @board[x]
ys[y]
activate: ->
goban: this
goban.jCanvas.click (e) ->
goban.onClick e
document.Goban: Goban
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment