<link href="https://fonts.googleapis.com/css?family=Press+Start+2P&display=swap" rel="stylesheet" />

<div id="frame">
  <h1>Alvanoid</h1>
  <div>Points: <span id="points">0</span></div>
  <div id="board">
    <div id="tile-00" class="tile red" data-row="0"></div>
    <div id="tile-01" class="tile red" data-row="0"></div>
    <div id="tile-02" class="tile red" data-row="0"></div>
    <div id="tile-03" class="tile red" data-row="0"></div>
    <div id="tile-04" class="tile red" data-row="0"></div>
    <div id="tile-05" class="tile red" data-row="0"></div>

    <div id="tile-10" class="tile blue" data-row="1"></div>
    <div id="tile-11" class="tile blue" data-row="1"></div>
    <div id="tile-12" class="tile blue" data-row="1"></div>
    <div id="tile-13" class="tile blue" data-row="1"></div>
    <div id="tile-14" class="tile blue" data-row="1"></div>
    <div id="tile-15" class="tile blue" data-row="1"></div>

    <div id="tile-20" class="tile green" data-row="2"></div>
    <div id="tile-21" class="tile green" data-row="2"></div>
    <div id="tile-22" class="tile green" data-row="2"></div>
    <div id="tile-23" class="tile green" data-row="2"></div>
    <div id="tile-24" class="tile green" data-row="2"></div>
    <div id="tile-25" class="tile green" data-row="2"></div>

    <div id="ball"></div>
    <div id="paddle"></div>
  </div>
</div>
html, body {
  background: #eee;
  color: #123;
  font-family: 'Press Start 2P', Arial, sans-serif;
  font-size: 16px;
}

h1 {
  font-size: 3.15rem;
  margin: 0;
  text-transform: uppercase;
  text-align: center;
  margin-bottom: 0.5rem;
}

h1 + div {
  margin-bottom: 0.5rem;
  font-size: 1.25rem;
}

#frame {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  -webkit-transform: translate(-50%, -50%);
}

#board {
  width: 400px;
  height: 600px;
  background: #123;
  position: relative;
}

.tile {
  width: 60px;
  height: 30px;
  box-sizing: border-box;
  border: 2px outset rgba(255, 255, 255, 0.75);
  position: absolute;
  border-radius: 4px;
}

.tile.hidden { opacity: 0.1; }

.tile[id$='0'] { left: 20px; }
.tile[id$='1'] { left: 80px; }
.tile[id$='2'] { left: 140px; }
.tile[id$='3'] { left: 200px; }
.tile[id$='4'] { left: 260px; }
.tile[id$='5'] { left: 320px; }

.tile.red { background: #d50000; }
.tile.blue { background: #0000d5; }
.tile.green { background: #009500; }
.tile.yellow { background: #d5d500; }

.tile[data-row='0'] { top: 100px; }
.tile[data-row='1'] { top: 130px; }
.tile[data-row='2'] { top: 160px; }
.tile[data-row='3'] { top: 190px; }

#paddle {
  width: 70px;
  height: 15px;
  background: #222;
  border-radius: 100px;
  box-sizing: border-box;
  border: 2px outset rgba(255, 255, 255, 0.75);
  position: absolute;
  top: 550px;
  left: 165px;
}

#ball {
  width: 10px;
  height: 10px;
  border-radius: 100%;
  border-radius: 100px;
  box-sizing: border-box;
  background: white;
  border: 2px outset rgba(0, 0, 0, 0.125);
  position: absolute;
  top: 540px;
  left: 195px;
}
/*******************************************************/
/* GameController.js                                   */
/*   - Select button will reset the game               */
/*   - Start button will start a new game after reset  */
/*   - Right/Left joystick/buttons to move the paddle  */
/*******************************************************/
gameControl.on('connect', gamepad => {
  alvanoid.init();
  gamepad.on('select', alvanoid.restart);
  gamepad.on('start', alvanoid.startGame);
  gamepad.on('right', alvanoid.moveRight);
  gamepad.on('left', alvanoid.moveLeft);
});

/*******************************************************/
/* Game logic (not great, this is only a demo)         */
/*******************************************************/
const boardBox = document.querySelector('#board').getBoundingClientRect();
alvanoid = {
  points: 0,
  speed: 9,
  offset: 165,
  active: false,
  paddle: null,
  ball: null,
  speedBall: [0, 0],
  posBall: [195, 540],
  gameOver: false,
  calculatePositionBall: function(speed, position, active) {
    let x = position[0] + speed[0];
    let y = position[1] + speed[1];
    if (active) {
      if (x < 0) {
        x = -x;
        this.speedBall[0] = -this.speedBall[0];
      }
      if (x > 390) {
        x = 390 - (x - 390);
        this.speedBall[0] = -this.speedBall[0];
      }
      if (y > 590) {
        this.gameOver = true;
      }
      if (y < 0) {
        y = -y;
        this.speedBall[1] = -this.speedBall[1];
      }
      if (y >= 540 && y < 550) {
        if (x >= this.offset && x <= this.offset + 70) {
          y = 540 - (y - 540);
          this.speedBall[1] = -this.speedBall[1];
        }
      }
      const tiles = document.querySelectorAll('.tile:not(.hidden)');
      for (let i = 0; i < tiles.length; i++) {
        const pos = tiles[i].getBoundingClientRect();
        const tilePosX = pos.left - boardBox.left;
        const tilePosY = pos.top - boardBox.top;
        let change = false;
        if (this.speedBall[0] > 0) {
          if (x >= tilePosX && x <= tilePosX + 3 && y >= tilePosY && y <= tilePosY + 30) {
            tiles[i].classList.add('hidden');
            x = tilePosX - (x - tilePosX);
            this.speedBall[0] = -this.speedBall[0];
            change = true;
          }
        } else {
          if (
            x <= tilePosX + 60 &&
            x >= tilePosX + 57 &&
            y >= tilePosY &&
            y <= tilePosY + 30
          ) {
            tiles[i].classList.add('hidden');
            x = tilePosX + 60 - (tilePosX + 60 - x);
            this.speedBall[0] = -this.speedBall[0];
            change = true;
          }
        }
        if (this.speedBall[1] > 0 && !change) {
          if (y >= tilePosY && y <= tilePosY + 3 && x >= tilePosX && x <= tilePosX + 60) {
            tiles[i].classList.add('hidden');
            y = tilePosY - (y - tilePosY);
            this.speedBall[1] = -this.speedBall[1];
            change = true;
          }
        } else if (!change) {
          if (
            y <= tilePosY + 30 &&
            y >= tilePosY + 27 &&
            x >= tilePosX &&
            x <= tilePosX + 60
          ) {
            tiles[i].classList.add('hidden');
            y = tilePosY - (tilePosY - y);
            this.speedBall[1] = -this.speedBall[1];
            change = true;
          }
        }
        if (change) {
          this.points += 100;
          document.querySelector('#points').textContent = this.points;
        }
      }
    }
    return { x, y };
  },
  paintItems: function() {
    const coordinates = alvanoid.calculatePositionBall(
      alvanoid.speedBall,
      alvanoid.posBall,
      alvanoid.active
    );
    alvanoid.posBall = [coordinates.x, coordinates.y];
    if (!alvanoid.active) {
      alvanoid.posBall = [alvanoid.offset + 30, 540];
    }
    alvanoid.paddle.style.left = alvanoid.offset + 'px';
    alvanoid.ball.style.left = alvanoid.posBall[0] + 'px';
    alvanoid.ball.style.top = alvanoid.posBall[1] + 'px';
    if (!alvanoid.gameOver) {
      requestAnimationFrame(alvanoid.paintItems);
    }
  },
  move: function(change) {
    this.offset += change;
    if (this.offset < 0) this.offset = 0;
    if (this.offset > 330) this.offset = 330;
  },
  restart: function() {
    if (alvanoid.gameOver) {
      setTimeout(alvanoid.paintItems, 100);
    }
    alvanoid.points = 0;
    alvanoid.speed = 10;
    alvanoid.offset = 165;
    alvanoid.active = false;
    alvanoid.speedBall = [0, 0];
    alvanoid.posBall = [195, 540];
    alvanoid.gameOver = false;
    document.querySelector('#points').textContent = '0';
    const tiles = document.querySelectorAll('.tile.hidden');
    for (let x = 0; x < tiles.length; x++) {
      tiles[x].classList.remove('hidden');
    }
  },
  startGame: function() {
    if (!alvanoid.active) {
      alvanoid.speedBall = [3, -3];
      alvanoid.active = true;
      alvanoid.gameOver = false;
    }
  },
  moveRight: function() {
    alvanoid.move(alvanoid.speed);
  },
  moveLeft: function() {
    alvanoid.move(-alvanoid.speed);
  },
  init: function() {
    const self = this;
    self.paddle = document.querySelector('#paddle');
    self.ball = document.querySelector('#ball');
    requestAnimationFrame(alvanoid.paintItems);
  }
};

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/npm/gamecontroller.js@1.1.0/dist/gamecontroller.min.js