Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
<header>
  <h1>TETRIS</h1>
</header>
  
<div id="preview-screen"></div>

<div id="screen"></div>

<div id="score">0</div>

<button id="start">Play</button>

<footer>
  <p>Created by <a href="https://remybeumier.be" target="_blank">Rémy Beumier</a></p>
</footer>
              
            
!

CSS

              
                
* {
  box-sizing: border-box;
}

html {
  
}

body {
  font-family: Arial;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
/*   background: linear-gradient(-45deg, #e53935 0 20px, #fb8c00  20px 40px, #fdd835 40px 60px, #43a047 60px 80px, #00acc1 80px 100px, #3949ab 100px 120px, #8e24aa 120px 140px, transparent 140px), linear-gradient(135deg, #e53935 0 20px, #fb8c00  20px 40px, #fdd835 40px 60px, #43a047 60px 80px, #00acc1 80px 100px, #3949ab 100px 120px, #8e24aa 120px 140px, #fff 140px); */
/*   background: linear-gradient(115deg, #e53935 0 40px, #fb8c00  40px 120px, #fdd835 60px 35%, #43a047 35% 65%, #00acc1 65% calc(100% - 120px), #3949ab calc(100% - 120px) calc(100% - 40px), #8e24aa calc(100% - 40px) 100%); */
  background: linear-gradient(-45deg, #3949ab, #80deea);
}

h1 {
  text-align: center;
  font-size: 32px;
  letter-spacing: 1rem;
  margin-right: -1rem;
  color: white;
}

#screen {
  background: #eee;
  border: solid 5px white;
  width: 210px;
  height: 410px;
  display: flex;
  flex-wrap: wrap;
}

#screen div {
  width: 20px;
  height: 20px;
  border: solid 1px white;
}

#screen div:nth-of-type(n+201) {
  border: transparent;
}

#preview-screen {
  display: flex;
  flex-wrap: wrap;
  width: 90px;
  height: 90px;
  margin-bottom: 20px;
  border: solid 5px white;
  background: #eee;
}

#preview-screen div {
  width: 20px;
  height: 20px;
  border: solid 1px white;
}

#preview-screen div:nth-of-type(n+17) {
  border: transparent;
}

.tetromino {
/*   background: red; */
}

#score {
  padding: 5px;
  color: white;
  
}

#start {
  width: 210px;
/*   border: solid 5px white; */
  cursor: pointer;
  background: white;
  border: none;
  padding: 5px;
  margin-bottom: 20px;
}

#start:hover,
#start:focus {
  background: #eee;
  border-color: #eee;
}

footer {
  font-size: 14px;
  padding: 10px;
  color: #fff;
}

footer a {
  text-decoration: none;
  color: #ccc;
}

footer a:hover {
  text-decoration: underline;
  color: #fff;
}

              
            
!

JS

              
                
// fix space bar moveBottom() morking weird with line -> next piece appears too low
// change tetrominoes appearing -> only first block, not full shape !!
// add mobile swipe


var screen = document.querySelector("#screen");
var previewScreen = document.querySelector("#preview-screen");
// create play screen
for (let i = 0; i<200; i++) {
  createEl("div", "", screen);
}
// create frozen screen
for (let i = 0; i<20; i++) {
  createEl("div", "", screen, "taken");
}
// create preview screen
for (let i = 0; i<20; i++) {
  createEl("div", "", previewScreen);
}

let squares = Array.from(document.querySelectorAll("#screen div"));

const scoreEl = document.querySelector("#score");
const startEl = document.querySelector("#start");
const width = 10;
let nextRandom = 0;
let timerId;
let speed = 500;
let score = 0;
let started = false;
let over = false;
const colors = ["#e53935", "#fb8c00", "#fdd835", "#43a047", "#00acc1", "#3949ab", "#8e24aa"];
// red, orange, yellow, green, cyan, indigo, purple

// the tetrominoes
const lt = [
  [1, width+1, width*2+1, width*2+2],
  [width, width+1, width+2, width*2],
  [0, 1, width+1, width*2+1],
  [2, width, width+1, width+2]
];

const jt = [
  [1, width+1, width*2+1, 2],
  [width, width+1, width+2, width*2+2],
  [1, width+1, width*2+1, width*2],
  [0, width, width+1, width+2]
];

const st = [
  [1, width+1, width+2, width*2+2],
  [width+1, width+2, width*2, width*2+1],
  [1, width+1, width+2, width*2+2],
  [width+1, width+2, width*2, width*2+1]
];

const zt = [
  [2, width+1, width+2, width*2+1],
  [width, width+1, width*2+1, width*2+2],
  [2, width+1, width+2, width*2+1],
  [width, width+1, width*2+1, width*2+2]
];

const tt = [
  [1, width, width+1, width+2],
  [1, width+1, width+2, width*2+1],
  [width, width+1, width+2, width*2+1],
  [1, width, width+1, width*2+1]
];

const ot = [
  [1, 2, width+1, width+2],
  [1, 2, width+1, width+2],
  [1, 2, width+1, width+2],
  [1, 2, width+1, width+2]
];

const it = [
  [1, width+1, width*2+1, width*3+1],
  [width, width+1, width+2, width+3],
  [1, width+1, width*2+1, width*3+1],
  [width, width+1, width+2, width+3]
];

const tetrominoes = [lt, jt, st, zt, tt, ot, it];

let currentPosition = 3;
let currentRotation = 0;

let random = Math.floor(Math.random() * tetrominoes.length);
let current = tetrominoes[random][currentRotation];

// draw the tetromino
function draw() {
  current.forEach(index => {
    squares[currentPosition + index].classList.add("tetromino");
    squares[currentPosition + index].style.backgroundColor = colors[random];
  });
}

function undraw() {
  current.forEach(index => {
    squares[currentPosition + index].classList.remove("tetromino");
    squares[currentPosition + index].style.backgroundColor = "";
  });
}

// assign function to keyCodes
function control(e) {
  if (e.keyCode === 37) {
    moveLeft();
  }
  else if (e.keyCode === 38) {
    rotate();
  }
  else if (e.keyCode === 39) {
    moveRight();
  }
  else if (e.keyCode === 40) {
    moveDown();
  }
  else if (e.keyCode === 32) {
    console.log("space -> move bottom");
    e.preventDefault();
    moveBottom();
  }
  else if (e.keyCode === 27) {
    startPause();
  }
  else {
    console.log(e.keyCode);
  }
}

function moveDown() {
  undraw();
  currentPosition += width;
  draw();
  freeze();
}

function moveBottom() {
  while (!current.some(index => squares[currentPosition + index + width].classList.contains("taken"))) {
    undraw();
    currentPosition += width;
    draw();
  }
  freeze();
}

//freeze when tetromino touches taken square
function freeze() {
  if (current.some(index => squares[currentPosition + index + width].classList.contains("taken"))) {
    current.forEach(index => squares[currentPosition + index].classList.add("taken"));
    // start a new tetromino falling
    random = nextRandom;
    nextRandom = Math.floor(Math.random() * tetrominoes.length);
    current = tetrominoes[random][currentRotation];
    currentPosition = 3;
    draw();
    displayShape();
    addScore();
    gameOver();
  }
}

// move tetromino left if room
function moveLeft() {
  undraw();
  if (!isAtLeft()) {
    currentPosition -= 1;
  }
  if (current.some(index => squares[currentPosition + index].classList.contains("taken"))) {
    currentPosition += 1;
  }
  draw();
}

function isAtLeft() {
  return current.some(index=> (currentPosition + index) % width === 0)
}

// move tetromino right if room
function moveRight() {
  undraw();
  if (!isAtRight()) {
    currentPosition += 1;
  }
  if (current.some(index => squares[currentPosition + index].classList.contains("taken"))) {
    currentPosition -= 1;
  }
  draw();
}

function isAtRight() {
  return current.some(index=> (currentPosition + index + 1) % width === 0)  
}

// FIX ROTATION OF TETROMINOS A THE EDGE --> https://github.com/kubowania/Tetris-Basic/commit/c7e804e936c0624c947116a9c83b67005c257d94
function checkRotatedPosition(P){
  P = P || currentPosition;
  if ((P+1) % width < 4) {
    if (isAtRight()) {
      currentPosition += 1;
      checkRotatedPosition(P);
    }
  }
  else if (P % width > 5) {
    if (isAtLeft()) {
      currentPosition -= 1;
      checkRotatedPosition(P);
    }
  }
}

// rotate tetromino
function rotate() {
  undraw();
  currentRotation ++;
  if (currentRotation === tetrominoes[random].length) {
    currentRotation = 0;
  }
  current = tetrominoes[random][currentRotation];
  checkRotatedPosition();
  draw();
}

// show next tetromino in preview
const displaySquares = document.querySelectorAll("#preview-screen div");
const displayWidth = 4;
const displayIndex = 0;

const upNextTetrominoes = [
  [1, displayWidth+1, displayWidth*2+1, displayWidth*2+2], //lTetromino
  [1, displayWidth+1, displayWidth*2+1, 2], //jTetromino
  [1, displayWidth+1, displayWidth+2, displayWidth*2+2], //sTetromino
  [2, displayWidth+1, displayWidth+2, displayWidth*2+1], //zTetromino
  [1, displayWidth, displayWidth+1, displayWidth+2], //tTetromino
  [1, 2, displayWidth+1, displayWidth+2], //oTetromino
  [1, displayWidth+1, displayWidth*2+1, displayWidth*3+1] //iTetromino
];

// display the preview tetromino
function displayShape() {
  displaySquares.forEach(square => {
    square.classList.remove("tetromino");
    square.style.backgroundColor = "";
  });
  upNextTetrominoes[nextRandom].forEach(index => {
    displaySquares[displayIndex + index].classList.add("tetromino");
    displaySquares[displayIndex + index].style.backgroundColor = colors[nextRandom];
  });
}

function startPause() {
  // pause
  if (timerId) {
    clearInterval(timerId);
    timerId = null;
    startEl.innerHTML = "Play";
    // document.removeEventListener("keyup", control);
  }
  // resume
  else if (started) {
    startEl.innerHTML = "Pause";
    draw();
    timerId = setInterval(moveDown, speed);
    document.addEventListener("keyup", control);
  }
  // restart
  else if (over) {
    resetGame();
    started = true;
    startEl.innerHTML = "Pause";
    draw();
    timerId = setInterval(moveDown, speed);
    nextRandom = Math.floor(Math.random() * tetrominoes.length);
    displayShape();
    document.addEventListener("keyup", control);
  }
  // play
  else {
    started = true;
    startEl.innerHTML = "Pause";
    draw();
    timerId = setInterval(moveDown, speed);
    nextRandom = Math.floor(Math.random() * tetrominoes.length);
    displayShape();
    document.addEventListener("keyup", control);
  }
}

startEl.addEventListener("click", (e) => {
  e.preventDefault();
  startPause();
});

function addScore() {
  for (let i=0; i< 199; i+=width) {
    const row = [i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8, i+9];
    if (row.every(index => squares[index].classList.contains("taken"))) {
      score += 10;
      scoreEl.innerHTML = score;
      row.forEach(index => {
        squares[index].classList.remove("taken");
        squares[index].classList.remove("tetromino");
        squares[index].style.backgroundColor = "";
      });
      const squaresRemoved = squares.splice(i, width);
      squares = squaresRemoved.concat(squares);
      squares.forEach(cell => screen.appendChild(cell));
    }
  }
}

function gameOver() {
  if (current.some(index => squares[currentPosition + index].classList.contains("taken"))) {
    scoreEl.innerHTML = "X";
    clearInterval(timerId);
    timerId = null;
    started = false;
    over = true;
    startEl.innerHTML = "Restart";
    document.removeEventListener("keyup", control);
  }
}

function resetGame() {
  for (let i=0; i<200; i++) {
    squares[i].classList.remove("tetromino", "taken");
    squares[i].style.backgroundColor = "transparent";
  }
  for (let i=0; i<20; i++) {
    displaySquares[i].classList.remove("tetromino");
    displaySquares[i].style.backgroundColor = "transparent";
  }
  score = 0;
  scoreEl.innerHTML = score;
}

function createEl(el, txt, prnt, cls) {
  var newEl = document.createElement(el);
  newEl.classList.add(cls);
  newEl.innerText = txt;
  prnt.appendChild(newEl);
}

              
            
!
999px

Console