Weex 版扫雷游戏开发

扫雷是一个喜闻乐见的小游戏,今天在看 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>

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

<script>
  module.exports = {
    data: {
      row: [
        { "col": [ {}, {} ] },
        { "col": [ {}, {} ] },
      ]
    }
  }
</script>

但是如果真的这么写的话,一个 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
},

初始化的时候生成每个节点的值,是否是一个雷,计算周围雷的总数,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;
      }
    }
}

然后做一次计算,把每个块周围的雷总数计算得到,这里有一个点可以优化,就是当点击第一次之后才去做布雷的操作,这样可以防止用户第一次就挂了。(如果你对扫雷有点了解的话,会知道在 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;
      }
    }
}

这个计算做完之后,通过 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]);
      }
    }
}

通过枚举把块 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();
  }
}

然后绑定 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"]];
}

玩过扫雷的都知道,当你挖开一个点,发现这个点周围一个雷都没有,那么程序会自动挖开这个点周围的八个点,同时这个行为会递归下去,直到一整片全部被挖开,在程序里面就是上面的 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
    }
  });
}

发现某个点为空之后进入 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;
    }
  });
}

当点开雷的时候直接进入 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
}

最后

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>
时间: 2024-08-30 22:48:31

Weex 版扫雷游戏开发的相关文章

Java版SLG游戏开发--数据的读取及保存

说到SLG游戏开发,无论其如何运转,里面都离不开各种数据的处理,一般来说,游戏越专业,需要处理的数据量将相对越大,类别也分得越细. 游戏离不开美工,也离不开策划,各项参数的专业划分同样是评价一款SLG游戏是否优秀的必要指标之一.所谓的好游戏仅仅画面出彩,配乐一流是绝对不够的,做"靓"很容易,做"专"则很难. 比如日本的超级机器人大战系列,自90年代初开始出现以来,截止到今天为止其中涉及的动漫超过60部,出场知名人物多达600名以上,几乎涵盖了日本所有知名机器人动画的

php开发的简易扫雷游戏

我觉得用PHP+JS可以设计出挺牛逼的二维网页游戏,当然网络型的网页游戏后台数据的交互可以使用java来做数据处理. 开发的简易扫雷游戏-扫雷游戏开发">   <?php     $init = $_POST["init"];//game restart   $clickvalue = $_POST["clickvalue"];//minesweeping   $checkflag = 0;//Victory or defeat   $clic

C++实现简单的扫雷游戏(控制台版)_C 语言

C++新手的代码,请各位多包涵. 用C++写的一个简单的控制台版扫雷游戏.玩家通过输入方块的坐标来翻开方块. 只是一个雏形,能够让玩家执行翻开方块的操作并且判断输赢,还未添加标记方块.游戏菜单.记录游戏时间.重新开一局等等的功能. 玩家输入坐标的方式来翻开方块只适用于小型的"雷区",若"雷区"大了,用坐标会变得很不方便. 代码片段扫雷V1.1 #include<stdio.h> #include<Windows.h> #define YELL

C语言开发简易版扫雷小游戏_C 语言

前言: 想起来做这个是因为那时候某天知道了原来黑框框里面的光标是可以控制的,而且又经常听人说起这个,就锻炼一下好了. 之前就完成了那1.0的版本,现在想放上来分享却发现有蛮多问题的,而且最重要的是没什么注释[果然那时候太年轻]!现在看了也是被那时候的自己逗笑了,就修改了一些小bug,增加了算是详尽而清楚的注释,嗯,MSDN上面对各种函数的解释很详细的[又锻炼一下英语],顺便让开头和结尾的展示"动"了起来,就当作1.5的版本好了. 这个只是给出了一个实现的思路,其中肯定也有很多不合理的地

C#如何开发扫雷游戏

简单的总结一下,如何利用C#进行WinForm 扫雷小游戏的开发: 扫雷游戏的主要设计的类有三个: Main.Pane .MineField 1)Main 是主控窗体,负责项目的启动和关闭:并协调内部各个组建模块的协调工作. 2)Pane是一个方格的封装,是雷区的重要组建:它表示一个方格的当前状态,以及是否布雷等信息. 3)MineField是雷区的封装,是游戏的核心组建:它负责方格的布局以及地雷的分布:并控制玩家的基本操作以及正确的响应. 类的实现: 一. Pane类 功能描述:Pane是一个

《Android 游戏开发大全(第二版)》——6.5节闯关动作类游戏

6.5 闯关动作类游戏 Android 游戏开发大全(第二版) 这里要介绍的闯关动作类游戏,是区别于射击类游戏和格斗游戏的,闯关动作类游戏的节奏一般比较轻快,玩家的成就感主要来源于完成一个个关卡的挑战胜利,更具趣味性.闯关类动作游戏的设计重点不在战斗,而是在闯关,这样适应的玩家人群会更广,比较经典的闯关类动作游戏有"超级玛丽"(如图6-8所示)和冒险岛(如图6-9所示)等. 6.5.1 游戏玩法 玩家人数 玩家玩闯关动作类游戏的主要目标一般都在于过关斩将,并不十分需要别的玩家的阻挠或协

(译)【Unity教程】使用Unity开发Windows Phone上的横版跑酷游戏

原文 (译)[Unity教程]使用Unity开发Windows Phone上的横版跑酷游戏 译者注: 目前移动设备的跨平台游戏开发引擎基本都是采用Cocos2d-x或者Unity.一般而言2d用cocos2d-x 3d用unity,但是对于Windows Phone开发者, cocos2d-x for wp8是微软维护的,版本升级十分缓慢,到现在还是 V2.0 preview,我们不可能拿一个不太稳定的版本去开发游戏.与之相反,Unity4.2发布之后, 支持WP8和Windows8,当然也包括

《Android 游戏开发大全(第二版)》——6.3节益智类游戏

6.3 益智类游戏 Android 游戏开发大全(第二版) 益智游戏(Puzzle Game)是另外一种深受用户欢迎的游戏类型,很多人把益智游戏称作休闲游戏,但实际上很多益智游戏玩起来并不会很"休闲",如一些需要频繁思考的诸如数独之类的游戏.而休闲游戏中很大一部分游戏并不属于"益智"的范畴,如后面会提到的养成类游戏一般也划为休闲游戏. 益智类游戏的特色就是,游戏中会更多地依靠智力去解决问题,而现实生活中能够锻炼智力的游戏有很多,如纸牌类游戏.棋类游戏等都属于益智类游

《Android 游戏开发大全(第二版)》——6.1节射击类游戏

6.1 射击类游戏 Android 游戏开发大全(第二版) 射击类游戏(Shooting Game)是一种比较古老的游戏类型,手机游戏中的射击游戏也很流行,目前市面上的射击类游戏最多的是飞行射击游戏,比较著名的有雷电系列,还有一些是诸如坦克大战之类的操作性要求较高的射击游戏,本小节就来简单介绍一下射击类手机游戏的相关知识. 6.1.1 游戏玩法 下面从玩家人数.操作方式和取胜条件等几个方面分析射击类游戏的玩法. 玩家人数 射击类游戏通常为单人游戏,很少以二人对战或多人在线的方式进行,而且一般来说