我们在前10名中突围。 电报机器人

我们在前10名中突围。 电报机器人


背景知识


一切始于他们向我发送了指向Telegram中的一个机器人的链接并提供了玩意。
看起来像这样。



在我的第一场比赛之后,我获得了28分,并不是一个令人印象深刻的结果。 因此,您根本不需要任何东西-可以从原始单词的字母中查找单词的程序以及俄语单词的名词数据库。


走吧


我决定将sqlite3用于数据库,它是可移动的,并且最适合此任务。


底座的结构如下所示。


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

  • 单词-从名称可以明显看出,这是单词的存储字面意思。
  • 长度-字符长度。

有一个结构;为填充该结构,我使用了俄语单词的名词列表。


填充数据库并搜索单词后,决定在一个代码中实施,处理过程由标志分开。


基本文件的创建和表的创建也在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) } } 

插入()函数


在添加单词时,您需要记住我们使用西里尔字母,这就是为什么通常的len()函数不适合我们的原因,我们将使用utf8.RuneCountInString()正确计算单词长度。


if err.Error() != "UNIQUE constraint failed: words.word"添加错误检查-可能需要引入新字典,其中包含来自数据库的单词的副本。


 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 } 

要搜索构成来源的单词,您需要将其分解为字母。 一个单词可能包含几个相同的字母,为了说明数量,我们使用map[rune]int ,其中int是单词中找到的字母的数量。


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

搜索本身是在多线程模式下进行的,gorutine的数量=原单词的长度减去一个gorutine,因为 我们首先搜索由两个或多个字母组成的单词。


用这种方法,程序运行得太快了,尽管有时间,但每次聊天中都会有( time.Sleap(1 * time.Second) time.Second time.Sleap(1 * time.Second)的聊天程序发送答案= gorutine到机器人,这导致了我的电报无法在所有设备上使用10分钟。 我考虑到了这一点,在当前版本中,我延迟了发送,并将发送功能本身放在单独的gorutine中,该gorutine通过通用通道与其余函数进行通信。 像以前一样进行搜索。

我们使用waitGroup{}作为一种机制来结束对数据库中所有单词的搜索,然后关闭通道。


 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) } 

搜索功能从数据库中选择具有所需长度的所有单词,然后循环检查单词是否合适。 验证分多个阶段进行。 由于使用了map每次完成循环时我们都会创建一个新副本。 我们需要一个map的副本来检查单词中字母的数量,每次字母匹配时,我们将一个键的值减一直到其减为零,然后如果该字母的值= 0, ontain=false变量ontain=false赋给变量,并且当循环结束时,该单词将不会添加到通道中。


 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 } } } 

小型情况仍然如此,以便程序本身将答案发送到聊天室。 由于该漫游器无法与其他漫游器通信,因此我不得不使用我的个人帐户。 我决定使用开源客户端


在端口上运行它:9090。 我们将聊天消息发送给机器人。


 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) } } 

在debian上快速启动telegram-cli的命令。


安装所需的库。


 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 

克隆存储库。


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

配置的执行。


 ./configure make 

客户端在端口9090上启动


 bin/telegram-cli -P 9090 

为了让客户端找到该机器人,有必要运行客户端中已经存在的search WordsGame-bot命令,然后使用msg WordsGame-bot test命令检查结果,如果在您未将文本测试写入聊天室之后,尝试亲自玩游戏。
为了使客户端开始工作,请不要忘记登录,他会在您首次登录时建议您。

一切似乎都准备就绪。 该程序可以填补基础以及与机器人一起玩游戏,但前提是您自己要求机器人发出声音。


但这一切都很缓慢,但是我们要立即走第一行,为此,我们需要教程序向机器人请求单词。 我们将创建一个连接并发送msg WordsGame-bot /play命令,该bot有延迟,因此我们等待5秒钟。 之后,我们使用“ history WordsGame-bot 1请求故事中的最后一条消息,这将是答案,或者更确切地说,是我们应该用作来源的单词。 要从conn读取conn创建变量reply = make([]byte, 512) 。 用onn得到完整答案后onn它看起来像这样。


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

创建regexp.MustCompile("([-]{1,100})")来搜索西里尔字母。 之后,我们选择单词。


 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) } 

但是有一个问题,因为 找到所有单词后,我们关闭了频道。 要解决此问题,我们需要全局GODMOD变量。 将条件添加到findSubWords 。 现在,当我们使用-g开关时,GODMOD变量将设置为true,并且通道不会关闭,并且在完成循环之后,我们需要一个新单词。


  if !GODMOD { close(out) } 

现在您可以查看结果。



有用的链接


Source: https://habr.com/ru/post/zh-CN432278/


All Articles