diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..7c29f61
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.*
+*.sw[po]
diff --git a/jsssnake-generator-test.html b/jsssnake-generator-test.html
new file mode 100755
index 0000000..c8330ee
--- /dev/null
+++ b/jsssnake-generator-test.html
@@ -0,0 +1,538 @@
+
+
+
+
+
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+
+
diff --git a/jsssnake-test.html b/jsssnake-test.html
new file mode 100755
index 0000000..b14a48e
--- /dev/null
+++ b/jsssnake-test.html
@@ -0,0 +1,522 @@
+
+
+
+
+
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+
+
diff --git a/jsssnake-test.js b/jsssnake-test.js
new file mode 100755
index 0000000..ea07b45
--- /dev/null
+++ b/jsssnake-test.js
@@ -0,0 +1,144 @@
+function test(){
+ var field = Field("field")
+
+ // TODO UI...
+
+ // setup a wall...
+ for(var i=8; i < 20; i++){
+ field.Wall(field.cell(12, i))
+ }
+
+ // apple and apple creation on callback...
+ field.Apple(field.cell(10, 4))
+ field.on_apple_eaten = function(){
+ field.Apple(field.cell(13, 6))
+ // remove the event handler...
+ field.on_apple_eaten = null
+ }
+
+ // snake 0 script...
+ // test general control and being killed...
+ var s0 = field.Snake('black', field.cell(2, 2), 's', 3)
+ setTimeout(function(){s0.left()}, 3000)
+ setTimeout(function(){s0.right()}, 6000)
+
+ // snake 1 script...
+ // test general controls and killing...
+ var s1 = field.Snake('blue', field.cell(6, 2), 's', 5)
+ setTimeout(function(){s1.right()}, 6000)
+
+ // snake 2 script...
+ // test apple eating...
+ var s2 = field.Snake('silver', field.cell(7, 4), 'e', 2)
+ setTimeout(function(){s2.right()}, 5000)
+ setTimeout(function(){s2.right()}, 6000)
+
+ // snake 3 script...
+ // test n/s wall traversal...
+ var s3 = field.Snake('green', field.cell(15, 2), 'n', 4)
+ setTimeout(function(){s3.right()}, 2000)
+ setTimeout(function(){s3.right()}, 3000)
+
+ // snake 4 script...
+ // test l/r wall traversal...
+ var s4 = field.Snake('gold', field.cell(2, 15), 'w', 4)
+ setTimeout(function(){s4.right()}, 2500)
+ setTimeout(function(){s4.right()}, 3500)
+ setTimeout(function(){s4.right()}, 5000)
+
+ // general game commands...
+ field.start(500)
+ setTimeout(function(){field.stop()}, 28000)
+ setTimeout(function(){field.reset()}, 30000)
+
+
+ // test the Game object...
+ setTimeout(function(){
+ var game = JSSnakeGame(field)
+
+ game.Wall()
+ game.Wall()
+ game.Wall()
+ game.Wall()
+
+ game.Apple()
+ game.Apple()
+ game.Apple()
+ game.Apple()
+ }, 8000)
+
+ // test for special cases...
+ setTimeout(function(){
+ var game = JSSnakeGame(field)
+
+ game.field.reset()
+ for(var i=0; i < game.field.cells.length; i++)
+ game.Wall(game.field.Cell(i), 3, 'n')
+ game.field.reset()
+ for(var i=0; i < game.field.cells.length; i++)
+ game.Wall(game.field.Cell(i), 3, 's')
+ game.field.reset()
+ for(var i=0; i < game.field.cells.length; i++)
+ game.Wall(game.field.Cell(i), 3, 'e')
+ game.field.reset()
+ for(var i=0; i < game.field.cells.length; i++)
+ game.Wall(game.field.Cell(i), 3, 'w')
+ }, 21000)
+
+ setTimeout(function(){
+ var game = JSSnakeGame(field)
+
+ game.field.reset()
+ for(var i=0; i < game.field.cells.length; i++)
+ game.Apple(game.field.Cell(i))
+ }, 22000)
+
+ setTimeout(function(){
+ setTimeout(function(){
+ var game = JSSnakeGame(Field("field"))
+ game.field.reset()
+ game.levels.sand()
+ }, 100)
+
+ setTimeout(function(){
+ var game = JSSnakeGame(Field("field"))
+ game.field.reset()
+ game.levels.walls()
+ }, 2000)
+
+ setTimeout(function(){
+ var game = JSSnakeGame(Field("field"))
+ game.field.reset()
+ game.levels.dashed(20, 2)
+ }, 4000)
+
+ setTimeout(function(){
+ var game = JSSnakeGame(Field("field"))
+ game.field.reset()
+ game.levels.dashed(15)
+ }, 6000)
+
+ setTimeout(function(){
+ var game = JSSnakeGame(Field("field"))
+ game.field.reset()
+ game.levels.dashed(5, 18)
+ }, 8000)
+
+
+ setTimeout(function(){
+ var game = JSSnakeGame(Field("field"))
+ game.field.reset()
+ setInterval(function(){
+ game.field.reset()
+ game.levels.walls()
+
+ var APPLES = 4
+
+ for(var i=0; i < APPLES; i++)
+ game.Apple()
+ }, 1000)
+ }, 10000)
+ }, 24000)
+}
+
+// vim:set ts=4 sw=4 spell :
diff --git a/jsssnake.html b/jsssnake.html
new file mode 100755
index 0000000..209e08c
--- /dev/null
+++ b/jsssnake.html
@@ -0,0 +1,536 @@
+
+
+
+
+
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+ | |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+ |
+ |
+ |
+ |
+ |
+
+
+
+
diff --git a/jsssnake.js b/jsssnake.js
new file mode 100755
index 0000000..81e743e
--- /dev/null
+++ b/jsssnake.js
@@ -0,0 +1,423 @@
+var DEBUG = true
+
+function log(text){
+ document.getElementById('log').innerHTML += text + '
'
+}
+
+var Field = function(field_id){
+ return ({
+ // XXX HACK: get this from CSS...
+ APPLE_COLOR: 'red',
+ WALL_COLOR: 'gray',
+ FIELD_COLOR: 'white',
+ TICK: 200,
+
+ // interface...
+ init: function(field_id, on_kill, on_apple_eaten){
+ this.field = document.getElementById(field_id)
+
+ // this depends on topology...
+ // NOTE: we consider that the field may not change during operation...
+ this.cells = this.field.getElementsByTagName("td")
+ this.height = this.field.getElementsByTagName("tr").length
+
+ this.cell_count = this.cells.length
+ this.width = this.cell_count / this.height
+
+ this.on_kill = on_kill
+ this.on_apple_eaten = on_apple_eaten
+
+ this._timer = null
+
+ this.reset()
+
+ // rotation tables...
+ this._cw = {
+ 'n': 'e',
+ 's': 'w',
+ 'e': 's',
+ 'w': 'n'
+ }
+ this._ccw = {
+ 'n': 'w',
+ 's': 'e',
+ 'e': 'n',
+ 'w': 's'
+ }
+
+ return this
+ },
+ // setup/reset the field to it's original state.
+ reset: function(){
+ this._snakes = {}
+ this._tick = 0
+ this.stop()
+ for(var i=0; i < this.cells.length; i++){
+ var cell = this.Cell(i)
+ cell.o.style.backgroundColor = this.FIELD_COLOR
+ }
+ },
+
+ // do a single step...
+ step: function(){
+ var cells = this.cells
+
+ for(var i=0; i < cells.length; i++){
+ var cell = this.Cell(i)
+ // identify the object...
+ if(this.is_snake(cell)){
+ this.Snake(cell.o.style.backgroundColor, cell).step()
+ }
+ }
+ this._tick += 1
+ },
+ start: function(tick){
+ var that = this
+ if(tick === null){
+ tick = this.TICK
+ }
+ if(this._timer === null){
+ this._timer = setInterval(function(){that.step()}, tick)
+ }
+ },
+ stop: function(){
+ if(this._timer === null){
+ return
+ }
+ clearInterval(this._timer)
+ this._timer = null
+ },
+
+ // get a cell helper...
+ Cell: function(n){
+ var that = this
+ var cells = this.cells
+ return ({
+ // NOTE: this will be null if a cell does not exist.
+ o: cells[n],
+ index: n,
+ // NOTE: these are cyclic...
+ n: function(){
+ var t = n - that.width
+ if(t < 0)
+ t = that.cells.length + t
+ return that.Cell(t)
+ },
+ s: function(){
+ var t = n + that.width
+ if(t > that.cells.length-1)
+ t = t - that.cells.length
+ return that.Cell(t)
+ },
+ e: function(){
+ var t = n + 1
+ if(Math.floor(t/that.width) > Math.floor(n/that.width))
+ t = t - that.width
+ return that.Cell(t)
+ },
+ w: function(){
+ var t = n - 1
+ if(Math.floor(t/that.width) < Math.floor(n/that.width))
+ t = t + that.width
+ return that.Cell(t)
+ }
+ })
+ },
+ // get a cell by it's coordinates...
+ cell: function(x, y){
+ return this.Cell(x + (y-1) * this.width)
+ },
+
+ // add a snake to the field...
+ // XXX BUG: size of 1 makes the snake endless...
+ Snake: function(color, cell, direction, size){
+ var that = this
+
+ // draw the snake if it does not exist...
+ if(this._snakes[color] == null){
+ cell.o.style.backgroundColor = color
+ cell.o.age = size
+ this._snakes[color] = {
+ 'direction':direction,
+ 'size': size
+ }
+ }
+
+ // NOTE: the only things this uses from the above scope is color and that.
+ // NOTE: color is the onlu thing that can't change in a snake.
+ return ({
+ // XXX BUG: the last cell of a dead snake lives an extra tick...
+ kill: function(){
+ // this will disable moving and advancing the snake...
+ that._snakes[color].size = 0
+ if(that.on_kill != null){
+ that.on_kill(that)
+ }
+ },
+ step: function(){
+ var direction = that._snakes[color].direction
+ var size = that._snakes[color].size
+ var target = cell[direction]()
+
+ // skip a cell if it's already handled at this step.
+ if(cell.o.moved_at == that._tick){
+ return
+ }
+
+ // do this only for the head...
+ if(parseInt(cell.o.age) == size){
+ // handle field bounds...
+ if(target.o == null){
+ alert('out of bounds!')
+ return
+ }
+ // kill conditions: walls and other snakes...
+ if(that.is_snake(target) || that.is_wall(target)){
+ // XXX move this to a separate action
+ this.kill()
+ return
+ }
+ // apple...
+ if(that.is_apple(target)){
+ // grow the snake by one...
+ // XXX move this to a separate action
+ that._snakes[color].size += 1
+ size = that._snakes[color].size
+ if(that.on_apple_eaten != null){
+ that.on_apple_eaten(that)
+ }
+ }
+ // all clear, do the move...
+ target.o.style.backgroundColor = color
+ target.o.age = size
+ target.o.moved_at = that._tick
+ cell.o.age = size - 1
+
+ } else {
+ if(cell.o.age <= 1) {
+ cell.o.style.backgroundColor = that.FIELD_COLOR
+ }
+ cell.o.age = parseInt(cell.o.age) - 1
+ }
+ },
+
+ // user interface...
+ left: function(){
+ that._snakes[color].direction = that._ccw[that._snakes[color].direction]
+ },
+ right: function(){
+ that._snakes[color].direction = that._cw[that._snakes[color].direction]
+ }
+ })
+ },
+ is_snake: function(cell){
+ var snakes = this._snakes
+ var color = cell.o.style.backgroundColor
+
+ for(var c in snakes){
+ if(c == color)
+ return true
+ }
+ return false
+ },
+
+ Apple: function(cell){
+ cell.o.style.backgroundColor = this.APPLE_COLOR
+ return cell
+ },
+ is_apple: function(cell){
+ return cell.o.style.backgroundColor == this.APPLE_COLOR
+ },
+
+ Wall: function(cell){
+ cell.o.style.backgroundColor = this.WALL_COLOR
+ return cell
+ },
+ is_wall: function(cell){
+ return cell.o.style.backgroundColor == this.WALL_COLOR
+ },
+
+ is_empty: function(cell){
+ return cell.o.style.backgroundColor == this.FIELD_COLOR
+ }
+ }).init(field_id)
+}
+
+// this defines the basic game logic and controls the rules and levels...
+/*
+ NOTE: it is recommended to create game objects in the folowing order:
+ 1) walls
+ 2) apples
+ 3) players
+ */
+function JSSnakeGame(field){
+
+ var game = {
+ field: field,
+ TICK: 300,
+
+ // this enables snakes of the same colors...
+ SIMILAR_COLORS: false,
+ used_colors: function(){
+ // this is a workaround the inability to directly create an object
+ // with field names not a literal identifier or string...
+ var res = {}
+ res[field.FIELD_COLOR] = true
+ res[field.WALL_COLOR] = true
+ res[field.APPLE_COLOR] = true
+ return res
+ }(),
+
+ // utility methods...
+ _random_empty_cell: function(){
+ // NOTE: if we are really unlucky, this will take
+ // really long, worse, if we are infinitely unlucky
+ // this will take an infinite amount of time... (almost)
+ var field = this.field
+ var i = field.cells.length-1
+ var l = i
+ while(true){
+ var c = field.Cell(Math.round(Math.random()*l))
+ if(field.is_empty(c))
+ return c
+ i--
+ if(i == 0)
+ return null
+ }
+ },
+ // key handler...
+ // functions:
+ // - control snake - dispatch player-specific keys to player-specific snake
+ // - create player - two unused keys pressed within timeout, random color
+ //
+ // modes:
+ // - player add
+ // - game control
+ //
+ // NOTE: modes can intersect...
+ // NOTE: modes are game state dependant...
+ key_time_frame: 0.5,
+ pending_key: null,
+
+ _keyHandler: function(evt){
+ var name, color
+ var key = window.event ? event.keyCode : evt.keyCode
+
+ // find a target registered for key...
+ // XXX
+
+ // no one is registered...
+ // if wait time set and is not exceeded create a player and register keys
+ if(!this.pending_key || Date().getTime() - this.pending_key['time'] > this.key_time_frame ){
+ // if no wait time is set, set it and remember the key...
+ this.pending_key = {time: Date().getTime(), key: key}
+ } else {
+ // get name...
+ // XXX
+ // get color...
+ // XXX
+ this.Player(name, this.pending_key['key'], key, color)
+ this.pending_key = null
+ }
+ return true
+ },
+ // create a new player...
+ // NOTE: direction and position are optional...
+ // XXX BUG: players should not get created facing a wall directly...
+ Player: function(name, ccw_button, cw_button, color, cell, direction, size){
+ if(!this.SIMILAR_COLORS && this.used_colors[color] == true){
+ // error: that the color is already used...
+ return
+ }
+ // register controls...
+ // XXX
+
+ if(direction == null){
+ direction = ['n', 's', 'e', 'w'][Math.round(Math.random()*3)]
+ }
+ if(cell === null){
+ cell = this._random_empty_cell()
+ if(cell === null)
+ return
+ }
+ // create a snake...
+ this.used_colors[color] = true
+ return this.field.Snake(color, cell, direction, size)
+ },
+ // NOTE: position is optional...
+ Apple: function(cell){
+ // place an apple at a random and not occupied position...
+ var c = cell? cell: this._random_empty_cell()
+ if(c === null)
+ return
+ return this.field.Apple(c)
+
+ },
+ // NOTE: all arguments are optional...
+ Wall: function(cell, len, direction){
+ // generate random data for arguments that are not given...
+ if(cell == null){
+ cell = this._random_empty_cell()
+ if(cell === null)
+ return
+ }
+ if(direction == null){
+ direction = ['n', 's', 'e', 'w'][Math.round(Math.random()*3)]
+ }
+ if(len == null){
+ if(direction == 'n' || direction == 's')
+ var max = this.field.height
+ else
+ var max = this.field.width
+ len = Math.round(Math.random()*(max-1))
+ }
+ // place a wall...
+ for(var i=0; i < len; i++){
+ field.Wall(cell)
+ cell = cell[direction]()
+ }
+ },
+
+ // level generators and helpers...
+ levels: {
+ dotted: function(n){
+ for(var i=0; i < n; i++)
+ game.Wall(null, 1, null)
+ },
+ dashed: function(n, length){
+ if(length == null)
+ length = 3
+ for(var i=0; i < n; i++)
+ game.Wall(null, length, null)
+ },
+ // specific level styles...
+ sand: function(){
+ this.dotted(Math.round(game.field.cells.length/20))
+ },
+ walls: function(){
+ this.dashed(
+ Math.round(game.field.cells.length/90),
+ Math.min(
+ game.field.width,
+ game.field.height)-2)
+ }
+ },
+
+ start: function(){
+ // start the game...
+ field.start(this.TICK)
+ },
+ stop: function(){
+ field.stop()
+ }
+ }
+
+ field.on_apple_eaten = function(){game.Apple()}
+ field.on_kill = function(snake){game.used_colors[snake.color] = false}
+
+ //document.onkeyup = function(evt){return game._keyHandler(evt)}
+
+ return game
+}
+
+// vim:set ts=4 sw=4 spell :