使用JS实现2048小游戏

 JS实现2048小游戏源码

效果图:

 代码如下,复制即可使用:

(适用浏览器:360、FireFox、Chrome、Opera、傲游、搜狗、世界之窗. 不支持Safari、IE8及以下浏览器。)

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=0">
<title>使用JS实现2048小游戏</title>
<!-- 此处需要自己修改为自己的css路径  -->
<link rel="stylesheet" href="css/reset.min.css">
<link rel="stylesheet" href="css/style.css">

</head>
<body>

<header>
    <div class="container">
        <h1><span>2</span><span>0</span><span>4</span><span>8</span></h1>
        <p class="inspired">by the原2048的灵感。</p>
    </div>
</header>

<div class="container">
    <div class="directions">
        <p><strong>如何玩:</strong> 使用你的箭头键移动瓷砖。当两个瓦片相互滑动时,它们合并成一个!</p>
    </div>
    <div class="scores">
        <div class="score-container best-score">
            最佳: 
            <div class="score">
                <div id="bestScore">0</div>
            </div>
        </div>
        <div class="score-container">
            分数: 
            <div class="score">
                <div id="score">0</div>
                <div class="add" id="add"></div>
            </div>
        </div>
    </div>
    <div class="game">
        <div id="tile-container" class="tile-container"></div>
        <div class="end" id="end">游戏结束<div class="monkey"></div><button class="btn not-recommended__item js-restart-btn" id="try-again">再试一次</button></div>
    </div>

    <div class="not-recommended">
        <button class="btn not-recommended__item js-restart-btn" id="restart">重新启动游戏</button>
    </div>

</div>
<!-- 此处需要自己修改JS路径 -->
<script src="js/index.js"></script>

<div style="text-align:center;margin:10px 0; font:normal 14px/24px 'MicroSoft YaHei';">
<p>适用浏览器:360、FireFox、Chrome、Opera、傲游、搜狗、世界之窗. 不支持Safari、IE8及以下浏览器。</p>
</div>
</body>
</html>

 reset.min.css

html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}

style.css

@charset "UTF-8";
* {
  box-sizing: border-box;
}

a {
  color: #1B9AAA;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}
a:hover {
  color: #14727e;
}
a:focus, a:active {
  color: #0d4a52;
}

body,
html {
  position: relative;
  width: 100%;
  height: 100%;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
      -ms-flex-direction: column;
          flex-direction: column;
  font-family: "Arvo", Helvetica, sans-serif;
  font-family: 12px;
  color: #555;
  background: #F8FFE5;
}

strong {
  font-weight: bold;
}

p {
  line-height: 1.6;
}

.inspired {
  margin-top: 1em;
  font-size: 0.9rem;
  color: #9a9a95;
}

header {
  color: #F8FFE5;
  text-align: center;
}
header span {
  display: inline-block;
  box-sizing: border-box;
  width: 4rem;
  height: 4rem;
  line-height: 4rem;
  margin: 0 0.4rem;
  background: #FFC43D;
}
header span:nth-of-type(2) {
  background: #EF476F;
}
header span:nth-of-type(3) {
  background: #1B9AAA;
}
header span:nth-of-type(4) {
  background: #06D6A0;
}

h1 {
  font-size: 2.2rem;
}

.directions {
  padding: 2rem;
  border-top: 1px solid #9a9a95;
  border-bottom: 1px solid #9a9a95;
}

.container {
  margin: 0 auto;
  padding-bottom: 3.5rem;
  -webkit-box-flex: 1;
      -ms-flex: 1;
          flex: 1;
  width: 100%;
  max-width: 550px;
  text-align: center;
}
header .container {
  padding: 0;
  padding: 2rem 4rem;
  max-width: 900px;
}

.scores {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
}

.score-container {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  margin: 1.8rem;
  font-size: 1.2rem;
  line-height: 1;
  color: #555;
}
.score-container.best-score {
  color: #9a9a95;
}

.score {
  margin-left: 1rem;
  position: relative;
  font-weight: bold;
  font-size: 1.5rem;
  vertical-align: middle;
  text-align: right;
}

.game {
  position: relative;
  margin: 0 auto;
  background: #9a9a95;
  padding: 7px;
  display: inline-block;
  border-radius: 3px;
  box-sizing: border-box;
}

.tile-container {
  border-radius: 6px;
  position: relative;
  width: 400px;
  height: 400px;
}

.tile, .background {
  display: block;
  color: #F8FFE5;
  position: absolute;
  width: 100px;
  height: 100px;
  box-sizing: border-box;
  text-align: center;
}

.background {
  z-index: 1;
  text-align: center;
  border: 7px solid #9a9a95;
  background-color: #F8FFE5;
}

.tile {
  opacity: 0;
  z-index: 2;
  background: #FFC43D;
  color: #F8FFE5;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
  font-size: 1.8rem;
  align-items: center;
  -webkit-transition: 110ms ease-in-out;
  transition: 110ms ease-in-out;
  border-radius: 3px;
  border: 7px solid #9a9a95;
  box-sizing: border-box;
}
.tile--4 {
  background: #EF476F;
  color: #F8FFE5;
}
.tile--8 {
  background: #1B9AAA;
  color: #F8FFE5;
}
.tile--16 {
  background: #06D6A0;
  color: #F8FFE5;
}
.tile--32 {
  background: #f37694;
  color: #F8FFE5;
}
.tile--64 {
  background: #22c2d6;
  color: #F8FFE5;
}
.tile--128 {
  background: #17f8be;
  color: #F8FFE5;
}
.tile--256 {
  background: #ffd470;
  color: #F8FFE5;
}
.tile--512 {
  background: #eb184a;
  color: #F8FFE5;
}
.tile--1024 {
  background: #14727e;
  color: #F8FFE5;
}
.tile--2048 {
  background: #05a47b;
  color: #F8FFE5;
}
.tile--pop {
  -webkit-animation: pop 0.3s ease-in-out;
          animation: pop 0.3s ease-in-out;
  -webkit-animation-fill-mode: forwards;
          animation-fill-mode: forwards;
}
.tile--shrink {
  -webkit-animation: shrink 0.5s ease-in-out;
          animation: shrink 0.5s ease-in-out;
  -webkit-animation-fill-mode: forwards;
          animation-fill-mode: forwards;
}

.add {
  position: absolute;
  opacity: 0;
  left: 120%;
  top: 0;
  font-size: 1rem;
  color: #1B9AAA;
}
.add.active {
  -webkit-animation: add 0.8s ease-in-out;
          animation: add 0.8s ease-in-out;
}

@-webkit-keyframes add {
  0% {
    opacity: 1;
    top: 0;
  }
  100% {
    opacity: 0;
    top: -100%;
  }
}

@keyframes add {
  0% {
    opacity: 1;
    top: 0;
  }
  100% {
    opacity: 0;
    top: -100%;
  }
}
@-webkit-keyframes pop {
  0% {
    -webkit-transform: scale(0.5);
            transform: scale(0.5);
    opacity: 0;
  }
  90% {
    -webkit-transform: scale(1.1);
            transform: scale(1.1);
    opacity: 1;
  }
  100% {
    -webkit-transform: scale(1);
            transform: scale(1);
    opacity: 1;
  }
}
@keyframes pop {
  0% {
    -webkit-transform: scale(0.5);
            transform: scale(0.5);
    opacity: 0;
  }
  90% {
    -webkit-transform: scale(1.1);
            transform: scale(1.1);
    opacity: 1;
  }
  100% {
    -webkit-transform: scale(1);
            transform: scale(1);
    opacity: 1;
  }
}
@-webkit-keyframes shrink {
  0% {
    -webkit-transform: scale(1);
            transform: scale(1);
    opacity: 1;
  }
  100% {
    -webkit-transform: scale(0.9);
            transform: scale(0.9);
    opacity: 0.9;
  }
}
@keyframes shrink {
  0% {
    -webkit-transform: scale(1);
            transform: scale(1);
    opacity: 1;
  }
  100% {
    -webkit-transform: scale(0.9);
            transform: scale(0.9);
    opacity: 0.9;
  }
}
.end {
  opacity: 0;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
      -ms-flex-direction: column;
          flex-direction: column;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  background: rgba(85, 85, 85, 0.9);
  color: white;
  font-size: 2rem;
  -webkit-transition: opacity 0.3s ease-in-out;
  transition: opacity 0.3s ease-in-out;
}
.end btn {
  margin-top: 1rem;
}
.end.active {
  opacity: 1;
  z-index: 1000;
}

.monkey {
  font-size: 3rem;
  margin: 1rem 0;
}

.btn {
  font-family: inherit;
  font-size: 1rem;
  border: none;
  background: #1B9AAA;
  letter-spacing: 1px;
  color: white;
  font-weight: 300;
  padding: 0.9em 1.5em;
  border-radius: 3px;
  border: 1px solid transparent;
  cursor: pointer;
}
.btn:hover {
  background-color: #14727e;
}
.btn:active {
  background-color: #0d4a52;
}
.btn:focus {
  box-shadow: 0 0 10px #0d4a52 inset;
  outline: none;
}

.not-recommended {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  margin-top: 3rem;
}
.not-recommended * + * {
  margin-left: 10px;
}
.not-recommended__item + .not-recommended__annotation:before {
  font-size: 30px;
  content: "😐";
}
.not-recommended__item:hover + .not-recommended__annotation:before {
  content: "😟";
}
.not-recommended__item:focus + .not-recommended__annotation:before {
  content: "😄";
}
.not-recommended__item:active + .not-recommended__annotation:before {
  content: "😨";
}

 index.js

'use strict';

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var game = null;
var bestScore = 0;
var scoreDiv = document.getElementById('score');
var bestScoreDiv = document.getElementById('bestScore');
var addDiv = document.getElementById('add');
var endDiv = document.getElementById('end');
var size = 4;
var nextId = 1;
var score = 0;

function initGame() {
  game = Array(size * size).fill(null); // 4 x 4 grid, represented as an array
  initBestScore();
}

function initBestScore() {
  bestScore = localStorage.getItem('bestScore') || 0;
  bestScoreDiv.innerHTML = bestScore;
}

function updateDOM(before, after) {
  var newElements = getNewElementsDOM(before, after);
  var existingElements = getExistingElementsDOM(before, after);
  var mergedTiles = getMergedTiles(after);
  removeElements(mergedTiles);
  drawGame(newElements, true);
  drawGame(existingElements);
}

function removeElements(mergedTiles) {
  for (var _iterator = mergedTiles, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
    var _ref;

    if (_isArray) {
      if (_i >= _iterator.length) break;
      _ref = _iterator[_i++];
    } else {
      _i = _iterator.next();
      if (_i.done) break;
      _ref = _i.value;
    }

    var tile = _ref;

    var _loop = function _loop() {
      if (_isArray2) {
        if (_i2 >= _iterator2.length) return 'break';
        _ref2 = _iterator2[_i2++];
      } else {
        _i2 = _iterator2.next();
        if (_i2.done) return 'break';
        _ref2 = _i2.value;
      }

      var id = _ref2;

      var currentElm = document.getElementById(id);
      positionTile(tile, currentElm);
      currentElm.classList.add('tile--shrink');
      setTimeout(function () {
        currentElm.remove();
      }, 100);
    };

    for (var _iterator2 = tile.mergedIds, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
      var _ref2;

      var _ret = _loop();

      if (_ret === 'break') break;
    }
  }
}

function getMergedTiles(after) {
  return after.filter(function (tile) {
    return tile && tile.mergedIds;
  });
}

function getNewElementsDOM(before, after) {
  var beforeIds = before.filter(function (tile) {
    return tile;
  }).map(function (tile) {
    return tile.id;
  });
  var newElements = after.filter(function (tile) {
    return tile && beforeIds.indexOf(tile.id) === -1;
  });
  return newElements;
}

function getExistingElementsDOM(before, after) {
  var beforeIds = before.filter(function (tile) {
    return tile;
  }).map(function (tile) {
    return tile.id;
  });
  var existingElements = after.filter(function (tile) {
    return tile && beforeIds.indexOf(tile.id) !== -1;
  });
  return existingElements;
}

function drawBackground() {
  var tileContainer = document.getElementById('tile-container');
  tileContainer.innerHTML = '';
  for (var i = 0; i < game.length; i++) {
    var tile = game[i];
    var tileDiv = document.createElement('div');
    var x = i % size;
    var y = Math.floor(i / size);
    tileDiv.style.top = y * 100 + 'px';
    tileDiv.style.left = x * 100 + 'px';

    tileDiv.classList.add("background");
    tileContainer.appendChild(tileDiv);
  }
}

function positionTile(tile, elm) {
  var x = tile.index % size;
  var y = Math.floor(tile.index / size);
  elm.style.top = y * 100 + 'px';
  elm.style.left = x * 100 + 'px';
}

function drawGame(tiles, isNew) {
  var tileContainer = document.getElementById('tile-container');
  for (var i = 0; i < tiles.length; i++) {
    var tile = tiles[i];
    if (tile) {
      if (isNew) {
        (function () {
          var tileDiv = document.createElement('div');
          positionTile(tile, tileDiv);
          tileDiv.classList.add('tile', 'tile--' + tile.value);
          tileDiv.id = tile.id;
          setTimeout(function () {
            tileDiv.classList.add("tile--pop");
          }, tile.mergedIds ? 1 : 150);
          tileDiv.innerHTML = '<p>' + tile.value + '</p>';
          tileContainer.appendChild(tileDiv);
        })();
      } else {
        var currentElement = document.getElementById(tile.id);
        positionTile(tile, currentElement);
      }
    }
  }
}

function gameOver() {
  if (game.filter(function (number) {
    return number === null;
  }).length === 0) {
    var sameNeighbors = game.find(function (tile, i) {
      var isRightSame = game[i + 1] && (i + 1) % 4 !== 0 ? tile.value === game[i + 1].value : false;
      var isDownSame = game[i + 4] ? tile.value === game[i + 4].value : false;
      if (isRightSame || isDownSame) {
        return true;
      }
      return false;
    });
    return !sameNeighbors;
  }
}

function generateNewNumber() {
  // 0.9 probability of 2, 0.1 probability of 4
  var p = Math.random() * 100;
  return p <= 90 ? 2 : 4;
}

function addRandomNumber() {
  // Adds either a 2 or a 4 to an empty position in the game array
  var emptyCells = game.map(function (_, index) {
    return index;
  }).filter(function (index) {
    return game[index] === null;
  });
  if (emptyCells.length === 0) {
    return;
  }
  var newPos = emptyCells[Math.floor(Math.random() * emptyCells.length)];
  var newObj = {
    id: nextId++,
    index: newPos,
    value: generateNewNumber()
  };
  game.splice(newPos, 1, newObj);
}

function getIndexForPoint(x, y) {
  return y * size + x;
}

function reflectGrid(grid) {
  var reflectedGame = Array(size * size).fill(0);
  for (var row = 0; row < size; row++) {
    for (var col = 0; col < size; col++) {
      var index1 = getIndexForPoint(col, row);
      var index2 = getIndexForPoint(size - col - 1, row);
      reflectedGame[index1] = grid[index2];
    }
  }
  return reflectedGame;
}

function rotateLeft90Deg(grid) {
  var rotatedGame = Array(size * size).fill(0);
  for (var row = 0; row < size; row++) {
    for (var col = 0; col < size; col++) {
      var index1 = getIndexForPoint(col, row);
      var index2 = getIndexForPoint(size - 1 - row, col);
      rotatedGame[index1] = grid[index2];
    }
  }
  return rotatedGame;
}

function rotateRight90Deg(grid) {
  var rotatedGame = Array(size * size).fill(0);
  for (var row = 0; row < size; row++) {
    for (var col = 0; col < size; col++) {
      var index1 = getIndexForPoint(col, row);
      var index2 = getIndexForPoint(row, size - 1 - col);
      rotatedGame[index1] = grid[index2];
    }
  }
  return rotatedGame;
}

/*
For any cell whose neighbor to the right is empty, move that cell
to the right. For any cell whose neighbor to the right is equal
to the same value, combine the values together (e.g. 2+2 = 4)
*/
function shiftGameRight(gameGrid) {
  // reflect game grid
  var reflectedGame = reflectGrid(gameGrid);
  // shift left
  reflectedGame = shiftGameLeft(reflectedGame);
  // reflect back
  return reflectGrid(reflectedGame);
}

function shiftGameLeft(gameGrid) {
  var newGameState = [];
  var totalAdd = 0;
  // for rows
  for (var i = 0; i < size; i++) {
    // for columns
    var firstPos = 4 * i;
    var lastPos = size + 4 * i;
    var currentRow = gameGrid.slice(firstPos, lastPos);
    var filteredRow = currentRow.filter(function (row) {
      return row;
    });
    for (var _iterator3 = filteredRow, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
      var _ref3;

      if (_isArray3) {
        if (_i3 >= _iterator3.length) break;
        _ref3 = _iterator3[_i3++];
      } else {
        _i3 = _iterator3.next();
        if (_i3.done) break;
        _ref3 = _i3.value;
      }

      var row = _ref3;

      delete row.mergedIds;
    }

    for (var j = 0; j < filteredRow.length - 1; j++) {
      if (filteredRow[j].value === filteredRow[j + 1].value) {
        var sum = filteredRow[j].value * 2;
        filteredRow[j] = {
          id: nextId++,
          mergedIds: [filteredRow[j].id, filteredRow[j + 1].id],
          value: sum
        };
        filteredRow.splice(j + 1, 1);
        score += sum;
        totalAdd += sum;
      }
    }
    while (filteredRow.length < size) {
      filteredRow.push(null);
    };
    newGameState = [].concat(newGameState, filteredRow);
  }

  if (totalAdd > 0) {
    scoreDiv.innerHTML = score;
    addDiv.innerHTML = '+' + totalAdd;
    addDiv.classList.add('active');
    setTimeout(function () {
      addDiv.classList.remove("active");
    }, 800);
    if (score > bestScore) {
      localStorage.setItem('bestScore', score);
      initBestScore();
    }
  }
  return newGameState;
}

function shiftGameUp(gameGrid) {
  var rotatedGame = rotateLeft90Deg(gameGrid);
  rotatedGame = shiftGameLeft(rotatedGame);
  return rotateRight90Deg(rotatedGame);
}

function shiftGameDown(gameGrid) {
  var rotatedGame = rotateRight90Deg(gameGrid);
  rotatedGame = shiftGameLeft(rotatedGame);
  return rotateLeft90Deg(rotatedGame);
}

var buttons = document.querySelectorAll(".js-restart-btn");
var length = buttons.length;
for (var i = 0; i < length; i++) {
  if (document.addEventListener) {
    buttons[i].addEventListener("click", function () {
      newGameStart();
    });
  } else {
    buttons[i].attachEvent("onclick", function () {
      newGameStart();
    });
  };
};

document.addEventListener("keydown", handleKeypress);
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);

var xDown = null;
var yDown = null;

function handleTouchStart(evt) {
  xDown = evt.touches[0].clientX;
  yDown = evt.touches[0].clientY;
};

function handleTouchMove(evt) {
  var prevGame = [].concat(game);
  if (!xDown || !yDown) {
    return;
  }
  var xUp = evt.touches[0].clientX;
  var yUp = evt.touches[0].clientY;

  var xDiff = xDown - xUp;
  var yDiff = yDown - yUp;

  if (Math.abs(xDiff) > Math.abs(yDiff)) {
    if (xDiff > 0) {
      game = shiftGameLeft(game);
    } else {
      game = shiftGameRight(game);
    }
  } else {
    if (yDiff > 0) {
      game = shiftGameUp(game);
    } else {
      game = shiftGameDown(game);
    }
  }
  game = game.map(function (tile, index) {
    if (tile) {
      return _extends({}, tile, {
        index: index
      });
    } else {
      return null;
    }
  });
  addRandomNumber();
  updateDOM(prevGame, game);
  if (gameOver()) {
    setTimeout(function () {
      endDiv.classList.add('active');
    }, 800);
    return;
  }
  xDown = null;
  yDown = null;
};

function handleKeypress(evt) {
  var modifiers = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
  var whichKey = event.which;

  var prevGame = [].concat(game);

  if (!modifiers) {
    event.preventDefault();
    switch (whichKey) {
      case 37:
        game = shiftGameLeft(game);
        break;
      case 38:
        game = shiftGameUp(game);
        break;
      case 39:
        game = shiftGameRight(game);
        break;
      case 40:
        game = shiftGameDown(game);
        break;
    }
    game = game.map(function (tile, index) {
      if (tile) {
        return _extends({}, tile, {
          index: index
        });
      } else {
        return null;
      }
    });
    addRandomNumber();
    updateDOM(prevGame, game);
    if (gameOver()) {
      setTimeout(function () {
        endDiv.classList.add('active');
      }, 800);
      return;
    }
  }
}

function newGameStart() {
  document.getElementById('tile-container').innerHTML = '';
  endDiv.classList.remove('active');
  score = 0;
  scoreDiv.innerHTML = score;
  initGame();
  drawBackground();
  var previousGame = [].concat(game);
  addRandomNumber();
  addRandomNumber();
  updateDOM(previousGame, game);
}

newGameStart();

 如果有更好的方法或更多的功能,可以和我们大家一起分享哦,如有错误,欢迎联系我改正,非常感谢!!!

posted @ 2018-05-02 13:50  Foryourfuture  阅读(3614)  评论(1编辑  收藏  举报