moving Slang to its own repo...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-06-03 04:30:28 +03:00
parent 91beb4d0d4
commit 50de3c5d77
19 changed files with 4 additions and 5290 deletions

View File

@ -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

View File

@ -1,4 +0,0 @@
# Slang
For and interactive version of Slang interpreter go here:
http://flynx.github.io/Course-JavaScript/Slang/slang.html

View File

@ -1,7 +0,0 @@
{
"short_name": "Slang",
"name": "Slang",
"display": "standalone",
"Theme_color": "white"
}

View File

@ -1,11 +0,0 @@
CACHE MANIFEST
# Timestamp: 20170929020625
CACHE:
slang.html
slang.js
manifest.json
NETWORK:
*

View File

@ -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, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
// very basic syntax highlighting...
// comments...
.replace(/(\(.*)-(.*\))/g, '<span class="comment">$1&minus;$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('&', '&amp;')
.replace('<', '&lt;')
.replace('>', '&gt;')
.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>

File diff suppressed because it is too large Load Diff

View File

@ -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
View File

@ -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 : */

View File

@ -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>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>
<div id="log"></div>
</body>

View File

@ -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>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>
<div id="log"></div>
</body>

View File

@ -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 :

View File

@ -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>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>
<div id="log"></div>
</body>

View File

@ -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 :

View File

@ -1,7 +0,0 @@
{
"short_name": "SimpleSnake",
"name": "SimpleSnake",
"display": "standalone",
"orientation": "landscape",
"Theme_color": "white"
}

View File

@ -1,12 +0,0 @@
CACHE MANIFEST
# Timestamp: 20170425215205
CACHE:
simplesnake.html
simplesnake.css
simplesnake.js
manifest.json
NETWORK:
*

View File

@ -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;
}
/*********************************************************************/

View File

@ -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 : -->

View File

@ -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 : */