نحن تندلع في top10. البرقية بوت
الخلفية
بدأ كل شيء بحقيقة أنهم أرسلوا لي رابطًا إلى روبوت في 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()
، وسوف نستخدم 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 واحد منذ نبدأ بالبحث عن كلمات تتكون من حرفين أو أكثر.
من خلال هذا النهج ، عمل البرنامج بسرعة كبيرة وأرسل عدد الإجابات = gorutine إلى الدردشة إلى الروبوت ، على الرغم من أنه كان هناك time.Sleap(1 * time.Second)
في كل gorutine - هذا أدى إلى حظر Telegram الخاص بي من جميع الأجهزة لمدة 10 دقائق. لقد أخذت هذا الأمر في الاعتبار وفي الإصدار الحالي ، قمت بتأخير الإرسال ، وأضع وظيفة الإرسال نفسها في نطاق منفصل ، والذي يتصل بالباقي عبر قناة مشتركة. يتم البحث كما كان من قبل.
نستخدم 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
للمتغير و عندما تنتهي الدورة ، لن تتم إضافة الكلمة إلى القناة.
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")
أوامر لإطلاق 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
لكي يتمكن العميل من العثور على bot ، من الضروري تشغيل أمر search WordsGame-bot
بالفعل في العميل ، ثم تحقق من النتيجة باستخدام الأمر msg WordsGame-bot test
، إذا كنت بعد الإجراءات لم تكتب اختبار النص إلى دردشة الدردشة ، جرب لعب اللعبة معه شخصيًا.
لكي يبدأ العميل العمل ، لا تنسَ تسجيل الدخول ، سيقترح عليك تسجيل الدخول عند تسجيل الدخول لأول مرة.
يبدو أن كل شيء جاهز. يمكن للبرنامج ملء الأساس وكذلك لعب اللعبة مع الروبوت ، ولكن فقط إذا طلبت أنت بنفسك كلمات من الروبوت.
لكن كل هذا بطيء ، لكننا نريد أن نأخذ السطر الأول على الفور ، ولهذا نحتاج إلى تعليم البرنامج لطلب كلمات من الروبوت. سنقوم بإنشاء اتصال وإرسال الأمر msg WordsGame-bot /play
، msg WordsGame-bot /play
له تأخير ، لذلك نحن ننتظر 5 ثوان. بعد ذلك نطلب الرسالة الأخيرة من القصة مع history WordsGame-bot 1
سيكون هذا هو الجواب ، أو بالأحرى الكلمة التي يجب أن نستخدمها كمصدر. للقراءة من conn
بإنشاء reply = make([]byte, 512)
المتغير reply = make([]byte, 512)
. بعد أن حصلنا على الإجابة كاملة مع 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")
ولكن هناك مشكلة ، لأن أغلقنا القناة بعد أن وجدنا كل الكلمات. لإصلاح ذلك ، نحتاج إلى متغير GODMOD
العالمي. إضافة شرط إلى findSubWords
. الآن عندما نستخدم رمز التبديل -g ، يتم تعيين متغير GODMOD إلى صواب ولا تغلق القناة ، وبعد الانتهاء من الحلقة ، نطلب كلمة جديدة.
if !GODMOD { close(out) }
الآن يمكنك إلقاء نظرة على النتيجة.

روابط مفيدة