url-tetris

tetris inside the browser address bar
git clone https://tongong.net/git/url-tetris.git
Log | Files | Refs | README

commit a75f16207837d08ace25d2edfc016322461fe139
Author: tongong <tongong@gmx.net>
Date:   Sat, 30 May 2020 18:54:56 +0200

initial commit

Diffstat:
Afavicon.ico | 0
Aicon.svg | 9+++++++++
Aindex.html | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.js | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astyles.css | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 362 insertions(+), 0 deletions(-)

diff --git a/favicon.ico b/favicon.ico Binary files differ. diff --git a/icon.svg b/icon.svg @@ -0,0 +1,9 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<mask id="path-2-inside-1" fill="white"> +<path d="M12 10C12 9.44772 12.4477 9 13 9H18C19.1046 9 20 9.89543 20 11V15C20 16.1046 19.1046 17 18 17H13C12.4477 17 12 16.5523 12 16V10Z"/> +</mask> +<path d="M12 10C12 9.44772 12.4477 9 13 9H18C19.1046 9 20 9.89543 20 11V15C20 16.1046 19.1046 17 18 17H13C12.4477 17 12 16.5523 12 16V10Z" fill="white" stroke="black" stroke-width="4" mask="url(#path-2-inside-1)"/> +<rect x="7" y="10" width="6" height="6" fill="white" stroke="black" stroke-width="2"/> +<path d="M2 10H7V16H2C1.44772 16 1 15.5523 1 15V11C1 10.4477 1.44772 10 2 10Z" fill="white" stroke="black" stroke-width="2"/> +<path d="M8 4H12C12.5523 4 13 4.44772 13 5V10H7V5C7 4.44772 7.44772 4 8 4Z" fill="white" stroke="black" stroke-width="2"/> +</svg> diff --git a/index.html b/index.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" /> + <title>url-tetris</title> + <link rel="stylesheet" href="styles.css" /> + <script src="main.js"></script> + </head> + <body> + <img id="icon" src="icon.svg" alt="Tetris Icon" /> + <h1 id="header">url-tetris</h1> + <hr id="divider" /> + + <div id="content"> + <p>This is a version of the popular Tetris game running in your address bar!</p> + + <h2>CONTROLS</h2> + <table> + <tr> + <td>W or ⬆️</td> + <td>Move upwards</td> + </tr> + <tr> + <td>A or ⬅️</td> + <td>Move leftwards</td> + </tr> + <tr> + <td>S or ⬇️</td> + <td>Move downwards</td> + </tr> + <tr> + <td>D or ➡️</td> + <td>Rotate</td> + </tr> + <tr> + <td>SPACE</td> + <td>Start the game</td> + </tr> + </table> + + <h2>HOW DOES IT WORK</h2> + <p> + With the <a href="https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState">History.replaceState()</a> method the current url can be changed without reloading the page. For the graphics + <a href="https://en.wikipedia.org/wiki/Braille_Patterns">Braille Patterns</a> are used. + </p> + <p>Inspired by <a href="http://probablycorey.com/url-hunter">URL Hunter</a> and + <a href="http://flappybraille.ndre.gr">Flappy Braille</a>.</p> + <p>Code at the <a href="https://github.com/tongong/url-tetris"> GitHub repository</a>.</p> + </div> + + <footer>made with &#10084; by <a href="https://github.com/tongong">tongong</a></footer> + </body> +</html> diff --git a/main.js b/main.js @@ -0,0 +1,241 @@ +const tetrominoes = [ + [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 1], + [0, 0, 0, 0], + ], + [ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 1, 0], + [0, 0, 0, 0], + ], + [ + [0, 0, 0, 0], + [0, 1, 1, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + ], + [ + [0, 0, 0, 0], + [0, 1, 0, 0], + [0, 1, 1, 0], + [0, 0, 1, 0], + ], + [ + [0, 0, 0, 0], + [0, 0, 1, 0], + [0, 1, 1, 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], + ], +]; +var field = []; +var fallingTetrominoes = []; + +function FallingTetromino(tetrominoIndex, posX, posY) { + this.tetrominoIndex = tetrominoIndex; + this.posX = posX; + this.posY = posY; + this.rotation = 0; + this.render = function (onField) { + return addTetromino(onField, this.tetrominoIndex, this.posX, this.posY, this.rotation).field; + }; + this.rotate = function () { + this.rotation++; + this.rotation %= 4; + if (!addTetromino(field, this.tetrominoIndex, this.posX, this.posY, this.rotation).success) { + if (addTetromino(field, this.tetrominoIndex, this.posX, this.posY + 1, this.rotation).success) this.posY++; + else if (addTetromino(field, this.tetrominoIndex, this.posX, this.posY - 1, this.rotation).success) this.posY--; + else if (addTetromino(field, this.tetrominoIndex, this.posX, this.posY + 2, this.rotation).success) this.posY += 2; + else if (addTetromino(field, this.tetrominoIndex, this.posX, this.posY - 2, this.rotation).success) this.posY -= 2; + else { + this.rotation--; + if (this.rotation == -1) this.rotation = 3; + } + } + }; + this.goUp = function () { + this.posY--; + if (!addTetromino(field, this.tetrominoIndex, this.posX, this.posY, this.rotation).success) { + this.posY++; + } + }; + this.goDown = function () { + this.posY++; + if (!addTetromino(field, this.tetrominoIndex, this.posX, this.posY, this.rotation).success) { + this.posY--; + } + }; + this.goLeft = function () { + this.posX--; + if (!addTetromino(field, this.tetrominoIndex, this.posX, this.posY, this.rotation).success) { + this.posX++; + field = addTetromino(field, this.tetrominoIndex, this.posX, this.posY, this.rotation).field; + this.delete(); + } + }; + this.delete = function () { + fallingTetrominoes.forEach((e, index) => { + if (e === this) fallingTetrominoes.splice(index, 1); + }); + }; +} + +function setUrl(url) { + history.replaceState({}, url, "#" + url); +} + +// leftColoum and rightColoum are arrays of bit from the top to the bottom +function getBraille(leftColumn, rightColumn) { + // https://en.wikipedia.org/wiki/Braille_Patterns + return String.fromCharCode( + 0x2800 + + leftColumn[0] + + leftColumn[1] * 2 + + leftColumn[2] * 4 + + rightColumn[0] * 8 + + rightColumn[1] * 16 + + rightColumn[2] * 32 + + leftColumn[3] * 64 + + rightColumn[3] * 128 + ); +} + +function renderField(f) { + if (f.length % 2 == 1) f.push([0, 0, 0, 0]); + let urlsting = ""; + for (let index = 0; index < f.length; index += 2) { + urlsting += getBraille(f[index], f[index + 1]); + } + setUrl(urlsting); +} + +function renderTetrominoes() { + let newField = field; + fallingTetrominoes.forEach((e) => { + newField = e.render(newField); + }); + renderField(newField); +} + +function addTetromino(field, tetrominoIndex, posX, posY, rotation) { + // rotation -> from 0 to 4 clockwise + let newField = []; + let success = true; + + field.forEach((e1) => { + let line = []; + e1.forEach((e2) => { + line.push(e2); + }); + newField.push(line); + }); + let newTetromino = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]; + tetrominoes[tetrominoIndex].forEach((e1, x) => { + e1.forEach((e2, y) => { + switch (rotation) { + case 0: + newTetromino[x][y] = e2; + break; + case 1: + newTetromino[y][3 - x] = e2; + break; + case 2: + newTetromino[3 - x][3 - y] = e2; + break; + case 3: + newTetromino[3 - y][x] = e2; + break; + } + }); + }); + + let collision = false; + + newTetromino.forEach((e1, x) => { + e1.forEach((e2, y) => { + if (e2 == 1) { + if (y + posX < 0 || x + posY < 0 || x + posY > 3 || newField[y + posX][x + posY] == 1) { + success = false; + if (y + posX < 0 || newField[y + posX][x + posY] == 1) { + collision = true; + } + } else { + newField[y + posX][x + posY] = 1; + } + } + }); + }); + + return { success, collision, field: newField }; +} + +function upPressed() { + console.log("up"); + fallingTetrominoes.forEach((e) => e.goUp()); + renderTetrominoes(); +} + +function downPressed() { + console.log("down"); + fallingTetrominoes.forEach((e) => e.goDown()); + renderTetrominoes(); +} + +function leftPressed() { + console.log("left"); + fallingTetrominoes.forEach((e) => e.goLeft()); + renderTetrominoes(); +} + +function rightPressed() { + console.log("right"); + fallingTetrominoes.forEach((e) => e.rotate()); + renderTetrominoes(); +} + +function startGame() { + field = []; + for (let index = 0; index < 300; index++) { + field.push([0, 0, 0, 0]); + } + fallingTetrominoes = [new FallingTetromino(0, 60, 0), new FallingTetromino(5, 80, 0)]; + + frame(); +} + +function frame() { + fallingTetrominoes.forEach((e) => e.goLeft()); + renderTetrominoes(); + setTimeout(frame, 1000); +} + +document.addEventListener("keydown", (e) => { + if (e.code === "ArrowUp") upPressed(); + else if (e.code === "ArrowDown") downPressed(); + else if (e.code === "ArrowLeft") leftPressed(); + else if (e.code === "ArrowRight") rightPressed(); + else if (e.code === "KeyW") upPressed(); + else if (e.code === "KeyS") downPressed(); + else if (e.code === "KeyA") leftPressed(); + else if (e.code === "KeyD") rightPressed(); + else if (e.code === "Space") startGame(); +}); diff --git a/styles.css b/styles.css @@ -0,0 +1,57 @@ +@import url("https://fonts.googleapis.com/css2?family=Fira+Code:wght@600&display=swap"); + +body { + font-family: "Fira Code", monospace; +} + +#icon { + height: 60px; + display: block; + margin-left: auto; + margin-right: auto; + margin-top: 50px; +} + +#header { + text-align: center; + font-size: 40px; + margin-top: 10px; +} + +#divider { + color: black; + background-color: black; + height: 2px; + width: 1000px; + max-width: 100%; +} + +#content { + width: 800px; + margin: 0 auto; + max-width: 100%; + text-align: center; +} + +table { + margin: 0 auto; +} + +td { + padding: 5px 20px; +} + +h2 { + margin-top: 80px; + font-size: 30px; +} + +footer { + margin: 0 auto; + margin-top: 80px; + text-align: center; +} + +a { + color: #f08700; +}