Even more Canvas fun - Tetris in JavaScript
Tetris in JavaScript using the Canvas element, 'nuff said!
Score: Lines: Level:
The Code
The bulk of the code is involved with checking the bounds of various pieces and making sure that the proper thing happens in the event of a collision. About 300 lines total.
function Tetris(canvas_id, block_size, score_callback) {
var width = 14,
height = 20,
canvas_id = canvas_id,
block_size = block_size ? block_size : 20,
calculated_width = width * block_size,
calculated_height = height * block_size,
board,
pending_shape,
active_shape,
context,
level,
score,
lines;
var BLOCK_EMPTY = 0,
BLOCK_FULL = 1,
BLOCK_ACTIVE = 2;
// keys
var UP = 38, DOWN = 40, LEFT = 37, RIGHT = 39;
function Shape() {
var self = this;
var shapes = [
[[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]],
[[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]],
[[0, 0, 0, 0], [0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0]],
[[0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 1, 1, 0]],
[[0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0]],
[[0, 0, 0, 0], [0, 0, 1, 0], [0, 1, 1, 0], [0, 1, 0, 0]],
[[0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 1, 0]]
];
this.rotate = function() {
var new_shape = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
for (var j = 0; j < 4; j++)
for (var i = 0; i < 4; i++) {
new_shape[i][j] = self.shape[4 - j - 1][i];
}
self.shape = new_shape;
}
this.left_edge = function() {
for (var x = 0; x < 4; x++)
for (var y = 0; y < 4; y++)
if (self.shape[y][x] == BLOCK_FULL)
return x;
}
this.right_edge = function() {
for (var x = 3; x >= 0; x--)
for (var y = 0; y < 4; y++)
if (self.shape[y][x] == BLOCK_FULL)
return x;
}
this.bottom_edge = function() {
for (var y = 3; y >= 0; y--)
for (var x = 0; x < 4; x++)
if (self.shape[y][x] == BLOCK_FULL)
return y;
}
this.initialize = function() {
var rotations = parseInt(Math.random() * 4),
shape_idx = parseInt(Math.random() * shapes.length);
// grab a random shape
self.shape = shapes[shape_idx];
// rotate it a couple times
for (var i = 0; i < rotations; i++)
self.rotate();
}
this.clone = function() {
s = new Shape();
s.x = self.x;
s.y = self.y;
s.shape = self.shape;
return s;
}
}
function reset() {
board = [];
for (var y = 0; y < height; y++) {
var row = [];
for (var x = 0; x < width; x++)
row.push(0);
board.push(row);
}
score = 0;
lines = 0;
level = 1;
if (score_callback)
score_callback(score, lines, level);
pending_shape = new Shape();
pending_shape.initialize();
add_shape();
}
function add_shape() {
active_shape = pending_shape;
active_shape.x = width / 2 - 2;
active_shape.y = -1;
pending_shape = new Shape();
pending_shape.initialize();
if (is_collision(active_shape))
reset();
}
function rotate_shape() {
rotated_shape = active_shape.clone();
rotated_shape.rotate();
if (rotated_shape.left_edge() + rotated_shape.x < 0)
rotated_shape.x = -rotated_shape.left_edge();
else if (rotated_shape.right_edge() + rotated_shape.x >= width)
rotated_shape.x = width - rotated_shape.right_edge() - 1;
if (rotated_shape.bottom_edge() + rotated_shape.y > height)
return false;
if (!is_collision(rotated_shape))
active_shape = rotated_shape;
}
function move_left() {
active_shape.x--;
if (out_of_bounds() || is_collision(active_shape)) {
active_shape.x++;
return false;
}
return true;
}
function move_right() {
active_shape.x++;
if (out_of_bounds() || is_collision(active_shape)) {
active_shape.x--;
return false;
}
return true;
}
function move_down() {
active_shape.y++;
if (check_bottom() || is_collision(active_shape)) {
active_shape.y--;
shape_to_board();
add_shape();
return false;
}
return true;
}
function out_of_bounds() {
if (active_shape.x + active_shape.left_edge() < 0)
return true;
else if (active_shape.x + active_shape.right_edge() >= width)
return true;
return false;
}
function check_bottom() {
return (active_shape.y + active_shape.bottom_edge() >= height);
}
function is_collision(shape) {
for (var y = 0; y < 4; y++)
for (var x = 0; x < 4; x++) {
if (y + shape.y < 0)
continue;
if (shape.shape[y][x] && board[y + shape.y][x + shape.x])
return true;
}
return false;
}
function test_for_line() {
for (var y = height - 1; y >= 0; y--) {
var counter = 0;
for (var x = 0; x < width; x++)
if (board[y][x] == BLOCK_FULL)
counter++;
if (counter == width) {
process_line(y);
return true;
}
}
return false;
}
function process_line(y_to_remove) {
lines++;
score += level;
if (lines % 10 == 0)
level++;
for (var y = y_to_remove - 1; y >= 0; y--)
for (var x = 0; x < width; x++)
board[y + 1][x] = board[y][x];
if (score_callback)
score_callback(score, lines, level);
}
function shape_to_board() {
// transpose onto board
for (var y = 0; y < 4; y++)
for (var x = 0; x < 4; x++) {
var dx = x + active_shape.x,
dy = y + active_shape.y;
if (dx < 0 || dx >= width || dy < 0 || dy >=height)
continue;
if (active_shape.shape[y][x] == BLOCK_FULL)
board[dy][dx] = BLOCK_FULL;
}
var lines_found = 0;
while (test_for_line())
lines_found++;
return lines_found;
}
function move_piece(motion) {
if (motion == LEFT)
move_left();
else if (motion == RIGHT)
move_right();
else if (motion == UP)
rotate_shape();
else if (motion == DOWN)
move_down();
}
function draw_game_board() {
context.fillStyle = "#000";
context.fillRect(0, 0, calculated_width, calculated_height);
context.fillStyle = "#ccc";
for (var y = 0; y < height; y++)
for (var x = 0; x < width; x++)
if (board[y][x] == BLOCK_FULL || board[y][x] == BLOCK_ACTIVE)
draw_block(x, y);
context.fillStyle = "#fff";
for (var y = 0; y < 4; y++)
for (var x = 0; x < 4; x++) {
var dx = x + active_shape.x,
dy = y + active_shape.y;
if (active_shape.shape[y][x] == BLOCK_FULL)
draw_block(dx, dy);
}
t = setTimeout(function() { draw_game_board(); }, 30);
}
function draw_block(x, y) {
context.fillRect(x * block_size, y * block_size, block_size, block_size);
}
function handleKeys(e) {
var k;
var evt = (e) ? e : window.event;
k = (evt.charCode) ?
evt.charCode : evt.keyCode;
if (k > 36 && k < 41) {
move_piece(k);
return false;
};
return true;
}
function update_board() {
move_down();
t = setTimeout(function() { update_board(); }, 800 - (50 * level));
}
function initialize() {
var canvas = document.getElementById(canvas_id);
context = canvas.getContext('2d');
// create handlers
document.onkeypress = function(e) { return handleKeys(e) };
reset();
draw_game_board();
update_board();
}
initialize();
}
Comments (1)
Commenting has been closed.
Eric Martin | nov 29 2010, at 11:30pm
Seeing var self = this; made me chuckle a little bit. Impressive game, but some colors would be nice.