Ir para conteúdo
Faça parte da equipe! (2024) ×

Como construir um Bot em Python para games Web


Arkanun1000
 Compartilhar

Posts Recomendados

  • Velha Guarda

Fala ai galera, blz?

 

Encontrei este tutorial bacana, porem está em ingles e traduzir com o google pra ficar mais fácil a leitura.

No final do post terá o link com o artigo original em ingles. Obrigado!

 

Como construir um Bot em Python que pode jogar jogos na Web

Neste tutorial, exploraremos os prós e contras da construção de um jogo de jogo baseado em computador em Python, que poderá jogar o popular jogo Flash Sushi Go Round. Você pode usar as técnicas ensinadas neste tutorial para criar bots para testar automaticamente seus próprios jogos na web.

 

Pré-visualização final do resultado

 

Vamos dar uma olhada no resultado final em que estaremos trabalhando:

 

 

Pré-requisitos

 

Este tutorial e todo o código nele requer que algumas bibliotecas Python adicionais sejam instaladas. Eles fornecem um bom pacote de Python para um monte de código C de baixo nível que facilita muito o processo e a velocidade do script do bot.

 

Alguns dos códigos e bibliotecas são específicos do Windows. Pode haver equivalentes de Mac ou Linux, mas não iremos cobri-los neste tutorial.

 

Você precisará baixar e instalar as seguintes bibliotecas:

Todos os itens acima possuem auto instaladores; Executá-los instalará automaticamente os módulos em seu diretório \ lib \ site-packages e, em teoria, ajustará seu pythonPath de acordo. No entanto, na prática, isso nem sempre acontece. Se você começar a receber mensagens de erro de importação após a instalação, provavelmente você precisará ajustar manualmente suas variáveis de ambiente. Mais informações sobre como ajustar variáveis de caminho podem ser encontradas aqui.

 

A ferramenta final que precisaremos é um programa de pintura decente. Sugiro Paint.NET como uma excelente opção gratuita, mas qualquer programa com regras que exibem suas medidas em pixels pode ser usado.

 

Usaremos alguns jogos como exemplos ao longo do caminho.

 

Introdução

 

Este tutorial é escrito para dar uma introdução básica ao processo de construção de bots que jogam jogos baseados no navegador. A abordagem que vamos tomar é provavelmente um pouco diferente do que a maioria esperaria quando pensasse em um bot. Ao invés de fazer um programa que fica entre o cliente e o código de injeção do servidor (como um botão Quake ou C / S), nosso bot se sentará puramente no "exterior". Nós confiamos nas técnicas de Visão de Computador e nas chamadas da API do Windows para reunir informações necessárias e gerar movimentos.

 

Com essa abordagem, perdemos um pouco de detalhes refinados e controle, mas compensamos isso em tempo de desenvolvimento reduzido e facilidade de uso. A automação de uma função de jogo específica pode ser feita em algumas linhas curtas de código, e um botão de início a término (para um jogo simples) pode ser ativado em algumas horas.

 

As alegrias desta abordagem rápida são tais que, uma vez que você se familiarize com o que o computador pode facilmente "ver", você começará a ver os jogos de forma ligeiramente diferente. Um bom exemplo é encontrado em jogos de quebra-cabeças. Uma construção comum envolve a exploração de limitações de velocidade humana para forçá-lo a uma solução menos do que ideal. É divertido (e muito fácil) "quebrar" esses jogos através de scripts em movimentos que nunca poderiam ser realizados por um ser humano.

 

Esses bots também são muito úteis para testar jogos simples - ao contrário de um ser humano, um bot não ficará entediado jogando o mesmo cenário uma e outra vez.

 

O código-fonte para todos os exemplos do tutorial, bem como para um dos bots de exemplo concluídos, pode ser encontrado aqui.

 

Diverta-se!

 

Passo 1: Criar um novo projeto Python

 

Em uma nova pasta, clique com o botão direito do mouse e selecione Novo> Documento de texto.

 

right_click_new_text.png

 

Uma vez feito, mude o nome do arquivo 'Novo documento de texto' para 'quickGrab.py' (sem as aspas) e confirme que deseja alterar a extensão do nome do arquivo.

 

 

quickgrab_py.png

 

 

Finalmente, clique com o botão direito do mouse no nosso arquivo recém-criado e selecione "Editar com IDLE" no menu de contexto para iniciar o editor

 

 

edit_w_idle.png

 

Etapa 2: Configurando o seu primeiro captador de tela

Começaremos a trabalhar no nosso bot, explorando a função básica de captura de tela. Uma vez em funcionamento, passaremos por linha por linha, pois esta função (e suas muitas iterações) servirá como espinha dorsal do nosso código.

 

Em quickgrab.py, digite o seguinte código:

 

É necessário se cadastrar para acessar o conteúdo.

 

A execução deste programa deve dar-lhe um instantâneo completo da área da tela:

 

full_snap_600x480.png

 

O código atual agarra toda a largura e altura da sua área de tela e armazena-o como um PNG no seu diretório de trabalho atual.

 

Agora vamos passar pelo código para ver exatamente como isso funciona.

 

As primeiras três linhas:

 

 

É necessário se cadastrar para acessar o conteúdo.

 

... são as "declarações de importação" apropriadamente mencionadas. Isso diz ao Python que carregue nos módulos listados no tempo de execução. Isso nos dá acesso aos seus métodos através da sintaxe module.attribute .

 

O primeiro módulo é parte da Python Image Library que instalamos anteriormente. Como o próprio nome sugere, isso nos dá a funcionalidade de gabbing de tela básica em que nosso bot confiará.

 

A segunda linha importa o módulo do sistema operacional (sistema operacional). Isso nos dá a capacidade de navegar facilmente pelos diretórios do nosso sistema operacional. Vai ser útil quando começamos a organizar ativos em diferentes pastas.

 

Esta importação final é o módulo de tempo incorporado. Bem, use isso principalmente para carimbar a hora atual em instantâneos, mas pode ser muito útil como um temporizador para bots que precisam de eventos desencadeados ao longo de um determinado número de segundos.

 

As próximas quatro linhas constituem o coração da nossa função screenGrab() .

 

É necessário se cadastrar para acessar o conteúdo.

 

A primeira linha def screenGrab() define o nome da nossa função. Os parênteses vazios significam que não espera nenhum argumento.

 

Linha 2, box=() atribui uma tupla vazia a uma variável chamada "caixa". Vamos preencher isso com argumentos na próxima etapa.

 

Linha 3, im = ImageGrab.grab() cria um instantâneo completo da tela e retorna uma imagem RGB para a instância.

 

A Linha 4 pode ser um pouco complicada se você não estiver familiarizado com o funcionamento do módulo de Time . A primeira parte im.save( chama o método "salvar" da classe Image. Ele espera dois argumentos. O primeiro é o local onde salvar o arquivo e o segundo é o formato do arquivo.

 

Aqui nós definimos o local primeiro chamando os.getcwd() . Isso obtém o diretório atual do código que está sendo executado e o retorna como uma string. Em seguida, adicione um + . Isso será usado entre cada novo argumento para concatenar todas as cordas juntas.

 

A próxima peça '\\full_snap__ dá ao nosso arquivo um nome simples e descritivo. (Porque a barra invertida é um caractere de escape em Python, temos que adicionar dois deles para evitar cancelar uma de nossas letras).

 

O próximo é o bit de cabelo: str(int(time.time())) . Isso aproveita os métodos incorporados do Python. Vamos explicar esta peça trabalhando de dentro para fora:

 

time.time() retorna o número de segundos desde Epoch, que é dado como um tipo Float. Uma vez que estamos criando um nome de arquivo, não podemos ter o decimal lá, então primeiro o convertem em um número inteiro envolvendo-o em int() . Isso nos aproxima, mas o Python não pode concatenar o tipo Int com o tipo String , então o último passo é embrulhar tudo na função str() para nos dar um bom timestamp utilizável para o nome do arquivo. A partir daqui, tudo o que resta é adicionar a extensão como parte da string: + '.png' e passar o segundo argumento que é novamente o tipo da extensão: "PNG" .

 

A última parte do nosso código define a função main() e diz para chamar a função screenGrab() sempre que for executada.

 

E aqui, no final, é uma convenção Python que verifica se o script é de nível superior e, se assim, permite que ele seja executado. Traduzido, significa simplesmente que ele só executa main() se é executado sozinho. Caso contrário - se, por exemplo, for carregado como um módulo por um script diferente do Python - ele apenas fornece seus métodos em vez de executar seu código.

 

É necessário se cadastrar para acessar o conteúdo.

 

Passo 3: a caixa de encadernação

A função ImageGrab.grab() aceita um argumento que define uma caixa delimitadora. Esta é uma tupla de coordenadas seguindo o padrão de (x, y, x, y) onde,

 

  1. O primeiro par de valores ( x,y.. define o canto superior esquerdo da caixa
  2. O segundo par ..x,y ) define o canto inferior direito.

A combinação destes permite copiar somente a parte da tela que precisamos.

 

Vamos colocar isso em prática.

 

Para este exemplo, vamos usar um jogo chamado Sushi Go Round . ( Muitoviciante. Você foi avisado.) Abra o jogo em uma nova guia e faça um instantâneo usando nosso código screenGrab() existente:

 

sushi_full_screen_grab.png

Um instantâneo da área de tela cheia.

 

Passo 4: Obtendo Coordenadas

Agora é hora de começar a extrair algumas coordenadas para a nossa caixa delimitadora.

 

Abra seu instantâneo mais recente em um editor de imagens.

 

sushi_inside_paint.png

A posição (0,0) está sempre localizada no canto superior esquerdo da imagem. Queremos colocar as coordenadas x e y para que nossa nova função de instantâneo seja definida (0,0) no canto mais à esquerda da área de jogo do jogo.

 

Os motivos para isso são duplos. Primeiro, torna as coordenadas no jogo muito mais fáceis quando precisamos apenas ajustar os valores em relação à área de reprodução versus a área inteira da resolução da tela. Em segundo lugar, pegar uma porção menor da tela reduz as despesas gerais de processamento necessárias. As gravações em tela cheia produzem um pouco de dados, o que pode dificultar a sua trajetória várias vezes por segundo.

 

sushi_xy.png

Se não for feito já, habilite a exibição da régua em seu editor e faça zoom no canto superior da área de reprodução até que você possa ver os pixels em detalhes:

 

sushi_zoom_set_xy.png

Passe o cursor sobre o primeiro pixel da área de reprodução e verifique as coordenadas exibidas na régua. Estes serão os dois primeiros valores da nossa tupla Box. Na minha máquina específica, esses valores são 157, 162 .

 

Navegue até a borda inferior da área de reprodução para obter o par inferior das coordenadas.

 

sushi_zoom_set_low_xy.png

Isso mostra as coordenadas de 796 e 641. A combinação destes com o nosso par anterior dá uma caixa com as coordenadas de (157,162,796,641) .

 

Vamos adicionar isso ao nosso código.

 

É necessário se cadastrar para acessar o conteúdo.

 

Na linha 6, atualizamos a tupla para manter as coordenadas da área de reprodução.

 

Salve e execute o código. Abra a imagem recém-salva e você deve ver:

 

play_area_snapshot.png

Sucesso! Uma captura perfeita da área de jogo. Nem sempre precisamos fazer esse tipo de busca intensiva por coordenadas. Uma vez que entramos no win32api, examinaremos alguns métodos mais rápidos para configurar as coordenadas quando não precisarmos de precisão de pixel perfeita.

 

Passo 5: planejamento para a flexibilidade

Como está agora, codificamos as coordenadas em relação à nossa configuração atual, assumindo nosso navegador e nossa resolução.Geralmente, é uma má idéia as coordenadas de código rígido desta maneira.Se, por exemplo, queremos executar o código em um computador diferente - ou dizer, um novo anúncio no site desloca ligeiramente a posição da área de jogo - teríamos que consertar manualmente e minuciosamente todas as nossas chamadas de coordenadas.

 

Então vamos criar duas novas variáveis: x_pad e y_pad . Estes serão usados para armazenar a relação entre a área do jogo e o resto da tela. Isso tornará muito fácil a porta do código de um lugar para outro, uma vez que cada nova coordenada será relativa às duas variáveis globais que vamos criar e para ajustar as mudanças na área da tela, tudo o que é necessário é redefinir esses dois variáveis.

 

Como já realizamos as medições, definir as almofadas para o nosso sistema atual é muito direto. Vamos configurar as almofadas para armazenar a localização do primeiro pixel fora da área de jogo. Do primeiro par de coordenadas x, y na nossa tupla da box , subtrair um 1 de cada valor. Então 157 torna-se 156 e 346 torna-se 345 .

 

Vamos adicionar isso ao nosso código.

 

É necessário se cadastrar para acessar o conteúdo.

 

Agora que estes estão configurados, começaremos a ajustar a tupla da caixa para estar em relação a esses valores.

 

É necessário se cadastrar para acessar o conteúdo.

 

Para o segundo par, vamos primeiro subtrair os valores das almofadas (156 e 345) das coordenadas (796, 825) e, em seguida, usar esses valores no mesmo formato Pad + Value .

 

É necessário se cadastrar para acessar o conteúdo.

 

Aqui, a coordenada x se torna 640 (769-156), e o y se torna 480 (825-345)

 

Pode parecer um pouco redundante no início, mas fazer este passo extra garante uma fácil manutenção no futuro.

 

Etapa 6: Criando uma Docstring

Antes de avançarmos, vamos criar um docstring no topo do nosso projeto.Uma vez que a maioria do nosso código será baseado em coordenadas de tela específicas e relacionamentos para coordenadas, é importante conhecer as circunstâncias em que tudo se alinhará corretamente. Por exemplo, coisas como a resolução atual, navegador, barras de ferramentas habilitadas (uma vez que alteram a área do navegador) e quaisquer ajustes necessários para centrar a área de reprodução na tela, todos afetam a posição relativa das coordenadas. Ter tudo isso documentado ajuda muito o processo de solução de problemas ao executar seu código em vários navegadores e computadores.

 

Uma última coisa a ter em conta é o espaço publicitário em constante mudança em sites de jogos populares. Se todas as suas chamadas de gravação de repente parem de se comportar conforme o esperado, uma nova introdução de coisas ligeiramente divertidas na tela é uma boa aposta.

 

Como exemplo, geralmente tenho os seguintes comentários no topo do meu código Python:

 

É necessário se cadastrar para acessar o conteúdo.

 

Largar toda essa informação no início do seu arquivo Python torna rápido e fácil verificar todas as configurações e o alinhamento da tela sem ter que controlar seu código tentando lembrar onde você armazenou essa coordenada x específica.

 

Passo 7: Transformando o quickGrab.py em uma ferramenta útil

Nós vamos conquistar nosso projeto neste momento, criando dois arquivos: um para armazenar todo o código do nosso bot e o outro para atuar como um utilitário geral de captura de tela. Nós estaremos tomando muitas capturas de tela enquanto buscamos coordenadas, então, ter um módulo separado pronto para ir, tornará as coisas muito mais rápidas.

 

Salve e feche o nosso projeto atual.

 

Na sua pasta, clique com o botão direito do mouse em quickGrab.py e selecione 'copiar' no menu.

 

copy_py.png

Agora, clique com o botão direito do mouse e selecione 'colar' no menu

 

paste_py.png

Selecione o arquivo copiado e renomeie-o para 'code.py'

 

copy_rename.png

A partir de agora, todas as novas adições e alterações de código serão feitas em code.py. O FastGrab.py agora funcionará apenas como uma ferramenta de instantâneo. Só precisamos fazer uma última modificação:

 

Altere a extensão do arquivo de .py, para .pyw e confirme as alterações.

 

change_pyw.png

Esta extensão informa o Python para executar o script sem iniciar o console.Então agora, quickGrab.pyw faz jus ao seu nome. Clique duas vezes no arquivo e ele executará silenciosamente seu código em segundo plano e salvará um instantâneo no seu diretório de trabalho.

 

Mantenha o jogo aberto em segundo plano (certifique-se de silenciá-lo antes que a música em loop o leve à loucura); voltaremos em breve. Temos mais alguns conceitos / ferramentas para apresentar antes de entrar no controle de coisas na tela.

 

Passo 8: Win32api - Uma breve visão geral

Trabalhar com o win32api pode ser um pouco intimidante inicialmente. Ele envolve o código do Windows C de baixo nível - o que é felizmente muito bem documentado aqui , mas um pouco como um labirinto para navegar pelo seu primeiro par de go-arounds.

 

Antes de iniciar o script de qualquer ação útil, vamos examinar de perto algumas das funções da API sobre as quais confiamos. Uma vez que tenhamos uma compreensão clara de cada parâmetro, será fácil ajustá-los para servir os fins que precisamos no jogo.

 

O win32api.mouse_event() :

 

É necessário se cadastrar para acessar o conteúdo.

 

O primeiro parâmetro dwFlags define a "ação" do mouse. Ele controla coisas como movimento, clique, rolagem, etc.

 

A lista a seguir mostra os parâmetros mais comuns usados durante o movimento de script.

 

dwFlags :

 

  • win32con.MOUSEEVENTF_LEFTDOWN
  • win32con.MOUSEEVENTF_LEFTUP
  • win32con.MOUSEEVENTF_MIDDLEDOWN
  • win32con.MOUSEEVENTF_MIDDLEUP
  • win32con.MOUSEEVENTF_RIGHTDOWN
  • win32con.MOUSEEVENTF_RIGHTUP
  • win32con.MOUSEEVENTF_WHEEL

Cada nome é auto-explicativo. Se você quisesse enviar um clique direito virtual, você passaria o win32con.MOUSEEVENTF_RIGHTDOWN para o parâmetro dwFlags .

 

Os dois parâmetros seguintes, dx e dy , descrevem a posição absoluta do mouse ao longo dos eixos x e y. Embora possamos usar esses parâmetros para rotear o movimento do mouse, eles usam um sistema de coordenadas diferente do que usamos. Então, vamos deixá-los definidos para zero e confiar em uma parte diferente da API para as nossas necessidades de mudança de mouse.

 

O quarto parâmetro é dwData . Esta função é usada se (e somente se) dwFlagscontém MOUSEEVENTF_WHEEL . Caso contrário, pode ser omitido ou definido como zero. dwData especifica a quantidade de movimento na roda de rolagem do mouse.

 

Um exemplo rápido para solidificar essas técnicas:

 

Se imaginarmos um jogo com um sistema de seleção de armas semelhante ao Half-Life 2 - onde as armas podem ser selecionadas girando a roda do mouse - nós apresentamos a seguinte função para examinar a lista de armas:

 

É necessário se cadastrar para acessar o conteúdo.

 

Aqui queremos simular a rolagem da roda do mouse para navegar nossa lista de armas teóricas, então passamos ...MOUSEEVENTF_WHEEL 'ação' para o dwFlag.Não precisamos de dx ou dy , dados de posição, então deixávamos aqueles definidos para zero, e queríamos rolar um clique na direção direta para cada "arma" na lista, então passamos o inteiro 120 para dwData (cada um O clique da roda é igual a 120).

 

Como você pode ver, trabalhar com mouse_event é simplesmente uma questão de conectar os argumentos certos ao lugar certo. Vamos agora passar para algumas funções mais utilizáveis

 

Etapa 5: clique básico do mouse

Vamos fazer três novas funções. Uma função geral do botão esquerdo e dois que manipulam os estados específicos para baixo e para cima.

 

Abra code.py com IDLE e adicione o seguinte à nossa lista de declarações de importação:

 

É necessário se cadastrar para acessar o conteúdo.

 

Como antes, isso nos dá acesso ao conteúdo do módulo através da sintaxe module.attribute .

 

Em seguida, faremos nossa primeira função de clique do mouse.

 

É necessário se cadastrar para acessar o conteúdo.

 

Lembre-se de que tudo o que estamos fazendo aqui é atribuir uma "ação" ao primeiro argumento do mouse_event . Não precisamos passar nenhuma informação de posicionamento, por isso estamos deixando os parâmetros de coordenadas em (0,0), e não precisamos enviar nenhuma informação adicional, então dwData está sendo omitido. A função time.sleep(.1) informa Python para interromper a execução pelo tempo especificado entre parênteses. Nós iremos adicionar isso através do nosso código, geralmente por uma quantidade muito curta de vezes. Sem isso, o "clique" pode avançar e disparar antes que os menus tenham a chance de atualizar.

 

Então, o que fizemos aqui é um clique esquerdo geral. Uma imprensa, uma versão. Vamos passar a maior parte do tempo com este, mas vamos fazer mais duas variações.

 

Os próximos dois são exatamente o mesmo, mas agora cada etapa é dividida em sua própria função. Estes serão usados quando precisamos manter pressionado o mouse por um longo período de tempo (para arrastar, disparar, etc.).

 

É necessário se cadastrar para acessar o conteúdo.

 

Passo 9: Movimento básico do mouse

Ao clicar no caminho, tudo o que resta é mover o mouse na tela.

 

Adicione as seguintes funções a code.py :

 

É necessário se cadastrar para acessar o conteúdo.

 

Essas duas funções atendem a propósitos distintamente diferentes. O primeiro será usado para mover scripts no programa. Graças a excelentes convenções de nomenclatura, o corpo da função faz exatamente como SetCursorPos() implica. Chamando essa função define o mouse nas coordenadas passadas para ele como uma tupla x,y . Observe que adicionamos em nossas almofadas x e y ; É importante fazer isso em qualquer lugar, uma coordenada é chamada.

 

O segundo é uma ferramenta simples que usaremos ao executar o Python de forma interativa. Ele imprime no console a posição atual do mouse como uma tupla x,y . Isso acelera o processo de navegar por menus sem ter que tirar uma foto e sair com uma régua. Nem sempre poderemos usá-lo, pois algumas atividades do mouse precisarão ser específicas de pixels, mas quando pudermos, é uma economia de tempo fantástica.

 

No próximo passo, vamos colocar algumas dessas novas técnicas para usar e começar a navegar nos menus do jogo. Mas antes de fazê-lo, exclua o conteúdo atual do main() em code.py e substitua-o por pass . Nós estaremos trabalhando com o prompt interativo para o próximo passo, então não precisaremos da função screenGrab() .

 

Etapa 10: Menus do jogo de navegação

get_cords() , e as próximas etapas, vamos tentar reunir as coordenadas de eventos que possamos usando o nosso método get_cords() . Usando isso, poderemos construir rapidamente o código para coisas como navegar em menus, limpar tabelas e fazer comida. Uma vez que tenhamos esse conjunto, será apenas uma questão de conectá-los à lógica do bot.

 

Vamos começar. Salve e execute seu código para abrir o shell Python. Uma vez que substituímos o corpo do main() por pass no último passo, você deve ver um invólucro em branco ao correr.

 

blank_console.png

Agora, antes mesmo de chegar à parte jogável do jogo, há quatro menus iniciais que precisamos passar. Eles são os seguintes:

 

 

1 - Botão inicial de "reprodução"

play_button.png

 

2 - Botão "continuar" do iPhone

iphone_cont_button.png

 

3 - Tutorial botão "Saltar"

skip_button.png

 

4 - O objetivo de hoje "Continuar"

daily_goal_cont_button.png

 

 

Teremos que obter as coordenadas para cada um destes e adicioná-los a uma nova função chamada startGame() . Posicione o shell IDLE para que você possa ver ambos e a área de jogo. Digite a função get_cords() , mas não pressione retornar ainda; Mova o mouse sobre o botão para o qual você precisa de coordenadas. Certifique-se de não clicar ainda, pois queremos que o foco permaneça no shell. Passe o mouse sobre o item do menu e pressione a tecla de retorno. Isso irá pegar a localização atual do mouse e imprimir no console uma tupla contendo os valores x,y . Repita isso para os três menus restantes.

 

Deixe o shell aberto e organize-o para que você possa vê-lo, bem como o editor IDLE. Agora vamos agora adicionar a nossa função startGame() e preenchê-la com as nossas coordenadas recém-adquiridas.

 

É necessário se cadastrar para acessar o conteúdo.

 

Agora temos uma boa função compacta para ligar no início de cada jogo.Define a posição do cursor para cada um dos locais de menu que definimos anteriormente e, em seguida, diz ao mouse para clicar. time.sleep(.1) diz a Python que interrompa a execução por 1/10 de segundo entre cada clique, o que dá aos menus tempo suficiente para atualizar entre eles.

 

Salve e execute seu código e você deve ver um resultado semelhante a este:

 

Como um humano fraco leva-me um pouco mais de um segundo para navegar todos os menus à mão, mas o nosso bot agora pode fazê-lo em cerca de .4 segundos. Nada mal a todos!

 

Passo 11: Obter Coordenadas de Alimentos

Agora, vamos repetir o mesmo processo para cada um desses botões:

 

food_items.png

 

Mais uma vez, no shell Python, digite get_cords() , passe o mouse sobre a caixa de alimentos que você precisa e pressione a tecla Enter para executar o comando.

 

Como uma opção para acelerar ainda mais as coisas, se você tiver um segundo monitor ou for capaz de organizar o shell python de forma que você possa vê-lo, bem como a área do jogo, em vez de digitar e executar get_cords() cada vez nós precisamos disso, podemos configurar um loop simples for . Use um método time.sleep() para interromper a execução apenas o tempo suficiente para que você mova o mouse para a próxima localização que precisa de coordenadas.

 

Aqui está o loop para ação:

 

Vamos criar uma nova classe chamada Cord e usá-la para armazenar todos os valores de coordenadas que reunimos. Ser capaz de chamar Cord.f_riceoferece uma enorme vantagem de legibilidade ao passar as coordenadas diretamente para mousePos() . Como uma opção, você também pode armazenar tudo em um dictionary , mas acho a sintaxe da classe mais agradável.

 

É necessário se cadastrar para acessar o conteúdo.

 

Nós vamos armazenar muitas das nossas coordenadas nesta classe, e haverá uma sobreposição, então, adicionando o prefixo ' f_ ' nos informa que nos referimos aos locais de comida, em vez de, digamos, uma localização no telefone cardápio.

 

Vamos voltar a isso em um pouco. Há um pouco mais de caça coordenada para fazer!

 

Etapa 12: Obtendo coordenadas de placas vazias

Cada vez que um cliente termina de comer, eles deixam para trás uma placa que precisa ser clicada para ser removida. Então, precisamos obter a localização das placas vazias também.

 

x_marks_the_spot.png

 

Eu notei sua posição com um 'X' vermelho gigante. Repita o mesmo padrão nas duas últimas etapas para obter suas coordenadas. Armazene-os na string de comentário por enquanto.

 

É necessário se cadastrar para acessar o conteúdo.

 

Estamos chegando perto. Apenas mais algumas etapas de configuração preliminar antes de entrarmos nas coisas realmente divertidas.

 

Passo 13: Obtendo Coordenadas do Telefone

Ok, este será o conjunto final de coordenadas que temos para minar desta maneira específica.

 

Este tem muito mais para acompanhar, então você pode querer fazê-lo chamando manualmente a função get_cords() em vez do método de loop anteriormente usado. De qualquer forma, vamos passar por todos os menus do telefone para obter as coordenadas de cada item.

 

Este é um pouco mais envolvido no sentido de alcançar uma das telas de compra que precisamos, você precisa ter dinheiro suficiente para realmente comprar algo. Então você precisará fazer alguns pedaços de sushi antes de ir sobre o negócio da caça coordenada. No máximo, você terá que fazer dois roquetes de sushi, eu acredito. Isso o levará o suficiente para comprar arroz, o que nos levará à tela que precisamos.

 

Existem seis menus que temos para passar:

 

1 - O telefone

phone.png

 

2 - Menu inicial

phone_menu.png

 

3 - Toppings

phone_menu_toppings.png

 

4 - Arroz

phone_menu_rice.png

 

5 - Remessa

phone_delivery.png

 

 

Precisamos obter coordenadas para tudo, exceto Sake (embora você possa, se você quiser. Achei que o bot funcionava bem sem ele. Eu estava disposto a sacrificar a crítica ocasional do jogo em não ter que codificar na lógica.)

 

Obtendo as coordenadas:

 

Vamos adicionar tudo isso à nossa classe Cord. Usaremos o prefixo ' t_ ' para denotar que os tipos de comida são itens de menu toppings para telefone.

 

É necessário se cadastrar para acessar o conteúdo.

 

Bem! Finalmente, extraímos todos os valores de coordenadas que precisamos.Então vamos começar a fazer algo útil!

 

Etapa 14: Limpeza de tabelas

Vamos pegar nossas coordenadas previamente gravadas e usá-las para preencher uma função chamada clear_tables ().

 

É necessário se cadastrar para acessar o conteúdo.

 

Como você pode ver, isso parece mais ou menos exatamente como a nossa função startGame() anterior. Algumas pequenas diferenças:

 

Não temos funções time.sleep() entre os diferentes eventos de clique. Não precisamos aguardar qualquer atualização de menus, portanto, não precisamos acelerar nossas velocidades de clique.

 

Nós, no entanto, temos um longo tempo. time.sleep() no final. Embora não seja estritamente necessário, é bom adicionar essas pausas ocasionais em execução ao nosso código, algo apenas o suficiente para nos dar tempo para sair manualmente do loop principal do bot, se necessário (o que iremos chegar). Caso contrário, a coisa continuará a roubar a posição do mouse uma e outra vez, e você não poderá mudar o foco para o shell o suficiente para parar o script - o que pode divertir as primeiras duas ou três vezes enquanto luta contra um mouse , mas rapidamente perde seu charme.

 

Portanto, certifique-se de adicionar algumas pausas confiáveis em seus próprios robôs!

 

Passo 15: Fazer Sushi

A primeira coisa que precisamos fazer é aprender a fazer o sushi. Clique no livro de receitas para abrir o manual de instruções. Todos os tipos de sushi encontrados ao longo do jogo serão encontrados em suas páginas. Vou notar os três primeiros abaixo, mas deixo para você catalogar o resto.

 

recipes.png

 

 

É necessário se cadastrar para acessar o conteúdo.

 

Agora, vamos configurar uma função que aceitará um argumento para "tipo de sushi" e, em seguida, montar os ingredientes apropriados com base no valor passado.

 

É necessário se cadastrar para acessar o conteúdo.

 

Isso funciona como todos os outros, mas com uma pequena alteração: ao invés de passar diretamente as coordenadas, os chamamos de atributos da nossa classe Cord .

 

A função foldMat() é chamada no final de cada processo de fabricação de sushi. Isso faz clic no tapete para rolar o sushi que acabamos de montar.Vamos definir essa função agora:

 

É necessário se cadastrar para acessar o conteúdo.

 

Vamos percorrer brevemente essa chamada de mousePos() , pois é um pouco encadernada. f_rice o primeiro valor da tupla do f_rice adicionando [0] no final do atributo. Lembre-se que este é o nosso valor x . Para clicar no tapete, só precisamos ajustar nossos valores x por um punhado de pixels, então adicionamos 40 à coordenada x atual e então passamos o f_rice[1] ao y .Isso muda a nossa posição x suficiente para a direita para nos permitir desencadear o tapete.

 

Observe que, após a chamada foldMat() , temos um long time.sleep() . O Mat leva bastante tempo para rolar, e os itens alimentares não podem ser clicados enquanto suas animações estão sendo executadas, então você só precisa esperar.

 

Etapa 16: Navegando no menu do telefone

Nesta etapa, definiremos todos os mousePos() para apontar para os itens de menu apropriados, mas vamos deixá-lo por agora. Isso faz parte do programa que será envolvido e controlado pela lógica do bot. Revisaremos esta função depois de obter algumas novas técnicas ao nosso dispor.

 

É necessário se cadastrar para acessar o conteúdo.

 

 

É por esta etapa. Vamos fazer mais com isso mais tarde.

 

Breve introdução: fazer o computador ver

Agora estamos chegando aos bits muito interessantes. Vamos começar a ver como fazer o computador ver os eventos na tela. Esta é uma parte muito emocionante do processo, e é fácil pensar em pensar.

 

Outra parte pura do edifício bot é que, eventualmente, o bot pode fornecer-nos, os programadores, com informações suficientes que mais trabalho visão não é necessária. Por exemplo, no caso do bot Sushi, uma vez que temos a primeira corrida nível, o bot está cuspindo dados suficientes precisas sobre o que está acontecendo na tela que todos nós temos que fazer a partir desse ponto é levar esses dados é "ver" e simplesmente dizer-lhe como reagir a ela.

 

Outra grande parte do edifício bot está aprendendo o jogo, sabendo que valores você precisa manter o controle da relação que você pode ignorar. Por exemplo, vamos fazer nenhum esforço para rastrear o dinheiro na mão. É apenas algo que acabou por ser irrelevante para o bot. Tudo o que precisa de saber é se ele tem comida suficiente para continuar trabalhando. Então ao invés de manter o controle sobre o total de dinheiro, ele simplesmente verifica para ver se ele pode pagar algo, independentemente do preço, porque, como ele funciona no jogo, é apenas uma questão de alguns segundos antes de você pode dar ao luxo de repor alguma coisa. Então, se não pode pagar agora, ele só tenta novamente em alguns segundos.

 

O que me leva ao meu ponto final. A do método de força bruta contra o elegante. Algoritmos de visão tomar o tempo de processamento valioso. Verificando vários pontos em muitas regiões diferentes de área de jogo pode rapidamente corroer o seu desempenho bot, por isso se resume a uma questão de "faz o bot necessidade de saber se _______ aconteceu ou não?".

 

Como exemplo, um cliente do jogo Sushi poderia ser pensado como tendo quatro estados: não presentes, esperando, comendo, e acabado de comer. Quando terminar, eles deixam um prato vazio piscando para trás. Eu poderia gastar o poder de processamento em verificar todos os locais placa encaixando todos os seis locais de placa e, em seguida, verificando contra um valor esperado (que é propenso a falhas desde as placas acendem e apagam, fazendo um falso negativo uma grande possibilidade), ou .. . Eu poderia apenas força bruta meu caminho através clicando em cada localização placa a cada poucos segundos. Na prática, isso é tão eficaz como a solução 'elegante' de deixar o bot determinar o estado do cliente. Clicando seis locais leva uma fração de segundo, onde como agarrar e processamento de seis imagens diferentes é relativamente lento.Podemos usar o tempo que salvou em outras tarefas mais importantes de processamento de imagem.

 

Passo 17: importar Numpy e ImageOps

Adicione o seguinte à sua lista de instruções de importação.

 

É necessário se cadastrar para acessar o conteúdo.

 

ImageOps é outro módulo PIL. Ele é usado para executar operações (como grayscaling) em uma imagem.

 

Vou explicar brevemente a segunda para aqueles que não estão familiarizados com o Python. Nossas demonstrações de importação padrão carrega namespace do módulo (uma coleção de nomes de variáveis e funções). Assim, para acessar itens no âmbito de um módulo, temos que empregar o module.attributesytax. No entanto, usando uma from ___ importdeclaração que herdam os nomes em nosso escopo local. Ou seja, a module.attributesintaxe não é mais necessária. Eles não são de alto nível, por isso usá-los como faria com qualquer função built-in outro Python nós, como str()ou list(). Ao importar Numpy desta forma, permite-nos simplesmente chamar array(), em vez de numpy.array().

 

O curinga *significa importar tudo do módulo.

 

Passo 18: Fazendo o computador See

O primeiro método que vamos explorar é a de verificar um valor RGB específica de um pixel contra um valor esperado. Este método é bom para coisas estáticas, como menus. Desde que se trata de pixels específicos, geralmente é um pouco demasiado frágil para objetos em movimento. no entanto, suas varia de caso para caso. Às vezes é a técnica perfeita, outra vez você vai ter que resolver um método diferente.

 

Abra Sushi Go Round no seu navegador e começar um novo jogo. Ignorar seus clientes e abrir o menu do telefone. Você começa com nenhum dinheiro no banco, assim que tudo deve ser acinzentado como abaixo. Estes serão os valores RGB vamos verificar.

 

greyed_out.png

 

Em code.py, vá até a sua screenGrab()função. Nós vamos fazer as seguintes alterações:

 

É necessário se cadastrar para acessar o conteúdo.

 

Nós fizemos duas pequenas mudanças. Na linha 5 comentamos a nossa declaração save. Na linha 6 que agora devolver o Imageobjeto para uso fora da função.

 

Salvar e executar o código. Nós vamos fazer algum trabalho mais interativo.

 

Com o menu coberturas aberto e todos os itens acinzentado, execute o seguinte código:

 

É necessário se cadastrar para acessar o conteúdo.

 

Este atribui o snap shot levarmos em screenGrab()para a instância im. Por aqui, podemos chamar o getpixel(xy)método para pegar dados de pixel específicas.

 

Agora precisamos de obter valores RGB para cada um dos itens acinzentado. Estes farão o nosso 'valor esperado' que o bot irá testar contra quando ele faz suas próprias getpixel()chamadas.

 

Nós já temos as coordenadas que precisamos das etapas anteriores, então tudo o que temos a fazer é passá-las como argumentos para getpixel()e observe a saída.

 

Saída da nossa sessão interativa:

 

É necessário se cadastrar para acessar o conteúdo.

 

Precisamos adicionar esses valores à nossa buyFood()função na forma que lhe permite saber se algo é ou não disponível.

 

É necessário se cadastrar para acessar o conteúdo.

 

Aqui nós passar um nome de ingrediente para a buyFood()função. Uma série de instruções if/ else é usado para capturar o parâmetro transmitido e responder adequadamente. Cada garfo segue a mesma lógica exata, por isso vamos explorar o primeiro.

 

É necessário se cadastrar para acessar o conteúdo.

 

A primeira coisa que fazemos após o if food é clicar sobre o telefone e abra o item de menu apropriado - neste caso o menu Rice.

 

É necessário se cadastrar para acessar o conteúdo.

 

Em seguida vamos dar uma visão rápida da área da tela e chamar getpixel()para obter um valor RGB para o pixel nas coordenadas de Cord.buy_rice. Em seguida, testamos este contra nosso valor RGB previamente estabelecido para quando o item é acinzentado. Se for avaliado como True, sabemos que o item não é mais acinzentado, e não temos dinheiro suficiente para comprá-lo. Consequentemente, se avaliada para False, não podemos permitir isso.

 

 

É necessário se cadastrar para acessar o conteúdo.

 

Fornecendo que podemos pagar o ingrediente, nós simplesmente navegar através das caixas restantes necessários para comprar a comida.

 

É necessário se cadastrar para acessar o conteúdo.

 

Finalmente, se não podemos pagar a comida, nós dizemos Python para fechar o menu, espere um segundo, e tente o processo novamente. Geralmente é apenas uma questão de segundos entre ser capaz de pagar algo contra não ser capaz de pagar algo. Nós não vai fazê-lo neste tutorial, mas é bastante simples para adicionar lógica adicional para esta função para permitir que o bot decidir se ele precisa continuar esperando até que ele pode pagar alguma coisa, ou se é livre para fazer outras tarefas e retornar à um momento posterior.

 

Passo 19: Manter o controle de Ingredientes

Tudo bem, agora vamos devagar, aos poucos, começar a substituir áreas onde nós, a entidade externa, fornecem entrada e tomada de decisão com a lógica que pode ser executado por si só.

 

Precisamos dispositivo de uma forma de manter o controle de quantas ingredientes que temos atualmente na mão. Nós poderíamos fazer isso através de ping na tela em determinadas áreas, ou pela média de cada caixa de ingrediente (nós vamos chegar a esta técnica mais tarde), mas, de longe, o método mais simples e mais rápido é apenas para armazenar todas as nos itens mão em um dicionário.

 

A quantidade de cada ingrediente se mantém constante ao longo de cada nível. Você sempre começará com 10 dos itens 'comuns' (arroz, nori, o ROE), e 5 dos itens 'premium' (camarão, salmão, unagi).

 

food_items.png

 

Vamos adicionar esta informação a um dicionário.

 

É necessário se cadastrar para acessar o conteúdo.

 

Nossas chaves de dicionário segure o nome do ingrediente, e nós vamos ser capazes de obter quantidade atual, explorando os valores.

 

Passo 20: adicionar monitoramento de Código

Agora que temos nosso dicionário de valores. Vamos trabalhar com isso no código. Cada vez que fazemos algo, vamos subtrair os ingredientes utilizados. Toda vez que nós compramos, nós vamos adicioná-los novamente.

 

Vamos começar pela expansão da makeFood()função

 

É necessário se cadastrar para acessar o conteúdo.

 

Agora, cada vez que fazemos um pedaço de Sushi, reduzimos os valores em nosso foodOnHanddicionário pelo valor apropriado. Em seguida, vamos ajustar buyFood () para adicionar valores.

 

É necessário se cadastrar para acessar o conteúdo.

 

Agora, cada vez que um ingrediente é comprado, nós adicionamos a quantidade ao valor dicionário apropriado.

 

Passo 21: Verificação comida na mão

Agora que temos o nosso makeFood()e buyFood()funções criadas para modificar o foodOnHanddicionário, é preciso criar uma nova função para monitorar todas as alterações e verificar se um ingrediente caiu abaixo de um certo limite.

 

É necessário se cadastrar para acessar o conteúdo.

 

Aqui montamos um forloop para percorrer os pares de chave e valor do nosso foodOnHanddicionário. Para cada valor, ele verifica se o nome é igual a um dos ingredientes que precisamos; se assim for, em seguida, verifica para ver se o seu valor for menor ou igual a 3; e, finalmente, desde que seja inferior a 3, ele chama buyFood()com o tipo de ingrediente como o parâmetro.

 

Vamos testar isso um pouco.

 

Tudo parece estar funcionando razoavelmente bem, então vamos passar para algumas tarefas mais reconhecimento de imagem.

 

Passo 22: Valores Atravessando RGB - Configuração

Para ir mais longe com a nossa bot, precisamos reunir informações sobre que tipo de sushi é em que o cliente bolha. Fazer isso com o getpixel()método seria muito trabalhoso porque você precisa encontrar uma área em cada balão de pensamento que tem um valor RGB único, não compartilhada por qualquer outra bolha sushi tipo / pensamento. Dada a arte do estilo do pixel, o que por sua própria natureza tem uma paleta de cores limitada, você teria que lutar toneladas de sobreposição de cores nos tipos de sushi. Além disso, para cada novo tipo de sushi introduzido ao longo do jogo, você teria que inspecioná-lo manualmente para ver se ele tem um RGB único não encontrado em qualquer um dos outros tipos de sushi. Uma vez encontrado, certamente seria em um coordenadas diferentes do que os outros para que significa armazenar cada vez mais os valores das coordenadas - 8 tipos de sushi por vezes bolha 6 locais de assento significa 48 únicas coordenadas necessário!

 

Assim, em resumo, precisamos de um método melhor.

 

Digite o método dois: resumo Imagem / média. Esta versão funciona fora de uma lista de valores RGB em vez de um pixel específico. Para cada instantâneo que tomar, a imagem é grayscaled, carregado em uma matriz, e então somados. Esta soma é tratado da mesma como o valor RGB no getpixel()método. Vamos usá-lo para testar e comparar várias imagens.

 

A flexibilidade deste método é tal que uma vez que ele está configurado, no caso do nosso sushi bot, não mais trabalho é necessário em nossa parte. À medida que novos tipos de sushi são introduzidos os seus valores RGB únicas são somados e impresso na tela para nosso uso. Não há nenhuma necessidade de perseguir quaisquer coordenadas mais específicos como com getpixel().

 

Dito isto, ainda há um pouco de configuração necessária para esta técnica. Nós vamos precisar para criar algumas novas caixas delimitadoras para que processar apenas a área da tela que precisamos, em vez de toda a área de jogo.

 

Vamos começar. Navegue até a sua screenGrab()função e fazer uma segunda cópia do mesmo. Renomeie a cópia para grab()e fazer as seguintes alterações:

 

É necessário se cadastrar para acessar o conteúdo.

 

Linha 2: Nós estamos fazendo uma screengrab assim como temos antes, mas agora estamos convertendo-o em tons de cinza antes de atribuí-lo à instância im. A conversão para tons de cinza faz percorrendo todos os valores de cor muito mais rápido; em vez de cada pixel com um valor vermelho, verde e azul, ele só tem um valor que varia de 0-255.

 

Linha 3: Nós criamos uma matriz de valores de cores da imagem, utilizando o método PIL getcolors()e atribuí-los à variávela

 

Linha 4: Nós somar todos os valores da matriz e imprimi-los para a tela. Estes são os números que usaremos quando comparamos duas imagens.

 

Passo 23: Ajuste New Bounding Boxes

Comece um novo jogo e esperar que todos os clientes para encher. Dê um duplo clique em quickGrab.pypara tirar um instantâneo da área de jogo.

 

bubble_highlight.png

 

Vamos precisar para definir caixas delimitadoras dentro de cada uma dessas bolhas.

 

Zoom até que você pode ver os detalhes finos dos pixels

 

bubble_zoome_medium.png

 

Para cada bolha, precisamos ter certeza de canto superior esquerdo da nossa caixa delimitadora começa no mesmo local. Para isso, contam-se duas 'bordas' da esquerda interior da bolha. Queremos que o pixel branco na segunda 'borda' para marcar a nossa primeira x, y local.

 

bubble_arrows.png

 

Para obter o par mais baixo, adicione 63 para a posição x, e 16 para o y. Isto lhe dará uma caixa semelhante à abaixo:

 

bubble_box.png

Não se preocupe que não estamos recebendo toda a imagem do tipo Sushi. Desde que nós estamos somando todos os valores, mesmo uma pequena mudança em um pixel mudará total e deixe-nos saber algo novo está na tela.

 

Nós vamos criar seis novas funções, cada uma versão especializada do nosso general grab()um, e preencher os seus argumentos delimitadoras com as coordenadas de todas as bolhas. Uma vez que aqueles são feitas, vamos fazer uma função simples para chamar tudo de uma vez, apenas para fins de teste.

 

É necessário se cadastrar para acessar o conteúdo.

 

OK!Lotes de código, mas é tudo apenas versões especializadas de funções previamente definidas. Cada define uma caixa delimitadora, e passa para ImageGrab.Grab. A partir daí, converter para uma matriz de valores RGB e imprimir a soma para a tela.

 

Vá em frente e executar isso algumas vezes durante o jogo. Certifique-se de verificar que cada tipo de sushi, independentemente de qual bolha que se encontra, mostra a mesma soma de cada vez.

 

Passo 24: Crie um dicionário Tipos sushi

Depois de verificar que cada um dos tipos de sushi é sempre exibir o mesmo valor, gravar suas somas em um dicionário da seguinte forma:

 

É necessário se cadastrar para acessar o conteúdo.

 

Ter os números como a chave e as cadeias como os valores irá tornar mais fácil para baralhar as coisas de função para função sem perder o controle de tudo.

 

Etapa 25: Criar uma classe nenhuma bolha

A etapa final da nossa reunião bolha está recebendo as somas para quando não há bolhas presentes. Vamos usá-los para verificar quando os clientes vêm e vão.

 

Comece um novo jogo e executar rapidamente get_all_seats()antes que alguém tem a chance de aparecer. Os números ele imprime vamos colocar em uma classe chamada Blank. Como antes, você poderia usar um dicionário, se preferir.

 

É necessário se cadastrar para acessar o conteúdo.

 

Estamos quase lá agora! Um passo final e teremos um bot simples, trabalhando!

 

Passo 26: Juntando tudo

Tempo para entregar finalmente fora de controle para o nosso bot. Nós vamos script na lógica básica que irá deixá-lo responder aos clientes, tornar as suas ordens, e reabastecer seus ingredientes quando o começam a escassear.

 

O fluxo básico vai seguir esta: Verifique assentos> se o cliente, fazer a ordem> verificar food> se baixa, comprar comida> tabelas claras> repeat .

 

Esta é longa;

vamos começar.

 

É necessário se cadastrar para acessar o conteúdo.

 

A primeira coisa que fazemos é verificar comida na mão. A partir daí, tirar um instantâneo de posição um e atribuir a soma de s1. Depois disso, verificar para ver que s1não é igual Blank.seat_1. Se não , nós temos um cliente. Nós verificamos o nosso sushiTypesdicionário para ver que tem uma soma igual ao nosso s1. Se isso acontecer, nós então chamar makeFood()e passar o sushiTypecomo um argumento.

 

Clear_tables() é chamada a cada dois assentos.

 

Apenas uma última peça restante: a configuração do loop.

 

 

Passo 27: loop principal

Nós vamos criar um loop while muito simples de jogar o jogo.

Nós não fazer qualquer tipo de mecanismo de ruptura, de modo a parar a execução, clique no shell e pressione Ctr

l + C para enviar uma interrupção de teclado.

 

É necessário se cadastrar para acessar o conteúdo.

 

E é isso!

 

Atualizar a página, carregar o jogo, e definir o seu bot perder.

 

 

Então, é um pouco desajeitado e na necessidade de refinamento, mas permanece como um esqueleto decente para que você possa interagir em cima.

 

Uma versão mais completa do bot pode ser encontrada aqui . Ele tem várias correções de como se manter a par do que está sendo feito, não ficar preso nos menus de telefone e outras otimizações gerais.

 

Conclusão

Agora você tem todas as ferramentas que você precisa para ir sobre a construção de seus próprios bots simples. As técnicas utilizadas neste tutorial são bastante primitiva no mundo da Computer Vision, mas ainda assim, com persistência o suficiente, você pode criar muitas coisas legais com eles - mesmo fora do reino da bots jogo. Nós, por exemplo, executar vários scripts baseados nestas técnicas para automatizar tarefas repetitivas de software ao redor do escritório. É muito gratificante para remover uma tarefa humana com apenas algumas linhas de código.

 

[spoiler=Créditos e link do artigo original]

https://code.tutsplus.com/tutorials/how-to-build-a-python-bot-that-can-play-web-games--active-11117

 

 

qRXaV1L.png

Link para o comentário
Compartilhar em outros sites

Este tópico está impedido de receber novos posts.
 Compartilhar

×
×
  • Criar Novo...

Informação Importante

Nós fazemos uso de cookies no seu dispositivo para ajudar a tornar este site melhor. Você pode ajustar suas configurações de cookies , caso contrário, vamos supor que você está bem para continuar.