Programação avançada – capitulo 8 – maquina de estados, exemplo3 (revisitado)
Revisitar o exercício acerca do uso de maquina de estados com a implementação de um jogo de 3×3, onde existem dois jogadores.
A máquina de estados:
é um padrão de programação;
serve para abordar problemas mais complexos;
vai ajudar no fluxo na evolução das aplicações (redirecciona para o estado que vai fazer um determinado processamento);
permite identificar os momentos importantes, na evolução da aplicação/jogo;
Definição dos estados:
está relacionada com a interacção do utilizador;
existem transições entre os estados;
as transições de entrada (métodos) e podem/devem ser condicionados pelo estado do jogo;
as transições de saída (métodos) e podem/devem ser condicionados pelo estado do jogo;
existem então acções do utilizador sobre o jogo, e estas acções resultam em métodos, para haver evolução no jogo;
(identificar as etapas do jogo, que vão ser implementadas, para resolver mini problemas, sub dividir para se simplificar o código que é implementado);
Os estados, são assim:
são representados através de classes;
existe uma classe base, da qual os estados vão derivar (polimorfismo), normalmente do IEstado (através do implements);
e devem ser implementados todos os métodos abstractos que estão definidos no IEstado (usando return para o new “estado”) ou então fica nele próprio (return this).
(2)O IEstado, serve então:
é uma interface, dos estados (e não uma classe);
serve para representar/declarar o que existe, acções possíveis e comuns em todos os estados (através de métodos);
esses métodos são do tipo da interface IEstado;
mas atenção que podem existir algumas acções que podem não ser usadas/implementadas em todos os estados (fica o return this, nas bases), mas têm que ser aqui colocadas, construindo implementações default;
assim sendo os IEstado representam as evoluções (transições) do nosso modelo.
(3)O EstadoAdapter, serve então para:
é uma classe intermédia/genérica que vai implementar os comportamentos default, sem qualquer actualização de estado, para evitar a programação destas acções em estados onde não fazem sentido (normalmente ficam no mesmo estado).
só depois e em cada um dos estados é que os vamos implementar, pois só se justifica no contexto desse estado haver uma evolução na maquina de estados, e é em cada um desses estados que fica implementado
esta classe implementa o IEstado (implements)
implementa todos os métodos abstractos / default (return this), isto é, não existe evolução de estado/fica no mesmo estado.
surge aqui também os testes da evolução, assim estes estados vão ter que ter acesso aos dados do jogo (JogoDados), construtor e get e set
esta classe deve ser public abstract, para não ser instanciada,
e o JogoDados fica protected, evitando fazer uso do método getJogo, e pelas derivadas acedo a esta instância
(4)E agora construir os estados, que são classes derivadas e:
são extends EstadoAdapter
por ser protected a base, esta derivada tem que ter o construtor correspondente (super)
implementar os métodos correspondentes às acções que fazem sentido, são as setas de saída;
os outros não fazem sentido num estado, ficam com o default do EstadoAdapter
os métodos implementados em cada um dos estados fazem então um return new estado, ou this se voltar ao mesmo, sendo que é implementada também a lógica de cada um dos métodos
Surge uma classe
surge então o inicio da máquina de estados, representando numa classe que inicia o primeiro estado por onde arranca (new..), e que vai permitir aceder (get) ou alterar o estado (set)
esta classe vai ter que satisfazer as ordens do utilizador, define os métodos que permitem alterar/mudança de estado (métodos de transição)
Na iteração com o utilizador (texto ou gráfica):
recebe a maquina de estados;
obtém permanente o estado em que se encontra a máquina de estados;
faz uso de instanceof dos estados para invocar a interface que deve surgir ao utilizador.
neste exemplo:
(1)o JogoMaqEstados, gestão/ver a evolução/mudança entre os estados (no diagrama ir de um estado para outro), conhece o estado atual, é a classe de interface entre o IU do utilizador e todas as classes internas (quer aquelas que representam os estados, quer os dados, que representam o jogo (tem assim informação sobre o jogo (JogoDados), e o estado em que estamos (IEstado));
o JogoMaqEstados tem ainda a interface para todos os outros estados, onde actualiza a situação do estado;
os estados, recebem a entidade jogo, como referência;
já a grelha, onde estão as peças, o jogador, este tipo de dados ficam em JogoDados;
JogoDados, representa assim o tabuleiro do jogo, onde se fazem todas as verificações;
Jogador, representada cada um dos jogos;
Peca, as peças do tabuleiro de jogo;
IEstado;
EstadoAdapter
(10) A interface (IU) também precisa de ir buscar coisas ao jogo, mas quem trata disso é o JogoMaqEstados
package me_jogopecasr; import me_jogopecasr.iu.texto.IUtexto; import me_jogopecasr.logica.JogoMaqEstados; public class Me_jogoPecasR { public static void main(String[] args) { IUtexto iuTexto = new IUtexto(new JogoMaqEstados()); iuTexto.corre(); } }
package me_jogopecasr.iu.texto; import java.util.Scanner; import me_jogopecasr.logica.JogoMaqEstados; import me_jogopecasr.logica.estados.AguardaColocacao; import me_jogopecasr.logica.estados.AguardaDevolucao; import me_jogopecasr.logica.estados.AguardaInicio; import me_jogopecasr.logica.estados.IEstado; public class IUtexto { private JogoMaqEstados jogo; private boolean sair = false; public IUtexto(JogoMaqEstados jogo) { this.jogo = jogo; } void iuAguardaInicio() { if ((jogo.getJogador1() != null && jogo.getJogador1().isGanhou())) { System.out.println("\n" + jogo.getJogador1() + "\n" + jogo.grelhaToString()); } else if (jogo.getJogador2() != null && jogo.getJogador2().isGanhou()) { System.out.println("\n" + jogo.getJogador2() + "\n" + jogo.grelhaToString()); } System.out.println("\n=== AGUARDA INICIO ===\n" + (jogo.getJogador1() != null ? "" + jogo.getJogador1() : "") + (jogo.getJogador2() != null ? "" + jogo.getJogador2() : "")); while (true) { System.out.println("\n0 - Sair\n1 - Define nome de jogador\n2 - Comecar jogo"); char c = ' '; Scanner sc = new Scanner(System.in); c = sc.next().charAt(0); if ((c == '0')) { sair = true; return; } if ((c == '1')) { //1 - Define nome de jogador System.out.println("Numero (1 ou 2) e nome do jogador: "); while (!sc.hasNextInt()); int num = sc.nextInt(); System.out.println(" numero: " + num); String nome = sc.next(); jogo.defineNomeJogador(num, nome); System.out.println(" nome: " + nome); return; } if ((c == '2')) { //2 - Comecar jogo System.out.println("Comecar jogo: "); jogo.comecarJogo(); return; } } } void iuAguardaColocacao() { System.out.println("\n=== AGUARDA COLOCACAO === \n" + jogo.getJogador1() + jogo.getJogador2() + "\nJogador activo: " + jogo.getNomeJogadorActivo() + "\n" + jogo.grelhaToString()); System.out.println("\n1 - Jogar : linha coluna\n2 - Abandonar"); char c = ' '; Scanner sc = new Scanner(System.in); c = sc.next().charAt(0); if ((c == '1')) { System.out.print(jogo.getNomeJogadorActivo() + ">"); while (!sc.hasNextInt()); int linha = sc.nextInt(); while (!sc.hasNextInt()); int coluna = sc.nextInt(); jogo.jogar(jogo.getNumJogadorActivo(), linha, coluna); } if ((c == '2')) { jogo.abandonar(jogo.getNumJogadorActivo()); return; } } void iuAguardaDevolucao() { System.out.println("\n=== AGUARDA DEVOLUCAO === \n" + jogo.getJogador1() + jogo.getJogador2() + "\nJogador activo: " + jogo.getNomeJogadorActivo() + "\n" + jogo.grelhaToString()); // System.out.println("\nDevolver : linha coluna\nAbandonar: -1"); System.out.println("\n1 - Devolver : linha coluna\n2 - Abandonar"); char c = ' '; Scanner sc = new Scanner(System.in); c = sc.next().charAt(0); if ((c == '1')) { System.out.print(jogo.getNomeJogadorActivo() + ">"); while (!sc.hasNextInt()); int linha = sc.nextInt(); while (!sc.hasNextInt()); int coluna = sc.nextInt(); jogo.devolver(jogo.getNumJogadorActivo(), linha, coluna); } if ((c == '2')) { jogo.abandonar(jogo.getNumJogadorActivo()); return; } } public void corre() { while (!sair) { IEstado estado = jogo.getEstado(); if (estado instanceof AguardaInicio) { iuAguardaInicio(); } else if (estado instanceof AguardaColocacao) { iuAguardaColocacao(); } else if (estado instanceof AguardaDevolucao) { iuAguardaDevolucao(); } } } }
package me_jogopecasr.logica; public interface Constantes { int DIM = 3; }
package me_jogopecasr.logica; import java.util.ArrayList; import java.util.List; public class Jogador implements Constantes { private JogoDados jogo; private String nome; private List<Peca> mao = new ArrayList<Peca>(); private boolean ganhou; public Jogador(String nome, JogoDados j) { this.nome = nome; this.jogo = j; ganhou = false; } public void setNome(String nome) { this.nome = nome; } public String getNome() { return nome; } public boolean isGanhou() { return ganhou; } public void setGanhou(boolean ganhou) { this.ganhou = ganhou; } public void recebePecas() { mao.clear(); for (int i = 0; i < DIM; i++) { mao.add(new Peca(this)); } ganhou = false; } public void recebePeca(Peca peca) { mao.add(peca); } public boolean temPecas() { return mao.size() > 0; } public boolean jogar(int linha, int coluna) { if (mao.size() == 0) { return false; } Peca peca = mao.get(0); if (jogo.setPeca(peca, linha, coluna)) { // jogou mao.remove(0); if (jogo.ganhou(this)) { ganhou = true; } return true; } return false; } @Override public String toString() { return "Jogador " + nome + " mao=" + mao + (ganhou ? " == GANHOU ==" : "") + "\n"; } public int getNumPecas() { return mao.size(); } }
package me_jogopecasr.logica; import java.util.ArrayList; import java.util.List; public class JogoDados implements Constantes{ private List<Jogador> jogadores = new ArrayList<Jogador>(); private int numJogActivo; private Peca[][] grelha; public JogoDados() { jogadores.add(new Jogador("A", this)); jogadores.add(new Jogador("B", this)); grelha = new Peca[DIM][DIM]; } public Jogador getJogadorActivo() { return jogadores.get(numJogActivo - 1); } public String getNomeJogadorActivo() { return jogadores.get(numJogActivo - 1).getNome(); } public Jogador getJogadorNaoActivo() { if (numJogActivo == 1) { return jogadores.get(1); } else if (numJogActivo == 2) { return jogadores.get(0); } return null; } public int getNumJogadorActivo() { return numJogActivo; } public int getNumJogadorNaoActivo() { return (numJogActivo == 1? 1: 2); } public Jogador getJogador1() { return jogadores.get(0); } public Jogador getJogador2() { return jogadores.get(1); } public Peca getPeca(int linha, int coluna) { if (linha < 0 || linha >= DIM || coluna < 0 || coluna >= DIM) { return null; } if (grelha == null) { return null; } return grelha[linha][coluna]; } public boolean setPeca(Peca peca, int linha, int coluna) { if (linha < 0 || linha >= DIM || coluna < 0 || coluna >= DIM) { return false; } if (grelha[linha][coluna] != null) { return false; } grelha[linha][coluna] = peca; return true; } public boolean retiraPeca(int linha, int coluna) { if (linha < 0 || linha >= DIM || coluna < 0 || coluna >= DIM) { return false; } if (grelha[linha][coluna] == null) { return false; } grelha[linha][coluna] = null; return true; } public void jogaOutro() { numJogActivo = numJogActivo == 1 ? 2 : 1; } public boolean inicializa() { grelha = new Peca[DIM][DIM]; jogadores.get(0).recebePecas(); jogadores.get(1).recebePecas(); numJogActivo = 1; return true; } public boolean setNomeJogador(int num, String nome) { try{ jogadores.get(num-1).setNome(nome); return true; }catch(IndexOutOfBoundsException e){ return false; } } private boolean isEmDiagonalPrincipal(Jogador jogador) { for (int i = 0; i < DIM; i++) { if (grelha[i][i] == null || grelha[i][i].getJogador() != jogador) { return false; } } return true; } private boolean isEmDiagonalSecundaria(Jogador jogador) { for (int i = 0; i < DIM; i++) { if (grelha[i][DIM - 1 - i] == null || grelha[i][DIM - 1 - i].getJogador() != jogador) { return false; } } return true; } private boolean isEmDiagonal(Jogador jogador) { return isEmDiagonalPrincipal(jogador) || isEmDiagonalSecundaria(jogador); } private boolean isEmHorizontal(Jogador jogador, int linha) { for (Peca peca : grelha[linha]) { if (peca == null || peca.getJogador() != jogador) { return false; } } return true; } private boolean isEmVertical(Jogador jogador, int coluna) { for (int i = 0; i < grelha[coluna].length; i++) { Peca peca = grelha[i][coluna]; if (peca == null || peca.getJogador() != jogador) { return false; } } return true; } private boolean isEmHorizontal(Jogador jogador) { for (int i = 0; i < DIM; i++) { if (isEmHorizontal(jogador, i)) { return true; } } return false; } private boolean isEmVertical(Jogador jogador) { for (int i = 0; i < DIM; i++) { if (isEmVertical(jogador, i)) { return true; } } return false; } public boolean ganhou(Jogador jogador) { return isEmHorizontal(jogador) || isEmDiagonal(jogador) || isEmVertical(jogador); } public boolean jogar(int numeroJogador, int linha, int coluna) { if (linha < 0 || linha >= DIM || coluna < 0 || coluna >= DIM) { return false; } if(numJogActivo != numeroJogador){ return false; } Jogador j = getJogadorActivo(); if(!j.jogar(linha, coluna)){ return false; } return true; } public boolean devolver(int numeroJogador, int linha, int coluna) { if (linha < 0 || linha >= DIM || coluna < 0 || coluna >= DIM) { return false; } if(numJogActivo != numeroJogador){ return false; } Peca peca = getPeca(linha, coluna); Jogador j = getJogadorActivo(); if (peca == null || peca.getJogador() != j) { return false; } if(!retiraPeca(linha, coluna)){ return false; } j.recebePeca(peca); return true; } public String grelhaToString() { String s = ""; for (int i = 0; i < DIM; i++) { for (int j = 0; j < DIM; j++) { String casa; if (grelha[i][j] != null) { casa = "" + grelha[i][j].getJogador().getNome().charAt(0); } else { casa = " "; } s += "|\t" + casa + "\t"; } s += "|\n"; } return s; } public boolean acabou() { return ganhou(jogadores.get(0)) || ganhou(jogadores.get(1)); } }
package me_jogopecasr.logica; import me_jogopecasr.logica.estados.AguardaInicio; import me_jogopecasr.logica.estados.IEstado; public class JogoMaqEstados { JogoDados jogo; IEstado estado; public JogoMaqEstados() { //1) this.jogo = new JogoDados(); //7 this.estado = new AguardaInicio(jogo); } public JogoDados getJogo() { return jogo; } public void setJogo(JogoDados jogo) { this.jogo = jogo; } public IEstado getEstado() { return estado; } public void setEstado(IEstado estado) { this.estado = estado; } //8 redirecionar, agulhar, as acções para os estados, o interface para todas as outras classes public void defineNomeJogador(int numeroJogador, String nome) { //8.1 a evolução do estado, retorna o estado seguinte estado = estado.defineNomeJogador(numeroJogador, nome); //ou usar o setEstado } public void comecarJogo() { //no infitivo estado = estado.comecaJogo(); } public void jogar(int numeroJogador, int linha, int coluna) { estado = estado.joga(numeroJogador, linha, coluna); } public void devolver(int numeroJogador, int linha, int coluna) { estado = estado.devolve(numeroJogador, linha, coluna); } public void abandonar(int numeroJogador) { estado = estado.abandona(numeroJogador); } //9 redirencuionar para o modelo de dados public Jogador getJogador1() { return jogo.getJogador1(); } public Jogador getJogador2() { return jogo.getJogador2(); } //11 redirencuionar para o modelo de dados public String getNomeJogadorActivo() { return jogo.getNomeJogadorActivo(); } public String grelhaToString() { return jogo.grelhaToString(); } public int getNumJogadorActivo() { return jogo.getNumJogadorActivo(); } }
package me_jogopecasr.logica; public class Peca { private Jogador jogador; public Peca(Jogador jogador) { this.jogador = jogador; } public Jogador getJogador() { return jogador; } public String toString(){ return "" + jogador.getNome().charAt(0); } }
package me_jogopecasr.logica.estados; import me_jogopecasr.logica.JogoDados; //5 public class AguardaColocacao extends EstadoAdapter{ //6.2.3 ver se ele não colocou no mesmo local int l, c; //5.1 public AguardaColocacao(JogoDados jogo) { super(jogo); //6.2.5 l = c-1; } //6.2.4 public AguardaColocacao(JogoDados jogo, int l, int c) { super(jogo); this.l = l; this.c = c; } //5.2 @Override public IEstado abandona(int numeroJogador) { //poderia ver qual é o jogador e atribuir a vitória ao outro return new AguardaInicio(jogo); } //5.3 @Override public IEstado joga(int numeroJogador, int linha, int coluna) { //6.2.6, testar se é a mesma jogada if(linha == l && coluna == c){ return this; } //5.3.1, vamos realizar o jogar if(!jogo.jogar(numeroJogador, linha, coluna)){ return this; } //5.3.2, ver se ele ganhou if(jogo.getJogadorActivo().isGanhou()){ return new AguardaInicio(jogo); } //5.3.3, mudar de jogador jogo.jogaOutro(); //5.3.4, ver se tem peças if(jogo.getJogadorActivo().isGanhou()){ return new AguardaColocacao(jogo); //ou return this, fica no mesmo estado, e com l e c a -1 } //5.3.5, return new AguardaDevolucao(jogo); } }
package me_jogopecasr.logica.estados; import me_jogopecasr.logica.JogoDados; //6 public class AguardaDevolucao extends EstadoAdapter { public AguardaDevolucao(JogoDados jogo) { super(jogo); } //6.1 @Override public IEstado abandona(int numeroJogador) { return new AguardaInicio(jogo); } //6.2 @Override public IEstado devolve(int numeroJogador, int linha, int coluna) { //6.2.1 adcionar a devolção if(jogo.devolver(numeroJogador, linha, coluna)){ //o devolver já verifica return new AguardaColocacao(jogo, linha, coluna); //6.2.7 } //6.2.2 fica no mesmo estado return this; } }
package me_jogopecasr.logica.estados; import me_jogopecasr.logica.JogoDados; public class AguardaInicio extends EstadoAdapter{ //4) por ser protected a base, esta derivada tem que ter um construtor correspondente public AguardaInicio(JogoDados jogo) { super(jogo); } //4.2) implementar os métodos correspondentes às acções que fazem sentido, são as setas de saída @Override public IEstado defineNomeJogador(int numeroJogador, String nome) { //4.4 jogo.setNomeJogador(numeroJogador, nome); //4.3 return this; } @Override public IEstado comecaJogo() { //4.4 testes para começar um jogo if(jogo.getJogador1()== null || jogo.getJogador2() ==null){ //fico no mesmo estado return this; } //12 jogo.inicializa(); return new AguardaColocacao(jogo); } }
package me_jogopecasr.logica.estados; //3) existem acções que não fazem sent import me_jogopecasr.logica.JogoDados; public abstract class EstadoAdapter implements IEstado{ //3.2) acesso aos jogos de dados //3.3) protected, evitando fazer uso do método getJogo, e pelas derivadas acedo a esta instancia protected JogoDados jogo; public EstadoAdapter(JogoDados jogo) { this.jogo = jogo; } public JogoDados getJogo() { return jogo; } public void setJogo(JogoDados jogo) { this.jogo = jogo; } //3.1) @Override public IEstado defineNomeJogador(int numeroJogador, String nome) { return this; } @Override public IEstado comecaJogo() { return this; } @Override public IEstado joga(int numeroJogador, int linha, int coluna) { return this; } @Override public IEstado devolve(int numeroJogador, int linha, int coluna) { return this; } @Override public IEstado abandona(int numeroJogador) { return this; } }
package me_jogopecasr.logica.estados; //2) definir todos os estados public interface IEstado { IEstado defineNomeJogador(int numeroJogador, String nome); IEstado comecaJogo(); IEstado joga(int numeroJogador, int linha, int coluna); IEstado devolve(int numeroJogador, int linha, int coluna); IEstado abandona(int numeroJogador); }
0 thoughts on “Programação avançada – capitulo 8 – maquina de estados, exemplo3 (revisitado)”