Break out the top10. Telegraph bot

Break out the top10. Telegram bot


Prehistory


It all started with the fact that they sent me a link to the bot in the Telegram with a proposal to play.
It looks like this.



After my first game I earned 28 points, not very impressive result. So you need nothing at all - a program that finds words from the letters of the original word and the base of nouns of Russian words.


Go


For the base I decided to use sqlite3, it is mobile and for this task the most.


The structure of the base looks like this.


CREATETABLEIFNOTEXISTS words (
    word VARCHAR(225) UNIQUENOTNULL,
    lengthINTEGERNOTNULL
);

  • word - from the name it is clear that this is the stored literal meaning of the word.
  • length - character length.

There is a structure, to fill it, I used the list of nouns of Russian words .


It was decided to implement database filling and word search in one code, to separate processing by flags.


Also, the very creation of the base file and the creation of the table are implemented in init ()


funcinit() {
    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)
    }
}

Insert () function


When adding words, it is necessary to remember that we use Cyrillic, which is why the usual function len()does not suit us, we will use utf8.RuneCountInString()to calculate the word length correctly.


We add error checking if err.Error() != "UNIQUE constraint failed: words.word"- it is necessary to enable the introduction of new dictionaries that contain a copy of words from the database.


funcinsert(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
    }
    returnnil
}

To search for words included in the original, it is necessary to decompose it into letters. A word may contain several identical letters, to account for the number we use map[rune]intwhere intthis is the number of found letters in the word.


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

The search itself is carried out in multi-threaded mode, the number of gorutine = the length of the source word, minus one gorutine because Start by searching for words consisting of two or more letters.


With this approach, the program worked too fast and sent to the chat to the bot the number of answers = gorutine, although there was a gorutine in each time.Sleap(1 * time.Second)- this led to blocking my Telegram from all devices for 10 minutes. I took this into account and in the current version I set the delay for sending, and I sent the sending send to a separate gorutine, which communicates with the others through a common channel. The search is carried out as before.

We use waitGroup{}as a mechanism for ending the search for all words from the database, after which we close the channel.


funcfindSubWords(word string) {
    list := decay(word)
    for length := 2; length <= utf8.RuneCountInString(word); length++ {
        wg.Add(1)
        gofunc(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)
}

The search function selects from the database all the words with the desired length and goes through the loop to check if the word is appropriate. Verification is carried out in several stages. Because of the use of mapcreating a new copy every time we complete the passage through the cycle. mapWe need a copy to check for the number of letters in a word, each time a letter coincides, we decrement the value by key by one until it decreases to zero, after which such a letter has a value = 0, we assign the variable to the variable сontain=falseand at the end of the cycle will not be added to the channel.


funcsearch(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 = falsebreak
            }
        }
        if contain {
            out <- word
        }
    }
}

It remains the case for small, so that the program itself sends the answers to the chat. Since a bot with another bot cannot communicate, I had to use my personal account. I decided to use an open source client .


Running it on the port: 9090. We send chat messages to the bot.


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

Commands for quick launching telegram-cli on debian.


Install the necessary libraries.


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

Repository cloning


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

Execution configuration.


./configure
make

Run client on port 9090


bin/telegram-cli -P 9090

In order for the client to find the bot, you must already execute the command in the client search WordsGame-bot, then check the result with the command msg WordsGame-bot test, if after the actions you did not write the text test to the bot, try to play the game in person.
In order for the client to start working, do not forget to log in, he will offer when you log in for the first time.

Everything seems to be ready. The program can fill the base as well as play the game with the bot, but only if you yourself will request the word from the bot.


But all this is slow, but we want to immediately take the first line, and for this we need to teach the program to request words from the bot. Let's create a connection and send the command msg WordsGame-bot /play. The bot has a delay, so we wait 5 seconds. After that we request the last message from the history with the bot, history WordsGame-bot 1this will be the answer, or rather the word that we should use as the initial one. To read from conncreate a variable reply = make([]byte, 512). After we got the whole answer with сonnit looks something like this.


    history @manymanywords_bot 1
    ANSWER 58
    [16:10]  WordsGame-bot »»» дорабатывание

Create a regexp.MustCompile("([аА-яЯ]{1,100})")search for words from the Cyrillic alphabet. After that, choose our word.


elseif *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)
        iflen(word) <= 0 {
            log.Fatalf("somthing wrong %s", reply)
        }
        findSubWords(word[0])
        time.Sleep(5 * time.Minute)
    }

But there is a problem, because we closed the channel after we found all the words. To fix this we need a global variable GODMOD. Add to the findSubWordscondition. Now when we use the -g switch, the GODMOD variable is translated to true and the channel is not closed, and after the completion of the cycle, we request a new word.


if !GODMOD {
        close(out)
    }

Now you can look at the result.



useful links



Also popular now: