Jogo da Memória
Neste tutorial, vamos fazer um jogo da memória recorrendo ao nosso Arduino. Vamos usar 3 LEDs como indicadores luminosos (e fica, desde já, o desafio para quem quiser adicionar mais LEDs para tornar a tarefa mais complicada!) que deveremos posteriormente replicar no Serial Monitor do Arduino. Esta é a primeira vez que vamos usar o Serial Monitor diretamente embora já tenhamos usado a comunicação série no tutorial da Interface LED com Arduino e Processing. Vê abaixo o vídeo do jogo em ação!
Hardware
Material necessário: 3 LEDs (um verde, um amarelo e um vermelho), 3 resistências de 220 Ohms e alguns fios de montagem.
Para ajudar à montagem, criou-se o seguinte esquema de ligações usando o programa Fritzing.
Acaba por ser exatamente o mesmo esquema do tutorial anterior mas com um fim totalmente diferente.
PRO TIP: Se os teus LEDs tiverem com pouco brilho, isso deve-se ao facto das resistências terem um valor demasiado alto. Podes usar resistências menores ou colocar duas que tenhas em paralelo (a resistência resultante será menor que a menor das duas). Não colocar uma resistência não é recomendado, já que uma pequena variação de tensão se irá traduzir numa grande variação de corrente que poderá fundir o teu LED.
Desenvolvimento do programa
Este jogo envolve vários conceitos/etapas que vamos abordando ao longo do tutorial. A primeira coisa a fazer é definir os nosso três LEDs ligados aos pins 8, 9 e 10 como outputs.
Em seguida, precisamos também de iniciar a comunicação série que já sabemos como se faz. Vamos ainda adicionar a seed para o nosso gerador de números aleatórios que vai gerar as diferentes sequências (a mesma função que usámos no “Dado Virtual”; para mais informações consulta esse tutorial). Finalmente, e como o void setup()
só corre uma vez, vamos escrever duas linhas de código que irão aparecer no Serial Monitor.
Pela primeira vez, vamos fazer um print que é algo muito comum em programação. Fazer print significa escrever algo na consola para que o utilizador saiba o que fazer a seguir. Para fazermos um print para o Serial Monitor usamos o método Serial.print()
ou Serial.println()
. A diferença entre os dois assenta basicamente no facto deste último método fazer uma quebra de linha entre cada linha. Se usássemos o Serial.print()
a informação iria aparecer toda lado a lado sem qualquer tipo de espaçamento entre ela.
Vamos então informar o jogador que o jogo começou e o que ele precisa de fazer para começar a jogar.
Assim, quando corrermos o nosso programa e abrirmos o Serial Monitor, estas duas linhas de texto irão aparecer.
Início de cada nível
O nosso Jogo da Memória será dividido em 5 níveis diferentes cuja dificuldade vai aumentando substancialmente. O jogador sabe em que nível está uma vez que também isso será mostrado na consola. No entanto, deve ser feito um aviso de quando a sequência está pronta a ser mostrada. Uma boa alternativa é piscar todos os LEDs duas vezes, esperar um segundo e mostrar a sequência. Assim, o jogador nunca é apanhado desprevenido. Para facilitar as coisas, vamos criar uma função void blinkTwice()
que faz exatamente o que o nome diz: faz com que todos os LEDs pisquem duas vezes. Ora vocês já consequem claramente escrever este tipo de função. Escreve-a e depois podes confirmar abaixo (mas não sem tentares primeiro!) a solução.
Como podemos ver, este código é redundante (repete-se desnecessariamente). No fim deste tutorial, já terão as ferramentas necessárias para escrever esta função de um modo mais compacto.
As outras instruções serão dadas no void loop()
que será a última coisa a ser escrita já que vai juntar todas as componentes do jogo.
Gerar sequência aleatória
O nosso jogo vai gerar uma sequência aleatória que a cada número gerado tem um LED associado. A ordem proposta é a que se encontra na imagem abaixo.
Para gerarmos a nossa sequência aleatória vamos definir uma função só para este efeito. Neste caso, é a função void generateSequence (int tempo, int sequencia)
. A primeira coisa a fazer é chamar a função blinkTwice()
e dar um delay de 1 segundo antes da sequência começar.
Como podemos ver, esta função gera uma sequência baseada em dois parâmetros: o tempo que cada LED está aceso e o número de LEDs acesos que a sequência tem. Antes de gerarmos os nossos números aleatoriamente, precisamos de um sítio para os guardar. Para isso, usamos um tipo de estrutura dado conhecida como lista. Em C++ (que é a linguagem em que o Arduino IDE é baseado) precisamos sempre de definir o tamanho de uma lista. Por esta razão é que um dos argumentos da nossa função (int sequencia
) é o tamanho da sequência. Assim, não precisamos de reescrever uma função sempre que queremos adicionar mais elementos à nossa sequência.
Podemos ter uma lista de vários tipos de variáveis. No nosso caso, será uma lista que guarda inteiros. Não nos devemos esquecer que as listas são naturalmente indexadas a zero e que ter em conta os limites da nossa lista é essencial. Se prestarmos atenção, nós já conhecemos o conceito de lista intuitivamente. Uma String é uma lista de caracteres (char) que podemos chamar usando os respetivos índices tal como fazemos numa lista.
O int
desta linha refere-se ao facto de ser uma lista de inteiros. ordemLeds
é simplesmente o nome que foi dado à lista e sequencia
é o tamanho da lista. Geralmente, é um número mas neste caso é uma variável que remete para um inteiro.
Agora que já temos um sítio para guardar a sequência de números, podemos gerá-la. Queremos gerar tantos números quantos os indicados na variável sequência
. Para preenchermos a lista, usamos um ciclo for
. Um ciclo for
é útil quando pretendemos repetir uma dada instrução um determinado número de vezes.
Quando usamos um ciclo for
, precisamos de inicializar uma variável que vai contar o número de iterações e definir o seu valor mínimo e o seu valor máximo para o qual o ciclo deixa de correr. Temos ainda de dizer se queremos que a variável aumente ou diminua a cada iteração.
É quase uma convenção usar o i como a variável que vai contar as iterações. Inicializamo-la a 0 na primeira condição do ciclo. Na segunda condição, definimos o valor máximo da variável. Atenção que se trata de um menor e não de um menor ou igual. Por exemplo, se sequencia == 3
então i irá tomar os seguintes valores por ordem: 0, 1, 2. O ciclo itera 3 vezes. A terceira condição do ciclo diz-nos se a variável incrementa (i++) ou decrementa (i–). Esta notação significa que i++ == i + 1
e i-- == i - 1
.
Portanto, a cada iteração do nosso ciclo, um número aleatório é gerado entre 1 e 3. Lembra-te que o método random()
tem dois argumentos quando queremos gerar números num dado intervalo. Como queremos gerar números entre 1 e 3, passamos como argumentos 1 e 4 random(min inclusive, max exclusive)
.
Depois de gerado o número, guardamo-lo na lista que criámos anteriormente. Agora que a sequência aleatória já foi gerada e guardada, temos de pôr os nossos LEDs a piscar de acordo com ela. Para isso, usamos novamente um ciclo for
. Consegues pensar como podemos fazer isto?
As três condições são iguais às do último ciclo com a exceção de que substituímos o nome da variável. Começamos a ler a lista começando no índice 0. Se este valor for igual a 1, ligamos o primeiro LED; se for igual a 2, ligamos o segundo LED e assim sucessivamente. Usamos três condições do tipo if
para chegar a este resultado.
A linha led = ordemLeds[j];
usa uma variável chamada led para guardar o elemento da lista de índice j. Isto não é estritamente necessário para torna o código mais fácil de escrever. A variável led
é um int
que deve ser inicializado no início do código com a instrução int led
.
Como deves ter reparado, não fechámos o nosso ciclo for
. A última coisa a fazer é converter a nossa lista numa String (o porquê desta decisão será claro em breve). Para isso, inicializámos uma String também no início do código chamada sequenciaNumerica
.
Para fazer esta conversão, concatenamos a String sequenciaNumerica
, ou seja, adicionamos os elementos à String por ordem. Assim, depois do último else if ()
adicionamos a linha sequenciaNumerica = sequenciaNumerica + led;
. Para percebermos melhor o que está a acontecer, vamos ter em conta que na primeira iteração, sequenciaNumerica == ""
, ou seja, trata-se de uma String vazia.
Quando chegamos ao fim da primeira iteração do ciclo for
, juntamos o primeiro elemento da nossa lista à nossa String. Por exemplo, se o primeiro índice for 1, passamos de sequenciaNumerica = ""
para sequenciaNumerica == "1"
.
Falta apenas um pequeno pormenor para encerrarmos o método generateSequence()
. Ainda não estabelecemos que a String inicialmente é uma String vazia. Para isso, adicionamos a linha sequenciaNumerica = "";
antes do último ciclo for
.
Posto isto, o nosso método generateSequence
completo é então:
Validar a resposta do jogador
De seguida, precisamos de criar um método que valide a resposta do jogador. A melhor maneira de expressar a vitória ou a derrota num jogo é uma variável boolean
. Tem o valor true
se o jogador introduziu a resposta certa e o valor false
se o jogador introduzir a resposta errada.
Começamos por inicializar uma variável win
do tipo boolean
cujo resultado o método vai devolver. O seu valor por defeito é true
. A razão pela qual lhe damos um valor inicial é porque se este valor estivesse totalmente dependente de uma condição if()
, o método daria erro já que a condição poderia nunca se concretizar e não haveria nenhum valor para devolver.
Em seguida, fazemos um print para o Serial Monitor para que o jogador saiba que já pode introduzir a sua resposta. Até agora, temos:
Para esperarmos por uma resposta do utilizador, usamos uma condição if
vazia que apenas é validade quando o utilizador insere a sua resposta. O método Serial.available()
é diferente de 0 quando o jogador introduzir com o Serial Monitor.
Depois do jogador introduzir a sua resposta, vamos lê-la e guarda-la numa String user
, inicializada no início do nosso programa. Esta é a razão pela qual no método que gera a sequência, convertemos a lista numa String.
Agora que já temos a resposta do jogador guardada na String user
, basta compararmos com a String sequenciaNumerica
que gerámos. Para isso usamos o método equals()
que compara Strings e devolve true
se elas forem iguais. Se elas forem diferentes, win
passa a false
. Caso contrário, mantém-se inalterada como true
. Depois, basta fazer return win;
.
A condição do if()
pode parecer um pouco estranha mas é apenas uma maneira de escrever as mesmas coisas de uma forma mais condensada. O ponto de exclamação antes de invocarmos o método, nega a condição. Isto é precisamente o mesmo que ter:
onde !=
é o operador diferente de.
Juntar tudo!
O mais difícil já está feito. Agora só temos de juntar tudo no void loop()
.Para iniciar o jogo, vamos exigir que o jogador envie o caracter ‘s’ através do Serial Monitor. Isto é o equivalente ao “Press Start” que vemos muitas vezes nos jogos. Isto não é feito ao acaso: assim, o jogo só começa quando o jogador estiver preparado e tiver aberto o Serial Monitor.
Agora podemos começar a juntar os níveis e podemos fazer as combinações de tempo entre os LEDs acenderem e do tamanho da sequência que quisermos. Podes tornar o jogo o mais fácil ou o mais difícil que quiseres. O jogo deste tutorial tem 5 níveis com as seguintes caraterísticas.
Nível | Tempo (em segundos) | Tamanho da sequência |
---|---|---|
1 | 1 | 3 |
2 | 0.5 | 3 |
3 | 1 | 4 |
4 | 0.5 | 4 |
5 | 0.5 | 5 |
Como construímos os níveis? Basicamente, usando condições if
encadeadas.
Fazemos isto sucessivamente até termos tantos níveis quanto os desejados. Podes fazer imensas combinações diferentes e adicionar mais LEDs para tornares o jogo mais desafiante!