359 lines
9.0 KiB
JavaScript
359 lines
9.0 KiB
JavaScript
var canvas = document.getElementById('fuTris');
|
|
var context = canvas.getContext('2d');
|
|
canvas.width = 10*20*2+10+(5*20);
|
|
canvas.height = 16*20*2;
|
|
|
|
var field = [];
|
|
var background = [];
|
|
|
|
var game_running = true;
|
|
var subframe = 0;
|
|
var moved_steps = 0;
|
|
var speed = 100;
|
|
var points = 0;
|
|
var level = 0;
|
|
var stones = 0;
|
|
var pos = {left: 4, top: 0};
|
|
var angle = 0;
|
|
var shapes = [
|
|
// J
|
|
[[[0,1], [0,1], [1,1]],
|
|
[[1,0,0], [1,1,1]],
|
|
[[1,1], [1,0], [1,0]],
|
|
[[1,1,1], [0,0,1]]],
|
|
|
|
// []
|
|
[[[2,2], [2,2]]],
|
|
|
|
// T
|
|
[[[3,3,3], [0,3,0]],
|
|
[[0,3], [3,3], [0,3]],
|
|
[[0,3,0], [3,3,3]],
|
|
[[3,0], [3,3], [3,0]]],
|
|
|
|
// L
|
|
[[[4,4], [0,4], [0,4]],
|
|
[[0,0,4], [4,4,4]],
|
|
[[4,0], [4,0], [4,4]],
|
|
[[4,4,4], [4,0,0]]],
|
|
|
|
// 5
|
|
[[[0,5,5], [5,5,0]],
|
|
[[5,0], [5,5], [0,5]]],
|
|
|
|
// Z
|
|
[[[6,6,0], [0,6,6]],
|
|
[[0,6], [6,6], [6,0]]],
|
|
|
|
// ----
|
|
[[[7,7,7,7]], [[7], [7], [7], [7]]]
|
|
];
|
|
var colors = ['c1e184', '4f6c19', '699021', '8fc32e', '1a2308'];
|
|
var assets = {
|
|
rotate: document.getElementById('rotate-sound'),
|
|
drop: document.getElementById('drop-sound'),
|
|
move: document.getElementById('move-sound'),
|
|
point: document.getElementById('point-sound')
|
|
};
|
|
var audio_chanels = [];
|
|
|
|
var current_shape_type = 0;
|
|
var next_shape_type = Math.ceil(Math.random()*shapes.length-1);
|
|
var current_shape = shapes[current_shape_type];
|
|
|
|
function shape(type, angle) {
|
|
var s = shapes[type];
|
|
switch (s.length) {
|
|
case 1: return s[0];
|
|
case 2: return s[angle%2];
|
|
case 4: return s[angle];
|
|
}
|
|
}
|
|
|
|
function rect(x, y, w, h, scale, color) {
|
|
context.fillStyle = '#'+color;
|
|
context.fillRect(x*scale, y*scale, w*scale, h*scale);
|
|
}
|
|
|
|
function draw_shape(x, y, scale, type, angle) {
|
|
for (var xi=0; xi<shape(type, angle)[0].length; xi++)
|
|
for (var yi=0; yi<shape(type, angle).length; yi++)
|
|
if (shape(type, angle)[yi][xi] !== 0)
|
|
draw_brick(x+xi, y+yi, scale, type);
|
|
}
|
|
|
|
function draw_text(text, size, x, y) {
|
|
context.fillStyle = '#fff';
|
|
context.font = size+'px Verdana, DejaVu Sans';
|
|
context.fillText(text, x, y);
|
|
}
|
|
|
|
function draw_brick(x, y, scale, i) {
|
|
var color_offset = i > 1 ? 2 : 0;
|
|
rect(x*20+1, y*20+1, 18, 18, scale, colors[color_offset+1]);
|
|
rect(x*20+2, y*20+2, 16, 16, scale, colors[color_offset]);
|
|
|
|
// J, L, Z
|
|
if ([0, 3, 5].indexOf(i) != -1) {
|
|
rect(x*20+5, y*20+5, 10, 10, scale, colors[{0: 2, 3: 0, 5: 4}[i]]);
|
|
rect(x*20+8, y*20+8, 4, 4, scale, colors[{0: 0, 3: 2, 5: 0}[i]]);
|
|
}
|
|
|
|
// []
|
|
if (i == 1) rect(x*20+5, y*20+5, 10, 10, scale, colors[4]);
|
|
|
|
// 5
|
|
if (i ==4) rect(x*20+8, y*20+8, 4, 4, scale, colors[4]);
|
|
|
|
// T
|
|
if (i == 2) {
|
|
rect(x*20+5, y*20+5, 8, 8, scale, colors[0]);
|
|
rect(x*20+7, y*20+7, 8, 8, scale, colors[4]);
|
|
rect(x*20+7, y*20+7, 6, 6, scale, colors[2]);
|
|
}
|
|
|
|
// ----
|
|
if (i == 6) {
|
|
rect(x*20+5, y*20+6, 3, 3, scale, colors[0]);
|
|
rect(x*20+5, y*20+12, 3, 3, scale, colors[0]);
|
|
rect(x*20+11, y*20+4, 3, 3, scale, colors[0]);
|
|
rect(x*20+13, y*20+12, 3, 3, scale, colors[0]);
|
|
}
|
|
}
|
|
|
|
function draw() {
|
|
// game background
|
|
rect(0, 0, canvas.width, canvas.height, 1, '000');
|
|
|
|
// game field background
|
|
for (var x=0; x<10; x++)
|
|
for (var y=0; y<16; y++) {
|
|
rect(x*20, y*20, 20, 20, 2, background[x][y]);
|
|
if (field[x][y] !== 0) draw_brick(x, y, 2, field[x][y]-1);
|
|
}
|
|
|
|
// Current shape
|
|
draw_shape(pos.left, pos.top, 2, current_shape_type, angle);
|
|
|
|
// Next shape
|
|
draw_shape(21, 1, 1, next_shape_type, 0);
|
|
|
|
// Score
|
|
draw_text(points, 30, 420, 130);
|
|
draw_text('Points', 13, 420, 150);
|
|
|
|
// Level
|
|
draw_text(level, 30, 420, 230);
|
|
draw_text('Level', 13, 420, 250);
|
|
|
|
// Game over?
|
|
if (!game_running) {
|
|
draw_text("Game over", 60, 40, 250);
|
|
draw_text("Press R for a new game", 20, 140, 270);
|
|
}
|
|
}
|
|
|
|
function noice(offset, level) {
|
|
return offset+~~(Math.random()*level);
|
|
}
|
|
|
|
function freeze() {
|
|
for (var x=0; x<shape(current_shape_type, angle)[0].length; x++)
|
|
for (var y=0; y<shape(current_shape_type, angle).length; y++)
|
|
if (shape(current_shape_type, angle)[y][x] !== 0)
|
|
field[pos.left+x][pos.top+y] = shape(current_shape_type, angle)[y][x];
|
|
}
|
|
|
|
function clear_full_lines() {
|
|
var number_of_lines = 0;
|
|
|
|
for (var y=1; y<16; y++) {
|
|
var clear_line = true;
|
|
for (x=0; x<10; x++)
|
|
if (field[x][y] === 0) clear_line = false;
|
|
if (clear_line) {
|
|
number_of_lines++;
|
|
for (var yy=y; yy>0; yy--)
|
|
for (var xx=0; xx<10; xx++)
|
|
field[xx][yy] = field[xx][yy-1];
|
|
for (var xx=0; xx<10; xx++)
|
|
field[xx][0] = 0;
|
|
}
|
|
}
|
|
|
|
if (number_of_lines == 1) points += 10;
|
|
if (number_of_lines == 2) points += 25;
|
|
if (number_of_lines == 3) points += 50;
|
|
if (number_of_lines == 4) points += 100;
|
|
|
|
if (number_of_lines > 0) play_sound('point');
|
|
}
|
|
|
|
function play_sound(name) {
|
|
// Search for a free audio channel to play the sound
|
|
for (var a=0; a<audio_chanels.length; a++) {
|
|
now = new Date();
|
|
if (audio_chanels[a].finished < now.getTime()) {
|
|
audio_chanels[a].finished = now.getTime() + assets[name].duration * 1000;
|
|
audio_chanels[a].channel.src = assets[name].src;
|
|
audio_chanels[a].channel.play();
|
|
audio_chanels[a].channel.volume = 0.1;
|
|
}
|
|
}
|
|
}
|
|
|
|
function next_shape() {
|
|
stones++;
|
|
if (stones > 20) {
|
|
level++;
|
|
stones = 0;
|
|
}
|
|
|
|
pos.left = 4;
|
|
pos.top = 0;
|
|
current_shape_type = next_shape_type;
|
|
next_shape_type = Math.ceil(Math.random()*shapes.length-1);
|
|
current_shape = shapes[current_shape_type];
|
|
}
|
|
|
|
function will_collide(_angle, _pos) {
|
|
for (var x=0; x<shape(current_shape_type, _angle)[0].length; x++)
|
|
for (var y=0; y<shape(current_shape_type, _angle).length; y++)
|
|
if (pos.top+shape(current_shape_type, _angle).length >= 16 ||
|
|
shape(current_shape_type, _angle)[y][x] !== 0 &&
|
|
field[_pos.left+x][_pos.top+y+1] !== 0)
|
|
return true;
|
|
}
|
|
|
|
addEventListener('keydown', function(event) {
|
|
if (game_running) {
|
|
// Move left
|
|
if ((event.keyCode == 37 || event.keyCode == 72) &&
|
|
pos.left > 0 &&
|
|
!will_collide(angle, {left: pos.left-1, top: pos.top})) {
|
|
pos.left--;
|
|
play_sound('move');
|
|
draw();
|
|
}
|
|
|
|
// Move right
|
|
if ((event.keyCode == 39 || event.keyCode == 76) &&
|
|
pos.left < 10-shape(current_shape_type, angle)[0].length &&
|
|
!will_collide(angle, {left: pos.left+1, top: pos.top})) {
|
|
pos.left++;
|
|
play_sound('move');
|
|
draw();
|
|
}
|
|
|
|
// Rotate
|
|
if (event.keyCode == 38 || event.keyCode == 75) {
|
|
var new_angle = angle === 0 ? 3 : angle-1;
|
|
if (pos.left+shape(current_shape_type, new_angle)[0].length <= 10 &&
|
|
!will_collide(new_angle, pos))
|
|
angle = new_angle;
|
|
play_sound('rotate');
|
|
draw();
|
|
}
|
|
|
|
// Drop
|
|
if (event.keyCode == 40 || event.keyCode == 74) {
|
|
while(!will_collide(angle, {left: pos.left, top: pos.top+1}))
|
|
pos.top++;
|
|
pos.top++;
|
|
points += 2;
|
|
freeze();
|
|
clear_full_lines();
|
|
next_shape();
|
|
play_sound('drop');
|
|
draw();
|
|
}
|
|
}
|
|
|
|
// Pause the game
|
|
if (event.keyCode == 13 || event.keyCode == 80) {
|
|
game_running = !game_running;
|
|
if (game_running) loop();
|
|
}
|
|
|
|
// Restart the game
|
|
if (event.keyCode == 82 && !game_running) {
|
|
init();
|
|
}
|
|
});
|
|
|
|
function init() {
|
|
speed = 100;
|
|
points = 0;
|
|
level = 0;
|
|
stones = 0;
|
|
game_running = true;
|
|
subframe = 0;
|
|
moved_steps = 0;
|
|
|
|
for (var x=0; x<10; x++)
|
|
for (var y=0; y<16; y++)
|
|
field[x][y] = 0;
|
|
|
|
// Start the game
|
|
next_shape();
|
|
loop();
|
|
}
|
|
|
|
function loop() {
|
|
// Move the next step
|
|
subframe++;
|
|
if (subframe-speed > 0.0) {
|
|
subframe = 0;
|
|
speed = 100-(level*15);
|
|
|
|
// Check if we get blocked by the already dropped shapes
|
|
+function() {
|
|
if (will_collide(angle, pos)) {
|
|
freeze();
|
|
clear_full_lines();
|
|
next_shape();
|
|
|
|
// Game over?
|
|
if (moved_steps === 0 || will_collide(0, {left: 4, top: 0}))
|
|
game_running = false;
|
|
moved_steps = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}() || (pos.top++ && moved_steps++);
|
|
draw();
|
|
}
|
|
|
|
if (game_running) setTimeout(loop, 5);
|
|
}
|
|
|
|
function setup() {
|
|
// Load the assets
|
|
assets.rotate.load();
|
|
assets.drop.load();
|
|
assets.move.load();
|
|
assets.point.load();
|
|
|
|
// Init the audio chanels
|
|
for (var a=0; a<10; a++) {
|
|
audio_chanels[a] = {
|
|
channel: new Audio(),
|
|
finished: -1
|
|
};
|
|
}
|
|
|
|
// Initialize the game field and the background
|
|
for (var x=0; x<10; x++) {
|
|
field[x] = [];
|
|
background[x] = [];
|
|
for (var y=0; y<16; y++) {
|
|
var mod = noice(15, 15);
|
|
background[x][y] = '' + mod + mod + mod;
|
|
field[x][y] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
setup();
|
|
init();
|