Ok, então agora temos um tabuleiro de peças que podemos exibir com a face para cima ou para baixo. Mas ainda não conseguimos jogar o jogo. Para recordar, é assim que o jogo funciona:
  • Quando o jogo começa, todas as peças estão com face para baixo.
  • O jogador então vira duas cartas através do clique do mouse.
  • Se as duas peças têm a mesma imagem, elas continuam viradas para cima. Caso contrário, elas devem ser viradas com face para baixo novamente após um curto atraso.

Virando as peças com um clique

Vamos começar virando todas as peças com a face para baixo novamente:
for (var i = 0; i < tiles.length; i++) {
    tiles[i].drawFaceDown();
}
Para virar uma peça, o jogador deve clicá-la. Para responder ao clique em programas ProcessingJS, podemos definir uma função mouseClicked, que será executada toda vez que houver um clique do mouse.
mouseClicked = function() {
  // process click somehow
};
Quando nosso programa percebe que o jogador clicou em algum lugar, nós queremos que ele também verifique se ele clicou em uma peça usando mouseX e mouseY. Vamos começar adicionando um método isUnderMouse para Tile que retorna verdadeiro se um determinado x e y estão dentro da área de uma peça. De acordo com o modo como desenhamos as peças, o x e o y da peça correspondem ao canto superior esquerdo da peça, então deveríamos retornar verdadeiro apenas se o determinado x estiver entrethis.x e this.x + this.width, e se o determinado y estiver entre this.y e this.y + this.width:
Tile.prototype.isUnderMouse = function(x, y) {
    return x >= this.x && x <= this.x + this.width  &&
        y >= this.y && y <= this.y + this.width;
};
Agora que temos este método, podemos usar um laço for em mouseClicked para verificar se cada peça está sob mouseX e mouseY. Se estiver, então a desenharemos com a face voltada para cima.
mouseClicked = function() {
    for (var i = 0; i < tiles.length; i++) {
        if (tiles[i].isUnderMouse(mouseX, mouseY)) {
            tiles[i].drawFaceUp();
        }
    }
};
Vai ficar mais ou menos assim. Clique nas peças e veja o que acontece:
Notou alguma coisa? Implementamos um aspecto do jogo, permitindo que o jogador vire as peças, mas está faltando uma restrição importante: eles não deveriam poder virar mais de duas peças de uma vez.
Nós precisaremos acompanhar o número de peças viradas de alguma forma. Um jeito simples seria com uma variável global numFlipped que podemos incrementar cada vez que o jogador virar uma carta com a face voltada para cima. Assim poderíamos pular a verificação da peça se numFlipped for 2.
var numFlipped = 0;
mouseClicked = function() {
    for (var i = 0; i < tiles.length; i++) {
        if (tiles[i].isUnderMouse(mouseX, mouseY)) {
            if (numFlipped < 2) {
              tiles[i].drawFaceUp();
              numFlipped++;
            }
        }
    }
};
Mas isso não está totalmente certo – com esse código, o que aconteceria se nós clicássemos na mesma peça duas vezes seguidas? Pense nisso – isso a viraria duas vezes, faria numFlipped igual a 2, e impossibilitaria a peça de ser virada futuramente. Em vez disso, queremos que a peça seja virada somente se ela já não estiver virada com a face para cima.
Como sabemos se a peça que está atualmente sendo executada na estrutura está com a face para cima ou para baixo? já devemos ter chamado o métododrawFaceUp em algum ponto, mas nunca nos mantivemos informados sobre ele. Ba ba bum, é hora de usar um valor booleano! Vamos modificar os métodos de desenho para definir um valor booleano que nos diga o estado da peça.
Tile.prototype.drawFaceDown = function() {
    // ...
    this.isFaceUp = false;
};

Tile.prototype.drawFaceUp = function() {
    // ...
    this.isFaceUp = true;
};
Agora podemos modificar a verificação para nos certificarmos de que a peça está realmente com a face para baixo:
if (tiles[i].isUnderMouse(mouseX, mouseY)) {
            if (numFlipped < 2 && !tiles[i].isFaceUp) {
              tiles[i].drawFaceUp();
              numFlipped++;
            }
        }

O atraso na virada das peças

Ok, nossa lógica para virar apenas duas peças está completa. E agora? Vamos recapitular as regras do jogo:
Se as duas peças tiverem a mesma imagem, elas continuam com a face voltada para cima. Caso contrário, as peças são viradas novamente para baixo após alguns instantes.
Primeiro vamos implementar a segunda parte, que automaticamente vira as peças para baixo, porque será difícil testar a primeira parte se não pudermos procurar por novos pares com facilidade.
Nós sabemos como virar as peças para baixo utilizando o método turnFaceDown, mas como vamos fazer isso após um período de tempo? Toda linguagem e ambiente tem uma abordagem diferente para atrasar a execução de código, e precisamos descobrir como fazer isso em ProcessingJS. Nós precisamos de alguma maneira de controlar o tempo – se o período de atraso passou – e uma maneira de chamar o código após o período de tempo ter passado. Aqui está a minha sugestão:
  • Nós criamos uma variável global chamada delayStartFC, inicialmente vazia (null).
  • Na função mouseClicked, logo após a virada da segunda peça, armazenamos o valor atual de frameCount na delayStartFC. Essa variável nos informa quantos quadros (frames) já passaram desde o início do funcionamento do programa, e é uma maneira de contar o tempo em nossos programas.
  • Definimos a função draw, já que essa função será chamada continuadamente durante o funcionamento do programa, por isso sabemos que ela será chamada para cada valor de frameCount. Nessa função verificamos se o novo valor de frameCount é significativamente maior que o antigo e, caso seja, viramos todas as peças para baixo e definimos numFlipped como 0. Nós também redefinimos delayStartFC como vazio.
Realmente é uma boa solução que não requer muito código para ser implementada. Para otimizar a performance, podemos utilizar as funções loop e noLoop para ter certeza de que o código de desenho somente seja chamado quando houver um atraso. Juntando tudo, fica assim:
var numFlipped = 0;
var delayStartFC = null;

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

draw = function() {
    if (delayStartFC && (frameCount - delayStartFC) > 30) {
        for (var i = 0; i < tiles.length; i++) {
            tiles[i].drawFaceDown();
        }
        numFlipped = 0;
        delayStartFC = null;
        noLoop();
    }
};
Faça um teste – é muito legal como as peças viram para baixo automaticamente, não é?
Mas não queremos que as peças sempre virem para trás – lembre-se: se duas peças forem iguais, elas devem ficar com a face para cima. Isso significa que devemos verificar se há peças iguais toda vez que houver 2 peças viradas para cima antes de configurar o atraso. Em pseudocódigo, seria assim:
se houver duas peças viradas:
    se a primeira tem a mesma imagem que a segunda peça:
      mantenha as peças com face para cima
Já temos uma verificação para quando houver duas peças viradas para cima (numFlipped === 2), então, como podemos verificar se as peças são iguais? Primeiramente, precisamos de um modo de comparar as duas peças viradas. Nós poderíamos criar uma iteração que percorra nosso arranjo, ache todas as peças com isFaceUp definido como verdadeiro e armazene-as em um arranjo. Ou, tenho uma ideia melhor: vamos simplesmente armazenar todas as peças viradas em um arranjo, para um acesso mais fácil. Inclusive, podemos simplesmente substituir numFlipped por um arranjo e, assim, utilizar flippedTiles.length em todos os lugares em que utilizamos numFlipped.
Nossa função mouseClick modificada fica assim:
var flippedTiles = [];
var delayStartFC = null;

mouseClicked = function() {
    for (var i = 0; i < tiles.length; i++) {
        if (tiles[i].isUnderMouse(mouseX, mouseY)) {
            if (flippedTiles.length < 2 && !tiles[i].isFaceUp) {
                tiles[i].drawFaceUp();
                flippedTiles.push(tiles[i]);
                if (flippedTiles.length === 2) {
                    delayStartFC = frameCount;
                    loop();
                }
            } 
        }
    }
};
Agora, precisamos descobrir se as duas peças no array flippedTiles realmente possuem a mesma imagem. Bem, o que é a propriedade face? É um objeto – e, na verdade, a face das peças correspondentes devem ser exatamente o mesmo objeto, já que a variável está apontando para o mesmo espaço de memória do computador para ambas as peças. Isso é porque criamos cada objeto de imagem uma única vez (como em getImage("avatars/old-spice-man") e então colocamos o mesmo objeto no array de faces duas vezes:
var face = possibleFaces[randomInd];
    selected.push(face);
    selected.push(face);
Em JavaScript, pelo menos, o operador de igualdade vai retornar verdadeiro se ele for usado em duas variáveis que apontam para objetos, e ambas se referem ao mesmo objeto na memória. Isso significa que nossa verificação pode ser simples – podemos simplesmente usar o operador de igualdade na propriedade face de cada peça:
if (flippedTiles[0].face === flippedTiles[1].face) {
  // ...
  }
Agora que sabemos que as peças são iguais, precisamos mantê-las com a face para cima. Do jeito que está o código, todas as peças são viradas para baixo depois de alguns instantes. Poderíamos simplesmente não fazer a animação nesse caso, mas lembre-se, haverá mais animações no decorrer do programa, então não podemos confiar nisso. Em vez disso, precisamos encontrar um jeito de saber "Ei, quando viramos todas as peças de volta, não deveríamos virar essas em particular". Parece outro bom uso para uma propriedade booleana! Poderíamos definir uma propriedade isMatch como verdadeira dentro da estrutura if, e então somente viramos as peças para baixo se a propriedade não for verdadeira:
if (flippedTiles[0].face === flippedTiles[1].face) {
    flippedTiles[0].isMatch = true;
    flippedTiles[1].isMatch = true;
}
for (var i = 0; i < tiles.length; i++) {
    if (!tiles[i].isMatch) {
        tiles[i].drawFaceDown();
    }
}
Agora, quando você encontrar duas peças correspondentes, elas devem permanecer com a face voltada para cima após alguns instantes (e também após jogadas futuras):

Pontuação

Agora só falta uma coisa: a pontuação. Aqui está um lembrete sobre esta parte nas regras do jogo:
O objetivo do jogo é fazer com que todas as peças fiquem com a face virada para cima (isto é, encontrar todos os pares) no menor número de tentativas. Isso significa que, quanto menor o número de tentativas, melhor a pontuação.
Como fazemos para contar o número de tentativas? Bem, uma "tentativa" se dá cada vez que você vira duas peças, o que corresponde ao nosso if que verifica se flippedTiles.length === 2. Podemos adicionar uma nova variável global, numTries, que pode ser incrementada dentro desse if.
if (flippedTiles.length === 2) {
                    numTries++;
                    // ...
               }
Podemos mostrar a pontuação quando o jogo terminar – quando o jogador já tiver encontrado todos os pares. Como fazemos isso? Bem, temos um array com todas as nossas peças, e cada uma tem um valor booleano que nos diz se elas já foram correspondidas, então podemos simplesmente percorrer o array e verificar se todas retornam verdadeiro. Uma maneira elegante de codificar isso é inicializar uma variável booleana como verdadeira antes de percorrer o array, e fazer a comparação lógica entre ela e as demais variáveis booleanas das peças em cada iteração. Se ela não for comparada a nenhum valor falso (e o array tem comprimento maior do que 0 peças!), ela continuará sendo verdadeira no final. Isso vai ficar assim:
var foundAllMatches = true;
    for (var i = 0; i < tiles.length; i++) {
        foundAllMatches = foundAllMatches && tiles[i].isMatch;
    }
Quando encontrarmos todos os pares, podemos mostrar uma mensagem de congratulação para o usuário e o número de tentativas feitas, como por exemplo:
if (foundAllMatches) {
    fill(0, 0, 0);
    text("Você achou todos em " + numTries + " tentativas", 20, 360);
}
A propósito, vamos colocar todo esse código no fim da nossa função mouseClicked, assim ele será executado após verificarmos se os pares são correspondentes naquele turno.
Agora você pode testar nosso jogo, mas você deve levar algum tempo para ganhar o jogo (sem ofensa, é claro, eu também levo um tempo para conseguir!). Aqui vai uma dica para quando você estiver testando as partes do seu jogo que não são fáceis de alcançar – modifique o jogo temporariamente para que fique mais fácil chegar até elas. Por exemplo, neste jogo, mude os valores de NUM_ROWS e NUM_COLS para números menores, e assim você terminará o jogo muito mais rápido. Agora, tente fazer isso abaixo!