Skip to content

Instantly share code, notes, and snippets.

@fmarcia
Last active November 19, 2018 18:15
Show Gist options
  • Save fmarcia/d3df7b9e7267e25c013ddb3b59755ae7 to your computer and use it in GitHub Desktop.
Save fmarcia/d3df7b9e7267e25c013ddb3b59755ae7 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Tetris</title>
</head>
<body>
<script src="tetris.js"></script>
</body>
</html>
/*
* Tetris - 2006/06/22
*
* Copyright (c) 2006-2018 Franck Marcia
* Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php
*/
{
const padding = 2;
const cellSize = 22;
const totalSize = padding + cellSize;
const baseDuration = 600;
const points = [ 0, 40, 100, 300, 1200 ];
const colors = [ "#eaeaea", "#ffaa33", "#ffff66", "#0000ff", "#c0f", "#0e0", "#6cf", "#ff0000" ];
const startAt = [ 0, -1, -1, -1, 0, -1, -1, 0 ];
const shapes = [
// none
[],
// I
[ [ [], [1,1,1,1], [], [] ],[ [0,1], [0,1], [0,1], [0,1] ] ],
// T
[
[ [], [2,2,2], [0,2], [] ], [ [0,2], [2,2], [0,2], [] ],
[ [0,2], [2,2,2], [], [] ], [ [0,2], [0,2,2], [0,2], [] ]
],
// L
[
[ [], [3,3,3], [3], [] ], [ [3,3], [0,3], [0,3], [] ],
[ [0,0,3], [3,3,3], [], [] ], [ [0,3], [0,3], [0,3,3], [] ]
],
// J
[
[ [4], [4,4,4], [], [] ], [ [0,4,4], [0,4], [0,4], [] ],
[ [], [4,4,4], [0,0,4], [] ], [ [0,4], [0,4], [4,4], [] ]
],
// Z
[ [ [], [5,5], [0,5,5], [] ], [ [0,0,5], [0,5,5], [0,5], [] ] ],
// S
[ [ [], [0,6,6], [6,6], [] ], [ [0,6], [0,6,6], [0,0,6], [] ] ],
// O
[ [ [0,7,7], [0,7,7], [], [] ] ]
];
let containerElement, gridElement, nextElement, linesElement, scoreElement;
let gridContext, nextContext;
let startButton, stopButton;
let shape, currentShape, nextShape, duration;
let mode, timer, lines, score, grid;
let x, y, r, prevX, prevY, prevR;
function install() {
insertCSS();
insertHTML();
containerElement = document.querySelector("#tetris");
gridElement = document.querySelector("#tetris-grid");
nextElement = document.querySelector("#tetris-next");
gridContext = gridElement.getContext("2d");
nextContext = nextElement.getContext("2d");
linesElement = document.querySelector("#tetris-lines");
scoreElement = document.querySelector("#tetris-score");
startButton = document.querySelector("#tetris-start");
stopButton = document.querySelector("#tetris-stop");
document.addEventListener("keydown", handleKey, false);
startButton.addEventListener("click", route, false);
stopButton.addEventListener("click", stop, false);
stop();
}
function insertCSS() {
const style = document.createElement("style");
style.type = "text/css";
style.innerHTML = [
"#tetris { background-color:#f9f9f9; border:1px dashed #aaa; box-sizing:border-box; color:#666 }",
"#tetris { font-family:sans-serif; height:456px; left:50%; margin:-228px 0 0 -200px; overflow:hidden }",
"#tetris { padding:12px; position:fixed; top:-456px; transition:top 250ms ease-in-out; width:400px }",
"#tetris.tetris-visible { top:50% }",
"#tetris-grid { float:left }",
"#tetris-next { float:right; margin-bottom:12px }",
"#tetris-grid, #tetris-next { background-color:", colors[0], "; opacity:.3; transition:opacity 250ms }",
"#tetris-stats { clear:right; float:right; line-height:24px; text-align:right }",
"#tetris-stats > div { font-size:80%; margin-bottom:8px }",
"#tetris-lines, #tetris-score { font-size:250%; width:130px }",
"#tetris-cmd { float:right; font-size:120%; text-align:right; width:130px }",
"#tetris-start { color:#666; margin-bottom:8px }",
".tetris-running #tetris-grid, .tetris-running #tetris-next { opacity:1 }",
"#tetris-stop { opacity:0; transition:opacity 250ms }",
".tetris-running #tetris-stop { opacity:1 }"
].join("");
document.head.appendChild(style);
}
function insertHTML() {
document.body.insertAdjacentHTML("beforeend", [
`<div id="tetris">`,
`<canvas id="tetris-grid" height="430" width="238"></canvas>`,
`<canvas id="tetris-next" height="94" width="94"></canvas>`,
`<div id="tetris-stats">`,
`<div><div id="tetris-lines">0</div>lines</div>`,
`<div><div id="tetris-score">0</div>score</div>`,
`</div>`,
`<div id="tetris-cmd">`,
`<input id="tetris-start" type="button" value="start"/><br/>`,
`<input id="tetris-stop" type="button" value="stop"/>`,
`</div>`,
`</div>`
].join(""));
}
function handleKey(e) {
if (e.keyCode === 27) {
containerElement.classList.toggle("tetris-visible");
startButton.focus();
if (mode === "running" && !containerElement.classList.contains("tetris-visible")) {
pause();
}
} else if (mode === "running") {
switch(e.charCode || e.keyCode) {
case 37: case 74: case 106: return moveLeft(); // J
case 39: case 76: case 108: return moveRight(); // L
case 40: case 75: case 107: return moveDown(1); // K
case 38: case 73: case 105: return rotate(); // I
}
}
return false;
}
function route() {
switch (mode) {
case "stopped": return start();
case "running": return pause();
case "paused": return resume();
}
}
function start() {
containerElement.classList.add("tetris-running");
gridContext.clearRect(0, 0, gridElement.width, gridElement.height);
startButton.value = "pause";
grid = [];
for (let i = 0; i < 18; i += 1) {
grid.push([ 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8 ]);
}
grid.push([ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ]);
mode = "running";
lines = 0;
score = 0;
nextShape = newShape();
shift();
refresh();
duration = baseDuration;
timer = setTimeout(moveDown, duration);
}
function pause() {
clearTimeout(timer);
startButton.value = "resume";
mode = "paused";
}
function resume() {
timer = setTimeout(moveDown, duration);
startButton.value = "pause";
mode = "running";
}
function stop() {
clearTimeout(timer);
startButton.value = "start";
mode = "stopped";
containerElement.classList.remove("tetris-running");
}
function newShape() {
return Math.floor(Math.random()*7 + 1);
}
function drawNext() {
const s = shapes[nextShape][0];
const c = colors[nextShape];
const n = colors[0];
for (let i = 0; i < 4; i += 1) {
for (let j = 0; j < 4; j += 1) {
nextContext.fillStyle = s[i][j] ? c : n;
fillRect(nextContext, j, i);
}
}
}
function shift() {
currentShape = nextShape;
x = prevX = 4;
y = startAt[currentShape];
prevY = y - 1;
r = prevR = 0;
shape = shapes[currentShape];
if (check(0, x, y)) {
nextShape = newShape();
drawNext();
return true;
}
}
function check(r, x, y) {
for (let i = 0; i < 4; i += 1) {
for (let j = 0; j < 4; j += 1) {
if (shape[r][j][i] && grid[y + j] && grid[y + j][x + i]) {
return false;
}
}
}
return true;
}
function moveLeft() {
if (check(r, x - 1, y)) {
x -= 1;
refresh();
}
}
function moveRight() {
if (check(r, x + 1, y)) {
x += 1;
refresh();
}
}
function moveDown(once) {
if (check(r, x, y + 1)) {
y += 1;
refresh();
} else {
touchDown();
}
if (!once && mode === "running") {
timer = setTimeout(moveDown, duration);
}
}
function rotate() {
let t = (r + 1) % shape.length;
if (check(t, x, y)) {
prevR = r;
r = t;
refresh();
}
}
function fillRect(ctx, x, y) {
ctx.fillRect(x * totalSize, y * totalSize, cellSize, cellSize);
}
function touchDown() {
for (let i = 0; i < 4; i += 1) {
for (let j = 0; j < 4; j += 1) {
if (shape[r][j][i]) {
grid[y + j][x + i] = currentShape;
}
}
}
let lineCount = 0;
for (let i = 17, k = 17; i > -1 && lineCount < 4; i -= 1, k -= 1) {
if (!grid[i].includes(0)) {
gridContext.fillStyle = "#fff";
for (let j = 0; j < 10; j += 1) {
fillRect(gridContext, j, k);
}
lineCount += 1;
for (let j = i; j > 0; j -= 1) {
grid[j] = grid[j - 1].concat();
}
i += 1;
}
}
if (lineCount) {
setTimeout(animate.bind(null, lineCount), 150);
}
if (shift()) {
refresh();
} else {
stop();
}
}
function animate(lineCount) {
lines += lineCount;
const level = Math.floor(lines/10);
duration = baseDuration - Math.min(level, 21) * 25;
score += (level + 1) * points[lineCount];
for (let i = 0; i < 18; i += 1) {
for (let j = 1; j < 11; j += 1) {
gridContext.fillStyle = colors[grid[i][j]];
fillRect(gridContext, j - 1, i);
}
}
refresh();
}
function refresh() {
gridContext.fillStyle = colors[0];
for (let i = 0; i < 4; i += 1) {
for (let j = 0; j < 4; j += 1) {
if (shape[prevR][i][j]) {
fillRect(gridContext, prevX + j - 1, prevY + i);
}
}
}
gridContext.fillStyle = colors[currentShape];
for (let i = 0; i < 4; i += 1) {
for (let j = 0; j < 4; j += 1) {
if (shape[r][i][j]) {
fillRect(gridContext, x + j - 1, y + i);
}
}
}
prevX = x;
prevY = y;
prevR = r;
linesElement.innerHTML = lines;
scoreElement.innerHTML = score;
}
install();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment