From 1cf7e1d69a2083a9ae1848207c347e567f94349f Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Wed, 9 Mar 2011 16:14:08 +0300 Subject: [PATCH] initial commit... --- .gitignore | 2 + jsssnake-test.js | 144 +++++++++++++ jsssnake.html | 549 +++++++++++++++++++++++++++++++++++++++++++++++ jsssnake.js | 366 +++++++++++++++++++++++++++++++ 4 files changed, 1061 insertions(+) create mode 100644 .gitignore create mode 100755 jsssnake-test.js create mode 100755 jsssnake.html create mode 100755 jsssnake.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc6db1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.* +*.sp[pow] 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..d1cdf14 --- /dev/null +++ b/jsssnake.html @@ -0,0 +1,549 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
+
+ diff --git a/jsssnake.js b/jsssnake.js new file mode 100755 index 0000000..14a89d2 --- /dev/null +++ b/jsssnake.js @@ -0,0 +1,366 @@ +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, + // 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 + } + }, + // 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){ + // XXX register controls... + + 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... + 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 = 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 = null + + return game +} + +// vim:set ts=4 sw=4 spell :