Shize? I should shee! Macool, Macool, orra whyi deed ye diie?
of a trying thirstay mournin? Sobs they sighdid at Fillagain’s
chrissormiss wake, all the hoolivans of the nation, prostrated in
their consternation, and their duodisimally profusive plethora of
ululation.
of a trying thirstay mournin? Sobs they sighdid at Fillagain’s
chrissormiss wake, all the hoolivans of the nation, prostrated in
their consternation, and their duodisimally profusive plethora of
ululation.
[Click.]
- Constructor Function: Snake
function Snake(options) { "use strict"; /* Private Fields */ const parameters = assign({}, Snake.defaultParameters); Object.freeze(assign(parameters, options)); const thisSnake = this, delay = parameters.delay, initialSnakeLength = parameters.length, snakeWidth = parameters.width, eyeColor = parameters.eyeColor, stepSize = parameters.width, snakeBody = new List(), methods = {}, steps = new List(), callStack = new Stack(), keyCodeAngleMap = (function () { const map = Object.create(null); map[KeyCode.UpArrow] = Snake.North; map[KeyCode.DownArrow] = Snake.South; map[KeyCode.RightArrow] = Snake.East; map[KeyCode.LeftArrow] = Snake.West; return Object.freeze(map); })(); let thisSnakeIsDead = false, commands = new Queue, angle = Snake.South, sT = 1, sL = 0, timeoutId = undefined, skinColor = parameters.skinColor; let commandSet = commands; /* Public Properties */ Object.defineProperties(this, { skinColor: { get: getSkinColor, set: setSkinColor }, isHalted: { get: isHalted }, isSleeping: { get: isSleeping }, isDead: { get: isDead }, isBitingOwnBody: { get: isBitingOwnBody }, isBitingBodyOf: { value: isBitingBodyOf }, isBodyBeingBittenBy: { value: isBodyBeingBittenBy } }); /* Public Mutator Methods */ this.go = go; this.stop = halt; this.grow = grow; this.kill = die; this.goNorth = function (n) { return go(n, Snake.North); }; this.goSouth = function (n) { return go(n, Snake.South); }; this.goEast = function (n) { return go(n, Snake.East); }; this.goWest = function (n) { return go(n, Snake.West); }; this.sleep = sleep; this.wake = wake; this.pause = function(ms) { return interrupt().sleep(ms); } this.beginMethod = beginMethod; this.endMethod = endMethod; this.doMethod = doMethod; this.ifTrue = ifTrue; this.whileTrue = whileTrue; /* Initialize new snake. */ (function initialize() { createSnake(initialSnakeLength); window.addEventListener('keydown', function (e) { if (commands.length > 0) commands.clear(); if (callStack.length > 0) callStack.clear(); angle = keyCodeAngleMap[e.keyCode]; if (isHalted()) poke(); }); })(); /* Return new snake. */ return Object.freeze(this); /* Private Utilities */ // Operations function wake() { angle = getHead().angle; poke(); } function poke() { if (thisSnakeIsDead) return; if (commands.length === 0 && callStack.length > 0) { commands = callStack.pop(); } if (commands.length > 0) { angle = commands.remove().call(); } if (angle === undefined) { halt(); } else { performNextStep(); if (isBitingOwnBody()) { die(); } else { timeoutId = window.setTimeout(poke, delay); } } return angle; } function halt() { if (!isHalted()) { window.clearTimeout(timeoutId); timeoutId = undefined; } } function die() { thisSnakeIsDead = true; thisSnake.skinColor = 'transparent'; } function go(n, a) { if (a === undefined) a = angle; if (!isFinite(n)) n = 0; while (n-- > 0) { commandSet.add(function () { return a; }); } if (isHalted() && !isBeingProgrammed()) poke(); return thisSnake; } function performNextStep() { sT = sL = 0; switch(angle) { case Snake.North: sT = -1; break; case Snake.South: sT = 1; break; case Snake.East: sL = 1; break; case Snake.West: sL = -1; break; } step(sT*stepSize, sL*stepSize, angle); } function step(distTop, distLeft, angle) { if (distTop === 0 && distLeft === 0) return; steps.addAtTail({ distT: distTop, distL: distLeft, angle: angle} ); moveSegments(); } function moveSegments() { const n = snakeBody.length; let m = steps.length; for (let i = 0; i < n && m-- > 0; i++) { const snakeSegment = snakeBody.elementAt(i); const step = steps.elementAt(m); snakeSegment.top += step.distT; snakeSegment.left += step.distL; snakeSegment.rotate(step.angle); } moveBackBone(); } function moveBackBone() { const n = snakeBody.length; let m = steps.length; for (let i = 1; i < n && m-- > 0; i++) { const curr = getTransformValue(i); const prev = getTransformValue(i - 1); if (curr !== prev) { // Turning. if (turnedRight(curr, prev)) rotateVertebrae(i, Angle.quarterTurnLeft); else if (turnedLeft(curr, prev)) rotateVertebrae(i, Angle.quarterTurnRight); else if (reversed(curr, prev)) rotateVertebrae(i, Angle.halfTurn); if (i === (n - 1)) { // Straighten tail. const snakeSegment = snakeBody.elementAt(i); rotateVertebrae(i, Angle.straight); snakeSegment.transform = prev; } } else { // Going straight. rotateVertebrae(i, Angle.straight); } } } function rotateVertebrae(n, angle) { snakeBody.elementAt(n).vertebrae2.rotate(angle); } function setSkinColor(color) { skinColor = color; snakeBody.forEach(function (segment) { segment.skin.backgroundColor = color; }); } // Values function getHead() { return snakeBody.elementAt(0); } function getSkinColor() { return skinColor; } function isBeingProgrammed() { return commandSet !== commands; } function isHalted() { return timeoutId === undefined; } function isDead() { return thisSnakeIsDead; } function isSleeping() { return isHalted() && !isDead(); } function isBitingOwnBody() { return isBitingBodyOf(thisSnake); } function isBodyBeingBittenBy(head) { const n = snakeBody.length; for (let i = 1; i < n; i++) { if (snakeBody.elementAt(i).intersectsWith(head)) return true; } return false; } function isBitingBodyOf(snake) { return snake.isBodyBeingBittenBy(getHead()); } function getTransformValue(n) { return (n < 0) ? undefined : snakeBody.elementAt(n).transform; } function turnedRight(curr, prev) { switch (prev) { case Snake.GoingNorth: return curr === Snake.GoingEast; case Snake.GoingSouth: return curr === Snake.GoingWest; case Snake.GoingEast: return curr === Snake.GoingSouth; case Snake.GoingWest: return curr === Snake.GoingNorth; } return false; } function turnedLeft(curr, prev) { switch (prev) { case Snake.GoingNorth: return curr === Snake.GoingWest; case Snake.GoingSouth: return curr === Snake.GoingEast; case Snake.GoingEast: return curr === Snake.GoingNorth; case Snake.GoingWest: return curr === Snake.GoingSouth; } return false; } function reversed(curr, prev) { switch (prev) { case Snake.GoingNorth: return curr === Snake.GoingSouth; case Snake.GoingSouth: return curr === Snake.GoingNorth; case Snake.GoingEast: return curr === Snake.GoingWest; case Snake.GoingWest: return curr === Snake.GoingEast; } return false; } // Programming function interrupt() { if (commands.length > 0) { callStack.push(commands); commands = new Queue(); } return thisSnake; } function isTrue(expression) { if (typeof expression === 'function') return expression() === true; else return expression === true; } function fetchNextCommand() { return commands.remove(); } function skipNextCommand() { fetchNextCommand(); } function executeNextCommand() { return fetchNextCommand().call(); } function ifTrue(expression) { commandSet.add(function () { if (!isTrue(expression)) { skipNextCommand(); } return executeNextCommand(); }); } function whileTrue(expression) { const whileLoop = function () { const command = fetchNextCommand(); if (isTrue(expression)) { interrupt; commands.add(command); commands.add(whileLoop); commands.add(command); } return executeNextCommand(); }; commandSet.add(whileLoop); return thisSnake; } function doMethod(name) { commandSet.add(function () { interrupt(); loadMethod(name); return executeNextCommand(); }); if (isHalted() && !isBeingProgrammed()) { poke(); } return thisSnake; } function sleep(ms) { commandSet.add(function () { if (Number.isFinite(ms)) { window.setTimeout(wake, ms); } return undefined; }); return thisSnake; } function parseMethodName(name) { return (name === undefined) ? 'anonymous' : name; } function beginMethod(name) { name = parseMethodName(name); commandSet = methods[name] = new Queue(); return thisSnake; } function endMethod() { commandSet = commands; return thisSnake; } function loadMethod(name) { name = parseMethodName(name); const method = methods[name]; if (method !== undefined && method.length !== undefined) { let n = method.length; while (n-- > 0) { const cmd = method.remove(); commands.add(cmd); method.add(cmd); } } return thisSnake; } // Create function grow(n) { repeat(appendBodySegment, n === undefined ? 1 : n); return thisSnake; } function createSnake(n) { createHeadSegment(); return grow(n-1); } function createHeadSegment() { const head = createHead(); head.appendChild(head.skin = createFace()); return attachToSnake(head); } function appendBodySegment() { const segment = createBackboneSegment(); segment.appendChild(segment.skin = createSkin()); return attachToSnake(segment); } function attachToSnake(segment) { let last, top = 0, left = 0; if (snakeBody.length > 0) { last = snakeBody.elementAt(snakeBody.length - 1); top = last.top; left = last.left; } snakeBody.addAtTail(segment); segment.zIndex = 1000 - snakeBody.length; segment.render(); step(sT * stepSize, sL * stepSize, angle); segment.top = top; segment.left = left; return segment; } function createSkin() { return new Shape({ width: snakeWidth + Snake.units, height: snakeWidth + Snake.units, backgroundColor: skinColor }); } function createVertebrae(propertyName) { const width = snakeWidth / 8; const style = { left: ((snakeWidth - width) / 2) + Snake.units, width: width + Snake.units, height: (snakeWidth / 2) + Snake.units, backgroundColor: 'white' }; style[propertyName] = 0; return new Shape(style); } function createIntervertebralDisc() { const width = snakeWidth / 6; return new Shape({ left: ((snakeWidth - width) / 2) + Snake.units, top: ((snakeWidth - width) / 2) + Snake.units, width: width + Snake.units, height: width + Snake.units, backgroundColor: 'white', borderRadius: '50%', }); } function createBackground(color) { return new Shape({ width: snakeWidth + Snake.units, height: snakeWidth + Snake.units, backgroundColor: color }); } function createBackboneSegment() { const background = createBackground('black'); background.vertebrae2 = createBackground('transparent'); background.vertebrae2.appendChild(createVertebrae('bottom')) background.appendChild(background.vertebrae2); background.appendChild(createVertebrae('top')); return background.appendChild(createIntervertebralDisc()); } function createSkull() { const skull = new Shape({ width: snakeWidth + Snake.units, height: snakeWidth + Snake.units, bottom: 0, backgroundColor: 'white', borderRadius: '45%' }); return addEyes(skull, 'black'); } function createHead() { const background = createBackground('black'); background.appendChild(createTongue(background)); return background.appendChild(createSkull()); } function createFace() { return addEyes(createSkin(), eyeColor); } function createTongue(head) { const tongueLength = head.height / 2; const tongueWidth = head.width / 5; return new Shape({ width: tongueWidth + Snake.units, height: tongueLength + Snake.units, top: head.height + Snake.units, left: ((head.width / 2) - (tongueWidth / 2)) + Snake.units, backgroundColor: 'red', // Bifurcated (forked) tongue: clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 50% 70%, 0% 100%)' }); } function createEye(eyeColor) { const eyeDiameter = snakeWidth / 5; return new Shape({ borderRadius: '50%', width: eyeDiameter + Snake.units, height: eyeDiameter + Snake.units, backgroundColor: eyeColor }); } function addEyes(face, eyeColor) { const leftEye = createEye(eyeColor); const rightEye = createEye(eyeColor); const eyeInset = face.width / 6; const eyeDepth = 3 * face.height / 7; rightEye.right = leftEye.left = eyeInset + Snake.units; rightEye.bottom = leftEye.bottom = eyeDepth + Snake.units; face.appendChild(leftEye); return face.appendChild(rightEye); } } /* Snake Constants */ Object.defineProperties(Snake, { units: {value: 'px'}, North: {value: Angle.halfTurn}, South: {value: Angle.straight}, East: {value: Angle.quarterTurnLeft}, West: {value: Angle.quarterTurnRight} }); Object.defineProperties(Snake, { GoingNorth: {value: 'rotate(' + Snake.North + ')'}, GoingSouth: {value: 'rotate(' + Snake.South + ')'}, GoingEast: {value: 'rotate(' + Snake.East + ')'}, GoingWest: {value: 'rotate(' + Snake.West + ')'} }); Snake.defaultParameters = Object.freeze({ delay: 500, length: 4, width: 20, skinColor: 'green', eyeColor: 'blue' });