Last active
November 19, 2018 18:15
-
-
Save fmarcia/d3df7b9e7267e25c013ddb3b59755ae7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<title>Tetris</title> | |
</head> | |
<body> | |
<script src="tetris.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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