-
-
Save kiko/449535 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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