class Map extends EventEmitter
constructor: (size) ->
throw new Error "Size must be between 1 and 255" unless 1 < size < 255
@size = size
@setMaxListeners 2
@grid = new Array Math.pow(@size, 3)
for x in [0...Math.pow(@size, 3)]
@grid
= null
@rotation = 0
map = @
@blockDidChangeListener = ->
map.emit 'didChange'
@emit 'didChange'
forceUpdate: ->
@emit 'didChange'
setBlock: (block, x, y, z, silent = no) ->
@validateCoordinates x, y, z
block.setCoordinates x, y, z if block
[x, y] = @applyRotation x, y if @rotation
position = x + y * @size + z * @size * @size
@grid[position]?.removeListener 'didChange', @blockDidChangeListener
@grid[position] = block
@grid[position]?.addListener 'didChange', @blockDidChangeListener
@emit 'didChange' unless silent
getBlock: (x, y, z) ->
@validateCoordinates x, y, z
[x, y] = @applyRotation x, y if @rotation
return @grid[x + y * @size + z * @size * @size]
removeBlock: (x, y, z, silent = no) ->
block = @getBlock x, y, z
@setBlock null, x, y, z, yes
block?.removeListener 'didChange', @blockDidChangeListener
block?.setCoordinates null, null, null
@emit 'didChange' unless silent
return block
heightAt: (x, y) ->
@validateCoordinates x, y, 0
height = 0
while height < @size and @getBlock x, y, height
height++
return height
getStack: (x, y, z = 0) ->
@validateCoordinates x, y, z
return [] if z > height = @heightAt x, y
blocks = new Array
for currentZ in [z...height]
blocks.push @getBlock x, y, currentZ
return blocks
setStack: (blocks, x, y, z = 0, silent = no) ->
@validateCoordinates x, y, z
unless blocks.length - 1 + z < @size
throw new Error "Cannot place stack, height out of bounds"
for block in blocks
@setBlock block, x, y, z++, yes
@emit 'didChange' unless silent
removeStack: (x, y, z = 0, silent = no) ->
stack = @getStack x, y, z
for currentZ in [z...z + stack.length]
@setBlock null, x, y, z++, yes
@emit 'didChange' unless silent
return stack
validate: ->
@blocksEach (block) =>
[x, y, z] = block.getCoordinates()
if block and z > 0 and not @getBlock x, y, z - 1
throw new Error "Encountered floating block at #{x}:#{y}:#{z}"
validateCoordinates: (x, y, z) ->
throw new Error "Index out of bounds #{x}:#{y}:#{z}" unless 0 <= x < @size and
0 <= y < @size and
0 <= z < @size
applyRotation: (x, y) ->
switch @rotation
when 90 then return [@size - 1 - y, x]
when 180 then return [@size - 1 - x, @size - 1 - y]
when 270 then return [ y, @size - 1 - x]
else
return [x, y]
blocksEach: (functionToApply) ->
x = @size - 1
while x + 1
y = 0
while y < @size
z = 0
while z < @size
functionToApply block if block = @getBlock(x, y, z)
z++
y++
x--
coordinatesEach: (functionToApply) ->
x = @size - 1
while x + 1
y = 0
while y < @size
z = 0
while z < @size
functionToApply(x, y, z)
z++
y++
x--
rotateCW: -> @rotate true
rotateCCW: -> @rotate false
rotate: (clockwise, silent = no) ->
if clockwise
@rotation = (@rotation + 90) % 360
else
@rotation = (@rotation + 270) % 360
for block in @grid
if block
block.rotate clockwise, yes, yes, yes, yes
[x, y, z] = block.getCoordinates()
if clockwise
[x, y] = [ y, @size - 1 - x]
else
[x, y] = [@size - 1 - y, x]
block.setCoordinates x, y, z
@emit 'didRotate', clockwise unless silen