├── readme.md ├── index.js ├── test ├── shapes_test.js ├── shape_test.js └── board_test.js ├── package.json ├── shapes.js ├── shape.js └── board.js /readme.md: -------------------------------------------------------------------------------- 1 | ###Sirtet 2 | 3 | This isn't Tetris 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Board: require('./board'), 3 | Shape: require('./shape'), 4 | Shapes: require('./shape') 5 | }; 6 | -------------------------------------------------------------------------------- /test/shapes_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Shapes = require('./../shapes'); 3 | 4 | describe('shapes', function() { 5 | it('should return random shape names', function() { 6 | assert(Shapes.random()); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sirtet", 3 | "version": "0.0.4", 4 | "description": "This isn't Tetris", 5 | "homepage": "http://github.com/alexyoung/sirtet", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "./node_modules/.bin/mocha" 9 | }, 10 | "keywords": [ 11 | "games" 12 | ], 13 | "author": "Alex R. Young", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "mocha": "~1.17.1" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "http://github.com/alexyoung/sirtet.git" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /shapes.js: -------------------------------------------------------------------------------- 1 | var shapes = { 2 | I: [ 3 | [0, 0, 0, 0], 4 | [1, 1, 1, 1], 5 | [0, 0, 0, 0], 6 | [0, 0, 0, 0] 7 | ], 8 | J: [ 9 | [1, 0, 0], 10 | [1, 1, 1], 11 | [0, 0, 0] 12 | ], 13 | L: [ 14 | [0, 0, 1], 15 | [1, 1, 1], 16 | [0, 0, 0] 17 | ], 18 | O: [ 19 | [0, 1, 1, 0], 20 | [0, 1, 1, 0], 21 | [0, 0, 0, 0] 22 | ], 23 | S: [ 24 | [0, 1, 1], 25 | [1, 1, 0], 26 | [0, 0, 0] 27 | ], 28 | T: [ 29 | [0, 1, 0], 30 | [1, 1, 1], 31 | [0, 0, 0] 32 | ], 33 | Z: [ 34 | [1, 1, 0], 35 | [0, 1, 1], 36 | [0, 0, 0] 37 | ] 38 | }; 39 | 40 | var colours = { 41 | I: '#5BFDFF', 42 | J: '#F6AF00', 43 | L: '#3E00FF', 44 | O: '#FAFF00', 45 | S: '#44FF00', 46 | T: '#9C00FF', 47 | Z: '#F32100' 48 | }; 49 | 50 | var rotations = { 51 | 3: [ 52 | [[2, 0], [ 1, 1], [ 0, 2]], 53 | [[1, -1], [ 0, 0], [-1, 1]], 54 | [[0, -2], [-1, -1], [-2, 0]] 55 | ], 56 | 4: [ 57 | [[ 0, 0], [ 2, 1], [ 1, 2], [ 0, 0]], 58 | [[ 2,-1], [ 1, 0], [ 0, 1], [-1, 2]], 59 | [[ 1,-2], [ 0,-1], [-1, 0], [-2, 1]], 60 | [[ 0, 0], [-1,-2], [-2,-1], [ 0, 0]] 61 | ] 62 | }; 63 | 64 | var names = Object.keys(shapes); 65 | 66 | function randomShapeName() { 67 | return names[Math.floor(Math.random() * names.length)]; 68 | }; 69 | 70 | module.exports = { 71 | shapes: shapes, 72 | colours: colours, 73 | names: names, 74 | random: randomShapeName, 75 | rotations: rotations 76 | }; 77 | -------------------------------------------------------------------------------- /test/shape_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Board = require('./../board'); 3 | var Shape = require('./../shape'); 4 | var Shapes = require('./../shapes'); 5 | 6 | describe('shape', function() { 7 | var board = new Board(10, 10); 8 | 9 | it('should rotate the L as expected', function() { 10 | var shape = new Shape(5, 5, 'L', board); 11 | shape.rotate(); 12 | assert.deepEqual(shape.data, [[ 0, 1, 0 ], [ 0, 1, 0 ], [ 0, 1, 1 ]]); 13 | shape.rotate(); 14 | assert.deepEqual(shape.data, [[ 0, 0, 0 ], [ 1, 1, 1 ], [ 1, 0, 0 ]]); 15 | shape.rotate(); 16 | assert.deepEqual(shape.data, [[ 1, 1, 0 ], [ 0, 1, 0 ], [ 0, 1, 0 ]]); 17 | shape.rotate(); 18 | assert.deepEqual(shape.data, [[ 0, 0, 1 ], [ 1, 1, 1 ], [ 0, 0, 0 ]]); 19 | }); 20 | 21 | it('should rotate the I as expected', function() { 22 | var shape = new Shape(5, 5, 'I', board); 23 | shape.rotate(); 24 | assert.deepEqual(shape.data, [[ 0, 0, 1, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 1, 0 ], [ 0, 0, 1, 0 ]]); 25 | shape.rotate(); 26 | assert.deepEqual(shape.data, [[ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 1, 1, 1, 1 ], [ 0, 0, 0, 0 ]]); 27 | shape.rotate(); 28 | assert.deepEqual(shape.data, [[ 0, 1, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 1, 0, 0 ], [ 0, 1, 0, 0 ]]); 29 | }); 30 | 31 | it('should not rotate O', function() { 32 | var shape = new Shape(5, 5, 'O', board); 33 | shape.rotate(); 34 | 35 | assert.deepEqual(shape.data, [[ 0, 1, 1, 0 ], [ 0, 1, 1, 0 ], [ 0, 0, 0, 0 ]]); 36 | shape.rotate(); 37 | assert.deepEqual(shape.data, [[ 0, 1, 1, 0 ], [ 0, 1, 1, 0 ], [ 0, 0, 0, 0 ]]); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/board_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Board = require('./../board'); 3 | 4 | describe('The game board', function() { 5 | describe('Initialisation', function() { 6 | var board = new Board(10, 10); 7 | 8 | it('should create a shape', function() { 9 | assert(board.currentShape); 10 | }); 11 | }); 12 | 13 | describe('Falling', function() { 14 | var board = new Board(10, 10); 15 | 16 | it('should stop moving the shape when it hits the botton, and add a new one', function(done) { 17 | board.once('shape', function(newShape) { 18 | assert(true, 'Shape added!'); 19 | done(); 20 | }); 21 | 22 | for (var y = 0; y < board.height; y++) { 23 | board.moveDown = true; 24 | board.currentShape.moveDown(); 25 | } 26 | }); 27 | }); 28 | 29 | describe('Player movement', function() { 30 | it('should stop the shape moving at the left edge', function() { 31 | var board = new Board(10, 10); 32 | var lastX = board.currentShape.x; 33 | 34 | board.makeShape('I'); 35 | 36 | for (var i = 1; i < 3; i++) { 37 | board.currentShape.moveLeft(); 38 | assert.equal(board.currentShape.x, lastX - i); 39 | } 40 | 41 | board.currentShape.moveLeft(); 42 | assert.equal(board.currentShape.x, lastX - 2); 43 | }); 44 | 45 | it('should stop the shape moving at the right edge', function() { 46 | var board = new Board(10, 10); 47 | var lastX = board.currentShape.x; 48 | 49 | board.makeShape('I'); 50 | 51 | for (var i = 1; i < 3; i++) { 52 | board.currentShape.moveRight(); 53 | assert.equal(board.currentShape.x, lastX + i); 54 | } 55 | 56 | board.currentShape.moveRight(); 57 | assert.equal(board.currentShape.x, lastX + 2); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /shape.js: -------------------------------------------------------------------------------- 1 | var Shapes = require('./shapes'); 2 | 3 | module.exports = Shape; 4 | 5 | function Shape(x, y, name, board) { 6 | this.board = board; 7 | this.colour = Shapes.colours[name]; 8 | this.data = Shapes.shapes[name]; 9 | this.name = name; 10 | this.x = x; 11 | this.y = y; 12 | } 13 | 14 | Shape.prototype.atBottom = function() { 15 | return this.y >= (this.board.height - 1) || this.board.checkCollision(this, 0); 16 | }; 17 | 18 | Shape.prototype.moveDown = function() { 19 | if (!this.atBottom()) { 20 | this.y++; 21 | } else { 22 | this.makeStuck(); 23 | } 24 | }; 25 | 26 | Shape.prototype.makeBlankCells = function() { 27 | var cells = []; 28 | 29 | for (var y = 0; y < this.data.length; y++) { 30 | cells[y] = []; 31 | for (var x = 0; x < this.data[y].length; x++) { 32 | cells[y][x] = 0; 33 | } 34 | } 35 | 36 | return cells; 37 | }; 38 | 39 | Shape.prototype.moveLeft = function() { 40 | if (this.board.checkMoveLeft()) { 41 | this.x--; 42 | } 43 | }; 44 | 45 | Shape.prototype.moveRight = function() { 46 | if (this.board.checkMoveRight()) { 47 | this.x++; 48 | } 49 | }; 50 | 51 | Shape.prototype.rotate = function() { 52 | if (this.name === 'O') return; 53 | 54 | var matrix = Shapes.rotations[this.data.length]; 55 | var cells = this.makeBlankCells(); 56 | var tx; 57 | var ty; 58 | 59 | for (var y = 0; y < this.data.length; y++) { 60 | for (var x = 0; x < this.data[y].length; x++) { 61 | if (this.data[y][x] === 1) { 62 | tx = x + matrix[y][x][0]; 63 | ty = y + matrix[y][x][1]; 64 | 65 | cells[ty][tx] = 1; 66 | } 67 | } 68 | } 69 | 70 | this.data = cells; 71 | }; 72 | 73 | Shape.prototype.makeStuck = function() { 74 | this.board.addShape(this.board.currentShape); 75 | this.board.makeShape(); 76 | }; 77 | 78 | Shape.prototype.toString = function() { 79 | var str = '\n'; 80 | 81 | this.data.forEach(function(cells, i) { 82 | str += i + ' ' + cells.join(', ') + '\n'; 83 | }); 84 | 85 | return str; 86 | }; 87 | -------------------------------------------------------------------------------- /board.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var Shapes = require('./shapes'); 3 | var Shape = require('./shape'); 4 | var util = require('util'); 5 | 6 | module.exports = Board; 7 | 8 | function Board(width, height) { 9 | EventEmitter.call(this); 10 | this.running = true; 11 | this.moveDown = false; 12 | 13 | this.width = width; 14 | this.height = height; 15 | 16 | this.cells = this.makeCells(); 17 | this.addWalls(); 18 | this.score = 0; 19 | 20 | this.updateScore(); 21 | 22 | this.makeNextShape(); 23 | this.makeShape(); 24 | } 25 | 26 | util.inherits(Board, EventEmitter); 27 | 28 | Board.prototype.makeCells = function() { 29 | var cells = []; 30 | 31 | for (var y = 0; y < this.height; y++) { 32 | cells.push(this.makeLine()); 33 | } 34 | 35 | return cells; 36 | }; 37 | 38 | Board.prototype.makeLine = function() { 39 | var cells = []; 40 | 41 | for (var x = 0; x < this.width; x++) { 42 | cells.push(0); 43 | } 44 | 45 | return cells; 46 | }; 47 | 48 | Board.prototype.addWalls = function() { 49 | var i; 50 | var x; 51 | var y; 52 | 53 | for (y = 0; y < this.height; y++) { 54 | for (x = 0; x < this.width; x++) { 55 | if (y === this.height - 1 || x === 0 || x === this.width - 1) { 56 | this.cells[y][x] = 1; 57 | } 58 | } 59 | } 60 | }; 61 | 62 | Board.prototype.addShape = function(shape) { 63 | var x = shape.x; 64 | var y = shape.y; 65 | 66 | for (var i = 0; i < shape.data.length; i++) { 67 | this.copyCells(x, y + i - 1, shape.data[i], shape.colour); 68 | } 69 | }; 70 | 71 | Board.prototype.toString = function() { 72 | var mapped; 73 | var str = '\n'; 74 | 75 | this.cells.forEach(function(cells, i) { 76 | mapped = cells.map(function(c) { 77 | if (c === 0) { 78 | return ' '; 79 | } else if (c === 1) { 80 | return 'W'; 81 | } else { 82 | return 'P'; 83 | } 84 | }).join(', '); 85 | str += (i % 10) + ' ' + mapped + '\n'; 86 | }); 87 | 88 | return str; 89 | }; 90 | 91 | Board.prototype.copyCells = function(x, y, data, colour) { 92 | var cells = this.cells; 93 | 94 | data.forEach(function(d, i) { 95 | if (cells && cells[y] && cells[y][i + x] === 0) { 96 | cells[y][i + x] = d !== 0 ? colour : 0; 97 | } 98 | }); 99 | }; 100 | 101 | Board.prototype.makeShape = function(nameOverride) { 102 | if (this.running) { 103 | this.removeFullLines(); 104 | 105 | if (nameOverride) { 106 | this.makeNextShape(nameOverride); 107 | } 108 | 109 | this.currentShape = this.nextShape; 110 | this.emit('shape', this.currentShape); 111 | this.makeNextShape(); 112 | this.checkGameOver(); 113 | } 114 | }; 115 | 116 | Board.prototype.makeNextShape = function(nameOverride) { 117 | var name = nameOverride || Shapes.random(); 118 | this.nextShape = new Shape((this.width / 2) - 2, 1, name, this); 119 | this.emit('nextshape', this.nextShape); 120 | }; 121 | 122 | Board.prototype.checkGameOver = function() { 123 | if (this.checkCollision(this.currentShape, 0)) { 124 | this.running = false; 125 | this.emit('gameover'); 126 | } 127 | }; 128 | 129 | Board.prototype.checkCollision = function(shape, xOffset) { 130 | for (var i = 0; i < shape.data.length; i++) { 131 | if (this.checkLine(i, xOffset, i, shape)) { 132 | return true; 133 | } 134 | } 135 | 136 | return false; 137 | }; 138 | 139 | Board.prototype.checkLine = function(i, xOffset, yOffset, shape) { 140 | var cells = this.cells; 141 | 142 | return shape.data[i].some(function(d, x) { 143 | return d !== 0 && cells[shape.y + yOffset][shape.x + x + xOffset] !== 0; 144 | }); 145 | }; 146 | 147 | Board.prototype.checkMoveRight = function() { 148 | var shape = new Shape(this.currentShape.x + 1, this.currentShape.y - 1, this.currentShape.name, this); 149 | shape.data = this.currentShape.data; 150 | return !this.checkCollision(shape, 0); 151 | }; 152 | 153 | Board.prototype.checkMoveLeft = function() { 154 | var shape = new Shape(this.currentShape.x - 1, this.currentShape.y - 1, this.currentShape.name, this); 155 | shape.data = this.currentShape.data; 156 | return !this.checkCollision(shape, 0); 157 | }; 158 | 159 | Board.prototype.checkRotation = function() { 160 | if (this.currentShape.name === 'O') return false; 161 | 162 | var shape = new Shape(this.currentShape.x, this.currentShape.y, this.currentShape.name, this); 163 | shape.data = this.currentShape.data; 164 | shape.rotate(); 165 | return !this.checkCollision(shape, 0); 166 | }; 167 | 168 | Board.prototype.removeFullLines = function() { 169 | this.cells.slice(0, this.cells.length - 1).some(function(cells, i) { 170 | if (cells.some(function(cell) { return cell === 0; })) { 171 | return false; 172 | } else { 173 | this.removeLine(i); 174 | } 175 | }.bind(this)); 176 | }; 177 | 178 | Board.prototype.removeLine = function(i) { 179 | this.score++; 180 | this.updateScore(); 181 | 182 | this.cells.splice(i, 1); 183 | this.cells.unshift(this.makeLine()); 184 | this.cells[0][0] = 1; 185 | this.cells[0][this.width - 1] = 1; 186 | }; 187 | 188 | Board.prototype.updateScore = function(i) { 189 | this.emit('score', this.score); 190 | }; 191 | --------------------------------------------------------------------------------