If you're seeing this message, it means we're having trouble loading external resources on our website.

如果你被网页过滤器挡住,请确保域名*.kastatic.org*.kasandbox.org 没有被阻止.

主要内容

记忆游戏:翻转砖瓦

好的,现在有一个卡牌网格,我们可以让每格卡牌面朝上或面朝下显示。 但我们还无法进行游戏。 需要了解一下,这个游戏的规则:
  • 当游戏开始时,所有的牌都面朝下。
  • 玩家通过点击来选择,翻两张牌。
  • 如果两张牌有相同的图像,它们将保持面朝上。 如果没有,它们应该在短暂延迟后再次面朝下翻转。

点击翻转卡牌

现在,我们有一个程序能绘制一个卡牌网格,但不绘制任何其他东西。接下来,我们希望程序随着时间推移,绘制不同的东西——它将从绘制面朝下的卡牌开始,还会显示被点击的卡牌,如果一切顺利的话,它还会给玩家显示一个游戏获胜的图案(滑稽!)。
因此,让我们将所有绘图代码移动到ProcessingJS的draw函数中。当程序运行时,计算机将持续调用draw(),将根据卡牌是面朝上还是面朝下来不断绘制图案:
draw = function() {
    background(255, 255, 255);
    for (var i = 0; i < tiles.length; i++) {
        tiles[i].draw();
    }
};
让我们现在就把这些卡牌面朝上! 要翻转卡牌,玩家必须单击它。 为了响应在ProcessingJS程序中的单击,我们可以定义一个mouseClicked函数,并且每次单击鼠标时计算机都将执行该代码。
mouseClicked = function() {
  // process click somehow
};
当我们的程序发现玩家点击某处时,我们想要使用mouseXmouseY检查他们是否点击了某个卡牌。 让我们首先在Tile中添加一个isUnderMouse方法,如果给定的x和y在卡牌的区域内,则返回true
在我们绘制卡牌网格的方法里,卡牌的x和y对应于卡牌的左上角,因此只有当给定的x值介于this.xthis.x + this.size之间时,并且给定的y在this.ythis.y + this.size之间时,才返回true。
Tile.prototype.isUnderMouse = function(x, y) {
    return x >= this.x && x <= this.x + this.size  &&
        y >= this.y && y <= this.y + this.size;
};
现在我们有了这个方法,可以在mouseClicked中使用for循环来检查每个卡牌是否在mouseXmouseY范围之内。 如果是这样,我们将卡牌的isFaceUp属性设置为true
mouseClicked = function() {
  for (var i = 0; i < tiles.length; i++) {
    if (tiles[i].isUnderMouse(mouseX, mouseY)) {
      tiles[i].isFaceUp = true;
    }
  }
};
这就是它的效果。 点击这些卡牌,看看会发生什么:

限制卡牌翻转

发现了吗? 我们实现了游戏玩法的一个方面,玩家能够翻转卡牌,但我们错过了一个重要的限制:他们不应该一次翻转两个以上的卡片
我们需要以某种方式跟踪翻转的卡牌数量。 一个简单的方法是全局numFlipped变量,每当玩家将牌面朝上时我们就会增加。 如果numFlipped小于2且卡牌尚未面朝上,我们只翻转一块卡牌:
var numFlipped = 0;
mouseClicked = function() {
    for (var i = 0; i < tiles.length; i++) {
        var tile = tiles[i];
        if (tiles.isUnderMouse(mouseX, mouseY)) {
            if (numFlipped < 2 && !tile.isFaceUp) { 
              tile.isFaceUp = true;
              numFlipped++;
            }
        }
    }
};

延迟翻转卡牌

好的,双卡牌翻转逻辑已经完成。下一步是什么? 让我们再次回顾一下游戏规则:
如果两个卡牌具有相同的图像,则它们保持面朝上。否则,在一段时间后,卡牌会翻转初始状态。
我们将首先实现第二部分,系统会自动翻转卡牌,因为如果不能轻易地查找新的匹配,将难以测试第一部分。
我们知道如何通过将 isFaceUp 设置为 false 来翻转卡牌,但是我们如何 在一段时间之后做到这一点? 每种语言和环境都有不同的方法来延迟代码的执行,需要弄清楚如何在ProcessingJS中执行它。我们需要一些方法来跟踪时间 —— 延迟时间是否已经过去 —— 以及在经过一段时间后调用代码的方法。 下面是我的建议:
  • 我们创建一个名为delayStartFC的全局变量,初始值为null。
  • mouseClicked函数中,在翻转第二个卡牌之后,将frameCount的当前值存储在delayStartFC中。 该变量告诉我们自程序开始运行以来已经过了多少帧,这是在程序中告知时间的一种方式。
  • draw函数中,检查 frameCount 的新值是否显着高于旧值,如果是,我们翻转所有的卡牌并将numFlipped设置为0。 我们还会将delayStartFC重置为null
实际上这是一个很好的解决方案,不需要太多的代码来实现。为了优化性能,我们可以使用loopnoLoop函数来确保只在发生延迟时才调用draw代码。 下面是所有的效果:
var numFlipped = 0;
var delayStartFC = null;

mouseClicked = function() {
  for (var i = 0; i < tiles.length; i++) {
    var tile = tiles[i];
    if (tile.isUnderMouse(mouseX, mouseY)) {
      if (numFlipped < 2 && !tile.isFaceUp) {
        tile.isFaceUp = true;
        numFlipped++;
        if (numFlipped === 2) {
          delayStartFC = frameCount;
        }
        loop();
      } 
    }
  }
};

draw = function() {
  if (delayStartFC &&
     (frameCount - delayStartFC) > 30) {
    for (var i = 0; i < tiles.length; i++) {
      tiles[i].isFaceUp = false;
    }
    numFlipped = 0;
    delayStartFC = null;
    noLoop();
  }

  background(255, 255, 255);
  for (var i = 0; i < tiles.length; i++) {
    tiles[i].draw();
  }
};
翻转下面的一些卡牌 - 卡牌自动翻转的方式非常酷,对吧? 为了帮助你更好地理解它,请尝试更改延迟等待的时间以及在延迟开始之前必须翻转多少个卡牌的数量。

检查匹配卡牌

如果你成功匹配上面的卡牌,但看到它们又翻回来时,你可能会感到悲哀,因为,喂,你刚刚才成功匹配了一对! 所以现在是时候实现这个游戏规则了:
如果两个卡牌匹配,那么它们应该保持面朝上
这意味着我们应该在2张卡牌被翻转时检查卡牌的匹配,在我们设置延迟之前。 伪代码中,表达如下:
if there are two tiles flipped over:
    if first tile has same face as second tile:
       keep the tiles face up
我们已经检查是否有两个卡牌翻转过来(numFlipped === 2),那么如何检查卡牌是否存在相同的面? 首先,需要一些方法来访问两个被翻转的卡牌。 如何找到它们?
我们 可以 每次遍历数组,找到isFaceUp设置为true的所有卡牌,然后将它们存储到一个数组中。
这里有一条捷径:让我们总是将翻转的卡牌存储在一个数组中,以便于访问。 这样,每当玩家翻转卡牌时,不必遍历整个tiles数组。
作为第一步,我们可以用数组替换numFlipped,然后在之前使用numFlipped的地方替换成使用flippedTiles.lengthmouseClick函数如下所示:
var flippedTiles = [];
var delayStartFC = null;

mouseClicked = function() {
  for (var i = 0; i < tiles.length; i++) {
    var tile = tiles[i];
    if (tile.isUnderMouse(mouseX, mouseY)) {
      if (flippedTiles.length < 2 && !tile.isFaceUp) {
        tile.isFaceUp = true;
        flippedTiles.push(tile);
        if (flippedTiles.length === 2) {
          delayStartFC = frameCount;
          loop();
        }
      } 
    }
  }
};
现在,我们需要确认flippedTiles数组中的两个卡牌是否有相同的面。那么,什么是face属性? 它是一个对象 —— 实际上,匹配卡牌的面应该是完全相同的对象,也就是说匹配卡牌表示面的变量是指向计算机内存中的相同位置。 因为我们只创建了一个图像对象(比如getImage("avatars/old-spice-man")) 然后将相同的图像对象加进 faces 数组两次:
var face = possibleFaces[randomInd];
selected.push(face);
selected.push(face);
至少在JavaScript中,相等运算符,如果在两个指向对象的变量上使用它,并且这两个变量都引用内存中的同一个对象,将返回 true。这意味着检查可以很简单 —— 只需在每个卡牌的face属性上使用相等运算符:
if (flippedTiles[0].face === flippedTiles[1].face) {
  ...
}
现在我们知道如何匹配卡牌,还需要保持它们面朝上。目前,他们还会在延迟后全部翻转。在这种情况,下我们可以不设置动画,但请记住,在之后的回合中需要动画 —— 所以我们不能依赖它。
相反,我们需要一种方式来表示 “嘿,当我们将卡牌全部翻过来时,不应该翻这些特定的卡牌。” 听起来像布尔属性的用武之地!让我们在Tile构造函数中添加一个isMatch属性,然后只在那个if条件中将isMatch设置为true
if (flippedTiles[0].face === flippedTiles[1].face) {
  flippedTiles[0].isMatch = true;
  flippedTiles[1].isMatch = true;
}
现在我们可以使用该属性来决定是否在延迟之后将卡牌翻转。
for (var i = 0; i < tiles.length; i++) {
  var tile = tiles[i];
  if (!tile.isMatch) {
    tile.isFaceUp = false;
  }
}
在下面进行游戏吧!当你在下面找到两个匹配的图案时,它们应该在延迟之后(以及在接下来的回合之中)保持不变: