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);
}
Tags : , , ,