Conteúdo principal
Programação
Curso: Programação > Unidade 4
Lição 5: Programando um Jogo da MemóriaTabuleiro de peças
A primeira etapa para jogar o jogo de "Memória" é embaralhar aleatoriamente todas as peças e então colocá-las arrumadas em uma grade retangular, viradas para baixo, para que não possamos ver qual a imagem está do outro lado de cada peça.
Peças com a face para baixo
Para começar a programar o jogo, vamos nos preocupar apenas em criar peças com a face voltada para baixo, e deixar pra pensar em como faremos as imagens diferentes mais tarde.
A "peça" é um objeto tão importante do "Jogo da Memória" que usaremos princípios de orientação a objetos para definir um objeto do tipo
Tile
(peça, em inglês) e criaremos várias instâncias dele. Depois poderemos associar tanto propriedades (como localização e imagem) como comportamento (como virar as peças com a face voltada para baixo ou para cima) a cada um dos objetos Tile
.Para começar, iremos definir a função do construtor
Tile
. Como ainda não estamos lidando com imagens, iremos apenas passar os argumentos x
e y
a ele. Também vamos lembrar a largura (width) da peça, um argumento constante, em uma propriedade do objeto.var Tile = function(x, y) {
this.x = x;
this.y = y;
this.width = 70;
};
Agora que definimos o construtor, podemos usá-lo em um laço para criar peças com as posições x e y apropriadas. Na verdade, vamos usar dois laços for – um laço for aninhado – já que o conceito facilita a geração de coordenadas para uma grade.
Vamos começar criando um array vazio
tiles
:var tiles = [];
Nosso laço for externo itera em quantas colunas quisermos, enquanto nosso for interno itera para cada uma das linhas, e cada novo objeto de tipo
Tile
é inicializado com valores de x e y que correspondam à linha e à coluna.var NUM_COLS = 5;
var NUM_ROWS = 4;
for (var i = 0; i < NUM_COLS; i++) {
for (var j = 0; j < NUM_ROWS; j++) {
tiles.push(new Tile(i * 78 + 10, j * 78 + 40));
}
}
Ah, mas é difícil saber se as peças vão ficar legais, porque ainda não temos um código para desenhá-las! Na verdade, talvez devêssemos ter feito isso primeiro. Algumas vezes, na programação, é difícil saber o que fazer primeiro, não é verdade? Agora vamos adicionar um método para o objeto
Tile
que desenha uma peça com a face para baixo na tela. Vamos desenhar um retângulo arredondado com uma bela folha da Khan Academy na parte superior, na posição determinada.Tile.prototype.drawFaceDown = function() {
fill(214, 247, 202);
strokeWeight(2);
rect(this.x, this.y, this.width, this.width, 10);
image(getImage("avatars/leaf-green"), this.x, this.y, this.width, this.width);
};
E agora podemos ver como nossas peças ficaram. Vamos adicionar um novo laço for que itere por todas as peças e chame o método de desenho nelas:
for (var i = 0; i < tiles.length; i++) {
tiles[i].drawFaceDown();
}
É assim que nosso programa se parece, com toda a programação feita até agora. Tente colocar números diferentes nas estruturas aninhadas para ver como a grade muda, ou tente mudar a maneira de desenhar as peças (um logotipo diferente, talvez?).
Peças com a face para cima
Agora que temos uma grade de peças com as faces voltadas para baixo, vamos tratar de um problema mais complicado: dar a cada uma delas uma imagem, de forma que haja 2 peças de cada imagem no array, distribuídas de forma aleatória. Existem muitas formas de fazer isso, mas eis o que eu sugeriria:
- Criamos um array com as imagens possíveis, usando a função
getImage
para pegá-las na nossa biblioteca. - Vamos precisar de apenas 10 imagens para nossas 20 peças, então criamos um novo array que armazene 2 cópias de 10 imagens do primeiro array selecionadas aleatoriamente.
- Embaralhamos o array com as imagens selecionadas, assim os pares de imagens não ficarão em posições próximas no array.
- Na estrutura for aninhada, onde criamos as peças, vamos associar uma imagem do array a cada peça.
Esses passos podem não fazer sentido ainda – vamos implementar cada um deles e ver como fica.
Etapa 1: Criamos um array com as imagens possíveis, usando a função
getImage
para pegá-las na nossa biblioteca:var faces = [
getImage("avatars/leafers-seed"),
getImage("avatars/leafers-seedling"),
getImage("avatars/leafers-sapling"),
getImage("avatars/leafers-tree"),
getImage("avatars/leafers-ultimate"),
getImage("avatars/marcimus"),
getImage("avatars/mr-pants"),
getImage("avatars/mr-pink"),
getImage("avatars/old-spice-man"),
getImage("avatars/robot_female_1"),
getImage("avatars/piceratops-tree"),
getImage("avatars/orange-juice-squid")
];
Eu peguei algumas imagens, mas você pode escolher suas imagens favoritas. O importante é que este array tenha pelo menos 10 imagens, assim não faltarão imagens para nossas 20 peças. Contudo, podemos colocar muito mais que 10 imagens, para dar ao nosso jogo uma maior variedade a cada vez que ele for jogado, porque vamos limitar a lista na próxima etapa.
Etapa 2: Vamos precisar de apenas 10 imagens para nossas 20 peças, então criamos um novo array que armazene 2 cópias de 10 imagens selecionadas aleatoriamente do primeiro array.
Para isso, vamos criar um laço for que itere 10 vezes. Em cada iteração, pegamos aleatoriamente um índice do array
faces
, o inserimos duas vezes no array selected
, e então usamos o método splice para removê-los do array faces
, assim não selecionaremos o mesmo índice duas vezes. Esse último passo é muito importante!var selected = [];
for (var i = 0; i < 10; i++) {
// Escolhe um aleatoriamente do array de faces
var randomInd = floor(random(faces.length));
var face = faces[randomInd];
// Insere 2 cópias no array
selected.push(face);
selected.push(face);
// Remove do array faces para não escolhermos o mesmo
faces.splice(randomInd, 1);
}
Etapa 3: Embaralhamos o array com as imagens selecionadas, assim os pares de imagens não ficarão em posições próximas no array.
Você deve estar se perguntando: como embaralhar um array em JavaScript? Existem algumas técnicas, mas vou mostrar a minha preferida.
Em JavaScript, todo objeto array possui um método
sort
que irá ordenar o array "lexicograficamente". Isso significa que ele irá converter cada item em uma string, ordenando com se fossem palavras em um dicionário. Por exemplo, vamos ver como ele ordena um array de números e letras.var items = ["A", 1, "C", "H", 10, "D", 2];
items.sort();
1,10,2,A,C,D,H
Esse tipo de ordenação pode às vezes ser útil, mas na maioria das vezes queremos ordenar nosso array de outra forma. Por exemplo: se tivéssemos um array de números, poderíamos querer ordená-lo numericamente. É por isso que o método
sort
opcionalmente aceita um argumento, uma função callback que será chamada em cada par de itens de um array, e retornar um valor para indicar se ele deve ser classificado como maior que o outro. Um número negativo significa que o primeiro item deve ser o primeiro, um número positivo indica que o segundo item deve ser o primeiro, e zero fará com que a ordem dos itens não mude.Para ordenar um array numericamente do menor para o maior, podemos passar uma função que retorna
a-b
.var nums = [1, 5, 10, 2, 4];
nums.sort(function(a, b) {
return a-b;
});
// 1,2,4,5,10
OK. Então como nós usamos isso para ordenar um array aleatoriamente? Bem, nós precisamos apenas retornar um número aleatório desta função, um número que seja positivo ou negativo. Abaixo está como podemos fazer isso, em nosso array
selected
criado anteriormente.selected.sort(function() {
return 0.5 - random();
});
E agora temos um array de 10 pares de imagens, ordenado aleatoriamente!
Etapa 4: No laço aninhado, onde criamos as peças, vamos associar uma imagem do array para cada peça.
Nós temos 20 imagens no nosso array
selected
, e estamos iterando 20 vezes para instanciar novas peças em locais na grade. Para atribuir cada imagem a uma peça, podemos usar o método pop
no array. Esse método remove o último elemento do array e o retorna, é o jeito mais fácil de termos certeza que todas as imagens foram atribuídas sem que elas sejam duplicadas.for (var i = 0; i < NUM_COLS; i++) {
for (var j = 0; j < NUM_ROWS; j++) {
tiles.push(new Tile(i * 78 + 10, j * 78 + 40, selected.pop()));
}
}
Agora temos, teoricamente, imagens atribuídas para cada peça, mas não estamos exibindo-as ainda! Vamos adicionar um método para o objeto
Tile
que será responsável por desenhar as faces viradas para cima. Será similar ao de desenhá-las viradas para baixo exceto que a propriedade this.face
é passada para a função image
.Tile.prototype.drawFaceUp = function() {
fill(214, 247, 202);
strokeWeight(2);
rect(this.x, this.y, this.width, this.width, 10);
image(this.face, this.x, this.y, this.width, this.width);
};
Para minimizar a repetição de código, poderíamos colocar um método adicional responsável apenas por desenhar o contorno da peça, e então chamar esse método dentro de cada um dos métodos de desenho. Mas vamos deixar como está por enquanto.
Finalmente, para testar se tudo funciona, podemos mudar nosso for para chamar a função
drawFaceUp
ao invés de drawFaceDown
:for (var i = 0; i < tiles.length; i++) {
tiles[i].drawFaceUp();
}
Juntando tudo, aqui está nosso programa. Experimente reiniciá-lo para ver como as peças mudam a cada jogo.
Quer participar da conversa?
- Asdo dia 06/01/2021 quando estava seguindo passo a passo me deparei com uma duvida, quando cheguei no código de amostra estava tudo diferente. O meu código não funciona com a orientação do passo a passo. 23:34(3 votos)
- poderian colocar em protugues br?(2 votos)
- você e legal que ser meu amigo :}(1 voto)