diff --git a/src/blockrain.jquery.libs.js b/src/blockrain.jquery.libs.js deleted file mode 100644 index 21eb819..0000000 --- a/src/blockrain.jquery.libs.js +++ /dev/null @@ -1,2 +0,0 @@ -// jQuery Widget -(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){var t=0,i=Array.prototype.slice;e.cleanData=function(t){return function(i){var s,n,a;for(a=0;null!=(n=i[a]);a++)try{s=e._data(n,"events"),s&&s.remove&&e(n).triggerHandler("remove")}catch(o){}t(i)}}(e.cleanData),e.widget=function(t,i,s){var n,a,o,r,h={},l=t.split(".")[0];return t=t.split(".")[1],n=l+"-"+t,s||(s=i,i=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[l]=e[l]||{},a=e[l][t],o=e[l][t]=function(e,t){return this._createWidget?(arguments.length&&this._createWidget(e,t),void 0):new o(e,t)},e.extend(o,a,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),r=new i,r.options=e.widget.extend({},r.options),e.each(s,function(t,s){return e.isFunction(s)?(h[t]=function(){var e=function(){return i.prototype[t].apply(this,arguments)},n=function(e){return i.prototype[t].apply(this,e)};return function(){var t,i=this._super,a=this._superApply;return this._super=e,this._superApply=n,t=s.apply(this,arguments),this._super=i,this._superApply=a,t}}(),void 0):(h[t]=s,void 0)}),o.prototype=e.widget.extend(r,{widgetEventPrefix:a?r.widgetEventPrefix||t:t},h,{constructor:o,namespace:l,widgetName:t,widgetFullName:n}),a?(e.each(a._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete a._childConstructors):i._childConstructors.push(o),e.widget.bridge(t,o),o},e.widget.extend=function(t){for(var s,n,a=i.call(arguments,1),o=0,r=a.length;r>o;o++)for(s in a[o])n=a[o][s],a[o].hasOwnProperty(s)&&void 0!==n&&(t[s]=e.isPlainObject(n)?e.isPlainObject(t[s])?e.widget.extend({},t[s],n):e.widget.extend({},n):n);return t},e.widget.bridge=function(t,s){var n=s.prototype.widgetFullName||t;e.fn[t]=function(a){var o="string"==typeof a,r=i.call(arguments,1),h=this;return a=!o&&r.length?e.widget.extend.apply(null,[a].concat(r)):a,o?this.each(function(){var i,s=e.data(this,n);return"instance"===a?(h=s,!1):s?e.isFunction(s[a])&&"_"!==a.charAt(0)?(i=s[a].apply(s,r),i!==s&&void 0!==i?(h=i&&i.jquery?h.pushStack(i.get()):i,!1):void 0):e.error("no such method '"+a+"' for "+t+" widget instance"):e.error("cannot call methods on "+t+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var t=e.data(this,n);t?(t.option(a||{}),t._init&&t._init()):e.data(this,n,new s(a,this))}),h}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(i,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=t++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this.options=e.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(t,i){var s,n,a,o=t;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof t)if(o={},s=t.split("."),t=s.shift(),s.length){for(n=o[t]=e.widget.extend({},this.options[t]),a=0;s.length-1>a;a++)n[s[a]]=n[s[a]]||{},n=n[s[a]];if(t=s.pop(),1===arguments.length)return void 0===n[t]?null:n[t];n[t]=i}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=i}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled",!!t),t&&(this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus"))),this},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_on:function(t,i,s){var n,a=this;"boolean"!=typeof t&&(s=i,i=t,t=!1),s?(i=n=e(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),e.each(s,function(s,o){function r(){return t||a.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?a[o]:o).apply(a,arguments):void 0}"string"!=typeof o&&(r.guid=o.guid=o.guid||r.guid||e.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+a.eventNamespace,u=h[2];u?n.delegate(u,l,r):i.bind(l,r)})},_off:function(t,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(i).undelegate(i),this.bindings=e(this.bindings.not(t).get()),this.focusable=e(this.focusable.not(t).get()),this.hoverable=e(this.hoverable.not(t).get())},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,o=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(o)&&o.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var o,r=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),o=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),o&&e.effects&&e.effects.effect[r]?s[t](n):r!==t&&s[r]?s[r](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}}),e.widget}); \ No newline at end of file diff --git a/src/blockrain.jquery.src.js b/src/blockrain.jquery.src.js deleted file mode 100644 index 7fa74e0..0000000 --- a/src/blockrain.jquery.src.js +++ /dev/null @@ -1,1578 +0,0 @@ -((function ( $ ) { - - "use strict"; - - $.widget('aerolab.blockrain', { - - options: { - autoplay: false, // Let a bot play the game - autoplayRestart: true, // Restart the game automatically once a bot loses - showFieldOnStart: true, // Show a bunch of random blocks on the start screen (it looks nice) - theme: null, // The theme name or a theme object - blockWidth: 10, // How many blocks wide the field is (The standard is 10 blocks) - autoBlockWidth: false, // The blockWidth is dinamically calculated based on the autoBlockSize. Disabled blockWidth. Useful for responsive backgrounds - autoBlockSize: 24, // The max size of a block for autowidth mode - difficulty: 'normal', // Difficulty (normal|nice|evil). - speed: 20, // The speed of the game. The higher, the faster the pieces go. - asdwKeys: true, // Enable ASDW keys - - // Copy - playText: 'Let\'s play some Tetris', - playButtonText: 'Play', - gameOverText: 'Game Over', - restartButtonText: 'Play Again', - scoreText: 'Score', - - // Basic Callbacks - onStart: function(){}, - onRestart: function(){}, - onGameOver: function(score){}, - - // When a block is placed - onPlaced: function(){}, - // When a line is made. Returns the number of lines, score assigned and total score - onLine: function(lines, scoreIncrement, score){} - }, - - - /** - * Start/Restart Game - */ - start: function() { - this._doStart(); - this.options.onStart.call(this.element); - }, - - restart: function() { - this._doStart(); - this.options.onRestart.call(this.element); - }, - - gameover: function() { - this.showGameOverMessage(); - this._board.gameover = true; - this.options.onGameOver.call(this.element, this._filled.score); - }, - - _doStart: function() { - this._filled.clearAll(); - this._filled._resetScore(); - this._board.cur = this._board.nextShape(); - this._board.started = true; - this._board.gameover = false; - this._board.dropDelay = 5; - this._board.render(true); - this._board.animate(); - - this._$start.fadeOut(150); - this._$gameover.fadeOut(150); - this._$score.fadeIn(150); - }, - - - pause: function() { - this._board.paused = true; - }, - - resume: function() { - this._board.paused = false; - }, - - autoplay: function(enable) { - if( typeof enable !== 'boolean' ){ enable = true; } - - // On autoplay, start the game right away - this.options.autoplay = enable; - if( enable && ! this._board.started ) { - this._doStart(); - } - this._setupControls( ! enable ); - this._setupTouchControls( ! enable ); - }, - - controls: function(enable) { - if( typeof enable !== 'boolean' ){ enable = true; } - this._setupControls(enable); - }, - - touchControls: function(enable) { - if( typeof enable !== 'boolean' ){ enable = true; } - this._setupTouchControls(enable); - }, - - score: function(newScore) { - if( typeof newScore !== 'undefined' && parseInt(newScore) >= 0 ) { - this._filled.score = parseInt(newScore); - this._$scoreText.text(this._filled_score); - } - return this._filled.score; - }, - - freesquares: function() { - return this._filled.getFreeSpaces(); - }, - - showStartMessage: function() { - this._$start.show(); - }, - - showGameOverMessage: function() { - this._$gameover.show(); - }, - - /** - * Update the sizes of the renderer (this makes the game responsive) - */ - updateSizes: function() { - - this._PIXEL_WIDTH = this.element.innerWidth(); - this._PIXEL_HEIGHT = this.element.innerHeight(); - - this._BLOCK_WIDTH = this.options.blockWidth; - this._BLOCK_HEIGHT = Math.floor(this.element.innerHeight() / this.element.innerWidth() * this._BLOCK_WIDTH); - - this._block_size = Math.floor(this._PIXEL_WIDTH / this._BLOCK_WIDTH); - this._border_width = 2; - - // Recalculate the pixel width and height so the canvas always has the best possible size - this._PIXEL_WIDTH = this._block_size * this._BLOCK_WIDTH; - this._PIXEL_HEIGHT = this._block_size * this._BLOCK_HEIGHT; - - this._$canvas .attr('width', this._PIXEL_WIDTH) - .attr('height', this._PIXEL_HEIGHT); - }, - - - theme: function(newTheme){ - - if( typeof newTheme === 'undefined' ) { - return this.options.theme || this._theme; - } - - // Setup the theme properly - if( typeof newTheme === 'string' ) { - this.options.theme = newTheme; - this._theme = $.extend(true, {}, BlockrainThemes[newTheme]); - } - else { - this.options.theme = null; - this._theme = newTheme; - } - - if( typeof this._theme === 'undefined' || this._theme === null ) { - this._theme = $.extend(true, {}, BlockrainThemes['retro']); - this.options.theme = 'retro'; - } - - if( isNaN(parseInt(this._theme.strokeWidth)) || typeof parseInt(this._theme.strokeWidth) !== 'number' ) { - this._theme.strokeWidth = 2; - } - - // Load the image assets - this._preloadThemeAssets(); - - if( this._board !== null ) { - if( typeof this._theme.background === 'string' ) { - this._$canvas.css('background-color', this._theme.background); - } - this._board.render(); - } - }, - - - // Theme - _theme: { - - }, - - - // UI Elements - _$game: null, - _$canvas: null, - _$gameholder: null, - _$start: null, - _$gameover: null, - _$score: null, - _$scoreText: null, - - - // Canvas - _canvas: null, - _ctx: null, - - - // Initialization - _create: function() { - - var game = this; - - this.theme(this.options.theme); - - this._createHolder(); - this._createUI(); - - this._refreshBlockSizes(); - - this.updateSizes(); - - $(window).resize(function(){ - //game.updateSizes(); - }); - - this._SetupShapeFactory(); - this._SetupFilled(); - this._SetupInfo(); - this._SetupBoard(); - - this._info.init(); - this._board.init(); - - var renderLoop = function(){ - requestAnimationFrame(renderLoop); - game._board.render(); - }; - renderLoop(); - - if( this.options.autoplay ) { - this.autoplay(true); - this._setupTouchControls(false); - } else { - this._setupControls(true); - this._setupTouchControls(false); - } - - }, - - _checkCollisions: function(x, y, blocks, checkDownOnly) { - // x & y should be aspirational values - var i = 0, len = blocks.length, a, b; - for (; i= this._BLOCK_HEIGHT || this._filled.check(a, b)) { - return true; - } else if (!checkDownOnly && a < 0 || a >= this._BLOCK_WIDTH) { - return true; - } - } - return false; - }, - - - _board: null, - _info: null, - _filled: null, - - - /** - * Draws the background - */ - _drawBackground: function() { - - if( typeof this._theme.background !== 'string' ) { - return; - } - - if( this._theme.backgroundGrid instanceof Image ) { - - // Not loaded - if( this._theme.backgroundGrid.width === 0 || this._theme.backgroundGrid.height === 0 ){ return; } - - this._ctx.globalAlpha = 1.0; - - for( var x=0; x maxx) { maxx = blocks[i]; } - if (blocks[i+1] < miny) { miny = blocks[i+1]; } - if (blocks[i+1] > maxy) { maxy = blocks[i+1]; } - } - return { - left: minx, - right: maxx, - top: miny, - bottom: maxy, - width: maxx - minx, - height: maxy - miny - }; - } - }); - - return this.init(); - }; - - this._shapeFactory = { - line: function() { - return new Shape(game, game._shapes.line, false, 'line'); - }, - square: function() { - return new Shape(game, game._shapes.square, false, 'square'); - }, - arrow: function() { - return new Shape(game, game._shapes.arrow, false, 'arrow'); - }, - leftHook: function() { - return new Shape(game, game._shapes.leftHook, false, 'leftHook'); - }, - rightHook: function() { - return new Shape(game, game._shapes.rightHook, false, 'rightHook'); - }, - leftZag: function() { - return new Shape(game, game._shapes.leftZag, false, 'leftZag'); - }, - rightZag: function() { - return new Shape(game, game._shapes.rightZag, false, 'rightZag'); - } - }; - }, - - - _SetupFilled: function() { - var game = this; - if( this._filled !== null ){ return; } - - this._filled = { - data: new Array(game._BLOCK_WIDTH * game._BLOCK_HEIGHT), - score: 0, - toClear: {}, - check: function(x, y) { - return this.data[this.asIndex(x, y)]; - }, - add: function(x, y, blockType, blockVariation, blockIndex, blockOrientation) { - if (x >= 0 && x < game._BLOCK_WIDTH && y >= 0 && y < game._BLOCK_HEIGHT) { - this.data[this.asIndex(x, y)] = { - blockType: blockType, - blockVariation: blockVariation, - blockIndex: blockIndex, - blockOrientation: blockOrientation - }; - } - }, - getFreeSpaces: function() { - var count = 0; - for( var i=0; i=0; i--) { - this.data[i] = (i >= game._BLOCK_WIDTH ? this.data[i-game._BLOCK_WIDTH] : undefined); - } - }, - checkForClears: function() { - var startLines = game._board.lines; - var rows = [], i, len, count, mod; - - for (i=0, len=this.data.length; i 1 ) { - game._board.dropDelay *= 0.9; - } - } - - var clearedLines = game._board.lines - startLines; - this._updateScore(clearedLines); - }, - _updateScore: function(numLines) { - if( numLines <= 0 ) { return; } - var scores = [0,400,1000,3000,12000]; - if( numLines >= scores.length ){ numLines = scores.length-1 } - - this.score += scores[numLines]; - game._$scoreText.text(this.score); - - game.options.onLine.call(game.element, numLines, scores[numLines], this.score); - }, - _resetScore: function() { - this.score = 0; - game._$scoreText.text(this.score); - }, - draw: function() { - for (var i=0, len=this.data.length, row, color; i= this.dropDelay) || - (game.options.autoplay) || - (this.holding.drop && (now - this.holding.drop) >= this.holdingThreshold) ) { - drop = true; - moved = true; - this.dropCount = 0; - } - - // Move Left by holding - if( this.holding.left && (now - this.holding.left) >= this.holdingThreshold ) { - moved = true; - this.cur.moveLeft(); - } - - // Move Right by holding - if( this.holding.right && (now - this.holding.right) >= this.holdingThreshold ) { - moved = true; - this.cur.moveRight(); - } - - // Test for a collision, add the piece to the filled blocks and fetch the next one - if (drop) { - var cur = this.cur, x = cur.x, y = cur.y, blocks = cur.getBlocks(); - if (game._checkCollisions(x, y+1, blocks, true)) { - drop = false; - var blockIndex = 0; - for (var i=0; i 0) { - return blockTheme[0]; - } else { - return null; - } - } else { - return blockTheme; - } - } - - if( typeof falling !== 'boolean' ){ falling = true; } - if( falling ) { - if( typeof game._theme.primary === 'string' && game._theme.primary !== '' ) { - return game._theme.primary; - } else if( typeof game._theme.blocks !== 'undefined' && game._theme.blocks !== null ) { - return getBlockVariation(game._theme.blocks[blockType], blockVariation); - } else { - return getBlockVariation(game._theme.complexBlocks[blockType], blockVariation); - } - } else { - if( typeof game._theme.secondary === 'string' && game._theme.secondary !== '' ) { - return game._theme.secondary; - } else if( typeof game._theme.blocks !== 'undefined' && game._theme.blocks !== null ) { - return getBlockVariation(game._theme.blocks[blockType], blockVariation); - } else { - return getBlockVariation(game._theme.complexBlocks[blockType], blockVariation); - } - } - } - - }; - - game._niceShapes = game._getNiceShapes(); - }, - - // Utility Functions - _randInt: function(a, b) { return a + Math.floor(Math.random() * (1 + b - a)); }, - _randSign: function() { return this._randInt(0, 1) * 2 - 1; }, - _randChoice: function(choices) { return choices[this._randInt(0, choices.length-1)]; }, - - - /** - * Find base64 encoded images and load them as image objects, which can be used by the canvas renderer - */ - _preloadThemeAssets: function() { - - var game = this; - - var hexColorcheck = new RegExp('^#[A-F0-9+]{3,6}', 'i'); - var base64check = new RegExp('^data:image/(png|gif|jpg);base64,', 'i'); - - var handleAssetLoad = function() { - // Rerender the board as soon as an asset loads - if( game._board ) { - game._board.render(true); - } - }; - - var loadAsset = function(src) { - var plainSrc = src; - if( ! hexColorcheck.test( plainSrc ) ) { - // It's an image - src = new Image(); - src.src = plainSrc; - src.onload = handleAssetLoad; - } else { - // It's a color - src = plainSrc; - } - return src; - }; - - var startAssetLoad = function(block) { - // Assets can be an array of variation so they can change color/design randomly - if( $.isArray(block) && block.length > 0 ) { - for( var i=0; i
'); - this._$gameholder.css('position', 'relative').css('width', '100%').css('height', '100%'); - - this.element.html('').append(this._$gameholder); - - // Create the game canvas and context - this._$canvas = $(''); - if( typeof this._theme.background === 'string' ) { - this._$canvas.css('background-color', this._theme.background); - } - this._$gameholder.append(this._$canvas); - - this._canvas = this._$canvas.get(0); - this._ctx = this._canvas.getContext('2d'); - - }, - - - _createUI: function() { - - var game = this; - - // Score - game._$score = $( - '
'+ - '
'+ - '
'+ this.options.scoreText +'
'+ - '
0
'+ - '
'+ - '
').hide(); - game._$scoreText = game._$score.find('.blockrain-score-num'); - game._$gameholder.append(game._$score); - - // Create the start menu - game._$start = $( - '
'+ - '
'+ - '
'+ this.options.playText +'
'+ - ''+ this.options.playButtonText +''+ - '
'+ - '
').hide(); - game._$gameholder.append(game._$start); - - game._$start.find('.blockrain-start-btn').click(function(event){ - event.preventDefault(); - game.start(); - }); - - // Create the game over menu - game._$gameover = $( - '
'+ - '
'+ - '
'+ this.options.gameOverText +'
'+ - ''+ this.options.restartButtonText +''+ - '
'+ - '
').hide(); - game._$gameover.find('.blockrain-game-over-btn').click(function(event){ - event.preventDefault(); - game.restart(); - }); - game._$gameholder.append(game._$gameover); - - this._createControls(); - }, - - - _createControls: function() { - - var game = this; - - game._$touchLeft = $('').appendTo(game._$gameholder); - game._$touchRight = $('').appendTo(game._$gameholder); - game._$touchRotateRight = $('').appendTo(game._$gameholder); - game._$touchRotateLeft = $('').appendTo(game._$gameholder); - game._$touchDrop = $('').appendTo(game._$gameholder); - - }, - - - _refreshBlockSizes: function() { - - if( this.options.autoBlockWidth ) { - this.options.blockWidth = Math.ceil( this.element.width() / this.options.autoBlockSize ); - } - - }, - - - _getNiceShapes: function() { - /* - * Things I need for this to work... - * - ability to test each shape with this._filled data - * - maybe give empty spots scores? and try to maximize the score? - */ - - var game = this; - - var shapes = {}, - attr; - - for( var attr in this._shapeFactory ) { - shapes[attr] = this._shapeFactory[attr](); - } - - function scoreBlocks(possibles, blocks, x, y, filled, width, height) { - var i, len=blocks.length, score=0, bottoms = {}, tx, ty, overlaps; - - // base score - for (i=0; i best_score_for_shape) { - best_score_for_shape = score; - best_orientation_for_shape = i; - best_x_for_shape = x; - } - break; - } - } - } - } - - if ((evil && best_score_for_shape < best_score) || - (!evil && best_score_for_shape > best_score)) { - best_shape = shape; - best_score = best_score_for_shape; - best_orientation = best_orientation_for_shape; - best_x = best_x_for_shape; - } - } - - best_shape.best_orientation = best_orientation; - best_shape.best_x = best_x; - - return best_shape; - }; - - func.no_preview = true; - return func; - }, - - - _randomShapes: function() { - // Todo: The shapefuncs should be cached. - var shapeFuncs = []; - $.each(this._shapeFactory, function(k,v) { shapeFuncs.push(v); }); - - return this._randChoice(shapeFuncs); - }, - - - /** - * Controls - */ - _setupControls: function(enable) { - - var game = this; - - var moveLeft = function(start) { - if( ! start ) { game._board.holding.left = null; return; } - if( ! game._board.holding.left ) { - game._board.cur.moveLeft(); - game._board.holding.left = Date.now(); - game._board.holding.right = null; - } - } - var moveRight = function(start) { - if( ! start ) { game._board.holding.right = null; return; } - if( ! game._board.holding.right ) { - game._board.cur.moveRight(); - game._board.holding.right = Date.now(); - game._board.holding.left = null; - } - } - var drop = function(start) { - if( ! start ) { game._board.holding.drop = null; return; } - if( ! game._board.holding.drop ) { - game._board.cur.drop(); - game._board.holding.drop = Date.now(); - } - } - var rotateLeft = function() { - game._board.cur.rotate('left'); - } - var rotateRight = function() { - game._board.cur.rotate('right'); - } - - // Handlers: These are used to be able to bind/unbind controls - var handleKeyDown = function(evt) { - if( ! game._board.cur ) { return true; } - var caught = false; - - caught = true; - if (game.options.asdwKeys) { - switch(evt.keyCode) { - case 65: /*a*/ moveLeft(true); break; - case 68: /*d*/ moveRight(true); break; - case 83: /*s*/ drop(true); break; - case 87: /*w*/ game._board.cur.rotate('right'); break; - } - } - switch(evt.keyCode) { - case 37: /*left*/ moveLeft(true); break; - case 39: /*right*/ moveRight(true); break; - case 40: /*down*/ drop(true); break; - case 38: /*up*/ game._board.cur.rotate('right'); break; - case 88: /*x*/ game._board.cur.rotate('right'); break; - case 90: /*z*/ game._board.cur.rotate('left'); break; - default: caught = false; - } - if (caught) evt.preventDefault(); - return !caught; - }; - - - var handleKeyUp = function(evt) { - if( ! game._board.cur ) { return true; } - var caught = false; - - caught = true; - if (game.options.asdwKeys) { - switch(evt.keyCode) { - case 65: /*a*/ moveLeft(false); break; - case 68: /*d*/ moveRight(false); break; - case 83: /*s*/ drop(false); break; - } - } - switch(evt.keyCode) { - case 37: /*left*/ moveLeft(false); break; - case 39: /*right*/ moveRight(false); break; - case 40: /*down*/ drop(false); break; - default: caught = false; - } - if (caught) evt.preventDefault(); - return !caught; - }; - - function isStopKey(evt) { - var cfg = { - stopKeys: {37:1, 38:1, 39:1, 40:1} - }; - - var isStop = (cfg.stopKeys[evt.keyCode] || (cfg.moreStopKeys && cfg.moreStopKeys[evt.keyCode])); - if (isStop) evt.preventDefault(); - return isStop; - } - - function getKey(evt) { return 'safekeypress.' + evt.keyCode; } - - function keydown(evt) { - var key = getKey(evt); - $.data(this, key, ($.data(this, key) || 0) - 1); - return handleKeyDown.call(this, evt); - } - - function keyup(evt) { - $.data(this, getKey(evt), 0); - handleKeyUp.call(this, evt); - return isStopKey(evt); - } - - // Unbind everything by default - // Use event namespacing so we don't ruin other keypress events - $(document) .unbind('keydown.blockrain') - .unbind('keyup.blockrain'); - - if( ! game.options.autoplay ) { - if( enable ) { - $(document) .bind('keydown.blockrain', keydown) - .bind('keyup.blockrain', keyup); - } - } - }, - - - _setupTouchControls: function(enable) { - - var game = this; - - // Movements can be held for faster movement - var moveLeft = function(event){ - event.preventDefault(); - game._board.cur.moveLeft(); - game._board.holding.left = Date.now(); - game._board.holding.right = null; - game._board.holding.drop = null; - }; - var moveRight = function(event){ - event.preventDefault(); - game._board.cur.moveRight(); - game._board.holding.right = Date.now(); - game._board.holding.left = null; - game._board.holding.drop = null; - }; - var drop = function(event){ - event.preventDefault(); - game._board.cur.drop(); - game._board.holding.drop = Date.now(); - }; - var endMoveLeft = function(event){ - event.preventDefault(); - game._board.holding.left = null; - }; - var endMoveRight = function(event){ - event.preventDefault(); - game._board.holding.right = null; - }; - var endDrop = function(event){ - event.preventDefault(); - game._board.holding.drop = null; - }; - - // Rotations can't be held - var rotateLeft = function(event){ - event.preventDefault(); - game._board.cur.rotate('left'); - }; - var rotateRight = function(event){ - event.preventDefault(); - game._board.cur.rotate('right'); - }; - - // Unbind everything by default - game._$touchLeft.unbind('touchstart touchend click'); - game._$touchRight.unbind('touchstart touchend click'); - game._$touchRotateLeft.unbind('touchstart touchend click'); - game._$touchRotateRight.unbind('touchstart touchend click'); - game._$touchDrop.unbind('touchstart touchend click'); - - if( ! game.options.autoplay && enable ) { - game._$touchLeft.show().bind('touchstart click', moveLeft).bind('touchend', endMoveLeft); - game._$touchRight.show().bind('touchstart click', moveRight).bind('touchend', endMoveRight); - game._$touchDrop.show().bind('touchstart click', drop).bind('touchend', endDrop); - game._$touchRotateLeft.show().bind('touchstart click', rotateLeft); - game._$touchRotateRight.show().bind('touchstart click', rotateRight); - } else { - game._$touchLeft.hide(); - game._$touchRight.hide(); - game._$touchRotateLeft.hide(); - game._$touchRotateRight.hide(); - game._$touchDrop.hide(); - } - - } - - }); - -})(jQuery)); \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e6ff363 --- /dev/null +++ b/src/index.js @@ -0,0 +1,1443 @@ +import $ from 'jquery'; + +import Shape from './Shape'; +import BlockrainThemes from './themes'; + + + +const defaultOptions = { + autoplay: false, // Let a bot play the game + autoplayRestart: true, // Restart the game automatically once a bot loses + showFieldOnStart: true, // Show a bunch of random blocks on the start screen (it looks nice) + theme: null, // The theme name or a theme object + blockWidth: 10, // How many blocks wide the field is (The standard is 10 blocks) + autoBlockWidth: false, // The blockWidth is dinamically calculated based on the autoBlockSize. Disabled blockWidth. Useful for responsive backgrounds + autoBlockSize: 24, // The max size of a block for autowidth mode + difficulty: 'normal', // Difficulty (normal|nice|evil). + speed: 20, // The speed of the game. The higher, the faster the pieces go. + asdwKeys: true, // Enable ASDW keys + + // Copy + playText: 'Let\'s play some Tetris', + playButtonText: 'Play', + gameOverText: 'Game Over', + restartButtonText: 'Play Again', + scoreText: 'Score', + + // Basic Callbacks + onStart: function() {}, + onRestart: function() {}, + onGameOver: function(score) {}, + + // When a block is placed + onPlaced: function() {}, + // When a line is made. Returns the number of lines, score assigned and total score + onLine: function(lines, scoreIncrement, score) {} +} + +class Blockrain { + + constructor($el, options) { + this.element = $el; + this.options = $.extend({}, defaultOptions, options); + + // Theme + this._theme = {}; + + // UI Elements + this._$game = null; + this._$canvas = null; + this._$gameholder = null; + this._$start = null; + this._$gameover = null; + this._$score = null; + this._$scoreText = null; + + // Canvas + this._canvas = null; + this._ctx = null; + + this._board = null; + this._info = null; + this._filled = null; + + /** + * Shapes + */ + _shapeFactory = null; + + /** + * The shapes have a reference point (the dot) and always rotate left. + * Keep in mind that the blocks should keep in the same relative position when rotating, + * to allow for custom per-block themes. + */ + _shapes = { + /* + * X + * O XOXX + * X + * X + * . . + */ + line: [ + [ 0, -1, 0, -2, 0, -3, 0, -4], + [ 2, -2, 1, -2, 0, -2, -1, -2], + [ 0, -4, 0, -3, 0, -2, 0, -1], + [-1, -2, 0, -2, 1, -2, 2, -2] + ], + /* + * XX + * XX + */ + square: [ + [0, 0, 1, 0, 0, -1, 1, -1], + [1, 0, 1, -1, 0, 0, 0, -1], + [1, -1, 0, -1, 1, 0, 0, 0], + [0, -1, 0, 0, 1, -1, 1, 0] + ], + /* + * X X X + * XOX XO XOX OX + * . .X .X .X + */ + arrow: [ + [0, -1, 1, -1, 2, -1, 1, -2], + [1, 0, 1, -1, 1, -2, 0, -1], + [2, -1, 1, -1, 0, -1, 1, 0], + [1, -2, 1, -1, 1, 0, 2, -1] + ], + /* + * X X XX + * O XOX O XOX + * .XX . .X X + */ + rightHook: [ + [2, 0, 1, 0, 1, -1, 1, -2], + [2, -2, 2, -1, 1, -1, 0, -1], + [0, -2, 1, -2, 1, -1, 1, 0], + [0, 0, 0, -1, 1, -1, 2, -1] + ], + /* + * X XX X + * O XOX O XOX + * XX . X .X . + */ + leftHook: [ + [0, 0, 1, 0, 1, -1, 1, -2], + [2, 0, 2, -1, 1, -1, 0, -1], + [2, -2, 1, -2, 1, -1, 1, 0], + [0, -2, 0, -1, 1, -1, 2, -1] + ], + /* + * X XX + * XO OX + * X . + */ + leftZag: [ + [0, 0, 0, -1, 1, -1, 1, -2], + [2, -1, 1, -1, 1, -2, 0, -2], + [1, -2, 1, -1, 0, -1, 0, 0], + [0, -2, 1, -2, 1, -1, 2, -1] + ], + /* + * X + * XO OX + * .X XX + */ + rightZag: [ + [1, 0, 1, -1, 0, -1, 0, -2], + [2, -1, 1, -1, 1, 0, 0, 0], + [0, -2, 0, -1, 1, -1, 1, 0], + [0, 0, 1, 0, 1, -1, 2, -1] + ] + }; + + } + + /** + * Start/Restart Game + */ + start() { + this._doStart(); + this.options.onStart.call(this.element); + } + + restart() { + this._doStart(); + this.options.onRestart.call(this.element); + } + + gameover() { + this.showGameOverMessage(); + this._board.gameover = true; + this.options.onGameOver.call(this.element, this._filled.score); + } + + _doStart() { + this._filled.clearAll(); + this._filled._resetScore(); + this._board.cur = this._board.nextShape(); + this._board.started = true; + this._board.gameover = false; + this._board.dropDelay = 5; + this._board.render(true); + this._board.animate(); + + this._$start.fadeOut(150); + this._$gameover.fadeOut(150); + this._$score.fadeIn(150); + } + + pause() { + this._board.paused = true; + } + + resume() { + this._board.paused = false; + } + + autoplay(enable=true) { + // On autoplay, start the game right away + this.options.autoplay = enable; + if (enable && !this._board.started) { + this._doStart(); + } + this._setupControls(!enable); + this._setupTouchControls(!enable); + } + + controls(enable=true) { + this._setupControls(enable); + } + + touchControls(enable=true) { + this._setupTouchControls(enable); + } + + score(newScore) { + if (typeof newScore !== 'undefined' && parseInt(newScore) >= 0) { + this._filled.score = parseInt(newScore); + this._$scoreText.text(this._filled_score); + } + return this._filled.score; + } + + freesquares() { + return this._filled.getFreeSpaces(); + } + + showStartMessage() { + this._$start.show(); + } + + showGameOverMessage() { + this._$gameover.show(); + } + + /** + * Update the sizes of the renderer (this makes the game responsive) + */ + updateSizes() { + this._PIXEL_WIDTH = this.element.innerWidth(); + this._PIXEL_HEIGHT = this.element.innerHeight(); + + this._BLOCK_WIDTH = this.options.blockWidth; + this._BLOCK_HEIGHT = Math.floor(this.element.innerHeight() / this.element.innerWidth() * this._BLOCK_WIDTH); + + this._block_size = Math.floor(this._PIXEL_WIDTH / this._BLOCK_WIDTH); + this._border_width = 2; + + // Recalculate the pixel width and height so the canvas always has the best possible size + this._PIXEL_WIDTH = this._block_size * this._BLOCK_WIDTH; + this._PIXEL_HEIGHT = this._block_size * this._BLOCK_HEIGHT; + + this._$canvas .attr('width', this._PIXEL_WIDTH) + .attr('height', this._PIXEL_HEIGHT); + } + + theme(newTheme) { + if (typeof newTheme === 'undefined') { + return this.options.theme || this._theme; + } + + // Setup the theme properly + if (typeof newTheme === 'string') { + this.options.theme = newTheme; + this._theme = $.extend(true, {}, BlockrainThemes[newTheme]); + } + else { + this.options.theme = null; + this._theme = newTheme; + } + + if (typeof this._theme === 'undefined' || this._theme === null) { + this._theme = $.extend(true, {}, BlockrainThemes['retro']); + this.options.theme = 'retro'; + } + + if (isNaN(parseInt(this._theme.strokeWidth)) || typeof parseInt(this._theme.strokeWidth) !== 'number') { + this._theme.strokeWidth = 2; + } + + // Load the image assets + this._preloadThemeAssets(); + + if (this._board !== null) { + if (typeof this._theme.background === 'string') { + this._$canvas.css('background-color', this._theme.background); + } + this._board.render(); + } + } + + // Initialization + _create() { + let game = this; + + this.theme(this.options.theme); + + this._createHolder(); + this._createUI(); + + this._refreshBlockSizes(); + + this.updateSizes(); + + // $(window).resize(() => this.updateSizes); + + this._SetupShapeFactory(); + this._SetupFilled(); + this._SetupInfo(); + this._SetupBoard(); + + this._info.init(); + this._board.init(); + + const renderLoop = function() { + requestAnimationFrame(renderLoop); + game._board.render(); + }; + renderLoop(); + + if (this.options.autoplay) { + this.autoplay(true); + this._setupTouchControls(false); + } else { + this._setupControls(true); + this._setupTouchControls(false); + } + } + + _checkCollisions(x, y, blocks, checkDownOnly) { + // x & y should be aspirational values + let i = 0, len = blocks.length, a, b; + for (; i= this._BLOCK_HEIGHT || this._filled.check(a, b)) { + return true; + } else if (!checkDownOnly && a < 0 || a >= this._BLOCK_WIDTH) { + return true; + } + } + return false; + } + + /** + * Draws the background + */ + _drawBackground() { + if (typeof this._theme.background !== 'string') { + return; + } + + if (this._theme.backgroundGrid instanceof Image) { + + // Not loaded + if (this._theme.backgroundGrid.width === 0 || this._theme.backgroundGrid.height === 0) { + return; + } + + this._ctx.globalAlpha = 1.0; + + for (let x = 0; x < this._BLOCK_WIDTH; x++) { + for (let y = 0; y < this._BLOCK_HEIGHT; y++) { + let cx = x * this._block_size; + let cy = y * this._block_size; + + this._ctx.drawImage(this._theme.backgroundGrid, + 0, 0, this._theme.backgroundGrid.width, this._theme.backgroundGrid.height, + cx, cy, this._block_size, this._block_size); + } + } + + } else if (typeof this._theme.backgroundGrid === 'string') { + + let borderWidth = this._theme.strokeWidth; + let borderDistance = Math.round(this._block_size * 0.23); + let squareDistance = Math.round(this._block_size * 0.30); + + this._ctx.globalAlpha = 1.0; + this._ctx.fillStyle = this._theme.backgroundGrid; + + for (let x = 0; x < this._BLOCK_WIDTH; x++) { + for (let y = 0; y < this._BLOCK_HEIGHT; y++) { + let cx = x * this._block_size; + let cy = y * this._block_size; + + this._ctx.fillRect(cx+borderWidth, cy+borderWidth, this._block_size-borderWidth * 2, this._block_size-borderWidth * 2); + } + } + + } + + this._ctx.globalAlpha = 1.0; + } + + // TODO: move setups into constructor? + + _SetupShapeFactory() { + let game = this; + if (this._shapeFactory !== null) { return; } + + this._shapeFactory = { + line: function() { + return new Shape(game, game._shapes.line, false, 'line'); + }, + square: function() { + return new Shape(game, game._shapes.square, false, 'square'); + }, + arrow: function() { + return new Shape(game, game._shapes.arrow, false, 'arrow'); + }, + leftHook: function() { + return new Shape(game, game._shapes.leftHook, false, 'leftHook'); + }, + rightHook: function() { + return new Shape(game, game._shapes.rightHook, false, 'rightHook'); + }, + leftZag: function() { + return new Shape(game, game._shapes.leftZag, false, 'leftZag'); + }, + rightZag: function() { + return new Shape(game, game._shapes.rightZag, false, 'rightZag'); + } + }; + } + + _SetupFilled() { + let game = this; + if (this._filled !== null) { return; } + + this._filled = { + data: new Array(game._BLOCK_WIDTH * game._BLOCK_HEIGHT), + score: 0, + toClear: {}, + check: function(x, y) { + return this.data[this.asIndex(x, y)]; + }, + add: function(x, y, blockType, blockVariation, blockIndex, blockOrientation) { + if (x >= 0 && x < game._BLOCK_WIDTH && y >= 0 && y < game._BLOCK_HEIGHT) { + this.data[this.asIndex(x, y)] = { + blockType: blockType, + blockVariation: blockVariation, + blockIndex: blockIndex, + blockOrientation: blockOrientation + }; + } + }, + getFreeSpaces: function() { + let count = 0; + for (let i = 0; i < this.data.length; i++) { + count += (this.data[i] ? 1 : 0); + } + }, + asIndex: function(x, y) { + return x + y * game._BLOCK_WIDTH; + }, + asX: function(index) { + return index % game._BLOCK_WIDTH; + }, + asY: function(index) { + return Math.floor(index / game._BLOCK_WIDTH); + }, + clearAll: function() { + this.data = new Array(game._BLOCK_WIDTH * game._BLOCK_HEIGHT); + }, + _popRow: function(row_to_pop) { + for (let i = game._BLOCK_WIDTH * (row_to_pop + 1) - 1; i >= 0; i--) { + this.data[i] = i >= game._BLOCK_WIDTH ? this.data[i - game._BLOCK_WIDTH] : void 0; + } + }, + checkForClears: function() { + let startLines = game._board.lines; + let rows = [], i, len, count, mod; + + for (let i = 0, len = this.data.length; i < len; i++) { + mod = this.asX(i); + if (mod == 0) count = 0; + if (this.data[i] && typeof this.data[i] !== 'undefined' && typeof this.data[i].blockType === 'string') { + count += 1; + } + if (mod == game._BLOCK_WIDTH - 1 && count == game._BLOCK_WIDTH) { + rows.push(this.asY(i)); + } + } + + for (let i = 0, len = rows.length; i < len; i++) { + this._popRow(rows[i]); + game._board.lines++; + if (game._board.lines % 10 == 0 && game._board.dropDelay > 1) { + game._board.dropDelay *= 0.9; + } + } + + let clearedLines = game._board.lines - startLines; + this._updateScore(clearedLines); + }, + _updateScore: function(numLines) { + if (numLines <= 0) { return; } + let scores = [0, 400, 1000, 3000, 12000]; + if (numLines >= scores.length) { + numLines = scores.length - 1; + } + + this.score += scores[numLines]; + game._$scoreText.text(this.score); + + game.options.onLine.call(game.element, numLines, scores[numLines], this.score); + }, + _resetScore: function() { + this.score = 0; + game._$scoreText.text(this.score); + }, + draw: function() { + for (let i = 0, len = this.data.length, row, color; i= this.dropDelay) || + (game.options.autoplay) || + (this.holding.drop && (now - this.holding.drop) >= this.holdingThreshold)) { + drop = true; + moved = true; + this.dropCount = 0; + } + + // Move Left by holding + if (this.holding.left && (now - this.holding.left) >= this.holdingThreshold) { + moved = true; + this.cur.moveLeft(); + } + + // Move Right by holding + if (this.holding.right && (now - this.holding.right) >= this.holdingThreshold) { + moved = true; + this.cur.moveRight(); + } + + // Test for a collision, add the piece to the filled blocks and fetch the next one + if (drop) { + let cur = this.cur, x = cur.x, y = cur.y, blocks = cur.getBlocks(); + if (game._checkCollisions(x, y+1, blocks, true)) { + drop = false; + let blockIndex = 0; + for (let i = 0; i < cur.blocksLen; i += 2) { + game._filled.add(x + blocks[i], y + blocks[i+1], cur.blockType, cur.blockVariation, blockIndex, cur.orientation); + if (y + blocks[i] < 0) { + gameOver = true; + } + blockIndex++; + } + game._filled.checkForClears(); + this.cur = this.nextShape(); + this.renderChanged = true; + + // Stop holding drop (and any other buttons). Just in case the controls get sticky. + this.holding.left = null; + this.holding.right = null; + this.holding.drop = null; + + game.options.onPlaced.call(game.element); + } + } + } + + // Drop + if (drop) { + moved = true; + this.cur.y++; + } + + if (drop || moved) { + this.renderChanged = true; + } + + if (gameOver) { + this.gameover = true; + + game.gameover(); + + if (game.options.autoplay && game.options.autoplayRestart) { + // On autoplay, restart the game automatically + game.restart(); + } + this.renderChanged = true; + + } else { + + // Update the speed + this.animateDelay = 1000 / game.options.speed; + + this.animateTimeoutId = window.setTimeout(function() { + game._board.animate(); + }, this.animateDelay); + + } + }, + + createRandomBoard: function() { + let start = [], blockTypes = [], i, ilen, j, jlen, blockType; + + // Draw a random blockrain screen + blockTypes = Object.keys(game._shapeFactory); + + for (let i=0, ilen=game._BLOCK_WIDTH; i 0) { + return blockTheme[0]; + } else { + return null; + } + } else { + return blockTheme; + } + } + + if (typeof falling !== 'boolean') { falling = true; } + if (falling) { + if (typeof game._theme.primary === 'string' && game._theme.primary !== '') { + return game._theme.primary; + } else if (typeof game._theme.blocks !== 'undefined' && game._theme.blocks !== null) { + return getBlockVariation(game._theme.blocks[blockType], blockVariation); + } else { + return getBlockVariation(game._theme.complexBlocks[blockType], blockVariation); + } + } else { + if (typeof game._theme.secondary === 'string' && game._theme.secondary !== '') { + return game._theme.secondary; + } else if (typeof game._theme.blocks !== 'undefined' && game._theme.blocks !== null) { + return getBlockVariation(game._theme.blocks[blockType], blockVariation); + } else { + return getBlockVariation(game._theme.complexBlocks[blockType], blockVariation); + } + } + } + + }; + + game._niceShapes = game._getNiceShapes(); + } + + // Utility Functions + _randInt(a, b) { + return a + Math.floor(Math.random() * (1 + b - a)); + } + + _randSign() { + return this._randInt(0, 1) * 2 - 1; + } + + _randChoice(choices) { + return choices[this._randInt(0, choices.length-1)]; + } + + /** + * Find base64 encoded images and load them as image objects, which can be used by the canvas renderer + */ + _preloadThemeAssets() { + let game = this; + let hexColorcheck = new RegExp('^#[A-F0-9+]{3,6}', 'i'); + let base64check = new RegExp('^data:image/(png|gif|jpg);base64,', 'i'); + + const handleAssetLoad = function() { + // Rerender the board as soon as an asset loads + if (game._board) { + game._board.render(true); + } + }; + + const loadAsset = function(src) { + let plainSrc = src; + if (!hexColorcheck.test(plainSrc)) { + // It's an image + src = new Image(); + src.src = plainSrc; + src.onload = handleAssetLoad; + } else { + // It's a color + src = plainSrc; + } + return src; + }; + + const startAssetLoad = function(block) { + // Assets can be an array of variation so they can change color/design randomly + if (Array.isArray(block) && block.length > 0) { + for (let i = 0; i < block.length; i++) { + block[i] = loadAsset(block[i]); + } + } else if (typeof block === 'string') { + block = loadAsset(block); + } + return block; + }; + + // TODO: use same loop instead of 2. DRY + if (typeof this._theme.complexBlocks !== 'undefined') { + let keys = Object.keys(this._theme.complexBlocks); + // Load the complexBlocks + for (let i = 0; i < keys.length; i++) { + this._theme.complexBlocks[keys[i]] = startAssetLoad(this._theme.complexBlocks[keys[i]]); + } + } else if (typeof this._theme.blocks !== 'undefined') { + let keys = Object.keys(this._theme.blocks); + // Load the blocks + for (let i = 0; i < keys.length; i++) { + this._theme.blocks[keys[i]] = startAssetLoad(this._theme.blocks[keys[i]]); + } + } + + // Load the bg + if (typeof this._theme.backgroundGrid !== 'undefined' && + typeof this._theme.backgroundGrid === 'string' && + !hexColorcheck.test(this._theme.backgroundGrid)) { + let src = this._theme.backgroundGrid; + this._theme.backgroundGrid = new Image(); + this._theme.backgroundGrid.src = src; + this._theme.backgroundGrid.onload = handleAssetLoad; + } + } + + _createHolder() { + // Create the main holder (it holds all the ui elements, the original element is just the wrapper) + this._$gameholder = $('
'); + this._$gameholder.css('position', 'relative').css('width', '100%').css('height', '100%'); + + this.element.html('').append(this._$gameholder); + + // Create the game canvas and context + this._$canvas = $(''); + if (typeof this._theme.background === 'string') { + this._$canvas.css('background-color', this._theme.background); + } + this._$gameholder.append(this._$canvas); + + this._canvas = this._$canvas.get(0); + this._ctx = this._canvas.getContext('2d'); + } + + _createUI() { + // Score + this._$score = $( + '
'+ + '
'+ + '
'+ this.options.scoreText +'
'+ + '
0
'+ + '
'+ + '
').hide(); + this._$scoreText = this._$score.find('.blockrain-score-num'); + this._$gameholder.append(this._$score); + + // Create the start menu + this._$start = $( + '
'+ + ''+ + '
').hide(); + this._$gameholder.append(this._$start); + + this._$start.find('.blockrain-start-btn').click((e) => { + e.preventDefault(); + this.start(); + }); + + // Create the game over menu + this._$gameover = $( + '
'+ + '
'+ + '
'+ this.options.gameOverText +'
'+ + ''+ this.options.restartButtonText +''+ + '
'+ + '
').hide(); + this._$gameover.find('.blockrain-game-over-btn').click((e) => { + e.preventDefault(); + this.restart(); + }); + this._$gameholder.append(this._$gameover); + + this._createControls(); + } + + _createControls() { + this._$touchLeft = $('').appendTo(this._$thisholder); + this._$touchRight = $('').appendTo(this._$thisholder); + this._$touchRotateRight = $('').appendTo(this._$thisholder); + this._$touchRotateLeft = $('').appendTo(this._$thisholder); + this._$touchDrop = $('').appendTo(this._$gameholder); + } + + _refreshBlockSizes() { + if (this.options.autoBlockWidth) { + this.options.blockWidth = Math.ceil(this.element.width() / this.options.autoBlockSize); + } + } + + /* + * Things I need for this to work... + * - ability to test each shape with this._filled data + * - maybe give empty spots scores? and try to maximize the score? + */ + _getNiceShapes() { + let game = this; + + let shapes = {}, + attr; + + for (let attr in this._shapeFactory) { + shapes[attr] = this._shapeFactory[attr](); + } + + const scoreBlocks = function scoreBlocks(possibles, blocks, x, y, filled, width, height) { + let i, len=blocks.length, score=0, bottoms = {}, tx, ty, overlaps; + + // base score + for (let i = 0; i < len; i += 2) { + score += possibles[game._filled.asIndex(x + blocks[i], y + blocks[i+1])] || 0; + } + + // overlap score -- //TODO - don't count overlaps if cleared? + for (let i = 0; i < len; i += 2) { + tx = blocks[i]; + ty = blocks[i+1]; + if (typeof bottoms[tx] === 'undefined' || bottoms[tx] < ty) { + bottoms[tx] = ty; + } + } + overlaps = 0; + for (let tx in bottoms) { + tx = parseInt(tx); + for (let ty = bottoms[tx]+1, i = 0; y + ty < height; ty++, i++) { + if (!game._filled.check(x + tx, y + ty)) { + overlaps += i == 0 ? 2 : 1; //TODO-score better + //if (i == 0) overlaps += 1; + break; + } + } + } + + score = score - overlaps; + + return score; + } + + const resetShapes = function resetShapes() { + for (let attr in shapes) { + shapes[attr].x = 0; + shapes[attr].y = -1; + } + } + + //TODO -- evil mode needs to realize that overlap is bad... + const niceShapes = function niceShapes(filled, checkCollisions, width, height, mode, _one_shape) { + if (!_one_shape) resetShapes(); + + let possibles = new Array(width * height), + evil = mode == 'evil', + x, y, py, + attr, shape, i, blocks, bounds, + score, best_shape, best_score = (evil ? 1 : -1) * 999, best_orientation, best_x, + best_score_for_shape, best_orientation_for_shape, best_x_for_shape; + + for (let x = 0; x < width; x++) { + for (let y = 0; y <= height; y++) { + if (y == height || filled.check(x, y)) { + for (let py = y - 4; py < y; py++) { + possibles[filled.asIndex(x, py)] = py; //TODO - figure out better scoring? + } + break; + } + } + } + + // for each shape... + let opts = typeof _one_shape === 'undefined' ? shapes : {cur: _one_shape}; //BOO + for (let attr in opts) { //TODO - check in random order to prevent later shapes from winning + shape = opts[attr]; + best_score_for_shape = -999; + + // for each orientation... + for (let i = 0; i < (shape.symmetrical ? 2 : 4); i++) { //TODO - only look at unique orientations + blocks = shape.getBlocks(i); + bounds = shape.getBounds(blocks); + + // try each possible position... + for (let x = -bounds.left; x < width - bounds.width; x++) { + for (let y = -1; y < height - bounds.bottom; y++) { + if (game._checkCollisions(x, y + 1, blocks, true)) { + // collision + score = scoreBlocks(possibles, blocks, x, y, filled, width, height); + if (score > best_score_for_shape) { + best_score_for_shape = score; + best_orientation_for_shape = i; + best_x_for_shape = x; + } + break; + } + } + } + } + + if ((evil && best_score_for_shape < best_score) || + (!evil && best_score_for_shape > best_score)) { + best_shape = shape; + best_score = best_score_for_shape; + best_orientation = best_orientation_for_shape; + best_x = best_x_for_shape; + } + } + + best_shape.best_orientation = best_orientation; + best_shape.best_x = best_x; + + return best_shape; + }; + + niceShapes.no_preview = true; + return niceShapes; + } + + _randomShapes() { + // Todo: The shapefuncs should be cached. + const shapeFuncs = Object + .keys(this._shapeFactory) + .map((k) => { return this._shapeFactory[k]}) + ; + + return this._randChoice(shapeFuncs); + } + + /** + * Controls + * TODO: DRY this up!!! + */ + _setupControls(enable) { + let game = this; + + const moveLeft = function(start) { + if (!start) { + game._board.holding.left = null; + return; + } + if (!game._board.holding.left) { + game._board.cur.moveLeft(); + game._board.holding.left = Date.now(); + game._board.holding.right = null; + } + } + const moveRight = function(start) { + if (!start) { + game._board.holding.right = null; + return; + } + if (!game._board.holding.right) { + game._board.cur.moveRight(); + game._board.holding.right = Date.now(); + game._board.holding.left = null; + } + } + const drop = function(start) { + if (!start) { + game._board.holding.drop = null; + return; + } + if (!game._board.holding.drop) { + game._board.cur.drop(); + game._board.holding.drop = Date.now(); + } + } + const rotateLeft = function() { + game._board.cur.rotate('left'); + } + const rotateRight = function() { + game._board.cur.rotate('right'); + } + + // Handlers: These are used to be able to bind/unbind controls + const handleKeyDown = function(evt) { + if (!game._board.cur) { return true; } + let caught = true; + + if (game.options.asdwKeys) { + switch(evt.keyCode) { + case 65: /*a*/ moveLeft(true); break; + case 68: /*d*/ moveRight(true); break; + case 83: /*s*/ drop(true); break; + case 87: /*w*/ rotateRight(); break; + } + } + switch(evt.keyCode) { + case 37: /*left*/ moveLeft(true); break; + case 39: /*right*/ moveRight(true); break; + case 40: /*down*/ drop(true); break; + case 38: /*up*/ rotateRight(); break; + case 88: /*x*/ rotateRight(); break; + case 90: /*z*/ rotateLeft(); break; + default: caught = false; + } + if (caught) evt.preventDefault(); + return !caught; + }; + + const handleKeyUp = function(evt) { + if (!game._board.cur) { return true; } + let caught = true; + + if (game.options.asdwKeys) { + switch(evt.keyCode) { + case 65: /*a*/ moveLeft(false); break; + case 68: /*d*/ moveRight(false); break; + case 83: /*s*/ drop(false); break; + } + } + switch(evt.keyCode) { + case 37: /*left*/ moveLeft(false); break; + case 39: /*right*/ moveRight(false); break; + case 40: /*down*/ drop(false); break; + default: caught = false; + } + if (caught) evt.preventDefault(); + return !caught; + }; + + const isStopKey = function(evt) { + let cfg = { + stopKeys: {37:1, 38:1, 39:1, 40:1} + }; + + let isStop = (cfg.stopKeys[evt.keyCode] || (cfg.moreStopKeys && cfg.moreStopKeys[evt.keyCode])); + if (isStop) evt.preventDefault(); + return isStop; + } + + const getKey = function(evt) { return 'safekeypress.' + evt.keyCode; } + + const keydown = function(evt) { + let key = getKey(evt); + // TODO: before it was $.data(this) ... check this was the $el + $.data(game.element, key, ($.data(game.element, key) || 0) - 1); + return handleKeyDown(evt); + } + + const keyup = function(evt) { + $.data(this, getKey(evt), 0); + handleKeyUp.call(this, evt); + return isStopKey(evt); + } + + // Unbind everything by default + // Use event namespacing so we don't ruin other keypress events + $(document).unbind('keydown.blockrain') + .unbind('keyup.blockrain'); + + if (!game.options.autoplay && enable) { + $(document).bind('keydown.blockrain', keydown) + .bind('keyup.blockrain', keyup); + } + } + + _setupTouchControls(enable) { + let game = this; + + // Movements can be held for faster movement + const moveLeft = function(event) { + event.preventDefault(); + game._board.cur.moveLeft(); + game._board.holding.left = Date.now(); + game._board.holding.right = null; + game._board.holding.drop = null; + }; + const moveRight = function(event) { + event.preventDefault(); + game._board.cur.moveRight(); + game._board.holding.right = Date.now(); + game._board.holding.left = null; + game._board.holding.drop = null; + }; + const drop = function(event) { + event.preventDefault(); + game._board.cur.drop(); + game._board.holding.drop = Date.now(); + }; + const endMoveLeft = function(event) { + event.preventDefault(); + game._board.holding.left = null; + }; + const endMoveRight = function(event) { + event.preventDefault(); + game._board.holding.right = null; + }; + const endDrop = function(event) { + event.preventDefault(); + game._board.holding.drop = null; + }; + + // Rotations can't be held + const rotateLeft = function(event) { + event.preventDefault(); + game._board.cur.rotate('left'); + }; + const rotateRight = function(event) { + event.preventDefault(); + game._board.cur.rotate('right'); + }; + + // Unbind everything by default + game._$touchLeft.unbind('touchstart touchend click'); + game._$touchRight.unbind('touchstart touchend click'); + game._$touchRotateLeft.unbind('touchstart touchend click'); + game._$touchRotateRight.unbind('touchstart touchend click'); + game._$touchDrop.unbind('touchstart touchend click'); + + if (!game.options.autoplay && enable) { + game._$touchLeft.show().bind('touchstart click', moveLeft).bind('touchend', endMoveLeft); + game._$touchRight.show().bind('touchstart click', moveRight).bind('touchend', endMoveRight); + game._$touchDrop.show().bind('touchstart click', drop).bind('touchend', endDrop); + game._$touchRotateLeft.show().bind('touchstart click', rotateLeft); + game._$touchRotateRight.show().bind('touchstart click', rotateRight); + } else { + game._$touchLeft.hide(); + game._$touchRight.hide(); + game._$touchRotateLeft.hide(); + game._$touchRotateRight.hide(); + game._$touchDrop.hide(); + } + } + +} diff --git a/src/shape.js b/src/shape.js new file mode 100644 index 0000000..ab34f78 --- /dev/null +++ b/src/shape.js @@ -0,0 +1,95 @@ + +export default class Shape { + + constructor(game, orientations, symmetrical, blockType) { + this.x = 0; + this.y = 0; + // TODO: remove dep to game, current dependency is the wrong way + this.game = game; + this.symmetrical = symmetrical; + this.blockType = blockType; + this.blockVariation = null; + this.blocksLen = orientations[0].length; + this.orientations = orientations; + this.orientation = 0; // 4 possible + } + + init() { + this.orientation = 0; + this.x = Math.floor(this.game._BLOCK_WIDTH / 2) - 1; + this.y = -1; + + return this; + } + + rotate(direction) { + let orientation = (this.orientation + (direction === 'left' ? 1 : -1) + 4) % 4; + + //TODO - when past limit - auto shift and remember that too! + if (!this.game._checkCollisions(this.x, this.y, this.getBlocks(orientation))) { + this.orientation = orientation; + this.game._board.renderChanged = true; + } + } + + moveRight() { + if (!this.game._checkCollisions(this.x + 1, this.y, this.getBlocks())) { + this.x++; + this.game._board.renderChanged = true; + } + } + + moveLeft() { + if (!this.game._checkCollisions(this.x - 1, this.y, this.getBlocks())) { + this.x--; + this.game._board.renderChanged = true; + } + } + + drop() { + if (!this.game._checkCollisions(this.x, this.y + 1, this.getBlocks())) { + this.y++; + // Reset the drop count, as we dropped the block sooner + this.game._board.dropCount = -1; + this.game._board.animate(); + this.game._board.renderChanged = true; + } + } + + getBlocks(orientation) { // optional param + return this.orientations[typeof orientation !== 'undefined' ? orientation : this.orientation]; + } + + draw(_x, _y, _orientation) { + let blocks = this.getBlocks(_orientation), + x = typeof _x === 'undefined' ? this.x : _x, + y = typeof _y === 'undefined' ? this.y : _y, + i = 0, + index = 0; + + for (; i < this.blocksLen; i += 2) { + this.game._board.drawBlock(x + blocks[i], y + blocks[i+1], this.blockType, this.blockletiation, index, this.orientation, true); + index++; + } + } + + getBounds(_blocks) { // _blocks can be an array of blocks, an orientation index, or undefined + let blocks = Array.isArray(_blocks) ? _blocks : this.getBlocks(_blocks), + i=0, len=blocks.length, minx=999, maxx=-999, miny=999, maxy=-999; + for (; i < len; i += 2) { + if (blocks[i] < minx) { minx = blocks[i]; } + if (blocks[i] > maxx) { maxx = blocks[i]; } + if (blocks[i+1] < miny) { miny = blocks[i+1]; } + if (blocks[i+1] > maxy) { maxy = blocks[i+1]; } + } + return { + left: minx, + right: maxx, + top: miny, + bottom: maxy, + width: maxx - minx, + height: maxy - miny + }; + } + +} diff --git a/src/blockrain.jquery.themes.js b/src/themes.js similarity index 99% rename from src/blockrain.jquery.themes.js rename to src/themes.js index 9124c37..c92d425 100644 --- a/src/blockrain.jquery.themes.js +++ b/src/themes.js @@ -1,8 +1,8 @@ /** * Themes. You can add more custom themes to this object. */ -window.BlockrainThemes = { - 'custom': { +export let BlockrainThemes = { + custom: { background: '#040304', backgroundGrid: 'data:image/png;base64,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', complexBlocks: { @@ -15,7 +15,7 @@ window.BlockrainThemes = { leftZag: 'assets/blocks/custom/leftZag.png' } }, - 'candy': { + candy: { background: '#040304', backgroundGrid: 'data:image/png;base64,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', blocks: { @@ -28,7 +28,7 @@ window.BlockrainThemes = { leftZag: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAIAAADZrBkAAAACtElEQVR4Xg2Lu25cZRhF93f5L3OOPeOQmJuDhEIHDa/CC/B4SHSICqS0CERDE2MJAYKEmITEcewZz/nvH7O0taq16eTL++P66nhCDegOAEhghFYhDDM0hSgowQkKkDIwJi0pP/72G9j24vIi8S7nN34lYG1VyOKg0uVKFFIiY/XL+R8s977+6nvttBMkovTDz9/Np3G3fTlvYkoFJswzaxtya2haQ0nUdGa2PLY6GD/9+vjJ+Y+bD+K2vFxtrLS3cQq9t1rLwUZ7IpuOAmQQ8u9/PgFDWbG5708/jK/Ti9OzExErhYVU1TMrCZqV7fY2Or2+uQ5H7zgPMNQF5HL7+upZeFeHLU//etrraM2YedQBAXsnoPXHn8yrcHVzE0LICWoECTQf+SG19dRqK3tMUxSRZtkIZtqbsblW+vF8/Pzy1XoNvdthjDas7He7swdno+JkM21v997r6A1MIKNBwiCiXssqhNsbcAwQosPTeek1MzBa947NmnewYcGR8Oh1sZG8s5LvQoACaK0Yegi+965MNWXnxIAx0AdyWnqDMjuh1KoIkYGNUPqh0zHaYcpwoin1ZiBFjFBVEWLm1ppzTkQAsAQYQdwhTap+dBCRXzkW6g2lIpdqRkYE1v1+AZgEuiRo8G2YaCBo75ZylSCdiMnoYPVefO1k8AYm7rlAjUDi7/ZN18HIffbp5zFO22Vf0bzXfsBQUu4DIJ6P1/t/XowB9RGl4Xj94M62zy+vou2sDUySyhJjTDXpAeL9YFKXaxYnuoL2BRfnzxghj7YKm4liHkszGkOUjtxIYjKM9ncpTnPr4+HDj8j+U5/B5R5k/vfv344m6rfXHb3NSqKvdrs5xKUWeN95nUd69Oi9vpQI0PtfTAn7eE/eLF0Y92eUjrcDISBmrBjbgkrIDPFYCfoCLPgflXOjuIEFgMYAAAAASUVORK5CYII=' } }, - 'modern': { + modern: { background: '#000000', backgroundGrid: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAIAAAC0Ujn1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjZTg0NzU4MC00ODk3LTRkNjAtOWNhYi1mZTk1NzQ5NzhiNjkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTEzOEQwMDc5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTEzOEQwMDY5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDplNDRjOWZiNC0yNzE5LTQ3NDYtYmRmMi0wMmY2ZTA4ZjAxMmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MzMwNTNEOTk5MDM1MTFFNDlBMzlFNzY4RjBCNkNENzMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7Y01+zAAAAMklEQVR42mJgGAWjYBSMgkEJGIlUd+j/WjjbjjGYGC1MtHP10DR6FIyCUTAKBikACDAA0NoDCLGGjH8AAAAASUVORK5CYII=', primary: null, @@ -44,7 +44,7 @@ window.BlockrainThemes = { leftZag: '#fa1e1e' } }, - 'retro': { + retro: { background: '#000000', backgroundGrid: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAIAAAC0Ujn1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjZTg0NzU4MC00ODk3LTRkNjAtOWNhYi1mZTk1NzQ5NzhiNjkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTEzOEQwMDc5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTEzOEQwMDY5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDplNDRjOWZiNC0yNzE5LTQ3NDYtYmRmMi0wMmY2ZTA4ZjAxMmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MzMwNTNEOTk5MDM1MTFFNDlBMzlFNzY4RjBCNkNENzMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7Y01+zAAAAMklEQVR42mJgGAWjYBSMgkEJGIlUd+j/WjjbjjGYGC1MtHP10DR6FIyCUTAKBikACDAA0NoDCLGGjH8AAAAASUVORK5CYII=', primary: null, @@ -61,7 +61,7 @@ window.BlockrainThemes = { leftZag: '#fa1e1e' } }, - 'monochrome': { + monochrome: { background: '#000000', backgroundGrid: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAIAAAC0Ujn1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjZTg0NzU4MC00ODk3LTRkNjAtOWNhYi1mZTk1NzQ5NzhiNjkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTEzOEQwMDc5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTEzOEQwMDY5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDplNDRjOWZiNC0yNzE5LTQ3NDYtYmRmMi0wMmY2ZTA4ZjAxMmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MzMwNTNEOTk5MDM1MTFFNDlBMzlFNzY4RjBCNkNENzMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7Y01+zAAAAMklEQVR42mJgGAWjYBSMgkEJGIlUd+j/WjjbjjGYGC1MtHP10DR6FIyCUTAKBikACDAA0NoDCLGGjH8AAAAASUVORK5CYII=', primary: '#ffffff', @@ -69,12 +69,12 @@ window.BlockrainThemes = { stroke: '#000000', innerStroke: '#000000' }, - 'aerolab': { + aerolab: { background: '#ffffff', primary: '#ff7b00', secondary: '#000000' }, - 'gameboy': { + gameboy: { background: '#C4CFA1', primary: null, secondary: null, @@ -91,7 +91,7 @@ window.BlockrainThemes = { leftZag: '#595F45' } }, - 'vim': { + vim: { background: '#000000', backgroundGrid: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAIAAAC0Ujn1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjZTg0NzU4MC00ODk3LTRkNjAtOWNhYi1mZTk1NzQ5NzhiNjkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTEzOEQwMDc5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTEzOEQwMDY5MDQyMTFFNDlBMzlFNzY4RjBCNkNENzMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDplNDRjOWZiNC0yNzE5LTQ3NDYtYmRmMi0wMmY2ZTA4ZjAxMmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MzMwNTNEOTk5MDM1MTFFNDlBMzlFNzY4RjBCNkNENzMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7Y01+zAAAAMklEQVR42mJgGAWjYBSMgkEJGIlUd+j/WjjbjjGYGC1MtHP10DR6FIyCUTAKBikACDAA0NoDCLGGjH8AAAAASUVORK5CYII=', primary: '#C2FFAE', @@ -99,5 +99,5 @@ window.BlockrainThemes = { stroke: '#000000', strokeWidth: 3, innerStroke: null - }, -}; \ No newline at end of file + } +};