Home  >  Q&A  >  body text

How to add the undo function to the 2048 web version mini-game

<!DOCTYPE html>

<html>

  <head>

    <meta charset="UTF-8">

    <title>网页版2048游戏</title>

    <style>

      /* 游戏棋盘格 */

      body{font-family:Arial;text-align:center;}

      .game{margin:0 auto;/*margin-top:40px;*/text-align:center;display:inline-block;}

      .game-score{font-size:20px;margin:20px auto;}

      .game-container{background-color:#bbada0;border-radius:10px;position:relative;}

      .game-cell{border-radius:6px;background-color:#ccc0b3;position:absolute;}

      .game-num{width:0px;height:0px;border-radius:6px;font-weight:bold;font-size:40px;color:#fff;text-align:center;position:absolute;}

      .game-num-2{background:#eee4da;color:#776e65;}

      .game-num-4{background:#ede0c8;color:#776e65;}

      .game-num-8{background:#f2b179;}

      .game-num-16{background:#f59563;}

      .game-num-32{background:#f67c5f;}

      .game-num-64{background:#f65e3b;}

      .game-num-128{background:#edcf72;font-size:35px;}

      .game-num-256{background:#edcc61;font-size:35px;}

      .game-num-512{background:#9c0;font-size:35px;}

      .game-num-1024{background:#33b5e5;font-size:30px;}

      .game-num-2048{background:#09c;font-size:30px;}

      /*游戏结束*/

      .game-over{width:100%;height:100%;position:absolute;border-radius:10px;box-sizing:border-box;z-index:1;display:table;background:rgba(123,102,85,0.5)}

      .game-over-info{display:table-cell;vertical-align:middle}

      .game-over p{font-size:45px;color:#fff;margin:20px auto;}

      .game-over span{cursor:pointer;background-color:rgba(103,82,65,0.6);display:block;margin:20px auto;width:180px;padding:10px 10px;font-size:25px;color:#f7f2e5;border-radius:10px;border:1px solid #978271;transition:all .2s}

      .game-over span:hover{background-color:rgba(103,82,65,0.7);color:#fff}

      .game-hide{display:none;}

    </style>

  </head>

  <body>

    <div id="game" class="game">

      <div class="game-score">

        <blockquote>

          <p>分数:<span id="game_score">0</span></p>

        </blockquote>

      </div>

      <div id="game_container" class="game-container">

        <div id="game_over" class="game-over game-hide">

          <div class="game-over-info">

            <div id="game_over_info"></div>

            <span id="game_restart">重新开始</span>

          </div>

        </div>

      </div>

    </div>

    <script src="js/jquery-1.12.4.min.js"></script>

    <script src="js/Game2048.js"></script>

    <script>

      Game2048({prefix: 'game', len: 4, size: 100, margin: 20});

    </script>

  </body>

</html>





(function(window, document, $) {
  function Game2048(opt) {
    var prefix = opt.prefix, len = opt.len, size = opt.size, margin = opt.margin;
    var score = 0;
    var winNum = 2048;
    var isGameOver = true;
    var board = new Board(len);
    var view = new View(prefix, len, size, margin);
    view.init();
    board.onGenerate = function(e) {
      view.addNum(e.x, e.y, e.num);
    };
    board.onMove = function(e) {
      if (e.to.num >= winNum) {
        isGameOver = true;
        setTimeout(function() { view.win(); }, 300);
      }
      if (e.to.num > e.from.num) {
        score += e.to.num;
        view.updateScore(score);
      }
      view.move(e.from, e.to);
    };
    board.onMoveComplete = function(e) {
      if (!board.canMove()) {
        isGameOver = true;
        setTimeout(function() { view.over(score); }, 300);
      }
      if (e.moved) {
        setTimeout(function(){ board.generate(); }, 200);
      }
    };
    $(document).keydown(function(e) {
      if (isGameOver) {
        return false;
      }
      switch (e.which) {
        case 37: board.moveLeft();  break;
        case 38: board.moveUp();    break;
        case 39: board.moveRight(); break;
        case 40: board.moveDown();  break;
      }
    });
    function start() {
      score = 0;
      view.updateScore(0);
      view.cleanNum();
      board.init();
      board.generate();
      board.generate();
      isGameOver = false;
    }
    $('#' + prefix + '_restart').click(start);
    start();
  };
  // 数据处理
  function Board(len) {
    this.len = len;
    this.arr = [];
  }
  Board.prototype = {
    // 事件
    onGenerate: function() {},
    onMove: function() {},
    onMoveComplete: function() {},
    // 创建数组
    init: function() {
      for (var arr = [], x = 0, len = this.len; x < len; ++x) {
        arr[x] = [];
        for (var y = 0; y < len; ++y) {
            arr[x][y] = 0;
          }
      }
      this.arr = arr;
    },
    // 在随机位置增加一个随机数
    generate: function() {
      var empty = [];
      for (var x = 0, arr = this.arr, len = arr.length; x < len; ++x) {
        for (var y = 0; y < len; ++y) {
          if (arr[x][y] === 0) {
            empty.push({x: x, y: y});
          }
        }
      }
      if (empty.length < 1) {
        return false;
      }
      var pos = empty[Math.floor((Math.random() * empty.length))];
      this.arr[pos.x][pos.y] = Math.random() < 0.5 ? 2 : 4;
      this.onGenerate({x: pos.x, y: pos.y, num: this.arr[pos.x][pos.y]});
    },
    // 左移
    moveLeft: function() {
      var canMove = false;
      // 从上到下,从左到右
      for (var x = 0, len = this.arr.length; x < len; ++x) {
        for (var y = 0, arr = this.arr[x]; y < len; ++y) {
          // 从 y + 1 位置开始,向右查找
          for (var next = y + 1; next < len; ++next) {
            // 如果 next 单元格是 0,找下一个不是 0 的单元格
            if (arr[next] === 0) {
              continue;
            }
            // 如果 y 数字是 0,则将 next 移动到 y 位置,然后将 y 减 1 重新查找
            if (arr[y] === 0) {
              arr[y] = arr[next];
              this.onMove({from: {x: x, y: next, num: arr[next]}, to: {x: x, y: y, num: arr[y]}});
              arr[next] = 0;
              canMove = true;
              --y;
            // 如果 y 与 next 单元格数字相等,则将 next 移动并合并给 y
            } else if (arr[y] === arr[next]) {
              arr[y] += arr[next];
              this.onMove({from: {x: x, y: next, num: arr[next]}, to: {x: x, y: y, num: arr[y]}});
              arr[next] = 0;
              canMove = true;
            }
            break;
          }
        }
      }
      this.onMoveComplete({moved: canMove});
    },
    moveRight: function() {
      var moved = false;
       for (var x = 0, len = this.arr.length; x < len; ++x) {
        for (var y = len - 1, arr = this.arr[x]; y >= 0; --y) {
          for (var prev = y - 1; prev >= 0; --prev) {
            if (arr[prev] === 0) {
              continue;
            }
            if (arr[y] === 0) {
              arr[y] = arr[prev];
              this.onMove({from: {x: x, y: prev, num: arr[prev]}, to: {x: x, y: y, num: arr[y]}});
              arr[prev] = 0;
              moved = true;
              ++y;
            } else if (arr[y] === arr[prev]) {
              arr[y] += arr[prev];
              this.onMove({from: {x: x, y: prev, num: arr[prev]}, to: {x: x, y: y, num: arr[y]}});
              arr[prev] = 0;
              moved = true;
            }
            break;
          }
        }
      }
      this.onMoveComplete({moved: moved});
    },
    moveUp: function() {
      var canMove = false;
      for (var arr = this.arr, len = arr.length, y = 0; y < len; ++y) {
        for (var x = 0; x < len; ++x) {
          for (var next = x + 1; next < len; ++next) {
            if (arr[next][y] === 0) {
              continue;
            }
            if (arr[x][y] === 0) {
              arr[x][y] = arr[next][y];
              this.onMove({from: {x: next, y: y, num: arr[next][y]}, to: {x: x, y: y, num: arr[x][y]}});
              arr[next][y] = 0;
              canMove = true;
              --x;
            } else if (arr[x][y] === arr[next][y]) {
              arr[x][y] += arr[next][y];
              this.onMove({from: {x: next, y: y, num: arr[next][y]}, to: {x: x, y: y, num: arr[x][y]}});
              arr[next][y] = 0;
              canMove = true;
            }
            break;
          }
        }
      }
      this.onMoveComplete({moved: canMove});
    },
    moveDown: function() {
      var canMove = false;
      for (var arr = this.arr, len = arr.length, y = 0; y < len; ++y) {
        for (var x = len - 1; x >= 0; --x) {
          for (var prev = x - 1; prev >= 0; --prev) {
            if (arr[prev][y] === 0) {
              continue;
            }
            if (arr[x][y] === 0) {
              arr[x][y] = arr[prev][y];
              this.onMove({from: {x: prev, y: y, num: arr[prev][y]}, to: {x: x, y: y, num: arr[x][y]}});
              arr[prev][y] = 0;
              canMove = true;
              ++x;
            } else if (arr[x][y] === arr[prev][y]) {
              arr[x][y] += arr[prev][y];
              this.onMove({from: {x: prev, y: y, num: arr[prev][y]}, to: {x: x, y: y, num: arr[x][y]}});
              arr[prev][y] = 0;
              canMove = true;
            }
            break;
          }
        }
      }
      this.onMoveComplete({moved: canMove});
    },
    canMove: function() {
      for (var x = 0, arr = this.arr, len = arr.length; x < len; ++x) {
        for (var y = 0; y < len; ++y) {
          if (arr[x][y] === 0) {
            return true;
          }
          var curr = arr[x][y], right = arr[x][y + 1];
          var down = arr[x + 1] ? arr[x + 1][y] : null;
          if (right === curr || down === curr) {
            return true;
          }
        }
      }
      return false;
    }
  };
  // 视图处理
  function View(prefix, len, size, margin) {
    this.prefix = prefix;
    this.len = len;             // 单元格单边的数量(实际数量 len * len)
    this.size = size;	        // 每个单元格的边长
    this.margin = margin;	// 每个单元格的间距
    this.score = $('#' + prefix + '_score');
    this.container = $('#' + prefix + '_container');
    var containerSize = len * size + margin * (len + 1);
    this.container.css({width:containerSize , height: containerSize});
    this.nums = {};
  }
  View.prototype = {
    // 计算位置
    getPos: function(n) {
      return this.margin + n * (this.size + this.margin);
    },
    init: function() {
      for (var x = 0, len = this.len; x < len; ++x) {
        for (var y = 0; y < len; ++y) {
          var $cell = $('<div class="' + this.prefix + '-cell"></div>');
          $cell.css({
              width: this.size + 'px', height: this.size + 'px', 
              top: this.getPos(x), left: this.getPos(y)
          }).appendTo(this.container);
        }
      }
    },
    addNum: function(x, y, num) {
      var $num = $('<div class="' + this.prefix + '-num ' + this.prefix + '-num-' + num + ' ">');
      $num.text(num).css({
        top: this.getPos(x) + parseInt(this.size / 2),
        left: this.getPos(y) + parseInt(this.size / 2)
      }).appendTo(this.container).animate({
        width: this.size + 'px',
        height: this.size + 'px',
        lineHeight: this.size + 'px',
        top: this.getPos(x),
        left: this.getPos(y)
      }, 100);
      this.nums[x + '-' + y] = $num;
    },
    move: function(from, to) {
      var fromIndex = from.x + '-' + from.y, toIndex = to.x + '-' + to.y;
      var clean = this.nums[toIndex];
      this.nums[toIndex] = this.nums[fromIndex];
      delete this.nums[fromIndex];
      var prefix = this.prefix + '-num-';
      var pos = {top: this.getPos(to.x), left: this.getPos(to.y)};
      this.nums[toIndex].finish().animate(pos, 200, function() {
        if (to.num > from.num) {
          clean.remove();
          $(this).text(to.num).removeClass(prefix + from.num).addClass(prefix + to.num);
        }
      });
    },
    updateScore: function(score) {
      this.score.text(score);
    },
    win: function() {
      $('#' + this.prefix + '_over_info').html('<p>您获胜了</p>');
      $('#' + this.prefix + '_over').removeClass(this.prefix + '-hide');
    },
    over: function(score) {
      $('#' + this.prefix + '_over_info').html('<p>本次得分</p><p>' + score + '</p>');
      $('#' + this.prefix + '_over').removeClass(this.prefix + '-hide');
    },
    cleanNum: function() {
      this.nums = {};
      $('#' + this.prefix + '_over').addClass(this.prefix + '-hide');
      $('.' + this.prefix + '-num').remove();
    }
  };
  window['Game2048'] = Game2048;
})(window, document, jQuery);


傲冰吖傲冰吖2153 days ago1883

reply all(0)I'll reply

No reply
  • Cancelreply