English Русский Español
preview
Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI (Parte 3): Criando jogadas automáticas e Scripts de Teste em MQL5

Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI (Parte 3): Criando jogadas automáticas e Scripts de Teste em MQL5

MetaTrader 5Exemplos | 13 dezembro 2023, 08:53
447 0
Jonathan Pereira
Jonathan Pereira

Introdução

Neste terceiro artigo da série, mergulhamos mais fundo no universo das APIs REST e sua aplicação prática em sistemas. Após uma exploração detalhada do desenvolvimento de funções MQL5 e sua integração com um jogo da velha em Python via FastAPI, este artigo se propõe a avançar significativamente. Nosso foco agora se volta para a implementação de jogadas automáticas no jogo da velha, aumentando seu nível de desafio e interatividade. Além disso, dedicaremos atenção especial ao desenvolvimento de scripts de teste em MQL5, garantindo a robustez e eficácia de nosso sistema integrado.

Reconhecendo a complexidade deste projeto, percebo a necessidade de fornecer instruções claras e acessíveis para instalação e execução. Lamento por não ter incluído essas informações nas partes anteriores e agradeço a compreensão de todos. Com isso em mente, apresento um guia passo a passo, incluindo orientações para usuários do Windows que podem enfrentar restrições na execução de scripts.

Guia de Instalação e Execução

Pré-requisitos:

  • Python 3.6 ou superior.
  • MetaTrader 5 instalado em seu computador.
  • No Windows, verifique se a execução de scripts está habilitada. Se necessário, execute Set-ExecutionPolicy RemoteSignedno PowerShell como administrador para permitir a execução de scripts.

Passos para Instalação e Execução:

  1. Baixe e Extraia o Projeto: Após baixar o projeto do artigo, extraia os arquivos em uma pasta de sua escolha.
  2. Copie para a Pasta Experts: Mova a pasta extraída para dentro da pasta 'Experts', que está na instalação do seu terminal MetaTrader.
  3. Abra a Pasta no Terminal:
    • No Windows, você pode fazer isso buscando pelo 'Prompt de Comando' ou 'PowerShell' no menu iniciar, abrindo o programa e usando o comando cd caminho_da_pastapara navegar até a pasta do projeto.
    • No MacOS ou Linux, abra o 'Terminal' e use o comando cd caminho_da_pasta.
  4. Crie um Ambiente Virtual: Com a pasta do projeto aberta no terminal, execute python -m venv envpara criar um ambiente virtual.
  5. Ative o Ambiente Virtual: No Windows, execute env\Scripts\activate. No MacOS ou Linux, execute source env/bin/activate.
  6. Instale as Dependências: Com o ambiente virtual ativado, execute pip install -r requirements.txt.

Executando o Projeto:

  • Para Rodar a API do Jogo da Velha: Com o terminal ainda aberto na pasta do projeto, execute python AppTicTacToe.py.
  • Para Rodar no MetaTrader: Abra o MetaEditor, vá em Ferramentas > Opções > Compiladores. Cole o caminho da pasta 'scripts' do ambiente virtual em 'Local de Compilação Externo', clique em compilar e ou arraste o script a um gráfico.
  • Acessando o Swagger UI: Acesse localhost:8000/docs no navegador para interagir com a API através do Swagger UI.

Objetivo: A meta principal deste artigo é dupla: primeiramente, aprimorar o jogo da velha Python para que ele execute jogadas de forma autônoma, utilizando algoritmos de decisão inteligentes. Em segundo lugar, desenvolver e implementar testes unitários em MQL5 que validem e assegurem a confiabilidade das interações entre o código MQL5 e a API REST.

Este Artigo está dividido em 3 partes:

  1. Desenvolvimento de Jogadas Automáticas no Jogo da Velha: Detalhamento do processo de modificação do jogo da velha para incluir a lógica de jogadas automáticas, abordando as técnicas de programação utilizadas e os desafios encontrados.
  2. Criação de Scripts de Teste em MQL5: Exploração do processo de desenvolvimento de testes unitários em MQL5, focando em como eles podem validar a interação entre MQL5 e a API REST.
  3. Integração e Testes Práticos: Demonstração prática da integração das melhorias implementadas, incluindo a execução de testes e a avaliação dos resultados.
A ideia deste artigo se concentra na ideia de que, ao implementar jogadas automáticas no jogo da velha Python, estabelecemos uma base sólida para testes mais abrangentes e eficientes. Com o jogo respondendo de maneira autônoma, torna-se possível realizar testes rigorosos através de scripts MQL5, simulando interações reais com a API REST. Essa abordagem não só assegura que o jogo funcione conforme esperado em diferentes cenários, mas também valida a robustez da comunicação entre o código MQL5 e a interface da API.

Dessa forma, o desenvolvimento de um agente em MQL5 que interaja com o jogo da velha torna-se o próximo passo lógico. Este agente seria capaz de simular um usuário real, efetuando jogadas e respondendo às ações do jogo, o que proporcionaria um ambiente de teste ainda mais próximo da realidade. Essa estratégia permite não apenas verificar a funcionalidade do jogo e da API, mas também explorar e aprimorar os algoritmos de decisão usados nas jogadas automáticas, garantindo uma experiência de jogo mais desafiadora e envolvente.

A combinação de jogadas automáticas no jogo da velha e testes unitários em MQL5 cria um ciclo de desenvolvimento robusto, onde cada melhoria no jogo é validada e aperfeiçoada através de testes rigorosos. Este processo contínuo de desenvolvimento e teste assegura a criação de um sistema integrado confiável e eficaz, capaz de proporcionar não só uma experiência de jogo aprimorada, mas também insights valiosos para futuras integrações e desenvolvimentos em sistemas que precisem usar integração.


 


Desenvolvimento de Jogadas Automáticas

Nessa sessão, o foco inicial será compreender a estrutura e lógica do código existente do jogo. Dado que o jogo não possui uma interface gráfica e que a implementação das jogadas automáticas não utilizará algoritmos de decisão complexos, nosso objetivo será simplificar e tornar eficiente este processo.

O primeiro passo envolve analisar como o jogo processa as jogadas manuais, observando a lógica que governa os turnos e como o jogo determina os estados de vitória, derrota ou empate. Essa compreensão será essencial para integrar a funcionalidade de jogadas automáticas sem perturbar a mecânica de jogo existente.

Em seguida, a implementação das jogadas automáticas será feita de maneira mais ingênua. Em vez de um algoritmo sofisticado, podemos optar por uma abordagem mais direta, como selecionar aleatoriamente uma posição livre no tabuleiro para a jogada automática. Embora simples, esta abordagem deve ser eficaz o suficiente para simular um oponente e adicionar dinamismo ao jogo.

Ao olhar com atenção para o código do jogo da velha, vamos entender bem como ele funciona. Assim, podemos planejar como adicionar jogadas automáticas sem complicar o jogo. O objetivo é manter tudo fácil de usar, seguindo o estilo simples que o jogo já tem.

Inicialização do Jogo:

def __init__(self):
    self.board = [[' ' for _ in range(3)] for _ in range(3)]
    self.player_turn = True

Nesta seção, o jogo é inicializado com um tabuleiro vazio ( self.board), e a variável self.player_turn indica a vez do jogador. Este design minimalista proporciona um ponto de partida ideal para integrar a lógica de jogadas automáticas, sem complicar desnecessariamente o código existente.

Exibição do Tabuleiro:

def print_board(self):
    for row in self.board:
        print("|".join(row))
        print("-" * 5)
O método print_board gerencia a exibição do tabuleiro. A forma como o estado do jogo é apresentado é crucial para manter a usabilidade e compreensão do jogo, especialmente após a implementação das jogadas automáticas.


Verificação de Vencedor:

 def check_winner(self):
     for i in range(3):
         if self.board[i][0] == self.board[i][1] == self.board[i][2] != ' ':
             return self.board[i][0]
         if self.board[0][i] == self.board[1][i] == self.board[2][i] != ' ':
             return self.board[0][i]
     if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ':
         return self.board[0][0]
     if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ':
         return self.board[0][2]
     return None

Este método é fundamental para determinar o vencedor após cada jogada. Sua lógica será essencial para verificar o término do jogo, tanto para jogadas manuais quanto automáticas.

Realização de Jogadas:

 def make_move(self, row, col):
     if self.board[row][col] == ' ':
         if self.player_turn:
             self.board[row][col] = 'X'
         else:
             self.board[row][col] = 'O'
         self.player_turn = not self.player_turn
     else:
         print("Jogada inválida. Tente novamente.")

O método make_move é responsável pelas jogadas dos jogadores. A implementação das jogadas automáticas exigirá modificações neste método para alternar entre jogadas manuais e automáticas de forma eficiente.


Implementação de Jogadas Automáticas:

A implementação das jogadas automáticas no jogo da velha traz um novo desafio e diversão. Vamos colocar isso em prática com um novo método chamado machine_move. Primeiro, ele vai tentar achar um jeito de ganhar ou de impedir que o outro jogador ganhe. Se não tiver jeito, ele vai escolher um espaço vazio no tabuleiro de forma aleatória.

def machine_move(self):
    for i in range(3):
        for j in range(3):
            if self.board[i][j] == ' ':
                # Primeiro, tenta encontrar uma jogada vencedora para 'O'
                self.board[i][j] = 'O'
                if self.check_winner() == 'O':
                    return (i, j)  # Retorna a posição para a vitória
                self.board[i][j] = ' '

                # Em seguida, tenta bloquear uma jogada vencedora para 'X'
                self.board[i][j] = 'X'
                if self.check_winner() == 'X':
                    self.board[i][j] = 'O'  # Bloqueia a vitória do jogador
                    return (i, j)
                self.board[i][j] = ' '

    # Se não houver jogadas vencedoras, escolhe aleatoriamente uma posição livre
    available_moves = self.available_moves()
    if available_moves:
        move = random.choice(available_moves)
        self.board[move["row"]][move["col"]] = 'O'
        return (move["row"], move["col"])



Também teremos o método available_moves. Esse método é importante porque vai olhar o tabuleiro e mostrar todos os espaços que ainda estão livres. Assim, a gente garante que o computador só faça jogadas em lugares vazios.

def available_moves(self):
    moves = []
    for i in range(3):
        for j in range(3):
            if self.board[i][j] == ' ':
                moves.append({"row": i, "col": j})
    return moves


Com essas mudanças, o jogo da velha fica mais interessante. Ele continua simples como sempre, mas agora tem um toque a mais de surpresa e estratégia com as jogadas automáticas. Isso deixa o jogo ainda mais legal de jogar.

Integrar jogadas automáticas no jogo da velha Python adiciona uma camada de complexidade e interatividade, preparando o terreno para futuras implementações mais avançadas, como a introdução de um agente MQL5. A ideia é que, ao finalizar esta fase de desenvolvimento, o jogo não só ofereça um desafio maior para o jogador humano, mas também esteja pronto para interações mais sofisticadas com agentes externos.

A lógica de jogadas automáticas, embora inicialmente simples, estabelece uma base sólida para o jogo. Com a capacidade de realizar movimentos autônomos e responder de forma dinâmica ao contexto do jogo, o sistema se torna apto a simular um oponente realista. Isso é essencial para testar a eficiência e robustez do jogo em cenários variados, especialmente quando pensamos na futura implementação de um agente MQL5.

Ao planejar a introdução desse agente, o jogo da velha com jogadas automáticas já estará equipado para simular um ambiente de jogo real. O agente poderá interagir com o jogo, efetuando jogadas e reagindo às ações automáticas, criando um cenário mais próximo do que seria um jogo entre dois jogadores humanos. Essa interação permitirá avaliar não só a funcionalidade do jogo e da API, mas também a eficácia dos algoritmos de decisão empregados, abrindo caminho para melhorias e ajustes.

Além disso, a presença de um agente MQL5 proporcionará um ambiente de teste mais avançado e realista. Com isso, será possível simular situações de jogo diversas, verificando a resposta do sistema sob diferentes condições e garantindo a estabilidade e confiabilidade do jogo.

Abaixo o código completo do jogo:

class TicTacToe:

    def __init__(self):
        self.board = [[' ' for _ in range(3)] for _ in range(3)]
        self.player_turn = True

    def print_board(self):
        for row in self.board:
            print("|".join(row))
            print("-" * 5)
        print(f"Player {self.player_turn}'s turn")

    def check_winner(self):
        for i in range(3):
            if self.board[i][0] == self.board[i][1] == self.board[i][2] != ' ':
                return self.board[i][0]
            if self.board[0][i] == self.board[1][i] == self.board[2][i] != ' ':
                return self.board[0][i]
        if self.board[0][0] == self.board[1][1] == self.board[2][2] != ' ':
            return self.board[0][0]
        if self.board[0][2] == self.board[1][1] == self.board[2][0] != ' ':
            return self.board[0][2]
        return None

    def machine_move(self):
        for i in range(3):
            for j in range(3):
                if self.board[i][j] == ' ':
                    self.board[i][j] = 'O'
                    if self.check_winner() == 'O':
                        return (i, j)
                    self.board[i][j] = ' '
                    self.board[i][j] = 'X'
                    if self.check_winner() == 'X':
                        self.board[i][j] = 'O'
                        return (i, j)
                    self.board[i][j] = ' '

        for i in range(3):
            for j in range(3):
                if self.board[i][j] == ' ':
                    self.board[i][j] = 'O'
                    return (i, j)


    def available_moves(self):
        moves = []
        for i in range(3):
            for j in range(3):
                if self.board[i][j] == ' ':
                    moves.append({"row": i, "col": j})
        return moves

Agora que implementamos as jogadas automáticas no jogo da velha Python, o desafio é integrar essa funcionalidade à nossa API FastAPI. Esta etapa é fundamental para assegurar uma interação eficaz e sem falhas entre o jogo e o backend, preparando o terreno para futuras integrações, como a introdução de um agente MQL5.

Para que a API suporte as jogadas automáticas, precisamos fazer algumas adaptações importantes no código. Vamos detalhar os passos necessários para esta integração, mantendo o foco na simplicidade e eficiência.


Passos para Adaptar a API FastAPI às Jogadas Automáticas
  1. Gerenciamento Aperfeiçoado de Turnos: A API precisa identificar corretamente de quem é a vez de jogar - do jogador ou da máquina. Após cada jogada do jogador, a API deve verificar se é a vez da máquina e, em caso afirmativo, acionar a lógica de jogadas automáticas.

  2. Integração com a Lógica do  machine_move: A função  machine_move  no código do jogo da velha é essencial para as jogadas automáticas. Portanto, após cada jogada do jogador, a API deve invocar este método para determinar a resposta da máquina.

  3. Atualização Consistente do Estado do Jogo: Após cada jogada, seja do jogador ou da máquina, a API deve atualizar e refletir com precisão o estado do tabuleiro. Isso garante que o jogador sempre receba informações atualizadas e corretas sobre o jogo.

  4. Manuseio de Resultados de Jogo: A API deve ser capaz de identificar o fim do jogo, seja por uma vitória ou empate, e comunicar isso adequadamente. É vital que a API forneça informações claras sobre o vencedor do jogo ou declare um empate quando nenhuma jogada vencedora for possível.

  5. Respostas Claras e Informativas: A API deve responder com todos os detalhes necessários, como o estado atual do tabuleiro, as jogadas realizadas pela máquina, e o resultado do jogo, se houver. Isso assegura uma experiência de usuário fluida e informativa.


Exemplo de Implementação na API

Vamos modificar a função  play  na API para incorporar essas mudanças:

@app.post("/play/{game_id}/")
def play(game_id: int, move: PlayerMove):
    game = games.get(game_id)
    if not game:
        raise HTTPException(status_code=404, detail="Game not found")

    board = game.board
    if board[move.row][move.col] == ' ':
        board[move.row][move.col] = 'X'
    else:
        raise HTTPException(status_code=400, detail="Invalid move")

    player_move = {"row": move.row, "col": move.col, "symbol": 'X'}

    winner = game.check_winner()
    machine_move_result = None

    if not winner:
        game.player_turn = not game.player_turn
        if not game.player_turn:
            move_result = game.machine_move()
            if move_result:
                row, col = move_result
                machine_move_result = {"row": row, "col": col, "symbol": 'O'}
                winner = game.check_winner()
            game.player_turn = not game.player_turn

    return {
        "board": board,
        "player_move": player_move,
        "machine_move": machine_move_result,
        "winner": winner,
        "available_moves": game.available_moves()
    }


A implementação da funcionalidade de jogadas automáticas no jogo da velha Python, juntamente com a adaptação da API FastAPI para suportar essa funcionalidade, é importante para criar um sistema de jogo completo e interativo. Agora que a API está configurada para gerenciar jogadas automáticas, os jogadores podem interagir com o jogo de forma mais dinâmica. Quando um jogador faz uma jogada, a API verifica se é a vez da máquina jogar e, se for o caso, aciona a lógica de jogadas automáticas. Isso cria um fluxo de jogo contínuo e interativo, onde o jogador humano compete contra uma IA para vencer.

Além disso, a API fornece informações detalhadas sobre o estado atual do jogo, incluindo o tabuleiro atualizado, as jogadas realizadas pela máquina e o resultado do jogo, se houver um vencedor ou um empate. Isso torna a experiência do jogador mais envolvente e informativa.


Criação de Scripts de Teste em MQL5

No artigo anterior, aprendemos como criar e gerenciar requisições HTTP em MQL5. Agora, estamos aplicando esse conhecimento para desenvolver testes unitários robustos. Cada função de teste é cuidadosamente projetada para simular cenários reais de interação entre o código MQL5 e a API REST, garantindo que todos os aspectos da comunicação sejam testados e validados.

Os testes abrangem desde a inicialização do jogo até a execução de jogadas válidas e inválidas, além de verificar condições de vitória. Essa abordagem garante que o sistema como um todo seja confiável e estável, proporcionando uma base sólida para futuras expansões e integrações.

Ao final deste tópico, o leitor terá uma compreensão clara de como os testes unitários em MQL5 são estruturados e implementados, e como eles são essenciais para o desenvolvimento do sistema.


Estrutura dos Testes


O código de teste em MQL5 é organizado em três arquivos principais:

  1. Tests.mqh: Contém as funções de teste.
  2. Request.mqh: Gerencia as requisições HTTP.
  3. Tests.mq5: Script principal que executa os testes.


Tests.mqh

  • Assert(): Esta função é usada para verificar se uma condição específica é verdadeira. Ela é fundamental para validar os resultados esperados dos testes.

    Código e Explicação:

    void Assert(bool condition, const string message) {
      if(!condition) {
        Print("Test error: ", message);
      }
    }

    Se a condição passada para Assert for falsa, ela imprime uma mensagem de erro. Isso ajuda a identificar rapidamente falhas nos testes.

    • TestGameInitialization(): Testa a inicialização de um novo jogo, verificando se a API responde corretamente.
    Código e Explicação:
    void TestGameInitialization() {
      string url = "http://localhost:8000/start-game/";
      string response;
      int result = Request("GET", response, url);
      Assert(result == 200, "Game initialization failed");
      Assert(StringLen(response) > 0, "game_id missing in game initialization response");
    }Esta função faz uma requisição GET para iniciar um jogo e verifica se o código de resposta é 200 (OK) e se um 
    game_id  é retornado na resposta.

      Esta função faz uma requisição GET para iniciar um jogo e verifica se o código de resposta é 200 (OK) e se um game_id é retornado na resposta.


      • TestPlayerMove(): Verifica a funcionalidade de realizar uma jogada válida pelo jogador.

        Código e Explicação:

        // Test function to check player's move
        void TestPlayerMove()
          {
           string url = "http://localhost:8000/start-game/";
           string response;
           int result = -1;
           int game_id = -1;
        
           Request("GET", response, url);
        
           js.Deserialize(response);
           game_id = js["game_id"].ToStr();
        
        // Make a valid player move
           url = StringFormat("http://localhost:8000/play/%d/", game_id);
           string payload = "{\"row\": 0, \"col\": 0}";
           result = Request("POST", response, url, payload);
        
        // Check if the HTTP response code is 200 (OK)
           Assert(result == 200, "Player move failed");
        
        // Check if the response contains information about the player's move
        // (you can adjust this based on the actual response structure)
           Assert(StringFind(response, "player_move") != -1, "Player move response incomplete");
          }

        Após iniciar um jogo, esta função faz uma jogada válida e verifica se a API processa a jogada corretamente, retornando um código 200 e a informação da jogada no corpo da resposta.


        • TestInvalidPlayerMove(): Testa a resposta da API a uma jogada inválida.

          Código e Explicação:

          // Test function to check an invalid player move
          void TestInvalidPlayerMove()
            {
             string url = "http://localhost:8000/start-game/";
             string response;
             int result = -1;
             int game_id = -1;
          
             Request("GET", response, url);
          
             js.Deserialize(response);
             game_id = js["game_id"].ToStr();
          
          // Make an invalid player move (e.g., on an occupied position)
             url = StringFormat("http://localhost:8000/play/%d/", game_id);
             string payload = "{\"row\": 0, \"col\": 0}";
             Request("POST", response, url, payload);
          
          //repeat
             payload = "{\"row\": 0, \"col\": 0}";
             result = Request("POST", response, url, payload);
          
          // Check if the HTTP response code is 400 (Bad Request)
             Assert(result == 400, "Invalid player move not handled correctly");
            }
            Esta função tenta fazer uma jogada inválida (por exemplo, jogar em uma posição já ocupada) e verifica se a API retorna um código de erro 400 (Bad Request).

            • TestPlayerWin(): Simula uma sequência de jogadas que resulta na vitória de um jogador.

              Código e Explicação:
              // Test function to check player's victory
              void TestPlayerWin()
                {
                 string url = "http://localhost:8000/start-game/";
                 string response;
                 int result = -1;
                 int game_id = -1;
              
                 Request("GET", response, url);
              
                 js.Deserialize(response);
                 game_id = js["game_id"].ToStr();
              
              // Make moves for player X to win
                 url = StringFormat("http://localhost:8000/play/%d/", game_id);
              
                 string payload = "{\"row\": 0, \"col\": 0}";
                 result = Request("POST", response, url, payload);
                 Assert(result == 200, "Player X move 1 failed");
              
                 payload = "{\"row\": 0, \"col\": 2}";
                 result = Request("POST", response, url, payload);
                 Assert(result == 200, "Player X move 2 failed");
              
                 payload = "{\"row\": 2, \"col\": 2}";
                 result = Request("POST", response, url, payload);
                 Assert(result == 200, "Player X move 3 failed");
              
                 payload = "{\"row\": 1, \"col\": 2}";
                 result = Request("POST", response, url, payload);
                 Assert(result == 200, "Player X move 4 failed");
              
              // Check if the response contains information about the winner
                 js.Deserialize(response); // Deserialize the updated game state
              
              // Check if the HTTP response code is 200 (OK) after move 5
                 Assert(result == 200 && js["winner"].ToStr() == "X", "Player X victory failed");
              
                }

                Esta função executa uma série de jogadas que levariam à vitória de um jogador e verifica se a API reconhece corretamente a condição de vitória.

                • RunTests(): Função agregadora que executa todos os testes definidos.
                Código e Explicação:
                  // Function to run all tests
                  void RunTests()
                    {
                     TestGameInitialization();
                     TestPlayerMove();
                     TestInvalidPlayerMove();
                     TestPlayerWin();
                    }

                  Esta função simplesmente chama todas as funções de teste definidas anteriormente, facilitando a execução de todos os testes de uma só vez.

                  Anteriormente, construímos a biblioteca Requests para facilitar a comunicação entre o código MQL5 e APIs REST. Neste artigo, continuaremos a utilizar essa biblioteca, mas com algumas melhorias significativas implementadas para aprimorar sua funcionalidade e eficiência.


                  Mudanças Implementadas na Biblioteca Requests

                  A nova implementação da biblioteca Requests traz melhorias focadas principalmente na flexibilidade e no diagnóstico de erros. Vamos explorar as principais mudanças:

                  1. Inclusão do Parâmetro debug :

                    • Antiga Implementação: Nas versões anteriores, a biblioteca não oferecia uma opção direta para depuração. Qualquer saída de depuração precisava ser implementada manualmente.
                    • Nova Implementação: Agora, as funções SendGetRequest e SendPostRequest incluem um parâmetro debug . Quando ativado, este parâmetro permite a impressão de saídas de depuração, facilitando o rastreamento e a resolução de problemas.

                  2. Tratamento de Erros Aprimorado:

                    • Antiga Implementação: O tratamento de erros era mais básico, limitando-se a retornar o código de erro ou imprimir diretamente no console.
                    • Nova Implementação: A nova versão oferece um tratamento de erros mais sofisticado, permitindo um melhor diagnóstico de problemas de comunicação HTTP.


                  Por Que as Mudanças Foram Feitas? As alterações na biblioteca Requests foram motivadas pela necessidade de:

                  • Melhorar a Depuração: A capacidade de ativar ou desativar a depuração facilita o processo de desenvolvimento e manutenção, permitindo aos desenvolvedores ver rapidamente as respostas da API e identificar problemas.
                  • Aprimorar o Tratamento de Erros: Um tratamento de erros mais robusto é crucial para sistemas confiáveis, especialmente ao lidar com comunicações de rede onde várias questões podem surgir.


                  Impacto das Mudanças

                  Essas melhorias tornam a biblioteca Requests mais versátil e robusta. Com a opção de depuração, os desenvolvedores podem ter insights mais rápidos durante o desenvolvimento e teste de suas aplicações. Além disso, um tratamento de erros mais eficiente ajuda a identificar e resolver problemas de comunicação com a API, garantindo uma integração mais suave e confiável.


                  Mudança 1: Inclusão do Parâmetro debug

                  Antiga Implementação de SendGetRequeste SendPostRequest:

                  // Exemplo da antiga implementação de SendGetRequest
                  int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000) {
                     // ... código ...
                     out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
                     return (0);
                  }
                  

                  Nova Implementação com debug :

                  // Exemplo da nova implementação de SendGetRequest
                  int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false) {
                     // ... código ...
                     out = CharArrayToString(result, start_index, WHOLE_ARRAY, CP_UTF8);
                     if(debug) {
                         Print(out);
                     }
                     return res;
                  }
                  

                  A nova implementação inclui um parâmetro debug. Quando true , imprime a saída, facilitando a depuração.


                  Mudança 2: Tratamento de Erros Aprimorado

                  Antiga Implementação de SendGetRequest e SendPostRequest :

                  // Exemplo da antiga implementação de SendGetRequest
                  int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000) {
                     // ... código ...
                     if(res == -1) {
                         return (_LastError);
                     } else {
                         // Tratamento de erros HTTP
                         out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
                         Print(out);
                         return (ERR_HTTP_ERROR_FIRST + res);
                     }
                  }
                  

                  Nova Implementação com tratamento de erros aprimorado:

                  // Exemplo da nova implementação de SendGetRequest
                  int SendGetRequest(const string url, const string query_param, string &out, string headers = "", const int timeout = 5000, bool debug=false) {
                     // ... código ...
                     if(res == -1) {
                         return (_LastError);
                     } else {
                         // Tratamento de erros HTTP
                         out = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
                         if(debug) {
                             Print(out);
                         }
                         return res;
                     }
                  }
                  

                  Na nova implementação, o tratamento de erros HTTP é mais refinado, com a opção de imprimir a saída apenas se o modo de depuração estiver ativado.


                  Integração e Testes Práticos

                  Nesta fase crucial do nosso projeto, iremos demonstrar a integração das melhorias implementadas na biblioteca Requests e as jogadas automáticas no jogo da velha Python. Além disso, realizaremos testes práticos para avaliar a eficácia e robustez do sistema como um todo. Esta etapa é fundamental para garantir que todas as partes do nosso sistema funcionem de maneira harmoniosa e confiável.

                  Passos para Integração

                  Antes de prosseguirmos com os testes, é importante entender como a integração das novas funcionalidades foi realizada. Abaixo, descrevemos os principais passos que tomamos para garantir uma integração suave:

                  1. Atualização da Biblioteca Requests: Primeiramente, atualizamos nossa biblioteca Requests para incluir as melhorias que implementamos. Isso permitiu uma comunicação mais eficiente e a capacidade de depurar problemas de maneira mais eficaz.

                  2. Adaptação do Jogo da Velha: Integrar as jogadas automáticas no jogo da velha Python foi uma etapa importante. Modificamos o código do jogo para que ele pudesse reconhecer quando era a vez da máquina jogar e acionar a lógica de jogadas automáticas.

                  3. Implementação da Integração na API: Em seguida, adaptamos a API FastAPI para suportar as jogadas automáticas. Isso envolveu melhorias no gerenciamento de turnos, integração com a função machine_move , atualização consistente do estado do jogo e tratamento adequado de resultados de jogo.

                  Realização dos Testes

                  Agora que a integração está completa, é hora de realizar testes práticos para garantir que tudo funcione conforme o esperado. Criamos uma série de testes que abrangem diferentes aspectos da nossa implementação:

                  1. Teste de Criação de Novo Jogo (Swagger e Script de Testes): Para garantir que a criação de um novo jogo funcione de maneira consistente, realizaremos esse teste tanto através da interface Swagger quanto do nosso script de testes automatizados. Isso assegura que a funcionalidade esteja acessível de ambas as maneiras.

                  2. Teste de Jogada do Jogador (Swagger e Script de Testes): A funcionalidade de realizar uma jogada válida pelo jogador será testada tanto através do Swagger quanto do script de testes. Isso garante que a interação do jogador seja testada de maneira abrangente.

                  3. Teste de Jogada Inválida do Jogador: Para garantir que a API lida adequadamente com jogadas inválidas, realizaremos um teste em que tentaremos fazer uma jogada em uma posição já ocupada, verificando se a API retorna um código de erro apropriado.

                  4. Teste de Vitória do Jogador: Simularemos uma sequência de jogadas que resultará na vitória de um jogador e garantiremos que a API reconheça corretamente a condição de vitória.

                  Resultados e Avaliação

                  Ao realizar esses testes, esperamos obter resultados sólidos que validem a integridade do nosso sistema integrado. Avaliaremos se as jogadas automáticas funcionam conforme o esperado, se os erros são tratados de maneira adequada e se a API fornece respostas claras e informativas.

                  Esses testes práticos são essenciais para assegurar que nosso sistema esteja pronto para interações reais e futuras expansões. Com uma integração bem-sucedida e testes robustos, estamos mais próximos de criar um sistema de jogo confiável e eficaz.




                  Nas imagens acima, podemos observar o processo de teste da funcionalidade de criação de um novo jogo, que desempenha um papel essencial em nossa aplicação. O teste é realizado de forma abrangente e consistente, abordando a acessibilidade da função de duas maneiras distintas: através da interface Swagger e do nosso script de testes automatizados.

                  O Swagger, uma ferramenta de documentação e teste de API, permite que os desenvolvedores interajam com a API de forma visual e eficaz. Na imagem, podemos ver como a criação de um novo jogo é iniciada e testada diretamente através da interface Swagger, garantindo que a funcionalidade seja facilmente acessível e funcione conforme o esperado.

                  Além disso, o processo de teste também envolve o uso de um script de testes automatizados, que realiza verificações rigorosas para garantir a consistência e confiabilidade da funcionalidade. Isso demonstra nosso compromisso em manter a qualidade do sistema, independentemente da abordagem de teste utilizada.


                  Conclusão

                  Este artigo, que marca a continuação da série sobre APIs REST, focou na implementação e teste de jogadas automáticas no jogo da velha Python, integradas com o desenvolvimento de funções MQL5. O objetivo principal foi duplo: aprimorar o jogo da velha com jogadas autônomas e desenvolver testes unitários em MQL5 para validar a interação com a API REST. A integração dessas funcionalidades não só melhora o jogo em si, mas também estabelece uma base para testes mais abrangentes e eficientes.

                  No desenvolvimento de jogadas automáticas, foi crucial compreender a lógica do jogo existente para implementar uma nova funcionalidade de maneira eficiente e harmoniosa. A estratégia escolhida envolveu uma abordagem simples, porém eficaz, para tornar o jogo mais desafiador e dinâmico. Além disso, a preparação do ambiente para a introdução de um agente MQL5, que simula um usuário real, representa um passo importante para testes mais realistas.

                  Na parte de testes, a construção e implementação de scripts de teste em MQL5 asseguraram a robustez da comunicação entre o código MQL5 e a API REST. Os testes abrangiam desde a inicialização do jogo até a execução de jogadas válidas e inválidas, além de verificar condições de vitória. Este aspecto é fundamental para garantir a confiabilidade e estabilidade do sistema.

                  As melhorias implementadas na biblioteca Requests, como a inclusão de um parâmetro de depuração e um tratamento de erros mais sofisticado, contribuíram significativamente para a eficiência da comunicação e a facilidade de diagnóstico de problemas.

                  Finalmente, a fase de integração e testes práticos validou a eficácia das melhorias implementadas, tanto no jogo da velha quanto na biblioteca Requests. A realização de testes através da interface Swagger e scripts de testes automatizados confirmou a funcionalidade e confiabilidade do sistema como um todo.

                  Este artigo demonstra como a combinação de jogadas automáticas e testes unitários em MQL5 pode criar um ciclo de desenvolvimento robusto, assegurando um sistema integrado confiável e eficaz, capaz de oferecer uma experiência de jogo aprimorada e insights valiosos para futuras integrações e desenvolvimentos em sistemas de integração.



                  Arquivos anexados |
                  Parte_03.zip (65.15 KB)
                  Desenvolvendo um sistema de Replay (Parte 39): Pavimentando o Terreno (III) Desenvolvendo um sistema de Replay (Parte 39): Pavimentando o Terreno (III)
                  Antes de começarmos a segunda fase de desenvolvimento, é preciso reforçar algumas ideias. Então você sabe como forçar o MQL5 a fazer o que é preciso ser feito ?!?! Já tentou ir além do que a documentação informar ?!?! Se não. Se prepare. Pois irei começar a fazer coisas muito além do que grande parte faz normalmente.
                  Redes neurais de maneira fácil (Parte 51): ator-crítico comportamental (BAC) Redes neurais de maneira fácil (Parte 51): ator-crítico comportamental (BAC)
                  Nos últimos dois artigos, discutimos o algoritmo Soft Actor-Critic, que incorpora regularização de entropia na função de recompensa. Essa abordagem permite equilibrar a exploração do ambiente e a exploração do modelo, mas é aplicável apenas a modelos estocásticos. Neste artigo, exploraremos uma abordagem alternativa que é aplicável tanto a modelos estocásticos quanto determinísticos.
                  Tudo o que você precisa saber sobre a estrutura de um programa MQL5 Tudo o que você precisa saber sobre a estrutura de um programa MQL5
                  Qualquer programa em qualquer linguagem de programação possui uma estrutura específica. Neste artigo, você aprenderá os componentes básicos da estrutura de um programa na linguagem MQL5, o que pode ser extremamente útil ao criar um sistema de negociação ou uma ferramenta de negociação para o MetaTrader 5.
                  Teoria das Categorias em MQL5 (Parte 14): funtores com ordem linear Teoria das Categorias em MQL5 (Parte 14): funtores com ordem linear
                  Este artigo, parte de uma série de artigos sobre a implementação da teoria das categorias no MQL5, é dedicado aos funtores. Vamos explorar como a ordem linear pode ser mapeada em um conjunto de dados através dos funtores ao analisar dois conjuntos de dados que, à primeira vista, parecem não ter nenhuma conexão entre si.