Salimos en el top10. Telegram bot
Antecedentes
Todo comenzó con el hecho de que me enviaron un enlace a un bot en Telegram con una oferta para jugar.
Se ve algo como esto.

Después de mi primer juego, gané 28 puntos, un resultado no muy impresionante. Por lo tanto, no necesita nada en absoluto: un programa que encuentre palabras de las letras de la palabra original y una base de datos de sustantivos de palabras rusas.
Vamos
Decidí usar sqlite3 para la base de datos, es móvil y para esta tarea al máximo.
La estructura de la base se ve así.
CREATE TABLE IF NOT EXISTS words ( word VARCHAR(225) UNIQUE NOT NULL, length INTEGER NOT NULL );
- palabra: del nombre queda claro que este es el significado literal almacenado de la palabra.
- longitud - longitud del caracter.
Hay una estructura; para completarla, utilicé una lista de sustantivos de palabras rusas .
Al llenar la base de datos y buscar palabras, se decidió implementar en un código, el procesamiento se divide por banderas.
La creación del archivo base y la creación de la tabla también se implementan en 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) } }
Función Insert ()
Al agregar palabras, debe recordar que usamos el alfabeto cirílico, por lo que la función len()
habitual no nos conviene, usaremos utf8.RuneCountInString()
para calcular correctamente la longitud de la palabra.
Agregue la comprobación de errores if err.Error() != "UNIQUE constraint failed: words.word"
- necesario para la posibilidad de introducir nuevos diccionarios que contengan una copia de las palabras de la base de datos.
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 buscar las palabras que componen la fuente, es necesario descomponerla en letras. Una palabra puede contener varias letras idénticas, para contabilizar la cantidad que usamos map[rune]int
donde int
es el número de letras encontradas en la palabra.
func decay(word string) map[rune]int { var m = make(map[rune]int) for _, char := range word { m[char]++ } return m }
La búsqueda en sí se realiza en modo multiproceso, el número de gorutina = la longitud de la palabra original, menos una gorutina desde Comenzamos buscando palabras que consisten en dos o más letras.
Con este enfoque, el programa funcionó demasiado rápido y envió el número de respuestas = gorutina al chat al bot, aunque hubo time.Sleap(1 * time.Second)
en cada gorutina, esto llevó a bloquear mi Telegram de todos los dispositivos durante 10 minutos. Tomé esto en cuenta y en la versión actual puse un retraso en el envío, y puse la función de envío en una gorutina separada, que se comunica con el resto a través de un canal común. La búsqueda se lleva a cabo como antes.
Usamos waitGroup{}
como mecanismo para finalizar la búsqueda de todas las palabras de la base de datos y luego cerrar el 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 función de búsqueda selecciona de la base de datos todas las palabras con la longitud deseada y recorre el ciclo para verificar si la palabra es adecuada. La verificación se lleva a cabo en varias etapas. Debido al uso del map
creamos una nueva copia cada vez que completamos el ciclo del ciclo. Necesitamos una copia del map
para verificar el número de letras en una palabra, cada vez que una letra coincide, decrementamos el valor por una clave por una hasta que disminuya a cero, después de lo cual si dicha letra tiene un valor = 0, asignamos variable ontain=false
variable y cuando finaliza el ciclo, la palabra no se agregará al 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 } } }
Sigue siendo el caso para los pequeños, para que el programa mismo envíe las respuestas al chat. Como el bot no puede comunicarse con otro bot, tuve que usar mi cuenta personal. Decidí usar un cliente de código abierto.
Ejecutándolo en el puerto: 9090. Enviamos mensajes al chat al bot.
func send(in <-chan string) { conn, _ := net.Dial("tcp", "localhost:9090")
Comandos para iniciar rápidamente telegram-cli en debian.
Instale las bibliotecas requeridas.
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
Clonación de un repositorio.
git clone --recursive https://github.com/vysheng/tg.git && cd tg
La ejecución de la configuración.
./configure make
Lanzamiento del cliente en el puerto 9090
bin/telegram-cli -P 9090
Para que el cliente encuentre el bot, es necesario ejecutar el search WordsGame-bot
ya está en el cliente, luego verifique el resultado con el comando de msg WordsGame-bot test
, si después de las acciones no escribió la prueba de texto en el chat, intente jugar el juego personalmente.
Para que el cliente comience a trabajar, no olvide iniciar sesión, le sugerirá que inicie sesión por primera vez.
Todo parece estar listo. El programa puede llenar la base, así como jugar el juego con el bot, pero solo si usted mismo le pide palabras al bot.
Pero todo esto es lento, pero queremos tomar inmediatamente la primera línea, y para esto necesitamos enseñar al programa a solicitar palabras del bot. Crearemos una conexión y enviaremos el msg WordsGame-bot /play
, el bot tiene un retraso, por lo que esperamos 5 segundos. Después de lo cual, solicitamos el último mensaje de la historia con las history WordsGame-bot 1
Esta será la respuesta, o más bien la palabra que deberíamos usar como fuente. Para leer desde conn
cree la variable reply = make([]byte, 512)
. Después de obtener toda la respuesta con onn
se ve algo así.
history @manymanywords_bot 1 ANSWER 58 [16:10] WordsGame-bot »»»
Cree regexp.MustCompile("([-]{1,100})")
para buscar palabras del alfabeto cirílico. Después de lo cual, elegimos nuestra palabra.
else if *god { go send(out) for { var ( conn, _ = net.Dial("tcp", "localhost:9090")
Pero hay un problema, porque Cerramos el canal después de encontrar todas las palabras. Para solucionar esto, necesitamos la variable global GODMOD
. Agregue una condición para findSubWords
. Ahora, cuando usamos el modificador -g, la variable GODMOD se establece en verdadero y el canal no se cierra, y después de completar el ciclo, solicitamos una nueva palabra.
if !GODMOD { close(out) }
Ahora puedes mirar el resultado.

Enlaces utiles