K:= 1

Propaganda
Refinamentos sucessivos
Objetivos:
●
Estudar a técnica de refinamentos sucessivos
Estrutura de dados atual
Program Jogo2048;
USES CRT;
CONST
MAX = 4; DIR=1; ESQ=2; CIMA=3; BAIXO=4;
PERCENT = 5; MAXVALORPECA = 4;
TYPE tabuleiro = array [0..MAX+1,0..MAX+1] of integer;
Var
T: tabuleiro;
Direcao, Pontos: integer;
Algoritmo atual
Begin
IniciarJogo (T,Pontos);
MostrarJogoAtual (T,Pontos);
While ExisteMovimento (T) do
Begin
Direcao:= EscolheDirecao;
MovimentarPecas (T,Direcao,Pontos);
InserirNovaPeca (T);
MostrarJogoAtual (T,Pontos);
End;
End.
Detalhamento das subrotinas
●
Falta a implementação do procedimento:
MovimentarPecas (T,Direcao,Pontos)
Detalhamento das subrotinas
Como movimentar as peças do jogo na direção
desejada, segundo as regras?
●
Para simplificar o problema, vamos estudar o
caso de um deslocamento para a direita em um
vetor
●
Depois faremos as modificações necessárias
para adaptar a solução para uma matriz e
também para as outras direções
●
Abre parênteses...
Vamos considerar o caso de um vetor de
1..MAX inteiros que pode ter elementos nulos em
qualquer posição, inclusive em todas
●
Como deslocar todos os valores para a direita
realizando operações de fusão+soma de
elementos conforme as regras do jogo?
●
Movimentar um vetor à direita
Pelas regras do jogo, quando uma peça é
movimentada para a direita, se ela se choca com
outra peça de igual valor, as duas se tornam uma
só, sendo que o valor desta é a soma dos valores
das duas peças originais
●
Porém, uma peça que é resultado de uma
fusão+soma não pode ser fundida+somada outra
vez
●
Movimentar um vetor à direita
Podemos imaginar vários algoritmos, mais ou
menos eficientes. Basicamente eles vão diferir na
eficiência ou na complexidade do código ou
ainda no uso ou não de estruturas auxiliares
●
Movimentar um vetor à direita
Primeira opção:
Passar três vezes pelo vetor: (1) compactar
todos à direita, eliminando os zeros; (2) somar
elementos consecutivos que são iguais; (3) outra
compactação
●
Movimentar um vetor à direita
Segunda opção:
Usar vetor auxiliar para facilitar o processo de
soma e eliminação dos zeros e depois copiar de
volta
●
Movimentar um vetor à direita
Terceira opção:
Tentar passar pelo vetor apenas uma única vez
sem vetor auxiliar
●
Movimentar um vetor à direita
●
Vamos implementar a primeira opção:
Passar três vezes pelo vetor: (1) compactar
todos à direita, eliminando os zeros; (2) somar
elementos consecutivos que são iguais; (3) outra
compactação
Compactar um vetor à direita
●
Primeiro subproblema:
Compactar um vetor de 1..MAX elementos
inteiros movendo todos à direita, eliminando os
zeros. O vetor pode estar cheio e não ter zeros.
Também pode conter apenas zeros. Os
elementos mais a esquerda que sobrarem devem
ficar com valor nulo.
Compactar um vetor à direita
Ideia: K é o maior índice do vetor que pode
apontar para conteúdos nulos. À direita de K,
todos os elementos do vetor são não nulos
garantidamente
●
K
0 ou N 0 ou N 0 ou N 0 ou N 0 ou N
N1
N2
N3
Compactar um vetor à direita
Observar que o conteúdo apontado por K pode
ser nulo ou não, o importante é que à sua direita
não há mais nulos
●
K
0 ou N 0 ou N 0 ou N 0 ou N 0 ou N
N1
N2
N3
Compactar um vetor à direita
Então basta percorrer os índices de K até 1
procurando o primeiro elemento não nulo
●
i
K
0 ou N 0 ou N 0 ou N 0 ou N 0 ou N
N1
N2
N3
Compactar um vetor à direita
●
Primeira situação: i = k e v[i] = v[k] é não nulo
i
K
0 ou N 0 ou N 0 ou N 0 ou N
N
N1
N2
N3
Compactar um vetor à direita
●
Solução: K:= K - 1; i:= i - 1
i
K
0 ou N 0 ou N 0 ou N 0 ou N
N
N1
N2
N3
Compactar um vetor à direita
●
Resultado da operação:
i
K
0 ou N 0 ou N 0 ou N 0 ou N
N
N1
N2
N3
Compactar um vetor à direita
Segunda situação: i < k. Neste caso todos os
elementos de i+1 até K (inclusive) são nulos
●
i
K
0 ou N
N
0
0
0
N1
N2
N3
Compactar um vetor à direita
●
Solução: v[K]:= v[i]; v[i]:= 0;
K:= k – 1; i:= i – 1;
i
K
0 ou N
0
0
0
N
N1
N2
N3
Compactar um vetor à direita
Procedure CompactarDireita (Var V: vetor);
Var i,K: integer;
Begin
(* código no próximo slide *)
End;
Compactar um vetor à direita
K:= MAX;
For i:= MAX downto 1 do
If v[i] <> 0 then
Begin
If v[K] = 0 then
Begin
v[K]:= v[i]; v[i]:= 0;
End;
K:= K - 1
End;
Aglomerar um vetor à direita
●
Segundo subproblema:
Fazer o processo de fusão+soma de um vetor
de 1..MAX elementos inteiros que está
garantidamente compactado à direita, isto é,
podem haver elementos nulos no início, mas a
partir do primeiro não nulo, todos os seguintes
são não nulos até MAX. O vetor pode estar
cheio.
Aglomerar um vetor à direita
Considerando que o vetor já está compactado à
direita, este vetor tem a seguinte propriedade:
existe um índice K tal que a partir dele não
existem elementos nulos à direita
●
K
0
0
0
N1
N2
N3
N4
N5
Aglomerar um vetor à direita
Estamos procurando por índices consecutivos
de igual conteúdo, desde o índice máximo até K,
que por sinal pode ser 1
●
K
0
0
0
N1
i
N1
N
MAX
N2
N2
Aglomerar um vetor à direita
Quando encontrados, basta somar os
conteúdos em um deles e tornar o outro nulo
●
K
0
0
0
N1
i
N1
N
N2
N2
Aglomerar um vetor à direita
●
Por exemplo, v[i] = v[i+1]
K
0
0
0
N1
i
N1
N
N2
N2
Aglomerar um vetor à direita
●
Logo: v[i+1]:= 2*v[i+1]; v[i]:= 0; i:= i – 2;
0
0
0
K
i
N1
N1
N
0
2*N2
Aglomerar um vetor à direita
Observar que se N = N2 não há problema de
soma indevida, pois i andou duas posições
●
0
0
0
K
i
N1
N1
N2
0
2*N2
Aglomerar um vetor à direita
●
Quando v[i] <> v[i+1] basta i:= i - 1
K
0
0
0
N1
i
N1
N
N2
N3
Aglomerar um vetor à direita
●
Quando v[i] <> v[i+1] basta i:= i - 1
0
0
0
K
i
N1
N1
N
N2
N3
Aglomerar um vetor à direita
Assim, dependendo do caso, em uma iteração
ocorre i:= i – 1 ou i:= i – 2.
●
Logo, a implementação deve ser feita com um
While
●
Se alguns zeros forem somados não há
problema
●
Aglomerar um vetor à direita
Procedure AglomerarDireita (Var V: vetor);
Var i: integer;
Begin
(* código no próximo slide *)
End;
Aglomerar um vetor à direita
i:= MAX - 1;
While (i >= 1) AND (v[i] <> 0) do
Begin
If v[i] = v[i+1] then
Begin
v[i+1]:= 2*v[i]; v[i]:= 0; i:= i – 2;
End
Else i:= i – 1;
End;
Fecha parênteses...
Agora podemos voltar à implementação do jogo
2048 adaptando a solução do problema anterior
para o caso de matrizes e também para que os
deslocamentos ocorram nas quatro direções
permitidas
●
Detalhamento das subrotinas
●
Para implementar:
MovimentarPecas (T,Direcao,Pontos)
●
Vamos usar o algoritmo seguinte
Detalhamento das subrotinas
Procedure MovimentarPecas (var T: tabuleiro; Direcao: integer; var Pontos: integer);
Var Moveu: boolean;
Begin
Moveu:= false;
Repeat
Case Direcao of
DIR: MovimentarDireita (T,Pontos,Moveu);
ESQ: MovimentarEsquerda (T,Pontos,Moveu);
CIMA: MovimentarCima (T,Pontos,Moveu);
BAIXO: MovimentarBaixo (T,Pontos,Moveu);
End;
Until Moveu;
End;
Detalhamento das subrotinas
Temos que implementar quatro procedimentos:
●
MovimentarDireita
●
MovimentarEsquerda
●
MovimentarCima
●
MovimentarBaixo
Vejamos o caso da direita, os outros são parecidos
Detalhamento das subrotinas
Procedure MovimentarDireita (var T: tabuleiro; var Pontos:
integer; var Moveu: boolean);
Begin
CompactarDireita (T,Moveu);
AglomerarDireita (T,Pontos,Moveu);
CompactarDireita (T,Moveu);
End;
Detalhamento das subrotinas
●
Como compactar a matriz à direita?
Basta adaptar os algoritmos estudados para o
caso de compactar vetores à direita
●
Detalhamento das subrotinas
Sabemos que se um algoritmo funciona para
um vetor, então ele deve funcionar para uma
linha (ou coluna) específica de uma matriz
●
Detalhamento das subrotinas
Adaptação ao caso de uma linha da matriz:
●
Seja M um valor fixo
●
Trocar as ocorrências de v[x] por T[M,x]
Detalhamento das subrotinas
Adaptação ao caso de uma linha da matriz:
●
Em seguida basta colocar o código em um laço:
For M:= 1 to MAX Do
Detalhamento das subrotinas
A pontuação é atualizada sempre que houver
uma soma, o que ocorre na procedure Aglomerar
●
As duas procedures de compactar e aglomerar
podem provocar movimentos e isto tem que ser
reportado
●
Detalhamento das subrotinas
Procedure CompactarDireita (Var T: tabuleiro; var Moveu:
boolean);
Var i,K,M: integer;
Begin
For M:= 1 to MAX do
Begin
(* código no próximo slide *)
End;
End;
Detalhamento das subrotinas
K:= MAX;
For i:= MAX downto 1 do
If T[M,i] <> 0 then
Begin
If T[M,K] = 0 then
Begin
T[M,K]:= T[M,i]; T[M,i]:= 0; Moveu:= true;
End;
K:= K – 1;
End;
Detalhamento das subrotinas
Procedure AglomerarDireita (Var T: tabuleiro; Var Pontos:
integer; var Moveu: boolean);
Var i,M: integer;
Begin
For M:= 1 to MAX do
Begin
(* código no próximo slide *)
End;
End;
Detalhamento das subrotinas
i:= MAX - 1;
While (i >= 1) AND (T[M,i] <> 0) do
Begin
If T[M,i] = T[M,i+1] then
Begin
T[M,i+1]:= 2*T[M,i+1]; T[M,i]:= 0; i:= i – 2;
Pontos:= Pontos + T[M,i+1]; Moveu:= true;
End
Else i:= i – 1;
End;
Detalhamento das subrotinas
Como movimentar para baixo?
A diferença de movimentar para a direita e
movimentar para baixo é simples:
●
basta inverter linhas e colunas!
Detalhamento das subrotinas
Como movimentar para baixo?
●
Trocar as ocorrência de T[M,X] por T[X,M]
Detalhamento das subrotinas
Como movimentar para a esquerda?
Para movimentar para a direita, o algoritmo
percorreu o vetor do final para o início
●
Logo, para movimentar para a esquerda, basta
inverter a direção, isto é, percorrer o vetor do
início para o final
●
Detalhamento das subrotinas
Procedure CompactarEsquerda (Var T: tabuleiro; var Moveu:
boolean);
Var i,K,M: integer;
Begin
For M:= 1 to MAX do
Begin
(* código no próximo slide *)
End;
End;
Detalhamento das subrotinas
K:= 1;
// antes era K:= MAX;
For i:= 1 to MAX do
// antes era For i:= MAX downto 1
If T[M,i] <> 0 then
Begin
If T[M,K] = 0 then
Begin
T[M,K]:= T[M,i]; T[M,i]:= 0; Moveu:= true;
End;
K:= K + 1;
End;
// antes era K:= K – 1;
Detalhamento das subrotinas
Procedure AglomerarEsquerda (Var T: tabuleiro; Var Pontos:
integer; var Moveu: boolean);
Var i,M: integer;
Begin
For M:= 1 to MAX do
Begin
(* código no próximo slide *)
End;
End;
Detalhamento das subrotinas
i:= 2;
While I <= MAX do
Begin
If T[M,i] = T[M,i-1] then
Begin
T[M,i-1]:= 2*T[M,i]; T[M,i]:= 0; i:= i + 2;
Pontos:= Pontos + T[M,i-1]; Moveu:= true;
End
Else i:= i + 1;
End;
Detalhamento das subrotinas
Como movimentar para cima?
Para movimentar para cima, basta inverter
linhas e colunas no algoritmo de movimentar
para a esquerda!
●
Detalhamento das subrotinas
●
Trocar as ocorrências de T[M,X] por T[X,M]
Detalhamento das subrotinas
Em resumo, temos quatro procedimentos
diferentes, mas parecidos, para se movimentar
elementos nas quatro direções
●
Finalmente...
●
Terminamos a implementação do jogo 2048!
●
Vejamos os jogo funcionando...
Considerações
Do ponto de vista de um jogo em um tabuleiro
4x4 nossa implementação é suficiente
●
Mas pensando em um tabuleiro maior podemos
fazer muitas otimizações
●
Considerações
Algumas destas otimizações estão sugeridas
nos slides anteriores desta aula e nos slides da
aula anterior
●
No restante da aula, e na próxima, vamos
trabalhar em algumas delas, as outras ficam
como exercício
●
Problema 1
●
Melhorar as procedures de deslocamento
Implementar uma única procedure para
movimentos na horizontal (direita e esquerda)
●
Problema 1
Vejamos novamente o algoritmo para
movimentar à esquerda
●
Problema 1
Procedure CompactarEsquerda (Var T: tabuleiro; var Moveu:
boolean);
Var i,K,M: integer;
Begin
For M:= 1 to MAX do
Begin
(* código no próximo slide *)
End;
End;
Problema 1
K:= 1;
// antes era K:= MAX;
For i:= 1 to MAX do
// antes era For i:= MAX downto 1
If T[M,i] <> 0 then
Begin
If T[M,K] = 0 then
Begin
T[M,K]:= T[M,i]; T[M,i]:= 0; Moveu:= true;
End;
K:= K + 1;
End;
// antes era K:= K – 1;
Problema 1
Podemos usar um parâmetro booleano
indicando se o movimento é para a direita ou
esquerda
●
Este parâmetro permite definir os limites dos
laços
●
Problema 1
Procedure CompactarHorizontal (Var T: tabuleiro; var Moveu:
boolean; direita: boolean);
Var i,K,M,sinal: integer;
Begin
For M:= 1 to MAX do
Begin
(* código no próximo slide *)
End;
End;
Problema 1
(* definindo os limites de início do laço e soma/subtração *)
If direita then
Begin
K:= MAX; i:= MAX; sinal:= 1;
End
Else
Begin
K:= 1; i:= 1; sinal:= -1
End;
Problema 1
While (direita AND (i >= 1)) OR (not direita AND (i <= MAX)) Do
Begin
If T[M,i] <> 0 then
Begin
If T[M,K] = 0 then
Begin
T[M,K]:= T[M,i]; T[M,i]:= 0; Moveu:= true;
End;
K:= K – 1*sinal;
End;
i:= i – 1*sinal;
End;
Problema 1
Procedure AglomerarHorizontal
(Var T: tabuleiro; Var Pontos: integer; direita: boolean);
Var i,M,sinal: integer;
Begin
For M:= 1 to MAX do
Begin
(* código no próximo slide *)
End;
End;
Problema 1
If direita then
Begin
i:= MAX – 1;
sinal:= 1;
End
Else
Begin
i:= 2;
sinal:= -1;
End;
Problema 1
While (direita AND (I >= 1)) OR (not direita AND (i <= MAX)) do
Begin
If T[M,i] = T[M,i+1*sinal] then
Begin
T[M,i+1*sinal]:= 2*T[M,i+1*sinal]; T[M,i]:= 0;
Pontos:= Pontos + T[M,i+1*sinal];
i:= i - 2*sinal;
End
Else i:= i - 1*sinal;
End;
Problema 2
De maneira análoga, pode-se unificar os dois
procedimentos que fazem movimentos na vertical
●
●
Fica como exercício
Detalhamento das subrotinas
Procedure MovimentarPecas (var T: tabuleiro; Direcao: integer; var Pontos: integer);
Var Moveu: boolean;
Begin
Moveu:= false;
Repeat
Case Direcao of
DIR: MovimentarHorizontal (T,Pontos,Moveu,true);
ESQ: MovimentarHorizontal (T,Pontos,Moveu,false);
CIMA: MovimentarVertical (T,Pontos,Moveu,false);
BAIXO: MovimentarVertical (T,Pontos,Moveu,true);
End;
Until Moveu;
End;
Detalhamento das subrotinas
Procedure MovimentarHorizontal (var T: tabuleiro; var Pontos:
integer; var Moveu: boolean; direita: boolean);
Begin
CompactarHorizontal (T,Moveu,direita);
AglomerarHorizontal (T,Pontos,direita);
CompactarHorizontal (T,Moveu,direita);
End;
Problema 3
Na verdade, é possível fazer um único
procedimentos que faz movimentos em direção,
bastando passar dois parâmetros booleanos
●
Um para indicar movimentos esquerda/direita e
outro para movimentos na horizontal/vertical
●
●
Fica também como exercício
Download