this.tetris = new TetrisGame(10, 24);
this.timer = new Timer();
for(var i = 0; i < pallette.length; i++){
var gray = (rgb[0] + rgb[1] + rgb[2]) / 3.0;
pallette_mono[i][0] = 255 * (1-mix) + gray * mix;
pallette_mono[i][1] = 255 * (1-mix) + gray * mix;
pallette_mono[i][2] = 255 * (1-mix) + gray * mix;
if(this.timer.updateStep()){
this.tetris.display(this);
function genRandomShape(){
var shp = []; shp.length = len;
var lut = []; lut.length = len;
for(var i = 0; i < len; i++){
var idx_pallette = 1 + parseInt(random(pallette.length-1));
var filled = 1 + parseInt(random(len-1));
for(var i = 0; i < filled; i++){
var idx_lut = parseInt(random(len - i));
lut[idx_lut] = lut[len - 1 - i];
var keypress_DOWN = false;
var keypress_LEFT = false;
var keypress_RIGHT = false;
function applyInput(new_delay){
if(this.tetris.pause) return;
if(keypress_UP ) this.tetris.rotate = true;
if(keypress_DOWN ) this.tetris.ty = +1;
if(keypress_LEFT ) this.tetris.tx = -1;
if(keypress_RIGHT) this.tetris.tx = +1;
this.timer.reset(new_delay);
if(key == 'P') this.tetris.pause = !this.tetris.pause;
if(key == 'R') this.tetris.restart = true;
keypress_UP |= (key === 'W') || (keyCode === UP_ARROW );
keypress_DOWN |= (key === 'S') || (keyCode === DOWN_ARROW );
keypress_LEFT |= (key === 'A') || (keyCode === LEFT_ARROW );
keypress_RIGHT |= (key === 'D') || (keyCode === RIGHT_ARROW);
keypress_UP ^= (key === 'W') || (keyCode === UP_ARROW );
keypress_DOWN ^= (key === 'S') || (keyCode === DOWN_ARROW );
keypress_LEFT ^= (key === 'A') || (keyCode === LEFT_ARROW );
keypress_RIGHT ^= (key === 'D') || (keyCode === RIGHT_ARROW);
this.tgrid = new TGrid(nx, ny);
this.timer = new Timer();
this.random_shape_chance = 0;
this.shape_next = undefined;
this.rows_per_level = 10;
this.shape_curr = this.shape_next;
if(random(7) < this.random_shape_chance){
this.shape_next = genRandomShape();
var idx_next = parseInt(random(shapelist.length));
this.shape_next = shapelist[idx_next].slice();
this.tgrid.setShape(this.shape_curr);
this.level += floor(this.rows_completed / this.rows_per_level);
this.rows_completed %= this.rows_per_level;
this.timer.duration = ceil(800 / sqrt(this.level));
this.game_over = this.tgrid.collision(0, 0);
if(this.rotate) this.tgrid.rotateShape();
if(!this.tgrid.collision(this.tx, 0)) this.tgrid.sx += this.tx;
if(!this.tgrid.collision(0, this.ty)) this.tgrid.sy += this.ty;
if(this.timer.updateStep()){
if(!this.tgrid.collision(0, 1)){
this.rows_completed += this.tgrid.updateRows();
var off, x, y, w, h, cell;
var canvas_w = canvas.width;
var canvas_h = canvas.height;
cell = ceil(Math.min(w / this.tgrid.nx, h / this.tgrid.ny));
w = this.tgrid.nx * cell;
h = this.tgrid.ny * cell;
x = parseInt((canvas_w - w) / 2.0);
y = parseInt((canvas_h - h) / 2.0);
canvas.rect(x-4,y-4,w+8,h+8);
canvas.rect(x-1,y-1,w+3,h+3);
var colors = (this.pause || this.game_over) ? pallette_mono : pallette;
this.displayGameGrid(canvas, x, y, w ,h, colors);
var h_ = canvas_h - 2 * off;
this.displayNextShape(canvas, x_, y_, w_ ,h_);
var txt1_title = "TETRIS";
var txt2_title = "Regular";
canvas.textAlign(CENTER, CENTER);
canvas.text(txt1_title, tx1, ty);
canvas.text(txt2_title, tx1, ty + 22);
var progress = round(100 * this.rows_completed / this.rows_per_level);
var ty = canvas_h/2 -130;
var txt_level = "LEVEL "+this.level;
var txt_progress = "LINES "+this.rows_completed+"/"+this.rows_per_level;
var txt_shapes = "SCORES "+this.shapes_count;
canvas.textAlign(CENTER, CENTER);
canvas.text(txt_level, tx1, ty);
canvas.text(txt_progress, tx1, ty+=24);
canvas.text(txt_shapes, tx1, ty+=16);
var txt_game_status = undefined;
if(this.game_over) txt_game_status = "GAME OVER";
if(this.pause ) txt_game_status = "PAUSE";
if(txt_game_status !== undefined){
canvas.textAlign(CENTER, CENTER);
canvas.fill( 0, 0, 0); canvas.text(txt_game_status, canvas_w/2+2, canvas_h/2+1);
canvas.fill(255, 224, 0); canvas.text(txt_game_status, canvas_w/2 , canvas_h/2 );
var ty = canvas_h - 6 * 15 - off;
canvas.text("W / UP" , tx1, ty); canvas.text("- ROTATE" , tx2, ty); ty += 15;
canvas.text("A / LEFT" , tx1, ty); canvas.text("- MOVE LEFT" , tx2, ty); ty += 15;
canvas.text("D / RIGHT", tx1, ty); canvas.text("- MOVE RIGHT", tx2, ty); ty += 15;
canvas.text("S / DOWN" , tx1, ty); canvas.text("- MOVE DOWN" , tx2, ty); ty += 25;
canvas.text("P" , tx1, ty); canvas.text("- PAUSE" , tx2, ty); ty += 15;
canvas.text("R" , tx1, ty); canvas.text("- RESTART" , tx2, ty); ty += 15;
displayGameGrid(pg, x, y, w, h, pallette){
for(var gy = 0; gy < ny; gy++){
for(var gx = 0; gx < nx; gx++){
for(var gy = 0; gy < ny; gy++){
for(var gx = 0; gx < nx; gx++){
var val_grid = this.tgrid.getGridVal(gx, gy);
var rgb = pallette[val_grid % pallette.length];
pg.fill(rgb[0], rgb[1], rgb[2]);
var ks = this.tgrid.shape_size;
var kr = ceil(this.tgrid.shape_size / 2.0);
for(var ky = 0; ky < ks; ky++){
for(var kx = 0; kx < ks; kx++){
var gx = this.tgrid.sx + kx - kr;
var gy = this.tgrid.sy + ky - kr;
var val_shape = this.tgrid.getShapeVal(kx, ky);
var rgb = pallette[val_shape % pallette.length];
pg.fill(rgb[0], rgb[1], rgb[2]);
displayNextShape(pg, x, y, w, h){
var shape = this.shape_next;
var shape_size = parseInt(sqrt(shape.length));
var kr = shape_size / 2.0;
var cw = min(w / 5.0, h / 5.0);
for(var ky = 0; ky < ks; ky++){
for(var kx = 0; kx < ks; kx++){
var cx = x + gx * cw + w/2.0;
var cy = y + gy * ch + h/2.0;
var val_shape = shape[ky * shape_size + kx];
this.duration = duration;
return millis() - this.time;
if(this.getTime() >= this.duration){
this.grid.length = nx * ny;
this.shape_size = parseInt(sqrt(shape.length));
this.sx = ceil(this.nx / 2);
this.sy = ceil(this.shape_size / 2);
for(var i = 0; i < this.grid.length; i++){
return x >= 0 && x < this.nx && y >= 0 && y < this.ny;
if(!this.isInsideGrid(x, y)){
return this.grid[y * this.nx + x];
return this.shape[y * this.shape_size + x];
this.grid[y * this.nx + x] = val;
var size = this.shape_size;
var cpy = this.shape.slice(0);
var ib = 0, ia = size * size;
for(var y = 1; y <= size; y++, ia++){
for(var x = 1; x <= size; x++, ib++){
this.shape[ib] = cpy[ia - x * size];
for(var y = 1; y <= size; y++, ia--){
for(var x = 1; x <= size; x++, ib++){
this.shape[ib] = cpy[ia + x * size];
this.rotateShapeDir(true);
if(this.collision(0, 0)){
this.rotateShapeDir(false);
var ks = this.shape_size;
var kr = ceil(this.shape_size / 2);
for(var ky = 0; ky < ks; ky++){
for(var kx = 0; kx < ks; kx++){
var px = this.sx + kx - kr + tx;
var py = this.sy + ky - kr + ty;
var val_grid = this.getGridVal (px, py);
var val_shape = this.getShapeVal(kx, ky);
if(val_grid * val_shape != 0){
for(var gy = 0; gy < this.ny; gy++){
var row_completed = true;
for(var gx = 0; gx < this.nx; gx++){
var gi = gy * this.nx + gx;
if(this.grid[gi] == 0) row_completed = false;
this.grid.copyWithin(this.nx, 0, gy * this.nx)
for(var gx = 0; gx < this.nx; gx++){
var ks = this.shape_size;
var kr = ceil(this.shape_size / 2);
for(var ky = 0; ky < ks; ky++){
for(var kx = 0; kx < ks; kx++){
var px = this.sx + kx - kr;
var py = this.sy + ky - kr;
var val_shape = this.getShapeVal(kx, ky);
this.setGridVal(px, py, val_shape);