雪球跳跃闯关游戏

Published on
/
/趣玩前端
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>雪球跳跃闯关游戏</title>
    <style>
      @import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
      html {
        font-size: 2.8vh;
      }
      @media screen and (orientation: portrait) {
        html {
          font-size: 2.8vw;
        }
      }

      body {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
        color: #000038;
        font-family: 'Archivo Black', 'Arial Black', sans-serif;
        font-size: 1.5rem;
        perspective: 100rem;
        transform-style: preserve-3d;
        background: linear-gradient(white, #919ea3 400%);
        overflow: hidden;
      }

      .snow-game__level-marker {
        position: absolute;
        z-index: 1;
        top: 1.1rem;
        left: 1rem;
      }
      .snow-game__button {
        background: white;
        border: 1px solid grey;
        border-radius: 1rem;
        padding: 0.5rem 1rem;
        text-transform: uppercase;
        font-family: 'Archivo Black', 'Arial Black', sans-serif;
        font-size: 1.5rem;
        cursor: pointer;
      }
      .snow-game__fullscreen-button {
        position: absolute;
        z-index: 1;
        top: 0.5rem;
        left: 4rem;
      }
      .snow-game__wrapper {
        position: relative;
        width: 30rem;
        height: 35rem;
        perspective: 100rem;
      }
      .snow-game__level {
        position: absolute;
        left: 0;
        top: calc(var(--time) * var(--cell) * -1);
        display: flex;
        flex-wrap: wrap;
        height: 100%;
        transform: rotate3d(1, 0, 0, 45deg) translateZ(
            calc(var(--time) * -0.5 * var(--cell))
          )
          translateY(calc(var(--time) * -0.1 * var(--cell)));
        transform-style: preserve-3d;
        transition: top 0.3s linear, transform 0.3s linear;
      }
      .snow-game__cell {
        position: relative;
        width: var(--cell);
        height: var(--cell);
      }
      .snow-game__cell--rock {
        transform: rotate3d(1, 0, 0, -45deg);
        transform-origin: bottom;
      }
      .snow-game__cell--rock:before {
        content: '';
        position: absolute;
        bottom: 0;
        left: 10%;
        width: 80%;
        height: 60%;
        border-radius: 3rem 3rem 1rem 1rem;
        background: radial-gradient(circle at 30% 80%, white -200%, #919ea3);
        box-shadow: 0 0.5rem 1rem 0 rgba(145, 158, 163, 0.5);
      }
      .snow-game__cell--rock:nth-child(2n):before {
        border-radius: 3rem 1rem 1rem 1rem;
      }
      .snow-game__cell--rock:nth-child(3n):before {
        border-radius: 1rem 5rem 1rem 1rem;
      }
      .snow-game__cell--rock:nth-child(4n):before {
        height: 50%;
      }
      .snow-game__cell--rock:nth-child(5n):before {
        height: 70%;
      }
      .snow-game__cell--lava:before {
        content: '';
        position: absolute;
        top: 40%;
        left: 0;
        width: 100%;
        height: 60%;
        background: linear-gradient(180deg, orange, #ff7d66);
        background-size: 100% 200%;
        animation: lava;
        animation-duration: 2s;
        animation-iteration-count: infinite;
      }
      @keyframes lava {
        0%,
        100% {
          background-position: 0 0;
        }
        50% {
          background-position: 0 100%;
        }
      }
      .snow-game__cell--end {
        background-image: linear-gradient(45deg, #000038 25%, transparent 25%),
          linear-gradient(-45deg, #000038 25%, transparent 25%), linear-gradient(
            45deg,
            transparent 75%,
            #000038 75%
          ), linear-gradient(-45deg, transparent 75%, #000038 75%);
        background-size: var(--cell) var(--cell);
        background-position: 0 0, 0 calc(var(--cell) / 2),
          calc(var(--cell) / 2) calc(var(--cell) / -2), calc(var(--cell) / -2) 0px;
      }
      .snow-game__player {
        position: absolute;
        top: calc(
          var(--top) * var(--cell) + (var(--cell) * 0.5) - var(--lifes) * 1rem
        );
        left: calc(
          var(--left) * var(--cell) + (var(--cell) * 0.5) - var(--lifes) * 1rem
        );
        width: calc(var(--lifes) * 2rem);
        height: calc(var(--lifes) * 2rem);
        border-radius: 50%;
        background: radial-gradient(circle at 40% 70%, white -200%, #eb80b1);
        transform: rotate3d(1, 0, 0, -45deg);
        transform-origin: bottom;
        transition: top 0.3s linear, left 0.5s ease, width 0.5s ease, height
            0.5s ease, transform 0.3s cubic-bezier(0.17, 0.67, 0.54, 1), box-shadow
            0.3s cubic-bezier(0.17, 0.67, 0.54, 1);
        box-shadow: 0 0.5rem 1rem 0 rgba(145, 158, 163, 0.5);
      }
      .snow-game__player--jump {
        transform: rotate3d(1, 0, 0, -45deg) translateZ(var(--cell)) translateY(calc(var(
                  --cell
                ) * -1));
        box-shadow: 0 var(--cell) 1rem 0 rgba(145, 158, 163, 0.5);
      }
      .snow-game__start,
      .snow-game__score {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        padding: 1rem;
        background-color: rgba(255, 255, 255, 0.6);
        cursor: default;
      }
      .snow-game__start--hidden,
      .snow-game__score--hidden {
        display: none;
      }
      .snow-game__title {
        font-size: 10vw;
      }
      .snow-game__title--highlighted {
        color: #eb80b1;
      }
      .snow-game__tutorial {
        font-size: 2rem;
        margin-bottom: 1rem;
        text-align: center;
      }
      .snow-game__tutorial-mobile {
        display: none;
      }
      .snow-game__score {
        font-size: 10vw;
        text-transform: uppercase;
      }
      .snow-game__click-layer {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
      @media screen and (max-width: 767px) {
        .snow-game__tutorial {
          font-size: 1.5rem;
        }
      }
    </style>
  </head>
  <body>
    <div id="snow-game-level-marker" class="snow-game__level-marker"></div>
    <div id="snow-game" class="snow-game__wrapper">
      <div id="snow-game-level" class="snow-game__level"></div>
      <div class="snow-game__snowball"></div>
    </div>
    <div id="click-layer" class="snow-game__click-layer"></div>
    <div id="start" class="snow-game__start">
      <div class="snow-game__title">
        PINK <span class="snow-game__title--highlighted">SNOW</span>
      </div>
      <div class="snow-game__tutorial">
        Use &#8678; &#8680; to move, &#8679; to jump
      </div>
      <button class="snow-game__start-button snow-game__button">Play</button>
    </div>
    <button
      id="fullscreen"
      class="snow-game__fullscreen-button snow-game__button"
    >
      Fullscreen
    </button>
    <div id="score" class="snow-game__score snow-game__score--hidden"></div>
    <script>
      const snowGame = document.getElementById('snow-game');
      const snowGameLevel = document.getElementById('snow-game-level');
      const snowGameLevelMarker = document.getElementById(
        'snow-game-level-marker'
      );
      const clickLayer = document.getElementById('click-layer');
      const startScreen = document.getElementById('start');
      const scoreScreen = document.getElementById('score');
      const fullscreen = document.getElementById('fullscreen');

      const rhythm = 300;
      let width = 30;
      let lifes = 2;
      let z = 0;
      let position = [];
      let time = -1;
      let currentLevel = 0;
      let score = 0;
      let rocks = [];
      const levels = [
        [
          '--o--',
          '--',
          '--',
          '',
          '-',
          '--',
          '-',
          '--',
          '--',
          '--x-x',
          '--',
          '',
          'xx',
          '--',
          'xxxxx',
          '--',
          'xxx--',
          '--',
          '--',
          'xxxxx',
          '--xx-',
          'x--xx',
          '',
          'xx',
          '--xx-',
          '--',
          '-',
          '--',
          '--',
          'xxxxx',
          '--xx-',
          'x--xx',
          '',
          'xxxx',
          '--xxx',
          '-xxl-',
          'xxxx-',
          'xx',
          'x--ll',
          '--',
          'll',
          'xx--x',
          '--',
          'xxx--',
          'll--',
          '--xxx',
          '--',
          '-xxxx',
          'xlxx',
          'eeeee',
        ],
        [
          '--o--',
          '--',
          'xx--x',
          'll-x',
          'x--xx',
          '-x',
          '--xxx',
          '--llx',
          '--xxx',
          'x',
          'x--x-',
          '--x-x',
          'eeeee',
        ],
        [
          '--o--',
          '--',
          'xx--x',
          'll-x',
          'x--xx',
          '-x',
          'xxxxx',
          '--llx',
          '--xxx',
          '--',
          '',
          'll',
          'x--ll',
          '-x',
          'xxlll',
          '--',
          '--',
          'x-llx',
          'x--x-',
          '--l-x',
          'eeeee',
        ],
        [
          '--o--',
          '',
          'll',
          'x--ll',
          '--xxx',
          'x-x',
          'xxl--',
          '--',
          '--',
          'x--',
          '--',
          'x',
          '--xxx',
          '-xxxx',
          'xx',
          'xxl--',
          '',
          '--',
          '--',
          'llll-',
          'x--',
          '--',
          '-x-x-',
          'x-x-x',
          '-x-x-',
          '-x-x-',
          'x-x-x',
          '-x-x-',
          '-x-x-',
          'x-x-x',
          '-x-x-',
          '--',
          '--',
          '--',
          '-x-xl',
          'x-xlx',
          '-x-x-',
          '-x-x-',
          'x-x-x',
          'lx-x-',
          'lx-x-',
          'xlx-x',
          'lx-x-',
          'lll',
          '--',
          '--',
          'xx-',
          'lx-x-',
          'xlxl',
          'x-x-x',
          'll',
          'eeeee',
        ],
        [
          '--o--',
          '--',
          'll',
          '--', '',
          '--l--',
          'l--l-',
          '-l--l',
          '--l--',
          '-l',
          'eeeee',
        ],
        [
          'l-o-x',
          '-l-x-',
          '--l--',
          'lx-l-',
          'xl--l',
          '--l-x',
          '--',
          '--',
          'xxxxx',
          '--',
          'xxxxx',
          '--',
          '--',
          'lllll',
          '--',
          'lllll',
          '--',
          'eeeee',
        ],
        [
          '--o--',
          '--',
          '-----',
          'xxxxx',
          '-xxxx',
          'x--xx',
          '--xxx',
          'llll-',
          'x-xxx',
          'llll-',
          'x-xxx',
          '-xx--',
          'x-xxx',
          '-xx--',
          'x-xxx',
          '-xx--',
          'eeeee',
        ],
        [
          'xxoxx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'xx-xx',
          'll-ll',
          'eeeee',
        ],
      ];

      let intervalId;

      const player = document.createElement('div');
      player.classList.add('snow-game__player');

      const positionPlayer = (x, y) => {
        position = [x, y];
        setPlayerData();
      };

      const setPlayerData = () => {
        player.style = `--top: ${position[1]}; --left: ${position[0]}; --lifes: ${lifes}`;
      };

      const buildLevel = () => {
        clearInterval(intervalId);
        if (currentLevel === levels.length) {
          scoreScreen.classList.remove('snow-game__score--hidden');
          scoreScreen.innerHTML = `Score: ${score}`;
          return;
        }
        snowGameLevel.innerHTML = '';
        lifes = 2;
        time = -1;
        rocks = [];
        snowGameLevelMarker.innerHTML = currentLevel;
        const level = levels[currentLevel];

        document.body.style = `--cell: ${width / level[0].length}rem`;

        level.forEach((row, rowIndex) => {
          const cells = row.split('');
          cells.forEach((cell, cellIndex) => {
            const newDiv = document.createElement('div');
            newDiv.classList.add('snow-game__cell');
            if (cell === 'o') {
              positionPlayer(cellIndex, rowIndex);
            } else if (cell === 'x') {
              rocks.push([cellIndex, rowIndex, 'rock']);
              newDiv.classList.add('snow-game__cell--rock');
            } else if (cell === 'l') {
              rocks.push([cellIndex, rowIndex, 'lava']);
              newDiv.classList.add('snow-game__cell--lava');
            } else if (cell === 'e') {
              newDiv.classList.add('snow-game__cell--end');
            }
            snowGameLevel.appendChild(newDiv);
          });
          snowGameLevel.appendChild(player);
        });
        // The snow starts rolling
        intervalId = setInterval(() => {
          time++;
          snowGame.style = `--time: ${time};`;
          positionPlayer(position[0], position[1] + 1);
          const collisions = rocks.filter(
            (rock) => rock[0] === position[0] && rock[1] === position[1] - 1
          );

          if (lifes <= 0) {
            buildLevel();
          } else if (position[1] === levels[currentLevel].length) {
            currentLevel = currentLevel + 1;
            score = score + lifes;
            buildLevel();
          } else if (collisions.length && z === 0) {
            if (collisions[0][2] === 'rock') {
              lifes = lifes - 1;
            } else if (collisions[0][2] === 'lava') {
              lifes = lifes - 2;
            }
            setPlayerData();
          }
        }, rhythm);
      };

      buildLevel();

      const jump = () => {
        if (z === 0) {
          z = 1;
          player.classList.add('snow-game__player--jump');
          setTimeout(() => {
            setTimeout(() => (z = 0), rhythm);
            player.classList.remove('snow-game__player--jump');
          }, rhythm);
        }
      };

      window.addEventListener('keydown', (event) => {
        if (event.key === 'ArrowRight') {
          positionPlayer(
            Math.min(position[0] + 1, levels[currentLevel][0].length - 1),
            position[1]
          );
        } else if (event.key === 'ArrowLeft') {
          positionPlayer(Math.max(position[0] - 1, 0), position[1]);
        } else if (event.key === 'ArrowUp') {
          jump();
        }
      });

      fullscreen.addEventListener('click', () => {
        if (!document.fullscreenElement) {
          document.documentElement.requestFullscreen();
        } else if (document.exitFullscreen) {
          document.exitFullscreen();
        }
      });

      startScreen.addEventListener('click', () => {
        startScreen.classList.add('snow-game__start--hidden');
      });

      clickLayer.addEventListener('click', (event) => {
        if (event.clientY < window.innerHeight / 3) {
          jump();
        } else if (event.clientX < window.innerWidth / 3) {
          positionPlayer(Math.max(position[0] - 1, 0), position[1]);
        } else if (event.clientX > (window.innerWidth * 2) / 3) {
          positionPlayer(
            Math.min(position[0] + 1, levels[currentLevel][0].length - 1),
            position[1]
          );
        }
      });
    </script>
  </body>
</html>