mirror of
https://github.com/flynx/Slang.git
synced 2025-10-28 10:10:07 +00:00
moving Slang to its own repo...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
91beb4d0d4
commit
50de3c5d77
@ -1,2 +1,4 @@
|
||||
Course-JavaScript
|
||||
=================
|
||||
# Slang
|
||||
|
||||
For and interactive version of Slang interpreter go here:
|
||||
http://flynx.github.io/Course-JavaScript/Slang/slang.html
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
# Slang
|
||||
|
||||
For and interactive version of Slang interpreter go here:
|
||||
http://flynx.github.io/Course-JavaScript/Slang/slang.html
|
||||
@ -1,7 +0,0 @@
|
||||
|
||||
{
|
||||
"short_name": "Slang",
|
||||
"name": "Slang",
|
||||
"display": "standalone",
|
||||
"Theme_color": "white"
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
CACHE MANIFEST
|
||||
# Timestamp: 20170929020625
|
||||
|
||||
CACHE:
|
||||
slang.html
|
||||
slang.js
|
||||
manifest.json
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
||||
279
Slang/slang.html
279
Slang/slang.html
@ -1,279 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html manifest="slang.appcache">
|
||||
<head>
|
||||
<title>Slang</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
</head>
|
||||
<style>
|
||||
|
||||
#bootstrap {
|
||||
display: none;
|
||||
}
|
||||
#bootstrap[shown] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.command {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.command:hover {
|
||||
background: #eee;
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
.command:empty:after {
|
||||
content: "input code here...";
|
||||
opacity: 0.5;
|
||||
}
|
||||
.command:focus:before {
|
||||
position: absolute;
|
||||
content: "ctrl+enter to run";
|
||||
opacity: 0.3;
|
||||
right: 15px;
|
||||
}
|
||||
.command:focus:after {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.command:last-child {
|
||||
border: red 2px solid;
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px 5px 0px silver;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
||||
.output {
|
||||
font-weight: bold;
|
||||
}
|
||||
.error:before,
|
||||
.output:before {
|
||||
content: ">";
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
margin: 5px;
|
||||
}
|
||||
.output:before {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
#words {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.word[word-type=constant] {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
.word[word-type=builtin] {
|
||||
font-style: italic;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
#stack {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
/* syntax... */
|
||||
|
||||
pre .comment {
|
||||
font-style: italic;
|
||||
color: gray;
|
||||
}
|
||||
pre :not(.comment) {
|
||||
font-weight: bold;
|
||||
}
|
||||
pre :not(.comment) .string {
|
||||
font-style: italic;
|
||||
color: blue;
|
||||
}
|
||||
pre :not(.comment) .word {
|
||||
font-weight: bold;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script src="slang.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
function stringifySlangCode(code){
|
||||
if(code == null){
|
||||
return code
|
||||
}
|
||||
if(typeof(code) == typeof('str') && /\s/.test(code)){
|
||||
return '\''+code+'\''
|
||||
} else if(code && code.constructor.name == 'Array'){
|
||||
return '[ '+code.map(stringifySlangCode).join(' ')+' ]'
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
function runCommand(){
|
||||
var stack = document.getElementById('stack')
|
||||
var console = document.getElementById('console')
|
||||
var commands = document.getElementsByClassName('command')
|
||||
var command = commands[commands.length-1]
|
||||
command.removeAttribute('contenteditable')
|
||||
|
||||
command.addEventListener('click', function(e){
|
||||
var commands = document.getElementsByClassName('command')
|
||||
var c = commands[commands.length-1]
|
||||
c.innerText = command.innerText
|
||||
c.focus()
|
||||
window.scrollTo(0,document.body.scrollHeight)
|
||||
}, false)
|
||||
|
||||
try{
|
||||
stack.innerText = 'stack: ' + stringifySlangCode(slang(command.innerText))
|
||||
|
||||
} catch(e) {
|
||||
stack.innerText = 'stack: ' + stringifySlangCode(CONTEXT.stack)
|
||||
var err = document.createElement('div')
|
||||
err.classList.add('error')
|
||||
if(e.message != null){
|
||||
err.innerText = e.message
|
||||
} else {
|
||||
err.innerText = e
|
||||
}
|
||||
console.appendChild(err)
|
||||
}
|
||||
|
||||
// create the next element...
|
||||
var next = document.createElement('div')
|
||||
next.classList.add('command')
|
||||
next.setAttribute('contenteditable', true)
|
||||
console.appendChild(next)
|
||||
next.focus()
|
||||
|
||||
window.scrollTo(0,document.body.scrollHeight)
|
||||
}
|
||||
|
||||
NAMESPACE.print = function(context){
|
||||
var c = document.getElementById('console')
|
||||
var o = context.stack[context.stack.length-1]
|
||||
console.log('>>>', o)
|
||||
|
||||
var data = document.createElement('div')
|
||||
data.classList.add('output')
|
||||
data.innerHTML = stringifySlangCode(o)
|
||||
c.appendChild(data)
|
||||
}
|
||||
|
||||
// XXX should this break exec?
|
||||
NAMESPACE.err = function(context){
|
||||
var c = document.getElementById('console')
|
||||
var e = context.stack[context.stack.length-1]
|
||||
console.error('>>>', e)
|
||||
|
||||
var err = document.createElement('div')
|
||||
err.classList.add('error')
|
||||
if(e.message != null){
|
||||
err.innerText = e.message
|
||||
} else {
|
||||
err.innerText = e
|
||||
}
|
||||
c.appendChild(err)
|
||||
}
|
||||
|
||||
|
||||
function toggleBootstrapCode(){
|
||||
var bootstrap = document.getElementById('bootstrap')
|
||||
if(bootstrap.hasAttribute('shown')){
|
||||
bootstrap.removeAttribute('shown')
|
||||
} else {
|
||||
bootstrap.setAttribute('shown', true)
|
||||
}
|
||||
}
|
||||
|
||||
function showAvailableWords(){
|
||||
document.getElementById('words').innerHTML = Object.keys(NAMESPACE)
|
||||
.sort()
|
||||
.filter(function(e){
|
||||
// skip words starting with '_'...
|
||||
if(e[0] == '_'){
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map(function(e){
|
||||
var code = NAMESPACE[e]
|
||||
var type
|
||||
if(code && code.constructor.name == 'Array'){
|
||||
type = 'word'
|
||||
code = stringifySlangCode(code)
|
||||
} else if(typeof(code) == typeof(function(){})){
|
||||
type = 'builtin'
|
||||
} else {
|
||||
type = 'constant'
|
||||
}
|
||||
return '<span class="word" word-type="'+type+'" title="'+code+'">'+e+'</span>'
|
||||
})
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<h1>Slang</h1>
|
||||
<a href="#" onclick="toggleBootstrapCode()">Toggle bootstrap code view...</a>
|
||||
<div id="bootstrap"></div>
|
||||
<h2>Available words</h2>
|
||||
<p>
|
||||
This section includes constants (red) and constant-like
|
||||
words (words that allways yield the same value),
|
||||
built-in words (blue), and 2'nd gen words (black):
|
||||
</p>
|
||||
<p id="words"></p>
|
||||
<h2>Slang Console</h2>
|
||||
<div id="console">
|
||||
<div class="command" contenteditable="true"></div>
|
||||
</div>
|
||||
<div id="stack"></div>
|
||||
</body>
|
||||
<script>
|
||||
var bootstrap = BOOTSTRAP
|
||||
// basic HTML compatibility stuff...
|
||||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<')
|
||||
// very basic syntax highlighting...
|
||||
// comments...
|
||||
.replace(/(\(.*)-(.*\))/g, '<span class="comment">$1−$2</span>')
|
||||
.replace(/(\([^\)]*\))/g, '<span class="comment">$1</span>')
|
||||
.replace(/(\s*--.*\n)/g, '<span class="comment">$1</span>')
|
||||
/*
|
||||
Object.keys(NAMESPACE).forEach(function(k){
|
||||
bootstrap = bootstrap.replace(
|
||||
RegExp('('+k
|
||||
.replace('&', '&')
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace(/([\|\[\]\.\*\/\\\?\+\-])/g, '\\$1')+')', 'g'),
|
||||
'<span class="word">$1</span>')
|
||||
})
|
||||
*/
|
||||
document.getElementById('bootstrap').innerHTML='<pre>'+bootstrap+'</pre>'
|
||||
|
||||
document.getElementById('console')
|
||||
.addEventListener("keyup", function(e) {
|
||||
if(e.keyCode == 13 && e.ctrlKey){
|
||||
runCommand()
|
||||
showAvailableWords()
|
||||
}
|
||||
}, false);
|
||||
|
||||
showAvailableWords()
|
||||
|
||||
</script>
|
||||
</html>
|
||||
1134
Slang/slang.js
1134
Slang/slang.js
File diff suppressed because it is too large
Load Diff
@ -1,222 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Teacher's Timer</title>
|
||||
|
||||
<style>
|
||||
|
||||
.timer {
|
||||
position: relative;
|
||||
min-height: 30px;
|
||||
min-width: 100px;
|
||||
border: solid 1px silver;
|
||||
font-size: 32px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.timer .sum {
|
||||
display: inline-block;
|
||||
min-width: 100px;
|
||||
background: gray;
|
||||
margin: 5px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
text-align: right;
|
||||
color: white;
|
||||
|
||||
border-left: solid 10px black;
|
||||
border-right: solid 10px black;
|
||||
|
||||
opacity: 0.8;
|
||||
}
|
||||
.timer .sum > span {
|
||||
margin-left: 10px;
|
||||
color: silver;
|
||||
}
|
||||
.timer .sum > span:last-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.timer .sum > span > span {
|
||||
color: white;
|
||||
text-shadow: black 0.1em 0.1em 0.2em;
|
||||
}
|
||||
|
||||
.timer .run {
|
||||
display: inline-block;
|
||||
background: silver;
|
||||
margin: 5px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.timer .start,
|
||||
.timer .end,
|
||||
.timer .duration {
|
||||
margin: 10px;
|
||||
}
|
||||
.timer .duration {
|
||||
font-weight: bold;
|
||||
text-shadow: black 0.1em 0.1em 0.2em;
|
||||
}
|
||||
|
||||
.timer .run:not(:nth-child(2)) .start,
|
||||
.timer .run:not(:last-child) .end {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timer .run:nth-child(2) {
|
||||
border-left: solid 10px gray;
|
||||
}
|
||||
.timer .run:last-child {
|
||||
border-right: solid 10px gray;
|
||||
}
|
||||
|
||||
#new_timer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 10px;
|
||||
border: solid 1px gray;
|
||||
border-radius: 50%;
|
||||
color: gray;
|
||||
font-size: 39px;
|
||||
text-align: center;
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
//var DEBUG = true
|
||||
|
||||
var TIMER_TEMPLATE = '<div class="run">'+
|
||||
'<span class="start" title="Start time">00:00:00</span>'+
|
||||
'<span class="duration" title="Duration">0</span>'+
|
||||
'<span class="end" title="Current/End time">00:00:00</span>'+
|
||||
'</div>'
|
||||
|
||||
function getFormattedTime(t){
|
||||
if(t != null){
|
||||
t = new Date(t)
|
||||
} else {
|
||||
t = new Date()
|
||||
}
|
||||
|
||||
var h = t.getHours()
|
||||
var m = t.getMinutes()
|
||||
var s = t.getSeconds()
|
||||
|
||||
return (h < 10 ? '0' + h : h) +':'+
|
||||
(m < 10 ? '0' + m : m) +':'+
|
||||
(s < 10 ? '0' + s : s)
|
||||
}
|
||||
|
||||
function getDurationInMinutes(a, b){
|
||||
var s = b - a
|
||||
// return the duration in seconds...
|
||||
if(window.DEBUG){
|
||||
// for debug use seconds for faster updates...
|
||||
return Math.floor(s/1000)
|
||||
}
|
||||
return Math.floor(s/1000/60)
|
||||
}
|
||||
|
||||
function formatSum(m){
|
||||
return '<span title="Astronomical hours">h:<span>'+ (m/60).toFixed(2) +'</span></span>'+
|
||||
'<span title="Academic hours">a:<span>'+ (m/45).toFixed(2) +'</span></span>'+
|
||||
'<span title="Minutes">m:<span>'+ m +'</span></span>'
|
||||
}
|
||||
|
||||
function createTimer(parent){
|
||||
var timer = document.createElement('div')
|
||||
timer.className = 'timer'
|
||||
timer.innerHTML = '<div class="sum">0</div>'
|
||||
parent.appendChild(timer)
|
||||
|
||||
timer.setAttribute('onclick', 'toggleTimer(this)')
|
||||
}
|
||||
|
||||
function toggleTimer(elem){
|
||||
elem = elem
|
||||
var t = elem.getAttribute('timer')*1
|
||||
|
||||
// stop timer...
|
||||
if(t){
|
||||
clearInterval(t)
|
||||
elem.removeAttribute('timer')
|
||||
|
||||
var start = elem.getAttribute('start')*1
|
||||
var end = Date.now()
|
||||
var d = getDurationInMinutes(start, end)
|
||||
|
||||
var duration = elem.getElementsByClassName('duration')
|
||||
duration = duration[duration.length-1]
|
||||
duration.innerText = d
|
||||
|
||||
var run = elem.getElementsByClassName('run')
|
||||
run = run[run.length-1]
|
||||
run.setAttribute('duration', d)
|
||||
|
||||
// setup a new timer...
|
||||
} else {
|
||||
var now = Date.now()
|
||||
elem.innerHTML += TIMER_TEMPLATE
|
||||
elem.setAttribute('start', now)
|
||||
|
||||
var run = elem.getElementsByClassName('run')
|
||||
run = run[run.length-1]
|
||||
|
||||
var start = elem.getElementsByClassName('start')
|
||||
start = start[start.length-1]
|
||||
|
||||
var end = elem.getElementsByClassName('end')
|
||||
end = end[end.length-1]
|
||||
|
||||
var duration = elem.getElementsByClassName('duration')
|
||||
duration = duration[duration.length-1]
|
||||
duration.innerText = 0
|
||||
|
||||
run.setAttribute('duration', 0)
|
||||
|
||||
// initial data...
|
||||
start.innerText = getFormattedTime(now)
|
||||
end.innerText = getFormattedTime(now)
|
||||
|
||||
elem.setAttribute('timer', setInterval(function(){
|
||||
var start = elem.getAttribute('start')*1
|
||||
var now = Date.now()
|
||||
var d = getDurationInMinutes(start, now)
|
||||
|
||||
run.setAttribute('duration', d)
|
||||
|
||||
// update end and duration values...
|
||||
end.innerText = getFormattedTime(now)
|
||||
duration.innerText = d
|
||||
|
||||
// update total...
|
||||
var sum = 0
|
||||
var runs = elem.getElementsByClassName('run')
|
||||
for(var i=0; i < runs.length; i++){
|
||||
sum += (runs[i].getAttribute('duration')*1)
|
||||
}
|
||||
elem.getElementsByClassName('sum')[0].innerHTML = formatSum(sum)
|
||||
}, 1000))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function setup(){
|
||||
//createTimer(document.getElementById('timers'))
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body onload="setup()">
|
||||
|
||||
<div id="timers"></div>
|
||||
<div id="new_timer" title="Add new timer" onclick="createTimer(document.getElementById('timers'))">+</div>
|
||||
|
||||
|
||||
</body>
|
||||
<!-- vim:set ts=4 sw=4 : -->
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
706
js-oop.js
706
js-oop.js
@ -1,706 +0,0 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
* The basics of JavaScript OOP
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
//
|
||||
// The basic prototype inheritance
|
||||
// -------------------------------
|
||||
//
|
||||
// First we'll create a basic object a
|
||||
|
||||
var a = {
|
||||
x: 1,
|
||||
y: 2,
|
||||
}
|
||||
|
||||
// Then we will create a new object using 'a' as a "base"
|
||||
|
||||
var b = Object.create(a)
|
||||
b.z = 3
|
||||
|
||||
// The object 'b' now has both access to it's own attributes ('z') and
|
||||
// attributes of 'a' ('x' and 'y')
|
||||
|
||||
b.x // -> 1
|
||||
b.z // -> 3
|
||||
|
||||
// What we see is that if the attribute is not found in the current
|
||||
// object the next object is checked, and so on, this next object is
|
||||
// called "prototype".
|
||||
// These prototype chains can be of any length.
|
||||
// Cycles in prototype chains are not allowed, see note further down for
|
||||
// an example.
|
||||
//
|
||||
// Note that this works for reading (and mutating) attributes, but when
|
||||
// writing or deleting we are affecting ONLY the local object and
|
||||
// attributes explicitly defined in it, or its' "own" attributes.
|
||||
|
||||
b.x = 321
|
||||
b.x // -> 321
|
||||
a.x // -> 1
|
||||
|
||||
// Notice also that a.x is no longer visible from 'b', this is called
|
||||
// "shadowing", we say: a.x is shadowed by b.x, now let us delete 'x'
|
||||
// from 'b' to reveal the shadowed a.x
|
||||
|
||||
delete b.x
|
||||
b.x // -> 1
|
||||
|
||||
// But, trying to delete .x from 'b' again will have no effect, this is
|
||||
// because .x no longer exists in 'b'
|
||||
|
||||
delete b.x
|
||||
b.x // -> 1
|
||||
|
||||
|
||||
// Now back to the mechanism that makes all of this work...
|
||||
//
|
||||
// First we'll try couple of easy ways to see the local and non-local
|
||||
// sets of attributes:
|
||||
|
||||
// show local or "own" only attribute names (keys)...
|
||||
Object.keys(b) // -> z
|
||||
|
||||
// show all accessible keys...
|
||||
for(var k in b){ console.log(k) }
|
||||
// -> x, y, z
|
||||
|
||||
// Another way to test if the attribute is own/local
|
||||
|
||||
b.hasOwnProperty('z') // -> true
|
||||
b.hasOwnProperty('x') // -> false
|
||||
|
||||
|
||||
// What happens under the hood is very simple: b references it's
|
||||
// "prototype" via the .__proto__ attribute:
|
||||
|
||||
b.__proto__ === a // -> true
|
||||
|
||||
|
||||
// We can read/set this special attribute just like any other attribute
|
||||
// on most systems.
|
||||
//
|
||||
// NOTE: we did not see .__proto__ in the list of accessible attributes
|
||||
// because it is a special attribute (property), it is implemented
|
||||
// internally and is not enumerable.
|
||||
// NOTE: cyclic prototype chains are actively not allowed, e.g. creating
|
||||
// a chain like the following will fail:
|
||||
// var a = {}
|
||||
// var b = Object.creating(a)
|
||||
// a.__proto__ = b
|
||||
//
|
||||
//
|
||||
// Thus, we could define our own equivalent to Object.create(..) like
|
||||
// this:
|
||||
|
||||
function clone(from){
|
||||
var o = {}
|
||||
o.__proto__ = from
|
||||
return o
|
||||
}
|
||||
|
||||
var c = clone(b)
|
||||
|
||||
|
||||
// Out of curiosity let's see if .__proto__ is defined on a basic object
|
||||
|
||||
var x = {}
|
||||
|
||||
x.__proto__ // -> {}
|
||||
|
||||
// Turns out it is, and it points to Object's prototype
|
||||
|
||||
x.__proto__ === Object.prototype
|
||||
// -> true
|
||||
|
||||
// We will discuss what this means and how we can use this in the next
|
||||
// sections...
|
||||
//
|
||||
// As a side note, Object.prototype is the "root" most object in
|
||||
// JavaScript and usually is "terminated" with null, i.e.:
|
||||
|
||||
Object.prototype.__proto__ === null
|
||||
|
||||
// We'll also need this a bit later...
|
||||
//
|
||||
// And can create an object with a null prototype like this:
|
||||
|
||||
var raw_obj = Object.create(null)
|
||||
var raw_obj = clone(null)
|
||||
|
||||
// or manually...
|
||||
var raw_obj = {}
|
||||
raw_obj.__proto__ = null
|
||||
|
||||
// These "raw" objects differ from normal objects in that they do not
|
||||
// inherit any interface methods, defined in the Object, like the
|
||||
// .hasOwnProperty(..) we used above, this can be useful in some cases.
|
||||
|
||||
|
||||
|
||||
// The Constructor Mechanism
|
||||
// -------------------------
|
||||
//
|
||||
// JavaScript provides a second, complementary mechanism to inherit
|
||||
// attributes, it resembles the class/object relationship in languages
|
||||
// like C++ but this resemblance is on the surface only, as it still
|
||||
// uses the same prototype mechanism as basis, as described above.
|
||||
//
|
||||
// We will start by creating a "constructor":
|
||||
|
||||
function A(){
|
||||
this.x = 1
|
||||
this.y = 2
|
||||
}
|
||||
|
||||
// Technically a constructor is just a function, what makes it a
|
||||
// "constructor" is only how we use it...
|
||||
|
||||
var a = new A()
|
||||
|
||||
|
||||
// Some terminology:
|
||||
// - in the above use-case 'A' is called a constructor,
|
||||
// - the object returned by new is called an "instance" (in this case
|
||||
// assigned to 'a'),
|
||||
// - the attributes set by the constructor (x and y) are called
|
||||
// "instance attributes" and are not shared (obviously) between
|
||||
// different instances, rather they are "constructed" for each
|
||||
// instance independently.
|
||||
//
|
||||
//
|
||||
// Let's look in more detail at what 'new' does here:
|
||||
// 1) creates an empty object
|
||||
// 2) sets a bunch of attributes on it, we'll skim this part for now
|
||||
// 3) passes the new object to the constructor via 'this'
|
||||
// 4) after the constructor finishes, this object is returned
|
||||
//
|
||||
// We could write a simplified equivalent function:
|
||||
|
||||
function construct(func){
|
||||
var obj = {}
|
||||
func.apply(obj)
|
||||
return obj
|
||||
}
|
||||
|
||||
var b = construct(A)
|
||||
|
||||
// But at this point this all looks like all we did is move the attribute
|
||||
// definition from a literal object notation into a constructor function,
|
||||
// effectively adding complexity.
|
||||
// And now instead of "inheriting" and reusing attributes we make a new
|
||||
// set for each individual instance.
|
||||
// So hat are we getting back from this?
|
||||
//
|
||||
// To answer this question we will need to look deeper under the hood,
|
||||
// specifically at a couple of special attributes:
|
||||
|
||||
// we saw this one before...
|
||||
a.__proto__ // -> {}
|
||||
|
||||
// this points back to the constructor...
|
||||
a.constructor // -> [Function A]
|
||||
|
||||
|
||||
// These are what makes this fun, lets write a more complete 'new'
|
||||
// re-implementation:
|
||||
|
||||
function construct(func, args){
|
||||
var obj = {}
|
||||
|
||||
// set some special attributes...
|
||||
obj.__proto__ = func.prototype
|
||||
|
||||
// call the constructor...
|
||||
var res = func.apply(obj, args)
|
||||
|
||||
// handle the return value of the constructor...
|
||||
if(res instanceof Object){
|
||||
return res
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
var b = construct(A)
|
||||
|
||||
|
||||
// There are two important things we added here:
|
||||
// 1) we now explicitly use the .prototype attribute that we saw earlier
|
||||
// 2) we return the resulting object in a more complicated way
|
||||
//
|
||||
// Each time a function is created in JavaScript it will get a new empty
|
||||
// object assigned to it's .prototype attribute.
|
||||
// On the function level, this is rarely used, but this object is very
|
||||
// useful when the function is used as a constructor.
|
||||
//
|
||||
// As we can see from the code above, the resulting object's .__proto__
|
||||
// points to the constructor's .prototype, this means not-own the
|
||||
// attributes accessed via that object are resolved to the prototype.
|
||||
// In the default case this is true, but in general it's a bit more
|
||||
// flexible, we'll see this in the next section.
|
||||
//
|
||||
// And the way we handle the return value makes it possible for the
|
||||
// constructor to return a custom object rather than use the one
|
||||
// provided in its "this" by new.
|
||||
//
|
||||
//
|
||||
// So if we add stuff to the constructor's .prototype they should be
|
||||
// accessible from the object
|
||||
|
||||
// the following three lines actually add attributes to the same
|
||||
// object...
|
||||
A.prototype.k = 123
|
||||
a.constructor.prototype.l = 321
|
||||
a.__proto__.m = 333
|
||||
|
||||
// for illustration, we'll set some object own attributes
|
||||
a.k = 'a!'
|
||||
b.k = 'b!'
|
||||
|
||||
a.k // -> 'a!'
|
||||
a.l // -> 321
|
||||
a.m // -> 333
|
||||
|
||||
|
||||
// These values are accessible from all objects constructed by A since
|
||||
// all of them point to A with both the .constructor and .__proto__
|
||||
// attributes
|
||||
|
||||
b.k // -> 'b!'
|
||||
b.l // -> 321
|
||||
b.m // -> 333
|
||||
|
||||
|
||||
// This works for any constructor, including built-in constructors and
|
||||
// since name resolution happens in runtime all instances will get the
|
||||
// new functionality live, as it is defined:
|
||||
|
||||
// a "class method", like .keys(..) but return all available keys...
|
||||
Object.allKeys = function(o){
|
||||
var res = []
|
||||
for(var k in o){
|
||||
res.push(k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
// now make these into real methods we can use from any object...
|
||||
Object.prototype.keys = function(){ return Object.keys(this) }
|
||||
Object.prototype.allKeys = function(){ return Object.allKeys(this) }
|
||||
|
||||
b.keys() // -> ['k']
|
||||
b.allKeys() // -> ['x', 'y', 'k', 'l', 'm']
|
||||
// NOTE: x and y are set in the A constructor
|
||||
// above...
|
||||
|
||||
|
||||
|
||||
// Inheritance chains
|
||||
// ------------------
|
||||
//
|
||||
// In both inheritance mechanisms, each step is checked via the same
|
||||
// rules recursively, this makes it possible to build inheritance chains
|
||||
//
|
||||
// We will create a chain:
|
||||
//
|
||||
// c -> b -> a
|
||||
//
|
||||
|
||||
var a = {x: 1}
|
||||
|
||||
var b = Object.create(a)
|
||||
b.y = 2
|
||||
|
||||
var c = Object.create(b)
|
||||
|
||||
|
||||
c.x // -> 1
|
||||
c.y // -> 2
|
||||
|
||||
|
||||
// Creating an inheritance chain via the constructor mechanism is a bit
|
||||
// more involved, and there are multiple ways to do this...
|
||||
//
|
||||
// Here we will create a similar chain to the above for comparison:
|
||||
//
|
||||
// C -> B -> A
|
||||
//
|
||||
|
||||
function A(){}
|
||||
|
||||
A.prototype.x = 1
|
||||
|
||||
|
||||
function B(){}
|
||||
// NOTE: if this is done after an instance is created, that instances'
|
||||
// .__proto__ will keep referencing the old prototype object.
|
||||
// see the next constructor for a way around this...
|
||||
B.prototype = Object.create(A.prototype)
|
||||
// NOTE: we'll need to overwire this to B as the value inherited from
|
||||
// A.prototype will obviously be A...
|
||||
B.prototype.constructor = B
|
||||
|
||||
B.prototype.y = 2
|
||||
|
||||
|
||||
function C(){}
|
||||
// NOTE: this is safer than Object.create as it does not overwrite
|
||||
// the original C.prototype and thus will affect all existing
|
||||
// instances of C, if any were created before this point...
|
||||
// NOTE: the C.prototype.constructor field is already set correctly
|
||||
// here as we are not replacing the object created by the
|
||||
// system...
|
||||
C.prototype.__proto__ = B.prototype
|
||||
|
||||
|
||||
var c = new C()
|
||||
|
||||
c.x // -> 1
|
||||
c.y // -> 2
|
||||
|
||||
|
||||
|
||||
// Checking inheritance (instanceof)
|
||||
// ---------------------------------
|
||||
//
|
||||
// An object is considered an instance of its' constructor and all other
|
||||
// constructors in the inheritance chain.
|
||||
|
||||
c instanceof C // -> true
|
||||
c instanceof B // -> true
|
||||
c instanceof A // -> true
|
||||
c instanceof Object // -> true
|
||||
|
||||
c instanceof function X(){}
|
||||
// -> false
|
||||
|
||||
|
||||
// This also works for our manually created objects
|
||||
|
||||
var cc = construct(C)
|
||||
|
||||
cc instanceof C
|
||||
|
||||
|
||||
// But this will not work outside the constructor model, i.e. if the right
|
||||
// parameter is not a function.
|
||||
|
||||
var x = {}
|
||||
var y = Object.create(x)
|
||||
|
||||
try{
|
||||
// this will fail as x is not a function...
|
||||
y instanceof x
|
||||
} catch(e){
|
||||
console.log('error')
|
||||
}
|
||||
|
||||
|
||||
// Again to make this simpler to understand we will implement our own
|
||||
// equivalent to instanceof:
|
||||
|
||||
function isInstanceOf(obj, proto){
|
||||
return obj === Function && proto === Function ? true
|
||||
: (isInstanceOf(proto, Function)
|
||||
&& (obj.__proto__ === proto.prototype ? true
|
||||
// NOTE: the last in this chain is Object.prototype.__proto__
|
||||
// and it is null
|
||||
: obj.__proto__ == null ? false
|
||||
// go down the chain...
|
||||
: isInstanceOf(obj.__proto__, proto)))
|
||||
}
|
||||
|
||||
|
||||
isInstanceOf(c, C) // -> true
|
||||
isInstanceOf(c, B) // -> true
|
||||
isInstanceOf(c, A) // -> true
|
||||
isInstanceOf(c, Object)
|
||||
// -> true
|
||||
|
||||
isInstanceOf(c, function X(){})
|
||||
// -> false
|
||||
|
||||
|
||||
// Also take note of the following cases:
|
||||
|
||||
Object instanceof Function
|
||||
// -> true
|
||||
Function instanceof Object
|
||||
// -> true
|
||||
Object instanceof Object
|
||||
// -> true
|
||||
Function instanceof Function
|
||||
// -> true
|
||||
|
||||
|
||||
// Now, the fact that a function object is both a function and an object
|
||||
// should be obvious:
|
||||
|
||||
function f(){}
|
||||
|
||||
f instanceof Function
|
||||
// -> true
|
||||
f instanceof Object
|
||||
// -> true
|
||||
|
||||
|
||||
|
||||
// Checking type (typeof)
|
||||
// ----------------------
|
||||
//
|
||||
// This section is mainly here for completeness and to address several
|
||||
// gotcha's.
|
||||
//
|
||||
// What typeof returns in JavaScript is not too useful and sometimes
|
||||
// even odd...
|
||||
|
||||
typeof c // -> 'object'
|
||||
|
||||
// This might differ from implementation to implementation but
|
||||
// essentially the main thing typeof is useful for is distinguishing
|
||||
// between objects and non-objects (numbers, strings, ...etc.)
|
||||
|
||||
// non-objects
|
||||
typeof 1 // -> 'number'
|
||||
typeof Infinity // -> 'number'
|
||||
typeof 'a' // -> 'string'
|
||||
typeof undefined // -> 'undefined'
|
||||
|
||||
// objects
|
||||
typeof {} // -> 'object'
|
||||
typeof [] // -> 'object'
|
||||
|
||||
// the odd stuff...
|
||||
typeof NaN // -> 'number'
|
||||
typeof null // -> 'object'
|
||||
typeof function(){} // -> 'function'
|
||||
|
||||
|
||||
// NOTE: the "non-object" term is not entirely correct here, they can
|
||||
// be called "frozen" objects in ES5 speak, but that is outside the
|
||||
// scope of this document.
|
||||
|
||||
|
||||
|
||||
// Methods and the value of 'this'
|
||||
// -------------------------------
|
||||
//
|
||||
// A "method" is simply an attribute that references a function.
|
||||
|
||||
function f(){
|
||||
return this
|
||||
}
|
||||
|
||||
var o = { f: f }
|
||||
|
||||
// Thus we call the attribute .f of object o a "method" of object o.
|
||||
//
|
||||
//
|
||||
// 'this' is a reserved word and is available in the context of a function
|
||||
// execution, not just in methods, but what value it references depends
|
||||
// on how that function is called...
|
||||
// 'this' is mostly useful and used in methods.
|
||||
//
|
||||
// A simple way to think about it is that 'this' always points to the
|
||||
// "context" of the function call.
|
||||
//
|
||||
// There are three distinct cases here:
|
||||
// - function call / implicit context
|
||||
// - new call / implicit context
|
||||
// - method call / explicit context
|
||||
//
|
||||
//
|
||||
// 1) function call (implicit)
|
||||
// In the first case the context is either global/window/module which
|
||||
// ever is the root context in a given implementation or undefined in
|
||||
// ES5 strict mode
|
||||
|
||||
f() // -> window/global/module
|
||||
|
||||
|
||||
// Strict mode example:
|
||||
//
|
||||
function strict_f(){
|
||||
'use strict'
|
||||
return this
|
||||
}
|
||||
|
||||
strict_f() // -> undefined
|
||||
|
||||
|
||||
// 2) new call (implicit)
|
||||
// Here as we have discussed before, 'this' is assigned a new object
|
||||
// with some special attributes set.
|
||||
|
||||
new f() // -> {}
|
||||
|
||||
|
||||
// 3) method call (explicit)
|
||||
// In the method call context this is set to the object from which the
|
||||
// method is called, i.e. the object left of the '.' or [ ] attribute
|
||||
// access operators...
|
||||
|
||||
o.f() // -> o
|
||||
o['f']() // -> o
|
||||
|
||||
|
||||
// ...or an explicitly passed to .call(..) / .apply(..) function methods
|
||||
|
||||
f.call(o) // -> o
|
||||
f.apply(o) // -> o
|
||||
|
||||
|
||||
// ES5 also defines a third way to make method calls: Function.bind which
|
||||
// creates a new function where 'this' is bound to the supplied object
|
||||
|
||||
var ff = f.bind(o)
|
||||
ff() // -> o
|
||||
|
||||
|
||||
// NOTE: all of the above 5 calls are the same.
|
||||
// NOTE: the resulting from .bind(..) function will ignore subsequent
|
||||
// .bind(..), .call(..) and .apply(..) method calls and 'this' will
|
||||
// always be the original bound object.
|
||||
// NOTE: the difference between strict and "quirks" modes is in the
|
||||
// following:
|
||||
// In quirks mode a function call is always done in the root
|
||||
// context, it's like implicitly calling a method of the global
|
||||
// object:
|
||||
// f() === window.f()
|
||||
// // -> true
|
||||
// In strict mode these are two different things, a function call
|
||||
// is done without a context ('this' is undefined) while calling
|
||||
// the same function via the global object is essentially a method
|
||||
// call, setting 'this' to what is to the left of the attribute
|
||||
// access operator:
|
||||
// strict_f() !== window.strict_f()
|
||||
// // -> true
|
||||
|
||||
|
||||
|
||||
// Common use-cases
|
||||
// ----------------
|
||||
//
|
||||
// Several common object construction patterns:
|
||||
//
|
||||
// * Literal objects...
|
||||
|
||||
var LiteralObject = {
|
||||
x: 1,
|
||||
|
||||
method: function(a){
|
||||
return this.x * a
|
||||
},
|
||||
}
|
||||
|
||||
var o = Object.create(LiteralObject)
|
||||
|
||||
|
||||
// Advantages:
|
||||
// - simple and non-verbose
|
||||
// - fully introspective
|
||||
// - flexible and non-restrictive
|
||||
// - supports basic inheritance
|
||||
//
|
||||
// Disadvantages:
|
||||
// - needs a seporate manual instance construction stage (no
|
||||
// constructor)
|
||||
// - does not provide support for some of the base language
|
||||
// infrastructure, like type and instance checking
|
||||
|
||||
|
||||
|
||||
// * Constructor object...
|
||||
|
||||
function ConstructorObject(){
|
||||
this.x = 1
|
||||
}
|
||||
ConstructorObject.prototype.method = function(a){
|
||||
return this.x * a
|
||||
}
|
||||
|
||||
var o = new ConstructorObject()
|
||||
|
||||
// Advantages:
|
||||
// - flexible
|
||||
// - fully introspective
|
||||
// - supports language mechanisms for type and instance checking
|
||||
// - supports inheritance
|
||||
//
|
||||
// Disadvantages:
|
||||
// - more complicated than the literal notation
|
||||
// - needs manual work to support inheritance, making it more even
|
||||
// complicated
|
||||
// - does not provide support for multiple inheritance
|
||||
|
||||
|
||||
|
||||
// * Walled objects / Walled data
|
||||
|
||||
function ObjectConstructor(){
|
||||
// private data and functions...
|
||||
var x = 1
|
||||
|
||||
// the actual object defining both public data and methods...
|
||||
return {
|
||||
y: 2,
|
||||
|
||||
method: function(a){
|
||||
// use the private and public data...
|
||||
return this.y * x * a
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var o = ObjectConstructor()
|
||||
|
||||
|
||||
// Advantages:
|
||||
// - supports hiding data from the user
|
||||
//
|
||||
// Disadvantages:
|
||||
// - non-introspective
|
||||
// - added complexity
|
||||
// - makes inheritance and extending very complicated and in some
|
||||
// cases impossible
|
||||
// - copies code rather than reuses it
|
||||
// - does not provide support for some of the base language
|
||||
// infrastructure, like type and instance checking
|
||||
//
|
||||
// NOTE: mostly inspired by languages supporting internal strict data
|
||||
// context restrictions (e.g. private data) from the C++ family,
|
||||
// e.g. C++, Java, C# and friends...
|
||||
// NOTE: this style is called "defensive" coding by some sources,
|
||||
// including this one ;)
|
||||
// NOTE: this approach has it's use-cases, mainly in code dealing with
|
||||
// security, though general use of this pattern is not recommended
|
||||
// as it adds lots of limitations and complexity without giving
|
||||
// back any real benefits in the general case.
|
||||
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
//
|
||||
// NOTE: several topics available in ES5 are intentionally excluded
|
||||
// from this document, these include:
|
||||
// - properties
|
||||
// - freezing/sealing
|
||||
// The general motivation for this is simple: they introduce
|
||||
// complexity and restrictions without giving any real benefits
|
||||
// in the common case.
|
||||
//
|
||||
// Cases where these features "might" be useful are:
|
||||
// - language design / language extending
|
||||
// - library code
|
||||
// Neither of these is a common case and the use of these features
|
||||
// for library code is debatable.
|
||||
//
|
||||
//
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */
|
||||
@ -1,538 +0,0 @@
|
||||
<script language="javascript" src="jsssnake.js"></script>
|
||||
<script language="javascript" src="jsssnake-test.js"></script>
|
||||
<script language="javascript">
|
||||
|
||||
function start(){
|
||||
var game = JSSnakeGame(Field("field"))
|
||||
|
||||
// zombification...
|
||||
game.field.reset()
|
||||
setInterval(function(){
|
||||
game.field.reset()
|
||||
game.levels.walls()
|
||||
|
||||
var APPLES = 4
|
||||
|
||||
for(var i=0; i < APPLES; i++)
|
||||
game.Apple()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
DEBUG = false
|
||||
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#field {
|
||||
border: solid silver 5px
|
||||
}
|
||||
#field td {
|
||||
border: none
|
||||
}
|
||||
|
||||
</style>
|
||||
<body onload="DEBUG?test():start()">
|
||||
<table id="field" width="400" height="400" border="1" cellspacing="0" cellpadding="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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="log"></div>
|
||||
</body>
|
||||
@ -1,522 +0,0 @@
|
||||
<script language="javascript" src="jsssnake.js"></script>
|
||||
<script language="javascript" src="jsssnake-test.js"></script>
|
||||
<script language="javascript">
|
||||
|
||||
function start(){
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#field {
|
||||
border: solid silver 5px
|
||||
}
|
||||
#field td {
|
||||
border: none
|
||||
}
|
||||
|
||||
</style>
|
||||
<body onload="DEBUG?test():start()">
|
||||
<table id="field" width="400" height="400" border="1" cellspacing="0" cellpadding="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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="log"></div>
|
||||
</body>
|
||||
@ -1,144 +0,0 @@
|
||||
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 :
|
||||
@ -1,536 +0,0 @@
|
||||
<script language="javascript" src="jsssnake.js"></script>
|
||||
<script language="javascript" src="jsssnake-test.js"></script>
|
||||
<script language="javascript">
|
||||
|
||||
function start(){
|
||||
var game = JSSnakeGame(Field("field"))
|
||||
|
||||
game.field.reset()
|
||||
|
||||
game.levels.walls()
|
||||
|
||||
var APPLES = 4
|
||||
|
||||
for(var i=0; i < APPLES; i++)
|
||||
game.Apple()
|
||||
|
||||
}
|
||||
|
||||
DEBUG = false
|
||||
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#field {
|
||||
border: solid silver 5px
|
||||
}
|
||||
#field td {
|
||||
border: none
|
||||
}
|
||||
|
||||
</style>
|
||||
<body onload="DEBUG?test():start()">
|
||||
<table id="field" width="400" height="400" border="1" cellspacing="0" cellpadding="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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="log"></div>
|
||||
</body>
|
||||
@ -1,423 +0,0 @@
|
||||
var DEBUG = true
|
||||
|
||||
function log(text){
|
||||
document.getElementById('log').innerHTML += text + '<br>'
|
||||
}
|
||||
|
||||
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 :
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"short_name": "SimpleSnake",
|
||||
"name": "SimpleSnake",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"Theme_color": "white"
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
CACHE MANIFEST
|
||||
# Timestamp: 20170425215205
|
||||
|
||||
CACHE:
|
||||
simplesnake.html
|
||||
simplesnake.css
|
||||
simplesnake.js
|
||||
manifest.json
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
||||
@ -1,165 +0,0 @@
|
||||
/**************************************************** Hints screen ***/
|
||||
|
||||
body.hints:before,
|
||||
body.hints:after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 12.5%;
|
||||
bottom: 12.5%;
|
||||
z-index: 100;
|
||||
}
|
||||
body.hints:before {
|
||||
content: "";
|
||||
width: 100%;
|
||||
border-top: dotted 0.3vmin rgba(255, 0, 0, 0.5);
|
||||
border-bottom: dotted 0.3vmin rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
body.hints:after {
|
||||
content: "↺↻";
|
||||
width: 37vmin;
|
||||
left: auto;
|
||||
right: 50%;
|
||||
|
||||
overflow: visible;
|
||||
border-right: dotted 0.3vmin rgba(255, 0, 0, 0.5);
|
||||
|
||||
color: rgba(255, 0, 0, 0.5);
|
||||
|
||||
font-size: 15vmin;
|
||||
line-height: 70vh;
|
||||
white-space: pre;
|
||||
letter-spacing: 38vw;
|
||||
}
|
||||
.hints .simplesnake {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.hints .title:before,
|
||||
.hints .title:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100vw;
|
||||
color: red;
|
||||
font-size: 5vh;
|
||||
line-height: 10vh;
|
||||
z-index: 100;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.hints .title:before {
|
||||
content: "New level";
|
||||
}
|
||||
.hints .title:after {
|
||||
bottom: 0;
|
||||
content: "Pause game";
|
||||
}
|
||||
.hints .title {
|
||||
display: block;
|
||||
}
|
||||
.hints .title h1:after {
|
||||
content: "Touch control hints...";
|
||||
display: block;
|
||||
position: relative;
|
||||
font-size: 4vmin;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
text-shadow: 3pt 3pt 10pt rgba(0,0,0,0.2);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************** Title ***/
|
||||
|
||||
.title {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
z-index: 500;
|
||||
color: rgba(255,0,0,0.7);
|
||||
}
|
||||
.title h1 {
|
||||
position: relative;
|
||||
display: block;
|
||||
top: 50%;
|
||||
font-size: 10vmin;
|
||||
margin-top: -22vmin;
|
||||
font-weight: bolder;
|
||||
text-shadow: 3pt 3pt 15pt rgba(0,0,0,0.2);
|
||||
}
|
||||
.title h1 sup {
|
||||
font-weight: normal;
|
||||
font-size: 5vh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/********************************************************* General ***/
|
||||
|
||||
body {
|
||||
background-color: rgb(253, 253, 253);
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/************************************************************ Game ***/
|
||||
|
||||
.simplesnake .field {
|
||||
position: absolute;
|
||||
left: 50vw;
|
||||
top: 50vh;
|
||||
margin-left: -45vmin;
|
||||
margin-top: -45vmin;
|
||||
|
||||
width: 90vmin;
|
||||
height: 90vmin;
|
||||
border: solid 1px silver;
|
||||
}
|
||||
|
||||
/* show score... */
|
||||
.simplesnake[score]:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100vw;
|
||||
color: gray;
|
||||
font-size: 2vh;
|
||||
line-height: 3vh;
|
||||
bottom: 1vh;
|
||||
|
||||
content: "Top score: " attr(snake) ": " attr(score) " " attr(state);
|
||||
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.simplesnake .wall {
|
||||
background-color: silver;
|
||||
}
|
||||
.simplesnake .apple {
|
||||
position: relative;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
body:not(.hints) .simplesnake.paused:before {
|
||||
content: "Paused...";
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
margin-top: -10vmin;
|
||||
font-size: 10vmin;
|
||||
font-weight: bolder;
|
||||
text-align: center;
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html manifest="simplesnake.appcache">
|
||||
<head>
|
||||
<title>Simple Snake</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="stylesheet" href="simplesnake.css">
|
||||
<script src="simplesnake.js"></script>
|
||||
|
||||
</head>
|
||||
<body onload="setup()" onclick="clearHints()" class="hints">
|
||||
|
||||
<div class="title"> <h1>SimpleSnake<sup class="version"></sup></h1> </div>
|
||||
|
||||
<div class="simplesnake"> </div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<!-- vim:set ts=4 sw=4 spell : -->
|
||||
@ -1,556 +0,0 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
* Simple Snake
|
||||
*
|
||||
* This code is designed to illustrate the non-intuitive approach to an
|
||||
* implementation, building a snake game as a cellular automaton rather
|
||||
* than the more obvious, set of entities (OOP) or a number of sets
|
||||
* of procedures and data structures, directly emulating the "tactile"
|
||||
* perception of the game, i.e. independent field, snakes, walls, apples
|
||||
* and their interactions.
|
||||
*
|
||||
* In this approach there are no entities, no snakes, no apples, no
|
||||
* walls, just a set of cells in a field and cell behaviours per game
|
||||
* step:
|
||||
* - empty cells, apples and walls just sit there
|
||||
* - "snake" cells:
|
||||
* - decrement age
|
||||
* - if age is 0 clear cell
|
||||
* - if cell has direction (i.e. snake head)
|
||||
* - if target cell is red (apple) increment age
|
||||
* - color new cell in direction:
|
||||
* - set age on to current age + 1
|
||||
* - set direction to current
|
||||
* - clear direction
|
||||
*
|
||||
* NOTE: that in the above description some details are omitted for
|
||||
* clarity...
|
||||
*
|
||||
*
|
||||
* This code is structured in a scalable and introspective way:
|
||||
* - Snake object is reusable as a prototype enabling multiple games
|
||||
* to run at the same time
|
||||
* - Snake implements an open external control scheme, i.e. it does not
|
||||
* impose a specific way to implementing the way to control the game
|
||||
* - Simple (but not trivial) code and code structure
|
||||
* - Introspective: no hidden/masked state or functionality
|
||||
* - No external dependencies
|
||||
*
|
||||
*
|
||||
* Goals:
|
||||
* - Show that the "intuitive" is not the only approach or is not
|
||||
* necessarily the simplest...
|
||||
* - Show one approach to a scalable yet simple architecture
|
||||
* - Illustrate several programming patterns and approaches:
|
||||
* - concatinative
|
||||
* - see how Snake methods are implemented and how they are used
|
||||
* in setup(..)...
|
||||
* - see Snake.call(..) and Snake.apply(..) methods and how they
|
||||
* enable extending the code inline...
|
||||
* - meta-programming
|
||||
* see: makeEvent(..)
|
||||
* - event-oriented-programming
|
||||
* see Snake events and how they are used in setup(..) to extend
|
||||
* the basic game logic...
|
||||
* - Show the use of several HTML5/CSS3 features including appCache,
|
||||
* touch events and keyboard events and handling...
|
||||
*
|
||||
*
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
var VERSION = '2.0'
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
function makeEvent(handler_attr){
|
||||
return function(func){
|
||||
if(func === null){
|
||||
delete this[handler_attr]
|
||||
|
||||
} else 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
|
||||
}
|
||||
}
|
||||
|
||||
var Snake = {
|
||||
config: {
|
||||
field_size: 32,
|
||||
interval: 150,
|
||||
},
|
||||
|
||||
_field: null,
|
||||
_cells: null,
|
||||
players: null,
|
||||
field_size: null,
|
||||
|
||||
get random_point(){
|
||||
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].classList.length > 0)
|
||||
|
||||
return {
|
||||
x: i%w,
|
||||
y: Math.floor(i/w),
|
||||
}
|
||||
},
|
||||
get random_direction(){
|
||||
return ('nesw')[Math.floor(Math.random() * 4)] },
|
||||
|
||||
// utils...
|
||||
call: function(func){
|
||||
return func.apply(this, [].slice.call(arguments, 1)) },
|
||||
apply: function(func, args){
|
||||
return func.apply(this, args) },
|
||||
normalize_point: function(point){
|
||||
point = point || {}
|
||||
var w = this.field_size.width
|
||||
var x = point.x % w
|
||||
x = x < 0 ? (x + w) : x
|
||||
var h = this.field_size.height
|
||||
var y = point.y % h
|
||||
y = y < 0 ? (y + h) : y
|
||||
return { x: x, y: y }
|
||||
},
|
||||
|
||||
// system...
|
||||
setup: function(field, size, interval){
|
||||
this.config.field_size = size || this.config.field_size
|
||||
this.config.interval = interval || this.config.interval
|
||||
field = field || this._field
|
||||
field = this._field = typeof(field) == typeof('str') ? document.querySelector(field)
|
||||
: field
|
||||
this._make_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
|
||||
.appleEaten(null)
|
||||
.snakeKilled(null)
|
||||
},
|
||||
_make_field: function(w){
|
||||
var l = []
|
||||
l.length = w || this.config.field_size
|
||||
l.fill('<td/>')
|
||||
this._field.innerHTML =
|
||||
`<table class="field" cellspacing="0">\n${
|
||||
l.map(function(){
|
||||
return ` <tr> ${ l.join('') } </tr>`
|
||||
}).join('\n')
|
||||
}\n</table>`
|
||||
},
|
||||
_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 on this tick...
|
||||
if(cell.tick == tick){
|
||||
return
|
||||
}
|
||||
|
||||
// snake...
|
||||
if(cell.age != null){
|
||||
// handle cell age...
|
||||
if(cell.age == 0){
|
||||
delete cell.age
|
||||
cell.classList.remove('snake')
|
||||
cell.style.backgroundColor = ''
|
||||
|
||||
} else {
|
||||
cell.age -= 1
|
||||
}
|
||||
|
||||
// snake head -> move...
|
||||
var direction = cell.direction
|
||||
if(directions.indexOf(direction) >= 0){
|
||||
// turn...
|
||||
if(that.players[color] != ''){
|
||||
var turn = that.players[color] || ''
|
||||
var j = turn == 'left' ? directions.indexOf(direction) - 1
|
||||
: directions.indexOf(direction) + 1
|
||||
j = j < 0 ? 3 : j
|
||||
direction = directions[j]
|
||||
that.players[color] = ''
|
||||
}
|
||||
|
||||
// get next cell index...
|
||||
var next =
|
||||
direction == 'n' ?
|
||||
(i < w ? l - w + i : i - w)
|
||||
: direction == 's' ?
|
||||
(i > (l-w-1) ? i - (l-w) : i + w)
|
||||
: direction == 'e' ?
|
||||
((i+1)%w == 0 ? i - (w-1) : i + 1)
|
||||
: (i%w == 0 ? i + (w-1) : i - 1)
|
||||
next = that._cells[next]
|
||||
|
||||
var age = cell.age
|
||||
var move = false
|
||||
|
||||
// special case: other snake's head -> kill both...
|
||||
if(next.direction){
|
||||
var other = next.style.backgroundColor
|
||||
next.classList.remove('snake')
|
||||
next.style.backgroundColor = ''
|
||||
// NOTE: we are not deleteing .direction here as
|
||||
// we can have upto 4 snakes colliding...
|
||||
next.direction = ''
|
||||
that.snakeKilled(other, next.age+1)
|
||||
that.snakeKilled(color, age+2)
|
||||
delete next.age
|
||||
|
||||
// apple -> increment age...
|
||||
} else if(next.classList.contains('apple')){
|
||||
age += 1
|
||||
move = true
|
||||
next.classList.remove('apple')
|
||||
that.appleEaten(color, age+2)
|
||||
|
||||
// empty -> just move...
|
||||
} else if(next.classList.length == 0){
|
||||
move = true
|
||||
|
||||
// other -> kill...
|
||||
} else {
|
||||
that.snakeKilled(color, age+2)
|
||||
}
|
||||
|
||||
// do the move...
|
||||
if(move){
|
||||
next.tick = tick
|
||||
next.style.backgroundColor = color
|
||||
next.classList.add('snake')
|
||||
next.age = age + 1
|
||||
next.direction = direction
|
||||
}
|
||||
|
||||
delete cell.direction
|
||||
}
|
||||
}
|
||||
cell.tick = tick
|
||||
})
|
||||
this.tick(tick)
|
||||
},
|
||||
|
||||
// constructors...
|
||||
snake: function(color, age, point, direction){
|
||||
point = this.normalize_point(point || this.random_point)
|
||||
|
||||
var head = this._cells[point.x + point.y * this.field_size.width]
|
||||
head.style.backgroundColor = color
|
||||
head.classList.add('snake')
|
||||
head.direction = direction || this.random_direction
|
||||
head.age = (age || 5) - 1
|
||||
this.players[color] = ''
|
||||
|
||||
return this
|
||||
.snakeBorn(color)
|
||||
},
|
||||
apple: function(point){
|
||||
point = this.normalize_point(point || this.random_point)
|
||||
var c = this._cells[point.x + point.y * this.field_size.width]
|
||||
c.classList.add('apple')
|
||||
c.style.backgroundColor = ''
|
||||
return this
|
||||
},
|
||||
wall: function(point, direction, length){
|
||||
direction = direction || this.random_direction
|
||||
point = this.normalize_point(point || this.random_point)
|
||||
var x = point.x
|
||||
var y = point.y
|
||||
length = length || Math.random() * this.field_size.width
|
||||
|
||||
while(length > 0){
|
||||
var c = this._cells[x + y * this.field_size.width]
|
||||
c.classList.add('wall')
|
||||
c.style.backgroundColor = ''
|
||||
|
||||
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
|
||||
},
|
||||
level: function(level){
|
||||
var that = this
|
||||
level.forEach(function(wall){
|
||||
that.wall.apply(that, wall) })
|
||||
return this
|
||||
},
|
||||
|
||||
// events...
|
||||
snakeKilled: makeEvent('__killHandlers'),
|
||||
snakeBorn: makeEvent('__birthHandlers'),
|
||||
appleEaten: makeEvent('__appleEatenHandlers'),
|
||||
tick: makeEvent('__tickHandlers'),
|
||||
gameStarted: makeEvent('__startHandlers'),
|
||||
gameStopped: makeEvent('__stopHandlers'),
|
||||
|
||||
// actions...
|
||||
start: function(t){
|
||||
this.__timer = this.__timer
|
||||
|| setInterval(this._tick.bind(this), t || this.config.interval || 200)
|
||||
// reset player control actions...
|
||||
var that = this
|
||||
Object.keys(this.players)
|
||||
.forEach(function(k){ that.players[k] = '' })
|
||||
return this
|
||||
.tick()
|
||||
.gameStarted()
|
||||
},
|
||||
stop: function(){
|
||||
clearInterval(this.__timer)
|
||||
delete this.__timer
|
||||
delete this.__tick
|
||||
return this
|
||||
.gameStopped()
|
||||
},
|
||||
pause: function(){
|
||||
return this.__timer ? this.stop() : this.start() },
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// control event handlers...
|
||||
|
||||
var KEY_CONFIG = {
|
||||
' ': ['pause'],
|
||||
n: setup,
|
||||
ArrowLeft: ['left'],
|
||||
ArrowRight: ['right'],
|
||||
// IE compatibility...
|
||||
Left: ['left'],
|
||||
Right: ['right'],
|
||||
'?': function(){
|
||||
this
|
||||
.stop()
|
||||
.call(showHints) },
|
||||
}
|
||||
function makeKeyboardHandler(snake){
|
||||
return function(event){
|
||||
clearHints()
|
||||
var action = KEY_CONFIG[event.key]
|
||||
action
|
||||
&& (action instanceof Function ?
|
||||
action.call(snake)
|
||||
: action[0] in snake ?
|
||||
snake[action[0]].apply(snake, action.slice(1))
|
||||
: null) }}
|
||||
|
||||
var __DEBOUNCE = false
|
||||
var __DEBOUNCE_TIMEOUT = 100
|
||||
function makeTapHandler(snake){
|
||||
return function(event){
|
||||
// prevent clicks and touches from triggering the same action
|
||||
// twice -- only handle the first one within timeout...
|
||||
// NOTE: this should not affect events of the same type...
|
||||
if(__DEBOUNCE && event.type != __DEBOUNCE){ return }
|
||||
__DEBOUNCE = event.type
|
||||
setTimeout(function(){ __DEBOUNCE = false }, __DEBOUNCE_TIMEOUT)
|
||||
|
||||
clearHints()
|
||||
// top of screen (1/8)...
|
||||
;(event.clientY || event.changedTouches[0].pageY) <= (window.innerHeight / 8) ?
|
||||
setup()
|
||||
// bottom of screen 1/8...
|
||||
: (event.clientY || event.changedTouches[0].pageY) >= (window.innerHeight / 8)*7 ?
|
||||
Snake.pause()
|
||||
// left/right of screen...
|
||||
: (event.clientX || event.changedTouches[0].pageX) <= (window.innerWidth / 2) ?
|
||||
Snake.left()
|
||||
: Snake.right() }}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// misc stuff...
|
||||
|
||||
function showHints(){
|
||||
document.body.classList.add('hints') }
|
||||
function clearHints(){
|
||||
document.body.classList.remove('hints') }
|
||||
function digitizeBackground(snake, walls){
|
||||
snake._cells.forEach(function(c){
|
||||
var v = Math.floor(Math.random() * 6)
|
||||
// bg cell...
|
||||
c.classList.length == 0 ?
|
||||
(c.style.backgroundColor =
|
||||
`rgb(${255 - v}, ${255 - v}, ${255 - v})`)
|
||||
// wall...
|
||||
: walls && c.classList.contains('wall') ?
|
||||
(c.style.backgroundColor =
|
||||
`rgb(${220 - v*2}, ${220 - v*2}, ${220 - v*2})`)
|
||||
// skip the rest...
|
||||
: null })
|
||||
return snake
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var __CACHE_UPDATE_CHECK = 5*60*1000
|
||||
var __HANDLER_SET = false
|
||||
function setup(snake, timer, size){
|
||||
snake = snake || Snake
|
||||
|
||||
// levels...
|
||||
var A = Math.round((size || snake.config.field_size)/8)
|
||||
var Level = {
|
||||
W3: [
|
||||
[null, null, A*6],
|
||||
[null, null, A*6],
|
||||
[null, null, A*6],
|
||||
],
|
||||
Halves: [
|
||||
[null, null, A*8],
|
||||
],
|
||||
Quarters: [
|
||||
[null, 's', A*8],
|
||||
[null, 'e', A*8],
|
||||
],
|
||||
Random3: [[], [], []],
|
||||
|
||||
get random(){
|
||||
var l = Object.keys(this)
|
||||
.filter(function(e){ return e != 'random' })
|
||||
do {
|
||||
var level = this[l[ Math.round(Math.random()*l.length) ]]
|
||||
} while(!(level instanceof Array))
|
||||
return level
|
||||
},
|
||||
}
|
||||
|
||||
function showScore(color, age){
|
||||
score = snake.__top_score =
|
||||
(!snake.__top_score || snake.__top_score.score < age) ?
|
||||
{
|
||||
color: color || '',
|
||||
score: age || 0,
|
||||
}
|
||||
: snake.__top_score
|
||||
snake._field.setAttribute('score', score.score)
|
||||
snake._field.setAttribute('snake', score.color)
|
||||
snake._field.setAttribute('state', (
|
||||
score.score == age && score.color == color) ? '(current)' : '')
|
||||
}
|
||||
|
||||
// setup event handlers (only once)...
|
||||
if(!__HANDLER_SET){
|
||||
document.querySelectorAll('.version')
|
||||
.forEach(function(e){ e.innerHTML = VERSION })
|
||||
|
||||
// control handlers...
|
||||
document.addEventListener('keydown', makeKeyboardHandler(snake))
|
||||
document.addEventListener('touchstart', makeTapHandler(snake))
|
||||
//document.addEventListener('mousedown', makeTapHandler(snake))
|
||||
|
||||
// cache updater...
|
||||
var appCache = window.applicationCache
|
||||
if(appCache
|
||||
&& appCache.status != appCache.UNCACHED){
|
||||
appCache.addEventListener('updateready', function(){
|
||||
if(appCache.status == appCache.UPDATEREADY){
|
||||
console.log('CACHE: new version available...')
|
||||
appCache.swapCache()
|
||||
|
||||
confirm('New version ready, reload?')
|
||||
&& location.reload()
|
||||
}
|
||||
})
|
||||
setInterval(function(){ appCache.update() }, __CACHE_UPDATE_CHECK)
|
||||
}
|
||||
|
||||
__HANDLER_SET = true
|
||||
}
|
||||
|
||||
// setup the game...
|
||||
return snake
|
||||
// prepare the field/game...
|
||||
.setup('.simplesnake', size, timer)
|
||||
.call(digitizeBackground, snake)
|
||||
.call(function(){
|
||||
this.__snake_apples = []
|
||||
return this
|
||||
})
|
||||
|
||||
// load level...
|
||||
.level(Level.random)
|
||||
|
||||
// game events / meta game rules...
|
||||
// reconstruct eaten apples...
|
||||
.appleEaten(function(color, age){
|
||||
this.apple()
|
||||
showScore(color, age)
|
||||
})
|
||||
// one apple per snake...
|
||||
.snakeBorn(function(color){
|
||||
this.__snake_apples.indexOf(color) < 0
|
||||
&& this.apple()
|
||||
&& this.__snake_apples.push(color) })
|
||||
// reconstruct snakes and pause game...
|
||||
// XXX for multiplayer reconstruct the snake on timeout and do
|
||||
// not pause...
|
||||
.snakeKilled(function(color, age){
|
||||
this
|
||||
.pause()
|
||||
.snake(color, 3)
|
||||
showScore(color, 3)
|
||||
})
|
||||
// indicate game state...
|
||||
.gameStarted(function(){
|
||||
this._field.classList.remove('paused') })
|
||||
.gameStopped(function(){
|
||||
this._field.classList.add('paused') })
|
||||
|
||||
// game eleemnts...
|
||||
.apple()
|
||||
.snake('blue', 3)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 spell : */
|
||||
Loading…
x
Reference in New Issue
Block a user