From 9ed7cd53c7e2e8ea8acfb200c278b5156a1c3a47 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sun, 8 May 2011 03:08:58 +0200 Subject: [PATCH] Found a canvas tetris implementation, but needs some tweaking to integrate nicely. --- oliver_screen/static/tetris.js | 686 ++++++++++++++++++++++ oliver_screen/templates/public/index.html | 21 + 2 files changed, 707 insertions(+) create mode 100644 oliver_screen/static/tetris.js diff --git a/oliver_screen/static/tetris.js b/oliver_screen/static/tetris.js new file mode 100644 index 0000000..74afdf0 --- /dev/null +++ b/oliver_screen/static/tetris.js @@ -0,0 +1,686 @@ +/* http://mrcoles.com/media/js/tetris.js */ +$.fn.safekeypress = function(func, cfg) { + + cfg = $.extend({ + stopKeys: {37:1, 38:1, 39:1, 40:1} + }, cfg); + + function isStopKey(evt) { + 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 keypress(evt) { + var key = getKey(evt), + val = ($.data(this, key) || 0) + 1; + $.data(this, key, val); + if (val > 0) return func.call(this, evt); + return isStopKey(evt); + } + + function keydown(evt) { + var key = getKey(evt); + $.data(this, key, ($.data(this, key) || 0) - 1); + return func.call(this, evt); + } + + function keyup(evt) { + $.data(this, getKey(evt), 0); + return isStopKey(evt); + } + + return $(this).keypress(keypress).keydown(keydown).keyup(keyup); +}; + + +var getNiceShapes = function(shapeFactory, undefined) { + /* + * Things I need for this to work... + * - ability to test each shape with filled data + * - maybe give empty spots scores? and try to maximize the score? + */ + + var shapes = {}, + attr; + + for (attr in shapeFactory) { + shapes[attr] = 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; +}; + + +var undefined; +if (1) { +//(function(window, document, undefined) { + + var canvas = document.getElementById('game'), + ctx = canvas.getContext('2d'), + info_canvas = document.getElementById('info'), + info_ctx = info_canvas.getContext('2d'), + WIDTH = 10, + HEIGHT = 20, + PIXEL_WIDTH = $(canvas).width(), + PIXEL_HEIGHT = $(canvas).height(), + block_size = Math.ceil(PIXEL_WIDTH / WIDTH), + bevel_size = Math.floor(block_size / 10), + border_width = 2; + + function randInt(a, b) { return a + Math.floor(Math.random() * (1 + b - a)); } + function randSign() { return randInt(0, 1) * 2 - 1; } + function randChoice(choices) { return choices[randInt(0, choices.length-1)]; } + + function drawBlock(x, y, color, _ctx) { + // convert x and y to pixel + _ctx = _ctx || ctx; + x = x * block_size; + y = y * block_size; + _ctx.fillStyle = color; + _ctx.globalAlpha = .5; + _ctx.fillRect(x, y, block_size, block_size); + _ctx.globalAlpha = .5; + _ctx.fillRect(x + bevel_size, y + bevel_size, block_size - 2*bevel_size, block_size - 2*bevel_size); + _ctx.globalAlpha = 1; + } + + function Shape(orienations, color, symmetrical) { + + $.extend(this, { + x: 0, + y: 0, + symmetrical: symmetrical, + init: function() { + $.extend(this, { + orientation: 0, + x: Math.floor(WIDTH / 2) - 1, + y: -1 + }); + return this; + }, + color: color, + blocksLen: orienations[0].length, + orientations: orienations, + orientation: 0, // 4 possible + rotate: function(right) { + var orientation = (this.orientation + (right ? 1 : -1) + 4) % 4; + + //TODO - when past limit - auto shift and remember that too! + if (!checkCollisions(this.x, this.y, this.getBlocks(orientation))) + this.orientation = orientation; + }, + moveRight: function() { + if (!checkCollisions(this.x + 1, this.y, this.getBlocks())) + this.x++; + }, + moveLeft: function() { + if (!checkCollisions(this.x - 1, this.y, this.getBlocks())) + this.x--; + }, + getBlocks: function(orientation) { // optional param + return this.orientations[orientation !== undefined ? orientation : this.orientation]; + }, + draw: function(drop, _x, _y, _orientation, _ctx) { + if (drop) this.y++; + + var blocks = this.getBlocks(_orientation), + x = _x === undefined ? this.x : _x, + y = _y === undefined ? this.y : _y, + i = 0; + for (; 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 + }; + } + }); + + return this.init(); + }; + + var shapeFactory = { + line: function() { + /* + * X X + * O XOXX O XOXX + * X X + * X X + */ + var ver = [0, -1, 0, -2, 0, -3, 0, -4], + hor = [-1, -2, 0, -2, 1, -2, 2, -2]; + return new Shape([ver, hor, ver, hor], '#f90', true); + }, + square: function() { + /* + * XX + * XX + */ + var s = [0, 0, 1, 0, 0, -1, 1, -1]; + return new Shape([s, s, s, s], 'red', true); + }, + arrow: function() { + /* + * X X X + * XOX OX XOX XO + * X X X + */ + return new Shape([ + [0, -1, 1, -1, 2, -1, 1, -2], + [1, -2, 1, -1, 1, 0, 2, -1], + [0, -1, 1, -1, 2, -1, 1, 0], + [0, -1, 1, -1, 1, -2, 1, 0] + ], 'yellow'); + }, + rightHook: function() { + /* + * XX X X + * XOX O XOX O + * X X XX + */ + return new Shape([ + [0, 0, 0, -1, 1, -1, 2, -1], + [0, -2, 1, 0, 1, -1, 1, -2], + [0, -1, 1, -1, 2, -1, 2, -2], + [0, -2, 0, -1, 0, 0, 1, 0] + ], 'blue'); + }, + leftHook: function() { + /* + * X X XX + * XOX O XOX O + * X XX X + */ + return new Shape([ + [2, 0, 0, -1, 1, -1, 2, -1], + [0, 0, 1, 0, 1, -1, 1, -2], + [0, -2, 0, -1, 1, -1, 2, -1], + [0, 0, 0, -1, 0, -2, 1, -2] + ], 'purple'); + }, + leftZag: function() { + /* + * X + * XO OX + * XX X + */ + var ver = [0, 0, 0, -1, 1, -1, 1, -2], + hor = [0, -1, 1, -1, 1, 0, 2, 0]; + return new Shape([hor, ver, hor, ver], 'gray', true); + }, + rightZag: function() { + /* + * X + * OX OX + * XX X + */ + var ver = [0, -2, 0, -1, 1, -1, 1, 0], + hor = [0, 0, 1, 0, 1, -1, 2, -1]; + return new Shape([hor, ver, hor, ver], 'green', true); + } + + }; + + var shapeFuncs = []; + $.each(shapeFactory, function(k,v) { shapeFuncs.push(v); }); + + var filled = { + data: new Array(WIDTH * HEIGHT), + toClear: {}, + check: function(x, y) { + return this.data[this.asIndex(x, y)]; + }, + add: function(x, y, color) { + if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) + this.data[this.asIndex(x, y)] = color; + }, + asIndex: function(x, y) { + return x + y*WIDTH; + }, + asX: function(index) { + return index % WIDTH; + }, + asY: function(index) { + return Math.floor(index / WIDTH); + }, + _popRow: function(row_to_pop) { + for (var i=WIDTH*(row_to_pop+1) - 1; i>=0; i--) { + this.data[i] = (i >= WIDTH ? this.data[i-WIDTH] : undefined); + } + }, + checkForClears: function() { + var rows = [], i, len, count, mod; + + for (i=0, len=this.data.length; i 1) board.dropDelay -= 2; + } + }, + draw: function() { + for (var i=0, len=this.data.length, row, color; i= HEIGHT || filled.check(a, b)) { + return true; + } else if (!checkDownOnly && a < 0 || a >= WIDTH) { + return true; + } + } + return false; + } + + var niceShapes = getNiceShapes(shapeFactory); + + var board = { + animateDelay: 50, + cur: null, + + lines: 0, + + dropCount: 0, + dropDelay: 24, //5, + + init: function() { + this.cur = this.nextShape(); + + ctx.font = 'bold 30px/40px "lucida grande",helvetica,arial'; + ctx.fillStyle = '#333'; + ctx.fillText(' Click', 50, 120); + ctx.fillText('to start', 50, 160); + + var start = [], colors = [], i, ilen, j, jlen, color; + + for (i in shapeFactory) { + colors.push(shapeFactory[i]().color); + } + + for (i=0, ilen=WIDTH; i= this.dropDelay || window.autopilot) { + drop = true; + this.dropCount = 0; + } + + // test for a collision + if (drop) { + var cur = this.cur, x = cur.x, y = cur.y, blocks = cur.getBlocks(); + if (checkCollisions(x, y+1, blocks, true)) { + drop = false; + for (var i=0; i self.modesY) { + mode = self.modes[Math.floor((y - self.modesY) / 20)]; + if (mode) { + self.mode = mode; + board.nextShape(true); + } else if (self.autopilotY - 20 <= y && y <= self.autopilotY) { + window.autopilot = !window.autopilot; + } + } + }); + }, + circle: function(x, y, r, color) { + info_ctx.beginPath(); + if (color) info_ctx.fillStyle = color; + info_ctx.arc(x, y, r, 0, 2 * Math.PI, true); + info_ctx.closePath(); + info_ctx.fill(); + }, + radio: function(x, y, selected) { + y -= 4; + x += 8; + this.circle(x, y, 6, '#333'); + this.circle(x, y, 5, '#fff'); + if (selected) this.circle(x, y, 4, '#333'); + }, + checkBox: function(x, y, selected) { + y -= 4; + x += 8; + info_ctx.fillStyle = '#333'; + info_ctx.fillRect(x - 6, y - 6, 12, 12); + info_ctx.fillStyle = '#fff'; + info_ctx.fillRect(x - 5, y - 5, 10, 10); + + if (selected) { + info_ctx.strokeStyle = '#333'; + info_ctx.beginPath(); + info_ctx.moveTo(x - 6, y - 6); + info_ctx.lineTo(x + 6, y + 6); + info_ctx.moveTo(x - 6, y + 6); + info_ctx.lineTo(x + 6, y - 6); + info_ctx.closePath(); + info_ctx.stroke(); + } + } + }; + + + // + // + // + + info.init(); + board.init(); + + function startBoard(evt) { + evt.preventDefault(); + board.started = true; + board.animate(); + return false; + } + + $(document).keyup(function(evt) { + return (!board.started && (evt.keyCode == 13 || evt.keyCode == 32)) ? startBoard(evt) : true; + }); + + $(canvas).click(function(evt) { + return (!board.started) ? startBoard(evt) : true; + }); + + $(document).keyup(function(evt) { + if (evt.keyCode == 80) { /*p*/ + board.paused = !board.paused; + } else if (evt.keyCode == 192) { /*`*/ + window.autopilot = !window.autopilot; + } + }); + + $(document).safekeypress(function(evt) { + var caught = false; + if (board.cur) { + caught = true; + switch(evt.keyCode) { + case 37: /*left*/ board.cur.moveLeft(); break; + case 38: /*up*/ board.cur.rotate(true); break; + case 39: /*right*/ board.cur.moveRight(); break; + case 40: /*down*/ board.dropCount = board.dropDelay; break; + case 88: /*x*/ board.cur.rotate(true); break; + case 90: /*z*/ board.cur.rotate(false); break; + default: caught = false; + } + } + if (caught) evt.preventDefault(); + return !caught; + }); + +} +//})(this, this.document); diff --git a/oliver_screen/templates/public/index.html b/oliver_screen/templates/public/index.html index 0b6b786..c4a76d7 100644 --- a/oliver_screen/templates/public/index.html +++ b/oliver_screen/templates/public/index.html @@ -16,6 +16,11 @@ $( '#now_playing' ).load( '{% url tracks.views.get_now_playing %}', function() { resize_img(); + if(is_tetris_song()) { + play_tetris(); + } else { + dont_play_tetris(); + } }); }; /* Initial update */ @@ -41,6 +46,17 @@ image.width(1000); } } + function is_tetris_song() { + $("#now_playing").filter(function(){ + return /Tetris|Korobeiniki/i.test( $(this).text() ); + }); + } + function play_tetris() { + $("game").show(); + } + function dont_play_tetris() { + $("game").hide(); + } /* TODO: if the current song is a tetris song, then do something. */ @@ -52,6 +68,11 @@
+ +