Course-JavaScript/jsssnake/simplesnake.html
Alex A. Naanou 2ab556118f some refactoring...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2017-04-06 02:32:35 +03:00

381 lines
10 KiB
HTML

<!DOCTYPE html>
<html>
<style>
.snake.field {
width: 500px;
height: 500px;
border: solid 1px silver;
}
</style>
<script>
// XXX automate clearing of handlers...
function makeEvent(handler_attr){
return function(func){
if(func instanceof Function){
var handlers = this[handler_attr] = this[handler_attr] || []
handlers.push(func)
} else {
var that = this
var args = [].slice.call(arguments)
this[handler_attr]
&& this[handler_attr]
.forEach(function(handler){ handler.apply(that, args) })
}
return this
}
}
// XXX normalize x/y everywhere we input coordinates...
var Snake = {
config: {
apple_color: 'red',
wall_color: 'gray',
interval: 200,
},
_field: null,
_cells: null,
players: null,
field_size: null,
// utils...
random_point: function(){
var cells = this._cells
var l = cells.length
var w = this.field_size.width
do {
var i = Math.floor(Math.random() * l)
} while(cells[i].style.backgroundColor != '')
return {
x: i%w,
y: Math.floor(i/w),
}
},
// XXX BUG: going down into bottom left corner breaks the snake...
// ...check other corner cases!
// ...looks like the only corner case...
_tick: function(){
var that = this
var l = this._cells.length
var w = this.field_size.width
var h = this.field_size.height
var tick = this.__tick = (this.__tick + 1 || 0)
var directions = 'neswn'
this._cells.forEach(function(cell, i){
var color = cell.style.backgroundColor
// skip cells we touched...
if(cell.tick == tick){
return
}
// snake...
if(cell.age != null){
// handle cell age...
if(cell.age == 0){
delete cell.age
cell.style.backgroundColor = ''
} else {
cell.age -= 1
}
// head...
var direction = cell.direction
var next =
direction == 'n' ?
(i < w ? l - w + i : i - w)
: direction == 's' ?
(i > (l-w) ? i - (l-w) : i + w)
: direction == 'e' ?
((i+1)%w == 0 ? i - (w-1) : i + 1)
: direction == 'w' ?
(i%w == 0 ? i + (w-1) : i - 1)
: null
if(next != null){
next = that._cells[next]
// turn...
var turn = that.players[color] || ''
if(turn != ''){
var j = turn == 'left' ? directions.indexOf(direction) - 1
: directions.indexOf(direction) + 1
j = j < 0 ? 3 : j
direction = directions[j]
that.players[color] = ''
}
var age = cell.age
var move = false
// special case: other snake's head -- kill both...
if(next.direction){
var other = next.style.backgroundColor
next.style.backgroundColor = ''
// NOTE: we are not deleteing .direction here as
// we can have upto 4 snakes colliding...
next.direction = ''
delete next.age
that.snakeKilled(other)
that.snakeKilled(color)
// apples...
} else if(next.style.backgroundColor == that.config.apple_color){
age += 1
move = true
that.appleEaten()
// empty...
// NOTE: anything but an apple will kill the snake...
} else if(next.style.backgroundColor == ''){
move = true
// kill...
} else {
that.snakeKilled(color)
}
// do the move...
if(move){
next.tick = tick
next.style.backgroundColor = color
next.age = age + 1
next.direction = direction
}
delete cell.direction
}
}
cell.tick = tick
})
},
setup: function(field){
field = field || this._field
field = this._field = typeof(field) == typeof('str') ? document.querySelector(field)
: field
this._cells = [].slice.call(field.querySelectorAll('td'))
this.field_size = {
width: field.querySelector('tr').querySelectorAll('td').length,
height: field.querySelectorAll('tr').length,
}
this.players = {}
return this
},
clear: function(){
this.setup()
this._cells.forEach(function(c){ c.style.backgroundColor = '' })
this.players = {}
delete this.__appleEatenHandlers
delete this.__killHandlers
return this
},
// constructors...
// XXX normalize input x/y...
snake: function(color, point, direction, age){
point = point || this.random_point()
var head = this._cells[point.x + point.y * this.field_size.width]
head.style.backgroundColor = color
head.direction = direction
head.age = (age || 5) - 1
this.players[color] = ''
return this
},
apple: function(point){
point = point || this.random_point()
this._cells[point.x + point.y * this.field_size.width]
.style.backgroundColor = this.config.apple_color
return this
},
wall: function(point, direction, length){
point = point || this.random_point()
var x = point.x
var y = point.y
length = length || 1
while(length > 0){
this._cells[x + y * this.field_size.width]
.style.backgroundColor = this.config.wall_color
x += direction == 'e' ? 1
: direction == 'w' ? -1
: 0
x = x < 0 ? this.field_size.width + x
: x % this.field_size.width
y += direction == 'n' ? -1
: direction == 's' ? 1
: 0
y = y < 0 ? this.field_size.height + y
: y % this.field_size.height
length -= 1
}
return this
},
// events...
appleEaten: makeEvent('__appleEatenHandlers'),
snakeKilled: makeEvent('__killHandlers'),
// actions...
start: function(t){
this.__timer = this.__timer
|| setInterval(this._tick.bind(this), t || this.config.interval || 200)
return this
},
stop: function(){
clearInterval(this.__timer)
delete this.__timer
delete this.__tick
return this
},
left: function(color){
this.players[color || Object.keys(this.players)[0]] = 'left'
return this
},
right: function(color){
this.players[color || Object.keys(this.players)[0]] = 'right'
return this
},
// levels...
basicLevel: function(){
return this
.wall({x:3, y:10}, 's', 11)
.wall({x:3, y:5}, 'e', 5)
.wall({x:3+5, y:5}, 's', 5)
.wall({x:3+5, y:5+4}, 'e', 12) },
randomLevel: function(){
return this
.wall(null, 's', 11)
.wall(null, 's', 11)
.wall(null, 'e', 11)
.wall(null, 'e', 11) },
}
var HANDLER_SET = false
var KEY_CONFIG = {
ArrowLeft: 'left',
ArrowRight: 'right',
}
function kbHandler(event){
var action = KEY_CONFIG[event.key]
action
&& action in Snake
&& Snake[action]()
}
function setup(){
Snake
.setup('.snake')
// make setup(..) re-enterable..
.stop()
.clear()
//.randomLevel()
.basicLevel()
.appleEaten(function(){ this.apple() })
.apple()
.apple()
// XXX do something better with direction...
.snakeKilled(function(color){ this.snake(color, null, 's', 3) })
.snake('blue', null, 's', 3)
.start(300)
// setup kb handler (only once)...
if(!HANDLER_SET){
document.addEventListener('keydown', kbHandler)
HANDLER_SET = true
}
}
function test(game){
return Snake
.setup(game || '.snake')
// XXX BUG: this will break as soon as it reaches the corner...
.snake('blue', {x:0, y:0}, 's', 5)
// hit an apple...
.snake('green', {x:5, y:3}, 's', 5)
.apple({x:5, y:5})
// hit a wall...
.snake('silver', {x:14, y:3}, 'w', 5)
.wall({x:2, y:14}, 's', 7)
.start()
}
</script>
<head> <title>Simple Snake</title> </head>
<body onload="setup()">
<table class="snake field" cellspacing="0">
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
<tr> <td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td>
<td></td><td></td><td></td><td></td> <td></td><td></td><td></td><td></td> </tr>
</table>
<p>
XXX basic instructions...
</p>
</body>
</html>
<!-- vim:set ts=4 sw=4 spell : -->