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