diff --git a/README.md b/README.md index 86fb34b..7a441e1 100755 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -Course-JavaScript -================= \ No newline at end of file +# Slang + +For and interactive version of Slang interpreter go here: +http://flynx.github.io/Course-JavaScript/Slang/slang.html diff --git a/Slang/README.md b/Slang/README.md deleted file mode 100644 index 7a441e1..0000000 --- a/Slang/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Slang - -For and interactive version of Slang interpreter go here: -http://flynx.github.io/Course-JavaScript/Slang/slang.html diff --git a/Slang/manifest.json b/Slang/manifest.json deleted file mode 100755 index 4276016..0000000 --- a/Slang/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ - -{ - "short_name": "Slang", - "name": "Slang", - "display": "standalone", - "Theme_color": "white" -} diff --git a/Slang/slang.appcache b/Slang/slang.appcache deleted file mode 100755 index 9f31719..0000000 --- a/Slang/slang.appcache +++ /dev/null @@ -1,11 +0,0 @@ -CACHE MANIFEST -# Timestamp: 20170929020625 - -CACHE: -slang.html -slang.js -manifest.json - -NETWORK: -* - diff --git a/Slang/slang.html b/Slang/slang.html deleted file mode 100755 index 4896af6..0000000 --- a/Slang/slang.html +++ /dev/null @@ -1,279 +0,0 @@ - - - -Slang - - - - - - - - - - - - - -

Slang

- Toggle bootstrap code view... -
-

Available words

-

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

-

-

Slang Console

-
-
-
-
- - - diff --git a/Slang/slang.js b/Slang/slang.js deleted file mode 100755 index 17809a8..0000000 --- a/Slang/slang.js +++ /dev/null @@ -1,1134 +0,0 @@ -/********************************************************************** -* -* -* -**********************************************************************/ - -/* XXX for some odd reason this breaks the interpreter... -Array.prototype.toString = function(){ - return '[ ' + this.join(', ') + ' ]' -} -*/ - - -/*********************************************************************/ - -function run(context){ - - context.stack = context.stack == null ? [] : context.stack - - while(context.code.length > 0){ - - var cur = context.code.splice(0, 1)[0] - - // exit... - if(typeof(cur) == typeof('abc') && cur == '_exit'){ - return context - - // word - } else if(typeof(cur) == typeof('abc') && cur in context.ns){ - var word = context.ns[cur] - // low-level word... - if(typeof(word) == typeof(function(){})){ - var res = context.ns[cur](context) - - // hi-level word... - } else if(typeof(word) == typeof([]) && word && word.constructor.name == 'Array'){ - // XXX add isolation with a continuation... - context.code.splice.apply(context.code, [0, 0].concat(word)) - var res = undefined - - // variable... - } else { - var res = word - } - - if(res !== undefined){ - context.stack.push(res) - } - - // everything else... - } else { - context.stack.push(cur) - } - } - - return context -} - -// XXX make this add '\n' / EOL words to the stream... -//var SPLITTER = /\s*\([^\)]*\)\s*|\s*--.*[\n$]|\s*"([^"]*)"\s*|\s*'([^']*)'\s*|\s+/m -var SPLITTER = RegExp([ - /* XXX there are two ways to deal with comments: - // 1) lexer-based -- this section commented, next uncommented... - // 2) macro-based -- this section uncommented, next commented... - // #2 is a bit buggy... - // terms to keep in the stream... - '\\s*('+[ - '\\n', - '--', - ].join('|')+')', - //*/ - - //* lexer comments... - '\\s*\\([^\\)]*\\)\\s*', - '\\s*--.*[\\n$]', - //*/ - - // quoted strings... - '\\s*"([^"]*)"\\s*', - "\\s*'([^']*)'\\s*", - - // quote... - '\\s*(\\\\)', - - // whitespace... - '\\s+', - ].join('|'), - 'm') - - -// helpers... -// XXX should these skip quoted ends? -var collect = function(start, end){ - return function(context){ - var res = start ? [start] : [] - var code = context.code - var cur = code.shift() - res.push(cur) - while(cur != end && code.length > 0){ - cur = code.shift() - res.push(cur) - } - return res - } -} -var drop = function(start, end){ - var collector = collect(start, end) - //return function(context){ collector(context) } - return function(context){ console.log('XXX', collector(context).join(' ')) } -} - - -// pre-processor namespace... -var PRE_NAMESPACE = { - // comment... - // syntax: -- ... \n - // - // drop everything until '\n' - // - // NOTE: this depends on explicit '\n' words... - //'--': drop('--', '\n'), - '(': drop('(', ')'), - - // XXX use the real reader... - // block... - // syntax: [ ... ] - // NOTE: the shorthand ']]' will close ALL the open blocks. - // XXX should ']]' be smart enough to look ahead and close only the - // blocks not explicitly closed later??? - // ..see below for more... - '[': function(context){ - var block = [] - var code = context.code - var cur = code.splice(0, 1)[0] - while(cur != ']' && cur != ']]' && code.length > 0){ - if(cur == '['){ - cur = this['['](context) - } - block.push(cur) - cur = code.splice(0, 1)[0] - } - // we need this to be able to jump out of all the nested blocks, - // thus we'll keep the ']]' in code and remove it explicitly - // later... - if(cur == ']]'){ - // XXX should we look ahead and count the explicitly closed - // via ']' and ']]' blocks??? - // ...at this point this seems a bit complex... - // ...if there are more than one ']]' in a structure - // this might stop being deterministic... - code.splice(0, 0, cur) - } - if(code.length == 0 && cur != ']' && cur != ']]'){ - console.error('Did not find expected "]".') - } - return block - }, - // drop the closing words... - ']]': function(context){}, - ']': function(context){ console.error('Unexpected "]".') }, - - // XXX macros are not recursive... - 'macro:': function(context){ - var ident = context.code.splice(0, 1) - var cur = context.code.splice(0, 1) - - // as we do not have blocks yet we need to manually collect one ;) - if(cur[0] == '['){ - cur = [ this['['](context) ] - } - - this[ident] = cur[0] - }, - - // a no op... - '\n': function(){ }, -} - - -// main namespace... -var NAMESPACE = { - // constants... - 'true': true, - 'false': false, - 'null': 'null', - - 'nop': function(){}, - - 'is?': function(context){ - return context.stack.pop() === context.stack.pop() }, - - // XXX experimental... - // flip the code and stack... - // ... -- ... - '_flip': function(context){ - var stack = context.stack - context.stack = context.code.reverse() - context.code = stack.reverse() - }, - - // swap heads of stack and code - // ... ns nc -- ... - '_swapN': function(context){ - var c = context.stack.pop() - var s = context.stack.pop() - var l = context.stack.length - - // get the stack/code sections to swap... - var s_c = context.stack.splice(l-s, l) - var c_c = context.code.splice(0, c) - var l = context.stack.length - - // we need to pad e_c and c_c to requested length... - s_c = s_c.length < s ? s_c.concat(Array( s - s_c.length )) : s_c - c_c = c_c.length < c ? c_c.concat(Array( c - c_c.length )) : c_c - - // XXX we also need to shove something more sensible in the - // padding that undefined... - - context.code.splice.apply(context.code, [0, 0].concat(s_c)) - context.stack.splice.apply(context.stack, [l, 0].concat(c_c)) - }, - - // encapsulate stack to a block... - // ... -- [ ... ] - 's2b': function(context){ - context.stack = [ context.stack ] }, - // expand block to stack... - // NOTE: this will append the block contents to stack, w.o. replacing - // stack contents. this is different to _s2b - // ... [ ... ] -- ... ... - 'b2s': function(context){ - var c = context.stack.pop() - c = c === undefined ? [] : c - context.stack.splice.apply(context.stack, [context.stack.length, 0].concat(c)) - }, - 'print': function(context){ - console.log('>>>', context.stack[context.stack.length-1]) }, - - // turn a sting into a lexical list... - // c -- b - // XXX BUG see code... - 'lex': function(context){ - code = context.stack.pop() - if(typeof(code) == typeof('abc')){ - // XXX BUG: '"aaa" "bbb"' translates to ['"aaa"', '" "', '"bbb"'] - // i.e. quotes w/o whitespace are eaten... - if(/^\s*(['"]).*\1\s*$/m.test(code)){ - code = code.split(/^\s*(['"])(.*)\1\s*$/m)[2] - } - - //console.log(code) - - var res = [] - code = code - // split by strings whitespace and block comments... - .split(SPLITTER) - // parse numbers... - .map(function(e){ - // numbers... - if(/^[-+]?[0-9]+\.[0-9]+$/.test(e)){ - e = parseFloat(e) - } else if(/^[-+]?[0-9]+$/.test(e)){ - e = parseInt(e) - } - return e - }) - // remove undefined groups... - .filter(function(e){ - // NOTE: in JS 0 == '' is true ;) - return e !== undefined && e !== '' - }) - } - return code - }, - // pre-process a lexical list... - // a -- b - 'prep': function(context){ - var code = context.stack.pop() - - return run({ - stack: [], - code: code, - ns: context.pre_ns, - pre_ns: {}, - }).stack - }, - - // s c -- s - '_exec': function(context){ - // block... - var b = context.stack.pop() - if(typeof(b) == typeof([]) && b && b.constructor.name == 'Array'){ - b = b.slice() - } else { - b = [ b ] - } - // stack... - var s = context.stack.pop() - var res = run({ - stack: s, - code: b, - // NOTE: this can have side-effects on the context... - ns: context.ns, - pre_ns: context.pre_ns - }) - // XXX is this the right way to go? - context.ns = res.ns - context.pre_ns = res.pre_ns - return res.stack - }, - // quote - push the next elem as-is to stack... - // -- x - '\\': function(context){ - return context.code.splice(0, 1)[0] }, - - // comparisons and logic... - // a b -- c - 'and': function(context){ - var b = context.stack.pop() - var a = context.stack.pop() - if(a){ - return b - } else { - return a - } - }, - // a b -- c - 'or': function(context){ - var b = context.stack.pop() - var a = context.stack.pop() - if(a){ - return a - } else { - return b - } - }, - // x -- b - 'not': function(context){ - return !context.stack.pop() }, - // a b -- c - 'gt': function(context){ - return context.stack.pop() < context.stack.pop() }, - // a b -- c - 'eq': function(context){ - return context.stack.pop() == context.stack.pop() }, - - // stack operations... - // ... x -- x ... - 'rot': function(context){ - context.stack.splice(0, 0, context.stack.pop()) }, - // x ... -- ... x - 'tor': function(context){ - context.stack.push(context.stack.shift()) }, - // a b -- b a - 'swap': function(context){ - return context.stack.splice(context.stack.length-2, 1)[0] }, - // x -- x x - 'dup': function(context){ - return context.stack[context.stack.length-1] }, - // x -- x' - // NOTE: this will do a deep copy... - 'clone': function(context){ - return JSON.parse(JSON.stringify(context.stack.pop())) }, - // x -- - 'drop': function(context){ - context.stack.pop() }, - - // a -- b - // NOTE: all names are also strings so moo string? and 'moo' string? - // are the same... - 'string?': function(context){ - return typeof(context.stack.pop()) == typeof('str') }, - - // basic number operations... - // a -- b - 'number?': function(context){ - return typeof(context.stack.pop()) == typeof(123) }, - // a b -- c - 'add': function(context){ - return context.stack.pop() + context.stack.pop() }, - 'sub': function(context){ - return - context.stack.pop() + context.stack.pop() }, - 'mul': function(context){ - return context.stack.pop() * context.stack.pop() }, - 'div': function(context){ - return 1/context.stack.pop() * context.stack.pop() }, - - // block/list operations... - 'block?': function(context){ - var e = context.stack.pop() - return typeof(e) == typeof([]) && e && e.constructor.name == 'Array' - }, - // b n -- b e - 'at': function(context){ - var i = context.stack.pop() - if(i < 0){ - var l = context.stack[context.stack.length-1] - return l[l.length + i] - } - return context.stack[context.stack.length-1][i] - }, - // b e n -- b - 'to': function(context){ - var i = context.stack.pop() - var e = context.stack.pop() - if(i < 0){ - var l = context.stack[context.stack.length-1] - l[l.length + i] = e - } else { - context.stack[context.stack.length-1][i] = e - } - }, - // b e n -- b - 'before': function(context){ - var i = context.stack.pop() - var e = context.stack.pop() - if(i < 0){ - var l = context.stack[context.stack.length-1] - l.splice(l.length + i, 0, e) - } else { - context.stack[context.stack.length-1].splice(i, 0, e) - } - }, - // b -- b e - 'pop': function(context){ - return context.stack[context.stack.length-1].pop() }, - // b -- b l - 'len': function(context){ - return context.stack[context.stack.length-1].length }, - // b c -- b - 'map': function(context){ - var c = context.stack.pop() - var b = context.stack[context.stack.length-1] - for(var i=0; i < b.length; i++){ - // exec block in a separate context... - var res = run({ - //stack: [b, i, b[i], c], - stack: [b[i], c], - code: ['exec'], - // NOTE: this can have side-effects on the context... - ns: context.ns - }).stack - var l = res.length - if(l == 0){ - b.splice(i, 1) - i-- - } else { - b.splice.apply(b, [i, 1].concat(res)) - i += l - 1 - } - } - }, - - - // object stuff... - '{}': function(){ return {} }, - - 'object?': function(context){ - var o = context.stack[context.stack.length - 1] - return o && o.constructor === Object - }, - - // set item... - // o k v -- o - 'item!': function(context){ - var v = context.stack.pop() - var k = context.stack.pop() - var o = context.stack[context.stack.length - 1] - - o[k] = v - }, - - // test item... - // o k -- o t - 'item?': function(context){ - return context.stack.pop() in context.stack[context.stack.length - 1] }, - - // get item... - // o k -- o v - 'item': function(context){ - var k = context.stack.pop() - return context.stack[context.stack.length - 1][k] }, - - // remove/pop item from object... - // o k -- o v - 'popitem': function(context){ - var k = context.stack.pop() - var o = context.stack[context.stack.length - 1] - - var v = o[k] - delete o[k] - - return v - }, - - // o -- k - 'keys': function(context){ - return Object.keys(context.stack.pop()) }, - - // make a prototype of b... - // a b -- b - // NOTE: if a is false, reset prototype... - 'proto!': function(context){ - var b = context.stack.pop() - var a = context.stack.pop() - b.__proto__ = a === false ? {}.__proto__ : a - return b - }, - - // o -- p - // XXX what should this be: - // {} getproto - 'proto': function(context){ - return context.stack.pop().__proto__ }, - - // -- o - 'ns': function(context){ - return context.ns }, - // o -- - 'ns!': function(context){ - context.ns = context.stack.pop() }, -} - - -// NOTE: hate how JS handles multi-line strings... -var BOOTSTRAP = -`------------------------------------------------------------------------------- - - [S]lang is a [s]imple and complete [S]tack [lang]uage. - - Slang was designed for three main reasons: - - a means to experiment with several aspects of language design, - - an educational tool, to illustrate several programming language - concepts in a simple, hands-on manner, - - fun! - - - -------------------------------------------------------------------------------- - - Slang Basics - ------------ - - The system consists of: - - Stack - - Code - - Namespace - - basic runtime - - { NAMESPACE } - ^ - | - [ .. STACK .. ] <-- runtime -- [ .. CODE .. ] - - - A namespace is a basic key/value store. - - The runtime "reads" entities from the code stream one by one and depending on - whether an entity exists in the namespace it is either pushed on the stack - or evaluated. - - The evaluated entity is traditionally called a "word" (function in non-stack - languages). The only thing that makes a word different from any other entity - is that it matches a key in the namespace, as mentioned above. - - In Slang evaluation is done simply by executing the value of the matched - key/value pair in the namespace. An over-simplified way to explain - evaluation is to say that the content of the value is pushed to the - code stream to be read right away, that\'s almost it, if we skip a - couple of details (see: _exec, exec and for details see: eval) - - The system contains two types of words: - - Host words -- defined by the host system, - - User words -- defined within the system (like this bootstrap code). - - Words may read and affect any of the three system parts: - - Stack - - Code - - Namespace - - Traditioannly, in stack languages words affect only the stack, this is - one of the motivations to implement Slang, that is, to experiment with - different ways to go with stack languages. - - - TODO: add topological comparison/diff - - - ------------------------------------------------------------------------------ - - Stack effect notation - --------------------- - - Traditionally, stack languages use a "stack effect" notation to document how - words affect the stack state, a kind of before-after transformation. here is - a basic example showing how the word "add" works: - - stack code - | 1 2 add - 1 | 2 add - 1 2 | add - 1 2 [add] (a) - 3 | (b) - - - Here the stack effect represents the difference between two states: the - moment when the word is "read" (a) and the stack state after it is - evaluated (b) and is written like this: - - ( a b -- c ) - - - But, due to the fact that in Slang all three of the stack, code and namespace - can be affected by words, we need an extended stack effect notation. to - include at least the second most common case, the "code effect". - To illustrate, here is an example of a word that has a simple code effect, - the "+": - - stack code - | 1 + 2 - 1 | + 2 - 1 [+] 2 (a) - 3 | (b) - - - Here we see that in addition to affecting the stack, 2 is "pulled" from the - code stream. To indicate this we will use "|" that splits the stack (left) - and code (right) states, and write the stack effect for the word "+" like - this: - - ( a | b -- c | ) - - - NOTE: this notation is currently used as a means to documenting words and is - not interpreted in any way. - - - -------------------------------------------------------------------------------- - - Blocks / Lists - -------------- - - Basic words for block manipulation: - - Get block length - - [ 1 2 3 ] len - -> [ 1 2 3 ] 3 - - - Pop element form block tail - - [ 1 2 3 ] pop - -> [ 1 2 ] 3 - - - Push element to block tail - - [ 1 2 3 ] 4 push - -> [ 1 2 3 4 ] - - - NOTE: all indexes can be negative values, these will indicate the - position relative to the tail, -1 being the last element. - - Get element at position (0) - - [ 1 2 3 ] -1 at - -> [ 1 2 3 ] 3 - - - Put element (123) at position (0) - - [ 1 2 3 ] 123 0 to - -> [ 123 2 3 ] - - - Put element (123) before position (0) - - [ 1 2 3 ] 123 0 before - -> [ 123 1 2 3 ] - - - Like before but puts the element after position - - [ 1 2 3 ] 123 0 after - -> [ 1 123 2 3 ] - - - Expand block to stack -- "block 2 stack" - - [ 1 2 3 ] b2s - -> 1 2 3 - - - Map a block/word to each element in a block - - [ 1 2 3 ] [ 1 add ] map - -> [ 2 3 4 ] - - the returned value (stack) of the input block is put into the result - block, this enables us to both remove (empty stack) and expand (more - than one value) the resulting list... - - [ 1 2 3 4 ] [ dup ] map - -> [ 1 1 2 2 3 3 4 4 ] - - [ 1 2 3 4 ] [ dup 2 gt ? [ ] else [ . ] ] map - -> [ 3 4 ] - - - this enables us to construct words like filter, which makes the code - in the last example more readable: - - [ 1 2 3 4 ] [ 2 gt ] filter - -> [ 3 4 ] - - Reduce enables us to take a list and "reduce" it to a single value... - - [ 1 2 3 4 ] \\add reduce - -> 10 - - -------------------------------------------------------------------------------- - - Objects and namespaces - ---------------------- - - Get the namespace object... - - ns -> namespace - - - Set attribute (key-value pair) on object... - - o x 123 item! - -> o - - Since almost all object words return the original object we can chain - object operations like this: - - Create a variable word o and p and set them to empty objects... - - ns - o {} item! - p {} item! - . - - Get attribute x value... - - o x item - -> 123 - - Test if attribute x exists... - - o x item? - -> true - - Get block of attribute idents... - - o keys - -> [ ... ] - - Get and remove an attribute value from o... - - o x popitem - -> 123 - - Set prototype of o to p - - o p proto! - -> o - - Get prototype of o - - o proto - -> p - - -------------------------------------------------------------------------------- -s2b drop -- cleanup after docs... -ns {} proto! ns! . -- keep new words in a seporate context... --- --- With that out of the way, let\'s start with the bootstrap... - --- prepare the basic syntax for defining words... -ns - -- Some shorthands... - . ( x -- ) - [ drop ] item! - rot2 ( .. x y -- x y .. ) - [ rot rot ] item! - tor2 ( x y .. -- .. x y ) - [ tor tor ] item! - - -- Friendly exec... - exec ( b -- ... ) - [ s2b pop _exec b2s ] item! - -- Create a word... - word! ( w b -- ) - [ rot2 ns tor2 item! . ] item! - -- Word definition... - -- syntax: :: - :: ( | w b -- | ) - [ \\word! \\exec 2 2 _swapN ] item! -. - - --- misc... - -:: true? ( a -- b ) [ not not true eq ] -:: false? ( a -- b ) [ not true? ] - --- we already have gt and eq, now let\'s define the rest... -:: ne ( a b -- c ) [ eq not ] -:: lt ( a b -- c ) [ swap gt ] -:: ge ( a b -- c ) [ lt not ] -:: le ( a b -- c ) [ gt not ] - -:: inc ( a -- b ) [ 1 add ] -:: dec ( a -- b ) [ 1 sub ] -:: ! ( a -- b ) [ [ dup 1 ne ] ? [ dup 1 sub ! mul ] ] - - - --- Stack/code manipulation... - -:: _swap ( x | y -- y | x ) [ 1 1 _swapN ] -:: _push ( x | -- | x ) [ 0 _swapN ] -:: _pull ( | x -- x | ) [ 0 swap _swapN ] - -:: eval ( c -- ... ) [ lex prep exec ] --- like exec but will run a block in current context... -:: b2c [ len rot b2s tor 0 _swapN ] - -:: swap2 ( a _ b -- b _ a ) [ swap rot swap tor swap ] -:: dup2 ( a b -- a b a b ) [ dup swap2 dup rot swap2 tor swap ] - --- this is here for devel use only -:: _clear ( ... -- ) [ s2b print drop ] -:: _stack_size ( -- l ) [ s2b len swap b2s tor ] - - - --- Flow control... - --- Classic conditional word: --- [ cond ] [ A ] [ B ] if --- --- A bit too "polish" in my view ;) -:: if ( cond a b -- ... ) [ rot rot exec true? tor and tor or exec ] - --- Ternary operator, this can take two forms: --- COND ? A --- COND ? A else B --- --- We will define this in stages, first the helpers: --- run then block and drop \'else B\' if it exists... -:: _run_then ( a x | -- ... | x ) - ( a else | b -- ... | ) - [ \\exec swap dup \\else eq [ (drop else) drop \\drop _swap 6 ] and - [ (run as-is) 1 _push 4 ] or - b2s 0 _swapN ] --- if \'else B\' exists, run it, else cleanup... -:: _run_else ( a | -- | a ) - ( b else | b -- ... | ) - [ drop dup \\else eq [ drop \\exec _swap 4 ] and - [ 1 _push 2 ] or - b2s 0 _swapN ] --- And now the main operator... --- NOTE: this may actually have one of three stack effects... -:: ? ( c | a -- | ) - ( c | a -- ... | ) - ( c | a else b -- ... | ) - [ exec [ _run_then 1 ] and [ swap _run_else 2 ] or b2s 2 _swapN ] - - - --- List/block 2\'nd gen stuff... - --- make a new block instance shorthand... -:: [] [ [ ] clone ] - --- insert element after index... -:: after ( b e i -- b ) [ - -- special case, when at end, need to push the alement after it... - dup [ -1 eq ] ? - [ . push ] - else - [ inc before ]] - --- NOTE: the "]]" in the last definition, it\'s a shorthand, it closes --- ALL the open blocks to this point. --- ...thus it can be used ONLY as the very last word in a set. - --- push element to tail of block... -:: push ( b e -- b ) [ swap len rot swap tor to ] - --- Replace a pattern (p) in block with value (v)... --- NOTE: this will replace ALL patterns... -:: replace ( l v p -- l ) [ - swap - [ . \\VALUE ] clone - swap 2 to - rot - -- XXX for some reason ? without else messes things up... - [ dup \\PATTERN eq ? VALUE_BLOCK else [ ] ] clone - swap 2 to - tor 5 to - map ] - --- Filter the block via a condition... --- --- the condition block must have the folowing stack effect: elem -- bool -:: filter ( b c -- b ) [ - -- prepare the condition... - [ dup \\TEST exec ] clone - swap TEST replace - -- prepare the template... - [ TEST ? [ ] else [ . ] ] clone - swap TEST replace - map ] - -:: reduce ( L b -- s ) [ - rot clone - -- empty list, reduction is null... - [ len 0 eq ] ? - [ . tor . null ] - -- reduction of list of len 1 is it\'s content, so just pop it... - else [ [ len 1 eq ] ? - [ tor . b2s ] - -- and now recursively reduce the elements till the list is 1 in length... - -- XXX ugly - else [ - pop rot pop rot - [] tor push tor push - -- get and run the block... - tor dup clone rot _exec - -- process the result... - pop rot . tor push tor - reduce ]] - --- Create a block containing a range of numbers form 0 to n-1... -:: range ( n -- b ) [ - -- initial state... - [ dup number? ] ? - [ [] swap ] - -- get first elem... - else - [ 0 at ] - -- we got to the end... - [ dup 0 eq ] ? - drop - -- dec push new and continue... - else - [ 1 sub 0 before range ]] - --- Sum up the elements of a block... -:: sum ( L -- s ) [ [ add ] reduce ] - - --- Meta-word examples (experimental)... - --- Here is an infix operator example... --- :: + ( a | b -- c | ) [ \\exec 2 0 _swapN \\exec \\add 2 1 _swapN ] --- now let\'s make a meta function to make things shorter... -:: infix: ( | op word -- | ) [ - [ - -- format the word definition... - -- NAME WORD -> :: NAME WORD - s2b \\:: -2 before b2s - - -- our template... - -- exec the left side... - [ \\exec 2 0 _swapN - -- exec the right side and arragne the args for WORD... - \\exec \\WORD 2 1 _swapN ] clone - -- get the WORD and insert it into the template above (opsition 8)... - swap WORD replace - - -- push to code / run - 3 0 _swapN - -- swap the arguments and the code to be executed... - ] \\exec 2 2 _swapN ] - --- Now making a word/2 an infix operator is trivial... --- NOTE: these are at this point stupid and do not support priorities... -infix: + add -infix: - sub -infix: * mul -infix: / div - --- these need more thought... -infix: == eq -infix: != ne -infix: > gt -infix: < lt -infix: <= le -infix: >= ge - --- experimental... -infix: = word! - - --- Prefix operation definition... --- Example: --- :: echo: ( | txt -- | ) [ \\_flip \\print _flip ] --- swap stack and code untill the block finishes and consumes it's arguments --- then swap them back... -:: prefix: ( | op word -- | ) [ - [ - -- format the word definition... - -- NAME WORD -> :: NAME WORD - s2b \\:: -2 before b2s - - -- the code template - [ \\_flip \\exec \\WORD _flip ] clone - swap WORD replace - 3 0 _swapN - ] \\exec 2 2 _swapN ] - - - --- Tests and examples... - --- Mandatory "hello word" word example... -:: hi ( -- ) [ "Hello World!" print drop ] - --- Create a block containg a range of numbers from f to t, inclusive... -:: range/2 ( f t -- b ) - [ dup2 swap sub swap . inc range swap [] swap push \\+ 0 before map ] - --- this will enable us to create ranges via 0 .. 4 -infix: .. range/2 - ---:: range/3 ( a n s -- b ) --- [ swap range swap [] swap push \\* 0 before map ] - --- Execute block in a context... --- synctx: context: -prefix: context: [ ns {} proto! ns! exec ns proto ns! ] - - -` - - -var STARTUP = [[], BOOTSTRAP, 'lex', 'prep', '_exec', 'drop'] - - -// build bootstrap... -var CONTEXT = { - stack: [], - code: STARTUP.slice(), - ns: NAMESPACE, - pre_ns: PRE_NAMESPACE, -} - - -// run bootstrap... -run(CONTEXT) - - -// convenience... -function _slang(code, context){ - context = context == null ? CONTEXT : context - - context.code = code - return run(context).stack -} - - -function slang(code, context){ - context = context == null ? CONTEXT : context - - if(typeof(code) == typeof('abc')){ - code = [ '\\', code, 'lex', 'prep', 'exec' ] - } else { - code = [ code, 'prep', 'exec' ] - } - - context.code = code - return run(context).stack -} - - - -/********************************************************** RSlang ***/ -/* -var RS_PRE_NAMESPACE = { - // XXX using the ";" here just for the experiment, in the real thing - // if this thing pans out, that is, use indent... (a-la make/Python) - // XXX this reads ahead at the moment, but it must read back... - ';': function(context){ - var line = [] - var code = context.code - var cur = code.splice(0, 1)[0] - while(cur != ';' && code.length > 0){ - line.push(cur) - cur = code.splice(0, 1)[0] - } - - context.code.splice.apply(context.code, [0, 0].concat(line.reverse())) - }, - - '[': PRE_NAMESPACE['['], - 'macro:': PRE_NAMESPACE['macro:'], -} - -RS_CONTEXT = { - stack: [], - code: STARTUP.slice(), - ns: NAMESPACE, - pre_ns: PRE_NAMESPACE, -} - -// NOTE: we use the traditional bootstrap for this... -run(RS_CONTEXT) - -RS_CONTEXT.pre_ns = RS_PRE_NAMESPACE - -function rslang(code, context){ - context = context == null ? RS_CONTEXT : context - - return slang(code, context) -} -//*/ - - - -/********************************************************************** -* vim:set ts=4 sw=4 spell : */ diff --git a/TeachersTimer/index.html b/TeachersTimer/index.html deleted file mode 100755 index 5ddc93d..0000000 --- a/TeachersTimer/index.html +++ /dev/null @@ -1,222 +0,0 @@ - - -Teacher's Timer - - - - - - - - -
-
+
- - - - - diff --git a/js-oop-constructors.jpg b/js-oop-constructors.jpg deleted file mode 100755 index d937d82..0000000 Binary files a/js-oop-constructors.jpg and /dev/null differ diff --git a/js-oop.js b/js-oop.js deleted file mode 100755 index 8a2ac87..0000000 --- a/js-oop.js +++ /dev/null @@ -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 : */ diff --git a/jsssnake/jsssnake-generator-test.html b/jsssnake/jsssnake-generator-test.html deleted file mode 100755 index c8330ee..0000000 --- a/jsssnake/jsssnake-generator-test.html +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
-
- diff --git a/jsssnake/jsssnake-test.html b/jsssnake/jsssnake-test.html deleted file mode 100755 index b14a48e..0000000 --- a/jsssnake/jsssnake-test.html +++ /dev/null @@ -1,522 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
-
- diff --git a/jsssnake/jsssnake-test.js b/jsssnake/jsssnake-test.js deleted file mode 100755 index ea07b45..0000000 --- a/jsssnake/jsssnake-test.js +++ /dev/null @@ -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 : diff --git a/jsssnake/jsssnake.html b/jsssnake/jsssnake.html deleted file mode 100755 index 209e08c..0000000 --- a/jsssnake/jsssnake.html +++ /dev/null @@ -1,536 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
-
- diff --git a/jsssnake/jsssnake.js b/jsssnake/jsssnake.js deleted file mode 100755 index 81e743e..0000000 --- a/jsssnake/jsssnake.js +++ /dev/null @@ -1,423 +0,0 @@ -var DEBUG = true - -function log(text){ - document.getElementById('log').innerHTML += text + '
' -} - -var Field = function(field_id){ - return ({ - // XXX HACK: get this from CSS... - APPLE_COLOR: 'red', - WALL_COLOR: 'gray', - FIELD_COLOR: 'white', - TICK: 200, - - // interface... - init: function(field_id, on_kill, on_apple_eaten){ - this.field = document.getElementById(field_id) - - // this depends on topology... - // NOTE: we consider that the field may not change during operation... - this.cells = this.field.getElementsByTagName("td") - this.height = this.field.getElementsByTagName("tr").length - - this.cell_count = this.cells.length - this.width = this.cell_count / this.height - - this.on_kill = on_kill - this.on_apple_eaten = on_apple_eaten - - this._timer = null - - this.reset() - - // rotation tables... - this._cw = { - 'n': 'e', - 's': 'w', - 'e': 's', - 'w': 'n' - } - this._ccw = { - 'n': 'w', - 's': 'e', - 'e': 'n', - 'w': 's' - } - - return this - }, - // setup/reset the field to it's original state. - reset: function(){ - this._snakes = {} - this._tick = 0 - this.stop() - for(var i=0; i < this.cells.length; i++){ - var cell = this.Cell(i) - cell.o.style.backgroundColor = this.FIELD_COLOR - } - }, - - // do a single step... - step: function(){ - var cells = this.cells - - for(var i=0; i < cells.length; i++){ - var cell = this.Cell(i) - // identify the object... - if(this.is_snake(cell)){ - this.Snake(cell.o.style.backgroundColor, cell).step() - } - } - this._tick += 1 - }, - start: function(tick){ - var that = this - if(tick === null){ - tick = this.TICK - } - if(this._timer === null){ - this._timer = setInterval(function(){that.step()}, tick) - } - }, - stop: function(){ - if(this._timer === null){ - return - } - clearInterval(this._timer) - this._timer = null - }, - - // get a cell helper... - Cell: function(n){ - var that = this - var cells = this.cells - return ({ - // NOTE: this will be null if a cell does not exist. - o: cells[n], - index: n, - // NOTE: these are cyclic... - n: function(){ - var t = n - that.width - if(t < 0) - t = that.cells.length + t - return that.Cell(t) - }, - s: function(){ - var t = n + that.width - if(t > that.cells.length-1) - t = t - that.cells.length - return that.Cell(t) - }, - e: function(){ - var t = n + 1 - if(Math.floor(t/that.width) > Math.floor(n/that.width)) - t = t - that.width - return that.Cell(t) - }, - w: function(){ - var t = n - 1 - if(Math.floor(t/that.width) < Math.floor(n/that.width)) - t = t + that.width - return that.Cell(t) - } - }) - }, - // get a cell by it's coordinates... - cell: function(x, y){ - return this.Cell(x + (y-1) * this.width) - }, - - // add a snake to the field... - // XXX BUG: size of 1 makes the snake endless... - Snake: function(color, cell, direction, size){ - var that = this - - // draw the snake if it does not exist... - if(this._snakes[color] == null){ - cell.o.style.backgroundColor = color - cell.o.age = size - this._snakes[color] = { - 'direction':direction, - 'size': size - } - } - - // NOTE: the only things this uses from the above scope is color and that. - // NOTE: color is the onlu thing that can't change in a snake. - return ({ - // XXX BUG: the last cell of a dead snake lives an extra tick... - kill: function(){ - // this will disable moving and advancing the snake... - that._snakes[color].size = 0 - if(that.on_kill != null){ - that.on_kill(that) - } - }, - step: function(){ - var direction = that._snakes[color].direction - var size = that._snakes[color].size - var target = cell[direction]() - - // skip a cell if it's already handled at this step. - if(cell.o.moved_at == that._tick){ - return - } - - // do this only for the head... - if(parseInt(cell.o.age) == size){ - // handle field bounds... - if(target.o == null){ - alert('out of bounds!') - return - } - // kill conditions: walls and other snakes... - if(that.is_snake(target) || that.is_wall(target)){ - // XXX move this to a separate action - this.kill() - return - } - // apple... - if(that.is_apple(target)){ - // grow the snake by one... - // XXX move this to a separate action - that._snakes[color].size += 1 - size = that._snakes[color].size - if(that.on_apple_eaten != null){ - that.on_apple_eaten(that) - } - } - // all clear, do the move... - target.o.style.backgroundColor = color - target.o.age = size - target.o.moved_at = that._tick - cell.o.age = size - 1 - - } else { - if(cell.o.age <= 1) { - cell.o.style.backgroundColor = that.FIELD_COLOR - } - cell.o.age = parseInt(cell.o.age) - 1 - } - }, - - // user interface... - left: function(){ - that._snakes[color].direction = that._ccw[that._snakes[color].direction] - }, - right: function(){ - that._snakes[color].direction = that._cw[that._snakes[color].direction] - } - }) - }, - is_snake: function(cell){ - var snakes = this._snakes - var color = cell.o.style.backgroundColor - - for(var c in snakes){ - if(c == color) - return true - } - return false - }, - - Apple: function(cell){ - cell.o.style.backgroundColor = this.APPLE_COLOR - return cell - }, - is_apple: function(cell){ - return cell.o.style.backgroundColor == this.APPLE_COLOR - }, - - Wall: function(cell){ - cell.o.style.backgroundColor = this.WALL_COLOR - return cell - }, - is_wall: function(cell){ - return cell.o.style.backgroundColor == this.WALL_COLOR - }, - - is_empty: function(cell){ - return cell.o.style.backgroundColor == this.FIELD_COLOR - } - }).init(field_id) -} - -// this defines the basic game logic and controls the rules and levels... -/* - NOTE: it is recommended to create game objects in the folowing order: - 1) walls - 2) apples - 3) players - */ -function JSSnakeGame(field){ - - var game = { - field: field, - TICK: 300, - - // this enables snakes of the same colors... - SIMILAR_COLORS: false, - used_colors: function(){ - // this is a workaround the inability to directly create an object - // with field names not a literal identifier or string... - var res = {} - res[field.FIELD_COLOR] = true - res[field.WALL_COLOR] = true - res[field.APPLE_COLOR] = true - return res - }(), - - // utility methods... - _random_empty_cell: function(){ - // NOTE: if we are really unlucky, this will take - // really long, worse, if we are infinitely unlucky - // this will take an infinite amount of time... (almost) - var field = this.field - var i = field.cells.length-1 - var l = i - while(true){ - var c = field.Cell(Math.round(Math.random()*l)) - if(field.is_empty(c)) - return c - i-- - if(i == 0) - return null - } - }, - // key handler... - // functions: - // - control snake - dispatch player-specific keys to player-specific snake - // - create player - two unused keys pressed within timeout, random color - // - // modes: - // - player add - // - game control - // - // NOTE: modes can intersect... - // NOTE: modes are game state dependant... - key_time_frame: 0.5, - pending_key: null, - - _keyHandler: function(evt){ - var name, color - var key = window.event ? event.keyCode : evt.keyCode - - // find a target registered for key... - // XXX - - // no one is registered... - // if wait time set and is not exceeded create a player and register keys - if(!this.pending_key || Date().getTime() - this.pending_key['time'] > this.key_time_frame ){ - // if no wait time is set, set it and remember the key... - this.pending_key = {time: Date().getTime(), key: key} - } else { - // get name... - // XXX - // get color... - // XXX - this.Player(name, this.pending_key['key'], key, color) - this.pending_key = null - } - return true - }, - // create a new player... - // NOTE: direction and position are optional... - // XXX BUG: players should not get created facing a wall directly... - Player: function(name, ccw_button, cw_button, color, cell, direction, size){ - if(!this.SIMILAR_COLORS && this.used_colors[color] == true){ - // error: that the color is already used... - return - } - // register controls... - // XXX - - if(direction == null){ - direction = ['n', 's', 'e', 'w'][Math.round(Math.random()*3)] - } - if(cell === null){ - cell = this._random_empty_cell() - if(cell === null) - return - } - // create a snake... - this.used_colors[color] = true - return this.field.Snake(color, cell, direction, size) - }, - // NOTE: position is optional... - Apple: function(cell){ - // place an apple at a random and not occupied position... - var c = cell? cell: this._random_empty_cell() - if(c === null) - return - return this.field.Apple(c) - - }, - // NOTE: all arguments are optional... - Wall: function(cell, len, direction){ - // generate random data for arguments that are not given... - if(cell == null){ - cell = this._random_empty_cell() - if(cell === null) - return - } - if(direction == null){ - direction = ['n', 's', 'e', 'w'][Math.round(Math.random()*3)] - } - if(len == null){ - if(direction == 'n' || direction == 's') - var max = this.field.height - else - var max = this.field.width - len = Math.round(Math.random()*(max-1)) - } - // place a wall... - for(var i=0; i < len; i++){ - field.Wall(cell) - cell = cell[direction]() - } - }, - - // level generators and helpers... - levels: { - dotted: function(n){ - for(var i=0; i < n; i++) - game.Wall(null, 1, null) - }, - dashed: function(n, length){ - if(length == null) - length = 3 - for(var i=0; i < n; i++) - game.Wall(null, length, null) - }, - // specific level styles... - sand: function(){ - this.dotted(Math.round(game.field.cells.length/20)) - }, - walls: function(){ - this.dashed( - Math.round(game.field.cells.length/90), - Math.min( - game.field.width, - game.field.height)-2) - } - }, - - start: function(){ - // start the game... - field.start(this.TICK) - }, - stop: function(){ - field.stop() - } - } - - field.on_apple_eaten = function(){game.Apple()} - field.on_kill = function(snake){game.used_colors[snake.color] = false} - - //document.onkeyup = function(evt){return game._keyHandler(evt)} - - return game -} - -// vim:set ts=4 sw=4 spell : diff --git a/simplesnake/manifest.json b/simplesnake/manifest.json deleted file mode 100644 index 833e5bc..0000000 --- a/simplesnake/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "short_name": "SimpleSnake", - "name": "SimpleSnake", - "display": "standalone", - "orientation": "landscape", - "Theme_color": "white" -} diff --git a/simplesnake/simplesnake.appcache b/simplesnake/simplesnake.appcache deleted file mode 100755 index 3c41518..0000000 --- a/simplesnake/simplesnake.appcache +++ /dev/null @@ -1,12 +0,0 @@ -CACHE MANIFEST -# Timestamp: 20170425215205 - -CACHE: -simplesnake.html -simplesnake.css -simplesnake.js -manifest.json - -NETWORK: -* - diff --git a/simplesnake/simplesnake.css b/simplesnake/simplesnake.css deleted file mode 100755 index 013f755..0000000 --- a/simplesnake/simplesnake.css +++ /dev/null @@ -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; -} - - - -/*********************************************************************/ diff --git a/simplesnake/simplesnake.html b/simplesnake/simplesnake.html deleted file mode 100644 index 27c58ca..0000000 --- a/simplesnake/simplesnake.html +++ /dev/null @@ -1,22 +0,0 @@ - - - -Simple Snake - - - - - - - - - - - -

SimpleSnake

- -
- - - - diff --git a/simplesnake/simplesnake.js b/simplesnake/simplesnake.js deleted file mode 100755 index 6f109ee..0000000 --- a/simplesnake/simplesnake.js +++ /dev/null @@ -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('') - this._field.innerHTML = - `\n${ - l.map(function(){ - return ` ${ l.join('') } ` - }).join('\n') - }\n
` - }, - _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 : */