Weex 版扫雷游戏开发

简介: 扫雷是一个喜闻乐见的小游戏,今天在看 Weex 文档的过程中,无意中发现用 Weex 完全可以开发一个扫雷出来。当然这个扫雷和 Windows 那个有一点差距,不过麻雀虽小五脏俱全,随机布雷、自动挖雷、标雷那是必须有的。<br /><br />最后的效果是这个样子的:<br /><br /><img src="http://img4.tbcdn.cn/L1/461/1/e3e0deb067df8a
扫雷是一个喜闻乐见的小游戏,今天在看 Weex 文档的过程中,无意中发现用 Weex 完全可以开发一个扫雷出来。当然这个扫雷和 Windows 那个有一点差距,不过麻雀虽小五脏俱全,随机布雷、自动挖雷、标雷那是必须有的。

最后的效果是这个样子的:



界面是简陋了一点,好歹是可以用的,要啥自行车。其实这个 demo 主要是为了实践几件事情:

1. 界面指示器

2. 动态数据绑定
3. 更复杂的事件

扫雷的布局里面只需要用上 repeat 指示器,表示元素的重复出现,比如说一个 9*9 的雷区,布局文件非常的简单:
<template>
  <container>
    <container repeat="{{row}}" style="flex-direction: row; flex: 1;">
      <container repeat="{{col}}" style="flex: 1;">
        <text>{{text}}</text>
      </container>
    </container>
  </container>
</template>
AI 代码解读

这样的话我们用 script 里面的 data binding 就能把重复的元素布局好。例如:

<script>
  module.exports = {
    data: {
      row: [
        { "col": [ {}, {} ] },
        { "col": [ {}, {} ] },
      ]
    }
  }
</script>
AI 代码解读

但是如果真的这么写的话,一个 9*9 的布局不知道要搞到什么时候,幸亏 data-binding 也是支持动态化的。所以在游戏开始后生成这个布局就好了。

restart: function(e) { // restart game
    var row = [];
    var count = 0;
    this.board = this.max; // display remain mines
    this.finished = false;
    for (var i = 0; i < this.size; ++i) { // init data-binding
      var col = { "col": [] };
      for (var j = 0; j < this.size; ++j) {
        var tid = i * this.size + j;
        col["col"][j] = {
          tid: "" + tid, // identifier
          state: "normal", // state
          value: 0, // is a mine or not
          text: "", // text
          around: 0 // total count around this tile
        };
      }
      row[i] = col;
    }
    this.row = row; // will cause view tree rendering
    this.plant(); // arrange mines
    this.calculate(); // calculate around values
},
AI 代码解读

初始化的时候生成每个节点的值,是否是一个雷,计算周围雷的总数,state 表示当前的状态(正常、挖开、标记),同时用 tid 来标记一个块(tile identifier)。

随机的在雷区布雷,直到满足个数:

plant: function() { // arrange mines
    var count = 0;
    while (count < this.max) {
      var x = this.random(0, this.size);
      var y = this.random(0, this.size);
      var tile = this.row[x].col[y];
      if (tile.value == 0) {
        ++count;
        tile.value = 1;
      }
    }
}
AI 代码解读

然后做一次计算,把每个块周围的雷总数计算得到,这里有一个点可以优化,就是当点击第一次之后才去做布雷的操作,这样可以防止用户第一次就挂了。(如果你对扫雷有点了解的话,会知道在 Windows 扫雷里面,是出现过第一次点可能会挂和第一次点一定不会挂这两种的,区别就在这里)

calculate: function() { // calculate values around tiles
    for (var i = 0; i < this.size; ++i) {
      for (var j = 0; j < this.size; ++j) {
        var around = 0;
        this.map(i, j, function(tile) {
          around += tile.value;
        });
        this.row[i].col[j].around = around;
      }
    }
}
AI 代码解读

这个计算做完之后,通过 Weex 的 data-binding 就彻底反映到了 View 上面,每个块都有了数据。这里面有个 map 函数,是定义在 script 里面的一个用于枚举位于 (x, y) 的块周围八个点的一个函数:

map: function(x, y, callback) { // visit tiles around (x, y)
    for (var i = 0; i < 8; ++i) {
      var mx = x + this.vector[i][0];
      var my = y + this.vector[i][1];
      if (mx >= 0 && my >= 0 && mx < this.size && my < this.size) {
        callback(this.row[mx].col[my]);
      }
    }
}
AI 代码解读

通过枚举把块 callback 回来,这个函数有多次用到。

onclick: function(event) { // onclick tile
  if (this.unfinished()) {
    var tile = this.tile(event);
    if (tile.state == "normal") {
      if (tile.value == 1) { // lose game
        this.onfail();
      } else { // open it
        this.display(tile);
        if (tile.around == 0) {
          this.dfs(tile); // start dfs a tile
        }
        this.judge(); // game judgement
      }
    }
  }
},
onlongpress: function(event) { // onlongpress tile
  if (this.unfinished()) {
    var tile = this.tile(event);
    tile.state = tile.state == "flag" ? "normal" : "flag";
    if (tile.state == "flag") {
      --this.board;
      tile.text = this.strings.flag; // flag
    } else {
      ++this.board;
      tile.text = "";
    }
    this.judge();
  }
}
AI 代码解读

然后绑定 onclickonlongpress 函数,实现单击挖雷,长按标雷的功能。这里面的 tile 函数是通过事件发生的 event 对象取到元素的一个方法,值得一提的是这里面我试过官网说的 e.target.id 方法,拿到的是 undefined,所以我才在这里用了 tid 来标记一个元素。

tile: function(event) { // return tile object with click event
  var tid = event.target.attr["tid"];
  var pos = this.position(tid);
  return this.row[pos["x"]].col[pos["y"]];
}
AI 代码解读

玩过扫雷的都知道,当你挖开一个点,发现这个点周围一个雷都没有,那么程序会自动挖开这个点周围的八个点,同时这个行为会递归下去,直到一整片全部被挖开,在程序里面就是上面的 dfs 函数

dfs: function(tile) { // dfs a tile
  var pos = this.position(tile.tid);
  var context = this;
  tile.state = "open";
  this.map(pos["x"], pos["y"], function(node) {
    if (node.around == 0 && node.state == "normal") { // dfs
      context.dfs(node); // dfs recursively
    } else {
      context.display(node); // display tile
    }
  });
}
AI 代码解读

发现某个点为空之后进入 dfs,递归或者展示某个点。接下来就是对雷区局面的判定动作,分为 onfail 和 judge 两个部分。

judge: function() {
  var count = 0;
  this.foreach(function(tile) {
    if (tile.state == "open" || tile.state == "flag") {
      ++count;
    }
  });
  if (count == this.size * this.size) { // win
    this.finished = true;
    this.board = this.strings.win;
  }
},
onfail: function() { // fail
  this.board = this.strings.lose;
  this.finished = true;
  var mine = this.strings.mine;
  this.foreach(function(tile) {
    if (tile.value == 1) {
      tile.text = mine;
    }
  });
}
AI 代码解读

当点开雷的时候直接进入 onfail,否则进入 judge,如果满足胜利条件则游戏也结束。Weex 的 data 模块里面可以定义一个 JSON 数据,除了做数据绑定,也可以方便的储存其他的数据。

data: {
  size: 9,
  max: 10,
  board: 0,
  row: [],
  vector: [[-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1]],
  strings: {
    mine: "",
    flag: "",
    win: "YOU WIN!",
    lose: "YOU LOSE~"
  },
  finished: false
}
AI 代码解读

最后

Weex 提供的指示器和数据绑定是不错的东西,用它们可以完成更灵活的界面布局和数据展现。
尤其是数据绑定,他让数值的变化可以直接反馈到界面上,省去了一些繁杂的界面更新逻辑。

这也许是一个不太实用的 demo,但我觉得很有趣。下面是源码:

<template>
  <container>
    <text class="btn">{{board}}</text>
    <container repeat="{{row}}" style="flex-direction: row; flex: 1;">
      <container repeat="{{col}}" style="flex: 1;">
        <text tid="{{tid}}" onclick="onclick" onlongpress="onlongpress" class="{{state}} tile" around="{{around}}">{{text}}</text>
      </container>
    </container>
    <text onclick="restart" class="btn">START</text>
  </container>
</template>

<script>
  module.exports = {
    data: {
      size: 9,
      max: 10,
      board: 0,
      row: [],
      vector: [[-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1]],
      strings: {
        mine: "",
        flag: "",
        win: "YOU WIN!",
        lose: "YOU LOSE~"
      },
      finished: false
    },
    methods: {
      map: function(x, y, callback) { // visit tiles around (x, y)
        for (var i = 0; i < 8; ++i) {
          var mx = x + this.vector[i][0];
          var my = y + this.vector[i][1];
          if (mx >= 0 && my >= 0 && mx < this.size && my < this.size) {
            callback(this.row[mx].col[my]);
          }
        }
      },
      dfs: function(tile) { // dfs a tile
        var pos = this.position(tile.tid);
        var context = this;
        tile.state = "open";
        this.map(pos["x"], pos["y"], function(node) {
          if (node.around == 0 && node.state == "normal") { // dfs
            context.dfs(node); // dfs recursively
          } else {
            context.display(node); // display tile
          }
        });
      },
      random: function(min, max) { // generate random number between [min, max)
        return parseInt(Math.random() * (max - min) + min);
      },
      plant: function() { // arrange mines
        var count = 0;
        while (count < this.max) {
          var x = this.random(0, this.size);
          var y = this.random(0, this.size);
          var tile = this.row[x].col[y];
          if (tile.value == 0) {
            ++count;
            tile.value = 1;
          }
        }
      },
      calculate: function() { // calculate values around tiles
        for (var i = 0; i < this.size; ++i) {
          for (var j = 0; j < this.size; ++j) {
            var around = 0;
            this.map(i, j, function(tile) {
              around += tile.value;
            });
            this.row[i].col[j].around = around;
          }
        }
      },
      restart: function(e) { // restart game
        var row = [];
        var count = 0;
        this.board = this.max; // display remain mines
        this.finished = false;
        for (var i = 0; i < this.size; ++i) { // init data-binding
          var col = { "col": [] };
          for (var j = 0; j < this.size; ++j) {
            var tid = i * this.size + j;
            col["col"][j] = {
              tid: "" + tid,
              state: "normal",
              value: 0,
              text: "",
              around: 0
            };
          }
          row[i] = col;
        }
        this.row = row; // will cause view tree rendering
        this.plant(); // arrange mines
        this.calculate(); // calculate around values
      },
      unfinished: function() { // check game status
        var finished = this.finished;
        if (this.finished) { // restart if finished
          this.restart();
        }
        return !finished;
      },
      position: function(tid) { // return (x, y) with tile id
        var row = parseInt(tid / this.size);
        var col = tid % this.size;
        return { x: row, y: col };
      },
      display: function(tile) {
        tile.state = "open";
        tile.text = (tile.around == 0) ? "" : tile.around;
      },
      tile: function(event) { // return tile object with click event
        var tid = event.target.attr["tid"];
        var pos = this.position(tid);
        return this.row[pos["x"]].col[pos["y"]];
      },
      onclick: function(event) { // onclick tile
        if (this.unfinished()) {
          var tile = this.tile(event);
          if (tile.state == "normal") {
            if (tile.value == 1) { // lose game
              this.onfail();
            } else { // open it
              this.display(tile);
              if (tile.around == 0) {
                this.dfs(tile); // start dfs a tile
              }
              this.judge(); // game judgement
            }
          }
        }
      },
      onlongpress: function(event) { // onlongpress tile
        if (this.unfinished()) {
          var tile = this.tile(event);
          tile.state = tile.state == "flag" ? "normal" : "flag";
          if (tile.state == "flag") {
            --this.board;
            tile.text = this.strings.flag; // flag
          } else {
            ++this.board;
            tile.text = "";
          }
          this.judge();
        }
      },
      foreach: function(callback) { // enumerate all tiles
        for (var i = 0; i < this.size; ++i) {
          for (var j = 0; j < this.size; ++j) {
            callback(this.row[i].col[j]);
          }
        }
      },
      judge: function() {
        var count = 0;
        this.foreach(function(tile) {
          if (tile.state == "open" || tile.state == "flag") {
            ++count;
          }
        });
        if (count == this.size * this.size) { // win
          this.finished = true;
          this.board = this.strings.win;
        }
      },
      onfail: function() { // fail
        this.board = this.strings.lose;
        this.finished = true;
        var mine = this.strings.mine;
        this.foreach(function(tile) {
          if (tile.value == 1) {
            tile.text = mine;
          }
        });
      }
    }
  }
</script>

<style>
  .btn {
    margin: 2;
    background-color: #e74c3c;
    color: #ffffff;
    text-align: center;
    flex: 1;
    font-size: 66;
    height: 80;
  }
  
  .normal {
    background-color: #95a5a6;
  }
  
  .open {
    background-color: #34495e;
    color: #ffffff;
  }
  
  .flag {
    background-color: #95a5a6;
  }
  
  .tile {
    margin: 2;
    font-size: 66;
    height: 80;
    padding-top: 0;
    text-align: center;
  }
</style>
AI 代码解读
目录
相关文章
C 语言——实现扫雷小游戏
本文介绍了使用二维数组创建棋盘并实现扫雷游戏的方法。首先,通过初始化数组创建一个9x9的棋盘,并添加行列标识以便操作。接着,利用随机数在棋盘上布置雷。最后,通过判断玩家输入的坐标来实现扫雷功能,包括显示雷的数量和处理游戏胜利或失败的情况。文中提供了完整的代码实现。
81 1
C 语言——实现扫雷小游戏
震惊!JavaScript 与 WebAssembly 强强联合,开启前端性能传奇之旅,你准备好了吗?
【8月更文挑战第27天】在互联网飞速发展的今天,前端技术,特别是核心语言JavaScript,正经历着持续的革新。为了突破JavaScript在处理复杂计算时的性能局限,WebAssembly应运而生。作为一种高效的二进制格式,WebAssembly能以接近原生的速度在浏览器中运行,支持C、C++和Rust等语言编写的高性能代码。它与JavaScript相辅相成,前者专注于高性能计算任务(如游戏开发、图像处理),后者则负责页面的交互与逻辑控制。通过结合使用,二者为前端开发者提供了更为强大和灵活的工具集,共同推动前端技术进入一个全新的性能时代。
150 2
"跨界大战!React Native、Weex、Flutter:三大混合开发王者正面交锋,揭秘谁才是你移动应用开发的终极利器?"
【8月更文挑战第12天】随着移动应用开发的需求日益增长,高效构建跨平台应用成为关键。React Native、Weex与Flutter作为主流混合开发框架各具特色。React Native依托Facebook的强大支持,以接近原生的性能和丰富的组件库著称;Weex由阿里巴巴开发,性能优越尤其在大数据处理上表现突出;Flutter则凭借Google的支持及独特的Dart语言和Skia渲染引擎,提供出色的定制能力和开发效率。选择时需考量项目特性、团队技能及生态系统的成熟度。希望本文对比能助你做出最佳决策。
244 1
绝招放送:彻底解锁Unity UI系统奥秘,五大步骤教你如何缔造令人惊叹的沉浸式游戏体验,从Canvas到动画,一步一个脚印走向大师级UI设计
【8月更文挑战第31天】随着游戏开发技术的进步,UI成为提升游戏体验的关键。本文探讨如何利用Unity的UI系统创建美观且功能丰富的界面,包括Canvas、UI元素及Event System的使用,并通过具体示例代码展示按钮点击事件及淡入淡出动画的实现过程,助力开发者打造沉浸式的游戏体验。
247 0
|
10月前
|
游戏开发丨基于Tkinter的扫雷小游戏
游戏开发丨基于Tkinter的扫雷小游戏
186 3
通过Flutter实现在多端运行的扫雷游戏
当我们回忆起小时候的经典电脑游戏,扫雷一定是其中之一。这个简单而富有挑战的游戏不仅考验我们的智力和耐性,而且在完成后还会让我们感到一种无与伦比的成就感。现在,您可以使用Flutter来重新体验这个经典游戏,无论您是Flutter新手还是老手,都能通过本文,让您在Flutter的世界中开发出一个令人满意的扫雷游戏。
通过Flutter实现在多端运行的扫雷游戏
从零开始手把手教你使用javascript+canvas开发一个塔防游戏03敌人一波一波的出
从零开始手把手教你使用javascript+canvas开发一个塔防游戏03敌人一波一波的出
102 0
从零开始手把手教你使用javascript+canvas开发一个塔防游戏02敌人自动寻路
从零开始手把手教你使用javascript+canvas开发一个塔防游戏02敌人自动寻路
239 0

相关实验场景

更多