commit a75f16207837d08ace25d2edfc016322461fe139
Author: tongong <tongong@gmx.net>
Date: Sat, 30 May 2020 18:54:56 +0200
initial commit
Diffstat:
A | favicon.ico | | | 0 | |
A | icon.svg | | | 9 | +++++++++ |
A | index.html | | | 55 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.js | | | 241 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | styles.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 ❤ 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;
+}