Nous sortons en top10. Télégramme bot

Nous sortons en top10. Télégramme bot


Contexte


Tout a commencé avec le fait qu'ils m'ont envoyé un lien vers un bot dans Telegram avec une offre de jouer.
Cela ressemble à ceci.



Après mon premier match, j'ai gagné 28 points, ce qui n'est pas un résultat très impressionnant. Vous n'avez donc besoin de rien du tout - un programme qui trouve des mots à partir des lettres du mot d'origine et une base de données de noms de mots russes.


On y va


J'ai décidé d'utiliser sqlite3 pour la base de données, elle est mobile et pour cette tâche le plus.


La structure de la base ressemble à ceci.


CREATE TABLE IF NOT EXISTS words ( word VARCHAR(225) UNIQUE NOT NULL, length INTEGER NOT NULL ); 

  • mot - du nom, il est clair que c'est la signification littérale stockée du mot.
  • longueur - longueur de caractère.

Il y a une structure; pour la remplir, j'ai utilisé une liste de noms de mots russes .


En remplissant la base de données et en recherchant des mots, il a été décidé de l'implémenter en un seul code, le traitement est divisé par drapeaux.


La création du fichier de base et la création de la table sont également implémentées dans 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) } } 

Fonction Insert ()


Lors de l'ajout de mots, vous devez vous rappeler que nous utilisons l'alphabet cyrillique, c'est pourquoi la fonction len() habituelle ne nous convient pas, nous utiliserons utf8.RuneCountInString() pour calculer correctement la longueur du mot.


Ajoutez une vérification d'erreur if err.Error() != "UNIQUE constraint failed: words.word" - nécessaire pour la possibilité d'introduire de nouveaux dictionnaires contenant une copie des mots de la base de données.


 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 } 

Pour rechercher les mots qui composent la source, vous devez la décomposer en lettres. Un mot peut contenir plusieurs lettres identiques, pour comptabiliser la quantité que nous utilisons map[rune]intint est le nombre de lettres trouvées dans le mot.


 func decay(word string) map[rune]int { var m = make(map[rune]int) for _, char := range word { m[char]++ } return m } 

La recherche elle-même est effectuée en mode multithread, le nombre de gorutine = la longueur du mot d'origine, moins un gorutine depuis Nous commençons par rechercher des mots composés de deux lettres ou plus.


Avec cette approche, le programme a fonctionné trop rapidement et a envoyé le nombre de réponses = gorutine au chat au bot, bien que chaque gorutine ait le time.Sleap(1 * time.Second) - cela a conduit à bloquer mon télégramme de tous les appareils pendant 10 minutes. J'ai pris cela en compte et dans la version actuelle, j'ai défini un délai pour l'envoi et j'ai mis la fonction d'envoi elle-même dans un gorutine séparé, qui communique avec les autres via un canal commun. La recherche est effectuée comme précédemment.

Nous utilisons waitGroup{} comme mécanisme pour terminer la recherche de tous les mots de la base de données, puis fermons le 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) } 

La fonction de recherche sélectionne dans la base de données tous les mots de la longueur souhaitée et parcourt la boucle en vérifiant si le mot convient. La vérification s'effectue en plusieurs étapes. En raison de l'utilisation de la map nous créons une nouvelle copie chaque fois que nous terminons le cycle de boucle. Nous avons besoin d'une copie de la map pour vérifier le nombre de lettres dans un mot, chaque fois qu'une lettre correspond, nous décrémentons la valeur d'une clé par une jusqu'à ce qu'elle diminue à zéro, après quoi si une telle lettre a une valeur = 0, nous assignons variable ontain=false variable et à la fin du cycle, le mot ne sera pas ajouté au 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 } } } 

Cela reste le cas pour les petits, afin que le programme lui-même envoie les réponses au chat. Étant donné que le bot ne peut pas communiquer avec un autre bot, j'ai dû utiliser mon compte personnel. J'ai décidé d'utiliser un client open source .


Exécuter sur le port: 9090. Nous envoyons des messages au chat au bot.


 func send(in <-chan string) { conn, _ := net.Dial("tcp", "localhost:9090") // conncect to client telegram for word := range in { fmt.Fprintf(conn, "msg WordsGame-bot %v\n", word) time.Sleep(5 * time.Second) } } 

Commandes pour lancer rapidement telegram-cli sur debian.


Installez les bibliothèques requises.


 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 

Clonage d'un référentiel.


 git clone --recursive https://github.com/vysheng/tg.git && cd tg 

L'exécution de la configuration.


 ./configure make 

Lancement client sur le port 9090


 bin/telegram-cli -P 9090 

Pour que le client trouve le bot, il est nécessaire d'exécuter la commande de search WordsGame-bot déjà dans le client, puis de vérifier le résultat avec la commande de msg WordsGame-bot test , si après les actions vous n'avez pas écrit le test de texte dans le chat, essayez de jouer au jeu avec lui personnellement.
Pour que le client commence à travailler, n'oubliez pas de vous connecter, il vous proposera lors de votre première connexion.

Tout semble prêt. Le programme peut remplir la base et jouer au jeu avec le bot, mais seulement si vous demandez vous-même des mots au bot.


Mais tout cela est lent, mais nous voulons prendre immédiatement la première ligne, et pour cela, nous devons apprendre au programme à demander des mots au bot. Nous allons créer une connexion et envoyer la commande msg WordsGame-bot /play , le bot a un délai, nous attendons donc 5 secondes. Après quoi nous demandons le dernier message de l'histoire avec les history WordsGame-bot 1 l' history WordsGame-bot 1 Ce sera la réponse, ou plutôt le mot que nous devrions utiliser comme source. Pour lire à partir de conn créez la variable reply = make([]byte, 512) . Après avoir obtenu la réponse complète avec onn cela ressemble à ceci.


  history @manymanywords_bot 1 ANSWER 58 [16:10] WordsGame-bot »»»  

Créez regexp.MustCompile("([-]{1,100})") pour rechercher des mots de l'alphabet cyrillique. Après quoi, nous choisissons notre mot.


 else if *god { go send(out) for { var ( conn, _ = net.Dial("tcp", "localhost:9090") // conncect to client telegram reply = make([]byte, 512) r = regexp.MustCompile("([-]{1,100})") ) fmt.Fprintln(conn, "msg WordsGame-bot /play") time.Sleep(5 * time.Second) fmt.Fprintln(conn, "history WordsGame-bot 1") time.Sleep(2 * time.Second) _, err := conn.Read(reply) if err != nil { log.Fatalf("failed read connection %v", err) } word := r.FindAllString(string(reply), 1) if len(word) <= 0 { log.Fatalf("somthing wrong %s", reply) } findSubWords(word[0]) time.Sleep(5 * time.Minute) } 

Mais il y a un problème, car nous avons fermé le canal après avoir trouvé tous les mots. Pour résoudre ce problème, nous avons besoin de la variable globale GODMOD . Ajoutez une condition pour findSubWords . Maintenant, lorsque nous utilisons le commutateur -g, la variable GODMOD est définie sur true et le canal ne se ferme pas, et après avoir terminé la boucle, nous demandons un nouveau mot.


  if !GODMOD { close(out) } 

Vous pouvez maintenant regarder le résultat.



Liens utiles


Source: https://habr.com/ru/post/fr432278/


All Articles