Começamos no top10. Bot de telegrama
Antecedentes
Tudo começou com o fato de que eles me enviaram um link para um bot no Telegram com uma oferta para jogar.
Parece algo assim.

Depois do meu primeiro jogo, ganhei 28 pontos, um resultado não impressionante. Portanto, você não precisa de nada - um programa que encontra palavras das letras da palavra original e um banco de dados de substantivos de palavras russas.
Vamos lá
Decidi usar o sqlite3 para o banco de dados, ele é móvel e é mais para esta tarefa.
A estrutura da base se parece com isso.
CREATE TABLE IF NOT EXISTS words ( word VARCHAR(225) UNIQUE NOT NULL, length INTEGER NOT NULL );
- palavra - do nome fica claro que esse é o significado literal armazenado da palavra.
- comprimento - comprimento do caractere.
Existe uma estrutura: para preenchê-la, usei uma lista de substantivos de palavras russas .
Preenchendo o banco de dados e buscando palavras, decidiu-se implementar em um código, o processamento é dividido por sinalizadores.
A criação do arquivo base e a criação da tabela também são implementadas em init ()
func init() { var err error connection, err = sql.Open("sqlite3", "./words.db") if err != nil { log.Fatalf("Failed connection: %v", err) } _, err = connection.Exec(`CREATE TABLE IF NOT EXISTS words (word VARCHAR(225) UNIQUE NOT NULL, length INTEGER NOT NULL);`) if err != nil { log.Fatalf("Failed create database table words: %v", err) } }
Função Insert ()
Ao adicionar palavras, lembre-se de que usamos o alfabeto cirílico, e é por isso que a função len()
usual não nos convém, usaremos utf8.RuneCountInString()
para calcular corretamente o tamanho da palavra.
Adicione verificação de erro if err.Error() != "UNIQUE constraint failed: words.word"
- necessária para a possibilidade de introduzir novos dicionários que contêm uma cópia das palavras do banco de dados.
func insert(word string) error { _, err := connection.Exec("INSERT INTO words (word,length) VALUES(?,?)", word, utf8.RuneCountInString(word)) if err != nil && err.Error() != "UNIQUE constraint failed: words.word" { return err } return nil }
Para procurar as palavras que compõem a fonte, você precisa decompor em letras. Uma palavra pode conter várias letras idênticas, para calcular a quantidade que usamos map[rune]int
que int
é o número de letras encontradas na palavra.
func decay(word string) map[rune]int { var m = make(map[rune]int) for _, char := range word { m[char]++ } return m }
A pesquisa em si é realizada no modo multithread, o número de gorutina = o comprimento da palavra original, menos uma gorutina desde Começamos pesquisando por palavras que consistem em duas ou mais letras.
Com essa abordagem, o programa trabalhou muito rápido e enviou o número de respostas = gorutina para o bate-papo para o bot, embora houvesse time.Sleap(1 * time.Second)
em cada gorutina - isso levou a bloquear meu telegrama de todos os dispositivos por 10 minutos. Levei isso em consideração e, na versão atual, atrasei o envio e coloquei a função de envio em uma gorutina separada, que se comunica com o restante por um canal comum. A pesquisa é realizada como antes.
Usamos o waitGroup{}
como um mecanismo para finalizar a pesquisa de todas as palavras do banco de dados e fechar o canal.
func findSubWords(word string) { list := decay(word) for length := 2; length <= utf8.RuneCountInString(word); length++ { wg.Add(1) go func(out chan<- string, length int) { search(out, list, length) wg.Done() fmt.Println("Done: ", length) }(out, length) } wg.Wait() fmt.Println("search done") close(out) }
A função de pesquisa seleciona no banco de dados todas as palavras com o comprimento desejado e percorre o loop verificando se a palavra é adequada. A verificação é realizada em várias etapas. Devido ao uso do map
criamos uma nova cópia cada vez que concluímos o ciclo do loop. Precisamos de uma cópia do map
para verificar o número de letras em uma palavra, cada vez que uma letra corresponde, diminuímos o valor de uma chave por uma até que diminua para zero, após o qual, se essa letra tiver um valor = 0, atribuímos variável ontain=false
variável e quando o ciclo terminar, a palavra não será adicionada ao canal.
func search(out chan<- string, wordRuneList map[rune]int, length int) { wordList, err := selects(length) if err != nil { log.Printf("fail length %v, error: %v", length, err) } for _, word := range wordList { var ( wordCopyList = make(map[rune]int) contain = true ) for k, v := range wordRuneList { wordCopyList[k] = v } for _, r := range word { if _, ok := wordCopyList[r]; ok && wordCopyList[r] > 0 { wordCopyList[r]-- } else { contain = false break } } if contain { out <- word } } }
Continua sendo o caso dos pequenos, para que o próprio programa envie as respostas para o bate-papo. Como o bot não pode se comunicar com outro bot, tive que usar minha conta pessoal. Eu decidi usar um cliente de código aberto.
Executando-o na porta: 9090. Enviamos mensagens para o chat para o bot.
func send(in <-chan string) { conn, _ := net.Dial("tcp", "localhost:9090")
Comandos para iniciar rapidamente o telegram-cli no debian.
Instale as bibliotecas necessárias.
sudo apt install libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev libjansson-dev libpython-dev libgcrypt20 libz-dev make git
Clonando um repositório.
git clone --recursive https://github.com/vysheng/tg.git && cd tg
A execução da configuração.
./configure make
Lançamento do cliente na porta 9090
bin/telegram-cli -P 9090
Para que o cliente encontre o bot, é necessário executar o comando search WordsGame-bot
já no cliente e, em seguida, verifique o resultado com o msg WordsGame-bot test
, se após as ações que você não escreveu o teste de texto no chat, tente jogar o jogo pessoalmente.
Para que o cliente comece a trabalhar, não se esqueça de fazer login, ele sugerirá quando você fizer o login pela primeira vez.
Tudo parece estar pronto. O programa pode preencher a base e também jogar com o bot, mas somente se você pedir palavras do bot.
Mas tudo isso é lento, mas queremos pegar imediatamente a primeira linha e, para isso, precisamos ensinar o programa a solicitar palavras do bot. Vamos criar uma conexão e enviar o msg WordsGame-bot /play
, o bot tem um atraso, então esperamos 5 segundos. Depois disso, solicitamos a última mensagem da história com as history WordsGame-bot 1
Essa será a resposta, ou melhor, a palavra que devemos usar como fonte. Para ler do conn
crie a variável reply = make([]byte, 512)
. Depois de obtermos toda a resposta com onn
ela se parece com isso.
history @manymanywords_bot 1 ANSWER 58 [16:10] WordsGame-bot »»»
Crie regexp.MustCompile("([-]{1,100})")
para procurar palavras do alfabeto cirílico. Depois disso, escolhemos nossa palavra.
else if *god { go send(out) for { var ( conn, _ = net.Dial("tcp", "localhost:9090")
Mas há um problema, porque fechamos o canal depois que encontramos todas as palavras. Para consertar isso, precisamos da variável global GODMOD
. Adicione uma condição para findSubWords
. Agora, quando usamos a opção -g, a variável GODMOD é configurada como true e o canal não fecha e, após concluir o loop, solicitamos uma nova palavra.
if !GODMOD { close(out) }
Agora você pode ver o resultado.

Links úteis