<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>俄罗斯方块</title>
<style type="text/css">
* {
box-sizing: border-box;
}
#toolbar {
width: 222px;
margin: 0 auto 5px;
}
#toolbar > button {
font-size: 12px;
padding: 2px 5px;
border-radius: 5px;
cursor: pointer;
outline: none;
width: 100px;
}
#canvas-container {
box-sizing: content-box;
width: 220px;
margin: 0 auto;
border: 1px solid #555;
background: #f0f0f0;
}
#canvas-container > canvas {
vertical-align: top;
}
</style>
</head>
<body>
<div id='toolbar'>
<Button onclick='onClickStart(event)'>开始</Button>
<Button onclick='onClickPause(event)'>暂停</Button>
</div>
<div id='canvas-container'>
<canvas width="220px" height="440px">你的浏览器不支持canvas,请升级你的浏览器</canvas>
</div>
<script type="text/javascript">
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (w < 2 * r) {r = w / 2;}
if (h < 2 * r){ r = h / 2;}
this.beginPath();
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
this.closePath();
return this;
}
function createEmptyCubes() {
return (new Array(20)).fill(0).map(() => (new Array(10)).fill(0));
}
var canvas = document.getElementsByTagName('canvas')[0];
var context = canvas.getContext('2d');
var cubes = createEmptyCubes();
var shapes = {
S1: [[1, 2], [2, 2], [2, 1], [3, 1]],
S2: [[1, 0], [1, 1], [2, 1], [2, 2]],
Z1: [[1, 1], [2, 1], [2, 2], [3, 2]],
Z2: [[2, 0], [2, 1], [1, 1], [1, 2]],
L1: [[1, 0], [1, 1], [1, 2], [2, 2]],
L2: [[1, 2], [2, 2], [3, 2], [3, 1]],
L3: [[1, 0], [2, 0], [2, 1], [2, 2]],
L4: [[1, 1], [1, 2], [2, 1], [3, 1]],
J1: [[2, 0], [2, 1], [2, 2], [1, 2]],
J2: [[1, 1], [1, 2], [2, 2], [3, 2]],
J3: [[1, 0], [1, 1], [1, 2], [2, 0]],
J4: [[1, 1], [2, 1], [3, 1], [3, 2]],
I1: [[1, 0], [1, 1], [1, 2], [1, 3]],
I2: [[0, 1], [1, 1], [2, 1], [3, 1]],
O: [[1, 1], [2, 1], [1, 2], [2, 2]],
T1: [[1, 1], [2, 1], [3, 1], [2, 2]],
T2: [[2, 0], [2, 1], [2, 2], [3, 1]],
T3: [[2, 0], [1, 1], [2, 1], [3, 1]],
T4: [[1, 1], [2, 0], [2, 1], [2, 2]]
}
var colors = {
S: '#19f219',
Z: '#d30200',
L: '#d76400',
J: '#0600d1',
I: '#19f2f2',
O: '#d6d501',
T: '#aaf219'
};
var MAX_LEVEL = 10;
var SCORE_PER_LEVEL = 500;
var shapeX = 3;
var shapeY = 0;
var shape = 'J1';
var fall = false;
var duration = 500;
var timerId = null;
var over = false;
var score = 0;
var level = 0;
function getLevelFromScore(score) {
return Math.floor(score / SCORE_PER_LEVEL) + 1;
}
function getDurationFromLevel(level) {
if (level > 10) {
return 50;
} else {
return 550 - level * 50;
}
}
function drawScore(score, level) {
context.save();
context.fillStyle = 'black';
context.font = '14px 隶书';
context.fillText(`分数:${score}`, 5, 15);
context.textAlign = 'right';
context.fillText(`Level ${level}`, 215, 15);
context.restore();
}
function drawCube(x, y) {
context.roundRect(1 + x * 22, 1 + y * 22, 20, 20, 4);
context.fill();
}
function drawShape(shape, x, y) {
context.save();
context.fillStyle = colors[shape[0]];
for (const arr of shapes[shape]) {
drawCube(x + arr[0], y + arr[1]);
}
context.restore();
}
function drawBoard() {
for (let y = 0; y < 20; y++) {
for (let x = 0; x < 10; x++) {
if (cubes[y][x]) {
context.save();
context.fillStyle = cubes[y][x];
drawCube(x, y);
context.restore();
}
}
}
}
function drawGameOver() {
context.save();
context.fillStyle = 'rgba(100, 100, 100, 0.6)';
context.fillRect(0, 0, 220, 440);
context.fillStyle = 'white';
context.font = "30px Georgia";
context.fillText("Game Over", 30, 220);
context.restore();
}
function drawSuccess() {
context.save();
context.fillStyle = 'rgba(100, 100, 100, 0.6)';
context.fillRect(0, 0, 220, 440);
context.fillStyle = 'red';
context.font = "40px Georgia";
context.fillText("恭喜通关", 30, 220);
context.restore();
}
function drawPause() {
context.save();
context.fillStyle = 'rgba(100, 100, 100, 0.6)';
context.fillRect(0, 0, 220, 440);
context.fillStyle = 'white';
context.font = "30px Georgia";
context.fillText("Game Pause", 30, 220);
context.restore();
}
function drawStart() {
for (let y = 12; y < 20; y++) {
for (let x = 0; x < 10; x++) {
drawCube(x, y);
}
}
drawScore(0, 0);
context.save();
context.fillStyle = 'rgba(100, 100, 100, 0.6)';
context.fillRect(0, 0, 220, 440);
context.fillStyle = 'white';
context.font = "30px Georgia";
context.fillText("Start Game", 30, 220);
context.restore();
}
function update() {
context.clearRect(0, 0, 220, 440);
drawBoard();
if (shape) {
drawShape(shape, shapeX, shapeY);
}
drawScore(score, level);
if (over) {
drawGameOver();
}
}
function randomShape() {
const shape = ['S1', 'S2', 'Z1', 'Z2', 'L1', 'L2', 'L3', 'L4', 'J1', 'J2', 'J3', 'J4', 'I1', 'I2', 'O', 'T1', 'T2', 'T3', 'T4'];
const index = Math.floor(Math.random() * shape.length);
return shape[index];
}
function isOverflow(x, y) {
return x < 0 || x > 9 || y > 19;
}
function isOverlap(x, y) {
return cubes[y][x];
}
function overflow(x, y) {
return shapes[shape].some(cord => isOverflow(cord[0] + x, cord[1] + y) || isOverlap(cord[0] + x, cord[1] + y));
}
function copyCurrentShapeToCubes() {
for (const cord of shapes[shape]) {
cubes[cord[1] + shapeY][cord[0] + shapeX] = colors[shape[0]];
}
}
function clearRow(row) {
for (let x = 0; x < 10; x++) {
cubes[row][x] = 0;
}
}
function moveDown(row) {
for (let y = row; y > 0; y--) {
for (let x = 0; x < 10; x++) {
cubes[y][x] = cubes[y - 1][x];
}
}
clearRow(0);
}
function clearRows() {
var row = 0;
for (let y = 19; y > -1; y--) {
if (cubes[y].every(value => value)) {
moveDown(y);
y++;
row++;
}
}
return row;
}
function rowToScore(row) {
if (row > 0 && row < 5) {
return [10, 30, 60, 100][row - 1];
} else {
return 0;
}
}
function go() {
if (!fall) {
return;
}
if (!overflow(shapeX, shapeY + 1)) {
shapeY++;
update();
timerId = setTimeout(go, duration);
} else {
fall = false;
copyCurrentShapeToCubes();
score += rowToScore(clearRows());
level = getLevelFromScore(score);
duration = getDurationFromLevel(level);
shape = null;
if (level > MAX_LEVEL) {
level = MAX_LEVEL;
update();
drawSuccess();
} else {
update();
timerId = setTimeout(() => {
shapeX = 3;
shapeY = 0;
shape = randomShape();
if (!overflow(shapeX, shapeY)) {
fall = true;
go();
} else {
over = true;
update();
}
}, duration);
}
}
}
document.onkeydown = function onKeyDown(event) {
if (!fall) {
return;
}
if (event.key === 'ArrowLeft') {
if (!overflow(shapeX - 1, shapeY)) {
shapeX--;
update();
}
} else if (event.key === 'ArrowRight') {
if (!overflow(shapeX + 1, shapeY)) {
shapeX++;
update();
}
} else if (event.key === 'ArrowDown') {
if (!overflow(shapeX, shapeY + 1)) {
shapeY++;
update();
}
}
}
document.onkeyup = function onKeyUp(event) {
if (!fall) {
return;
}
if (event.key === 'ArrowUp') {
var origin = shape;
if (shape.length === 2) {
if (shape[0] === 'T' || shape[0] === 'L' || shape[0] === 'J') {
if (Number(shape[1]) < 4) {
shape = shape[0] + (Number(shape[1]) + 1);
} else {
shape = shape[0] + 1;
}
} else if (shape[0] === 'I' || shape[0] === 'S' || shape[0] === 'Z') {
if (shape[1] === '1') {
shape = shape[0] + 2;
} else {
shape = shape[0] + 1;
}
}
if (!overflow(shapeX, shapeY)) {
update();
} else {
shape = origin;
}
}
}
}
function onClickStart(event) {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
event.target.innerText = '重新开始';
score = 0;
shapeX = 3;
shapeY = 0;
level = 1;
duration = getDurationFromLevel(level);
shape = randomShape();
fall = true;
shapeY--;
cubes = createEmptyCubes();
over = false;
go();
}
function onClickPause(event) {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
if (over) {
return;
}
if (event.target.innerText === '暂停') {
if (fall) {
event.target.innerText = '继续';
fall = false;
drawPause();
}
} else {
event.target.innerText = '暂停';
fall = true;
go();
}
}
drawStart();
</script>
</body>
</html>