├── tetris.png ├── README.md ├── package.json └── index.js /tetris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mafintosh/tetris/HEAD/tetris.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tetris 2 | 3 | Play tetris in your terminal - in color 4 | 5 | npm install -g tetris 6 | 7 | ## How do I play? 8 | 9 | ![tetris](https://raw.github.com/mafintosh/tetris/master/tetris.png) 10 | 11 | Simply open a terminal and run `tetris` 12 | 13 | $ tetris 14 | 15 | It even works over ssh so you can tetris while you ops -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"tetris", 3 | "version":"0.1.6", 4 | "description":"play tetris in your terminal", 5 | "repository":"git@github.com:mafintosh/tetris.git", 6 | "keywords":["tetris","terminal","cli"], 7 | "bin":{ 8 | "tetris":"./index.js" 9 | }, 10 | "dependencies":{ 11 | "clivas":"0.1.x", 12 | "keypress":"0.1.x" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var clivas = require('clivas'); 4 | var keypress = require('keypress'); 5 | 6 | var WIDTH = 15; 7 | var HEIGHT = 20; 8 | 9 | clivas.alias('box-color', 'inverse+cyan'); 10 | clivas.alias('full-width', 2*WIDTH+4); 11 | clivas.flush(false); 12 | clivas.cursor(false); 13 | 14 | 'black white blue yellow green magenta'.split(' ').forEach(function(color, i) { 15 | clivas.alias('color-'+i, '2+inverse+'+color); 16 | }); 17 | 18 | var NUMBERS = [ 19 | [ 20 | 'xxx', 21 | 'x x', 22 | 'x x', 23 | 'x x', 24 | 'xxx' 25 | ],[ 26 | ' x', 27 | ' x', 28 | ' x', 29 | ' x', 30 | ' x' 31 | ],[ 32 | 'xxx', 33 | ' x', 34 | 'xxx', 35 | 'x ', 36 | 'xxx' 37 | ],[ 38 | 'xxx', 39 | ' x', 40 | 'xxx', 41 | ' x', 42 | 'xxx' 43 | ],[ 44 | 'x x', 45 | 'x x', 46 | 'xxx', 47 | ' x', 48 | ' x' 49 | ],[ 50 | 'xxx', 51 | 'x ', 52 | 'xxx', 53 | ' x', 54 | 'xxx' 55 | ],[ 56 | 'xxx', 57 | 'x ', 58 | 'xxx', 59 | 'x x', 60 | 'xxx' 61 | ],[ 62 | 'xxx', 63 | ' x', 64 | ' x', 65 | ' x', 66 | ' x' 67 | ],[ 68 | 'xxx', 69 | 'x x', 70 | 'xxx', 71 | 'x x', 72 | 'xxx' 73 | ],[ 74 | 'xxx', 75 | 'x x', 76 | 'xxx', 77 | ' x', 78 | ' x' 79 | ] 80 | ]; 81 | var FIGURES = [ 82 | [ 83 | [0,1,0], 84 | [0,1,0], 85 | [1,1,0] 86 | ],[ 87 | [false,1,0], 88 | [false,1,0], 89 | [false,1,1] 90 | ],[ 91 | [1,1,0], 92 | [0,1,1], 93 | [0,0,0] 94 | ],[ 95 | [0,1,1], 96 | [1,1,0], 97 | [0,0,0] 98 | ],[ 99 | [0,0,0,0], 100 | [1,1,1,1], 101 | [0,0,0,0], 102 | [0,0,0,0] 103 | ],[ 104 | [1,1], 105 | [1,1] 106 | ],[ 107 | [0,1,0], 108 | [1,1,1], 109 | [0,0,0] 110 | ] 111 | ]; 112 | 113 | var nextFigure = (Math.random()*FIGURES.length)|0; 114 | var nextColor = 1+((Math.random()*5)|0); 115 | 116 | var selectFigure = function() { 117 | figure = FIGURES[nextFigure]; 118 | nextFigure = (Math.random()*FIGURES.length)|0; 119 | for (var i = 0; i < figure.length; i++) { 120 | for (var j = 0; j < figure.length; j++) { 121 | figure[i][j] = figure[i][j] && nextColor; 122 | } 123 | } 124 | nextColor = 1+((Math.random()*5)|0); 125 | y = -figure.length; 126 | x = ((WIDTH / 2) - (figure.length / 2)) | 0; 127 | 128 | var btm = figure.length-1; 129 | 130 | while (allEmpty(figure[btm])) { 131 | y++; 132 | btm--; 133 | } 134 | }; 135 | var getScore = function() { 136 | var color = 1+(((score / 100)|0)%5); 137 | 138 | return ('00000'.substring(score.toString().length)+score.toString()).split('').map(function(d) { 139 | var num = NUMBERS[d]; 140 | return num; 141 | }).reduce(function(result, value) { 142 | value.forEach(function(line, i) { 143 | result[i] = (result[i] ? result[i] + ' ' : ''); 144 | result[i] += ' '+line.replace(/x/g, '{2+color-'+color+'}').replace(/ /g, ' '); 145 | }); 146 | return result; 147 | }, []); 148 | }; 149 | 150 | var board = []; 151 | var x = 0; 152 | var y = 0; 153 | var figure; 154 | var score = 0; 155 | 156 | for (var i = 0; i < HEIGHT; i++) { 157 | board[i] = []; 158 | for (var j = 0; j < WIDTH; j++) { 159 | board[i][j] = 0; 160 | } 161 | } 162 | 163 | var rotateFigureMutation = function(dir) { 164 | var result = []; 165 | for (var i = 0; i < figure.length; i++) { 166 | for (var j = 0; j < figure[i].length; j++) { 167 | var y = dir === 1 ? j : figure.length-j-1; 168 | var x = dir === 1 ? figure.length-1-i : i; 169 | result[y] = result[y] || []; 170 | result[y][x] = figure[i][j]; 171 | } 172 | } 173 | figure = result; 174 | }; 175 | var addFigureMutation = function(draw) { 176 | for (var i = 0; i < figure.length; i++) { 177 | for (var j = 0; j < figure[i].length; j++) { 178 | var py = y+i; 179 | var px = x+j; 180 | if (figure[i][j] && (px < 0 || px >= WIDTH)) return false; 181 | if (py < 0) continue; 182 | if (!figure[i][j]) continue; 183 | if (!board[py] || board[py][px] || board[py][px] === undefined) return false; 184 | if (!draw) continue; 185 | board[py][px] = figure[i][j] || board[py][px]; 186 | } 187 | } 188 | return draw ? true : addFigureMutation(true); 189 | }; 190 | var removeFigureMutation = function() { 191 | for (var i = 0; i < figure.length; i++) { 192 | for (var j = 0; j < figure[i].length; j++) { 193 | var py = y+i; 194 | var px = x+j; 195 | if (px < 0) continue; 196 | if (!figure[i][j] || !board[py] || board[py][px] === undefined) continue; 197 | board[py][px] = 0; 198 | } 199 | } 200 | }; 201 | 202 | var line = function() { 203 | var arr = []; 204 | for (var i = 0; i < WIDTH; i++) { 205 | arr[i] = 0; 206 | } 207 | return arr; 208 | }; 209 | var allEmpty = function(arr) { 210 | return !arr.some(function(val) { 211 | return val; 212 | }); 213 | }; 214 | var hasEmpty = function(arr) { 215 | return arr.some(function(val) { 216 | return !val; 217 | }); 218 | }; 219 | var moveFigure = function(dx,dy) { 220 | removeFigureMutation(); 221 | x += dx; 222 | y += dy; 223 | if (addFigureMutation()) return draw(); 224 | x -= dx; 225 | y -= dy; 226 | addFigureMutation(); 227 | draw(); 228 | }; 229 | var rotateFigure = function(dir) { 230 | removeFigureMutation(); 231 | rotateFigureMutation(dir); 232 | if (addFigureMutation()) return draw(); 233 | rotateFigureMutation(-dir); 234 | addFigureMutation(); 235 | }; 236 | var removeLines = function() { 237 | var modifier = 0; 238 | for (var i = 0; i < board.length; i++) { 239 | if (hasEmpty(board[i])) continue; 240 | board.splice(i,1); 241 | board.unshift(line()); 242 | if (!modifier) { 243 | modifier += 150; 244 | } 245 | modifier *= 2; 246 | speed += 10; 247 | } 248 | score += modifier; 249 | }; 250 | 251 | var draw = function() { 252 | clivas.clear(); 253 | 254 | var scoreDraw = getScore(); 255 | 256 | clivas.line(''); 257 | clivas.line(' {full-width+box-color}'); 258 | 259 | for (var i = 0; i < HEIGHT; i++) { 260 | var line = '{color-'+board[i].join('}{color-')+'}'; 261 | var padding = ' '; 262 | 263 | if (i > 3 && scoreDraw[i-4]) { 264 | padding = ' '+scoreDraw[i-4]; 265 | } 266 | if (i > 10 && FIGURES[nextFigure][i-11]) { 267 | padding = ' '+FIGURES[nextFigure][i-11].join('').replace(/false/g, '').replace(/0/g, ' ').replace(/[1-9]/g, '{2+color-'+nextColor+'}')+' '; 268 | } 269 | 270 | clivas.line(' {2+box-color}'+line+'{2+box-color}'+padding); 271 | } 272 | 273 | clivas.line(' {full-width+box-color} {green:tetris} {bold:'+require('./package.json').version+'} {green:by} {bold:@mafintosh}'); 274 | clivas.line(''); 275 | return true; 276 | }; 277 | 278 | var loop = function() { 279 | if (moveFigure(0,1)) return setTimeout(loop, speed); 280 | removeLines(); 281 | if (y < 0) { 282 | clivas.alias('box-color', 'inverse+red'); 283 | draw(); 284 | process.exit(0); 285 | return; 286 | } 287 | selectFigure(); 288 | setTimeout(loop, speed); 289 | }; 290 | 291 | var speed = 600; 292 | 293 | setInterval(function() { 294 | speed -= 20; 295 | speed = Math.max(speed, 50); 296 | }, 10000); 297 | 298 | setTimeout(loop, speed); 299 | 300 | selectFigure(); 301 | addFigureMutation(); 302 | draw(); 303 | 304 | keypress(process.stdin); 305 | 306 | process.stdin.on('keypress', function(ch, key) { 307 | if (key.name === 'c' && key.ctrl) return process.exit(0); 308 | if (key.name === 'right' || key.name === 'l') { 309 | moveFigure(1, 0); 310 | } 311 | if (key.name === 'left' || key.name === 'h') { 312 | moveFigure(-1, 0); 313 | } 314 | if (key.name === 'down' || key.name === 'j') { 315 | if (moveFigure(0, 1)) { 316 | score++; 317 | } 318 | } 319 | if (key.name === 'up' || key.name === 'k') { 320 | rotateFigure(1); 321 | } 322 | if (key.name === 'space' || (key.name === 'g' && key.shift)) { 323 | while (moveFigure(0, 1)) { 324 | score++; 325 | } 326 | } 327 | }); 328 | process.stdin.resume(); 329 | 330 | try { 331 | process.stdin.setRawMode(true); 332 | } catch (err) { 333 | require('tty').setRawMode(true); 334 | } 335 | --------------------------------------------------------------------------------