من علاقه زیادی به حل کردن پازل دارم. مخصوصاً پازل کلمات و کلاً هر بازی دیگهای که با متن و کلمه سر و کار داره. اما همیشه یک مشکلی دارم. اونم اینه که حافظهام خیلی وقتا یاری نمیکنه و کلمات یادم نمیاد. چند روز پیش دستم خورد و از بوکمارکهای مرورگر، بازی واجور باز شد. خیلی وقت بود بازی نکرده بودم. چندتا از پازلهاشو حل کردم ولی خیلی طول میکشید تا کلمات یادم بیاد و معمولاً از ۶ حدس رد میشد. رفتم سراغ بازی نسخه اصلی یا همون Wordle. این بازی در اوایل انتشارش خیلی معروف شد و خیلی از افراد بطور روزانه اونو بازی میکردن و توی سوشال مدیا مراحلشون رو به اشتراک میذاشتن.
با توجه به مطالعات اخیرم در حوزه هوش مصنوعی مخصوصاً زیرشاخه پردازش زبان طبیعی (NLP)، به فکرم زد این چالش رو در قالب یک مسئله NLP حل کنم. البته بیشتر یک مسئله از نوع علوم داده و آمار است. با این حال این کار رو انجام دادم و خواستم که مراحل انجامش رو بنویسم. تا برای افرادی که تازه وارد این حوزه شدن، یک تمرین فان باشه. یا حتی برای افراد کم حافظهای مثل خودم یک ابزار خوب باشه 🙂
بازی Wordle چیه؟
در این بازی شما باید یک کلمه ۵ حرفی رو حدس بزنید. و فقط ۶بار فرصت دارید تا کلمه مد نظر رو پیدا کنید. بازی با وارد کردن هر کلمه، به شما راهنمایی میکنه که چه حروفی از اون کلمه درست هستن و چه حروفی نه.
مثلا بعنوان حدس اول. من کلمه APPLE رو وارد کردم. حروفی که به رنگ زرد درآمدند، در کلمه اصلی وجود دارند اما در جایگاه دیگر. حروفی که سبز هستند، در کلمه وجود دارند و جایگاهشونم درسته. اما حروف خاکستری در کلمه وجود ندارند و نباید در حدسهای بعدی استفاده بشن. حالا با دیدن این راهنمایی. من باید دنبال کلمه ۵ حرفی باشم که حرف سومش p باشه و شامل حروف a، p و l هم باشه.
چطوری شانس برد رو زیاد کنیم؟
بطور کلی چندتا نکته باید رعایت بشه:
- هیچوقت از حروف حذف شده (خاکستری) مجدداً استفاده نکنیم.
- هیچوقت حرفی که میدونیم در یک جایگاه نیست (خانه زرد) رو در اون جایگاه استفاده نکنیم.
- همیشه قالب کلی کلمه رو با توجه به جایگاههای درست (خانههای سبز) در نظر بگیریم و بعد جایگاههای خالی رو پر کنیم.
اما چندتا نکته مهم دیگه هست که باعث میشه در قدمهای اول دید بهتری نسبت به کلمه جواب پیدا کنیم:
- با کلمهای شروع کنیم که مرسومترین حروف الفبارو داشته باشه. معمولاً بهتره شامل دو یا بیشتر حروف صدادار باشه. مثل SERAI.
- از کلماتی که عموماً پرتکرار هستن استفاده کنیم.
با پایتون حلش کنیم
بیاید برنامهای بنویسیم که نکات بالارو برامون رعایت کنه و از تمام کلمات موجود انگلیسی یا فارسی برامون محتملترین کلمات رو انتخاب کنه. حالا ما میتونیم راحتتر پازل رو حل کنیم. در این پروژه از کتابخانه Pandas برای ساخت و مدیریت دیتاست استفاده شده.
آمادهسازی دیتاست
انتظار ندارید که پایتون کلمات رو خلق کنه؟ 🙂 خب معلومه که باید کلمات رو بهش بدیم و اون هم داخلشون جستجو کنه. مثل کاری که ما توی ذهنمون میکنیم.
کلمات پرتکرار
در قدمهای اول بازی که ممکنه دید کاملی نسبت به کلمه نداشته باشیم، حالتهای خیلی زیادی برای انتخاب کلمه وجود داره. یا حتی اگر گاهی بین انتخاب چند کلمه محدود شک داشتیم، میتونیم از کلمهای که مرسومتر و پرتکرارتره استفاده کنیم. برای اینکه بدونیم کدوم کلمه پرتکراره. نیاز به تهییه یک دیتاست داریم. کاری که من کردم پیدا کردن فهرستی از کلمات با معنی و دیتای صفحات ویکیپدیا بود. و تنها کاری که کردم این بود که تعداد تکرار هر کلمه از دیکشنری رو در متنون صفحات ویکیپدیا بشمارم و نتیجه رو در یک دیتاست ذخیره کنم.
import pandas as pd
wikipedia = pd.read_csv("datasets/en-wikipedia.csv") # should include content of each page
words = pd.read_csv("datasets/en-words.csv") # should include unique meaningful words
words_count = {w: 0 for w in words["word"]}
for content in wikipedia["content"]:
for word in content.split():
if (w := word.strip()) in words_count:
words_count[w] += 1
df = pd.DataFrame({"word": words_count.keys(), "count": words_count.values()})
df.to_csv("english_unigram_freq.csv", index=False)
دیتاست مربوط به کلمات و ویکیپدیارو میتونید توی اینترنت پیدا کنید. برای این بازی میتونید از این دیتاست آماده سایت Kaggle هم استفاده کنید که بهاندازه کافی خوبه.
حروف پرتکرار
معمولاً برای شروع بازی بهتره از کلماتی استفاده کنیم که حروف پرتکرار دارند. مثلا در انگلیسی حرف a در اکثریت کلمات استفاده شده. یا بقیه حروف صدادار. پس اگر کلمهای انتخاب کنیم که شامل این حروف باشه، به جواب نزدیکتر میشیم. در اینجا من یک برنامه نوشتم که حروف یکتای کلمات رو بشماره. بعد بیاد به ازای هر کلمه، مجموع تعداد حروف پرتکرارش رو حساب کنه و ذخیره کنه. مثلا اگر داشته باشیم:
- a: 10
- p: 4
- l: 7
- e: 12
امتیاز پرتکرار بودن کلمه apple میشه 10 + 4 + 7 + 12 = 33. توجه داشته باشید که امتیاز حروف یکتای کلمه رو جمع زدیم و مثلا برای p فقط یکبار از 4 استفاده کردیم. دلیل این کار اینه که میخوایم از دادن امتیاز بالا به کلماتی که حروف تکراری دارن جلوگیری کنیم.
from collections import defaultdict
import pandas as pd
dataset_path = "datasets/english_unigram_freq.csv"
word_length = 5
output_path = "datasets/5_letter_scored_english_words.csv"
words = pd.read_csv(dataset_path) # Dataset should have at least 'word' column.
words["word"] = words["word"].str.lower()
# Count common letters in all words.
letters = defaultdict(int)
for word in words["word"]:
for letter in str(word):
letters[letter] += 1
words["score"] = words["word"].map(lambda w: sum(letters[l] for l in set(str(w))))
words = words[words["word"].str.len() == word_length]
words.to_csv(output_path, index=False)
دیتاست مورد استفاده در این کد باید دیتاستی از کلمات و تعداد تکرارشون باشه که قبلتر آماده کردیم. من این دیتاست رو برای زبان انگلیسی و فارسی آماده کردیم و در اختیار شما قرار میدم.
نکتهای که اینجا باید توجه کنید اینه که برای کم کردن حجم دیتاست، فقط کلمات ۵ حرفی رو جدا کردیم. اگر میخواید این برنامه رو برای بازیهای با کلمات کمتر یا بیشتر طراحی کنید، باید این مقدار word_length رو تغییر بدید و یک دیتاست جدید ایجاد کنید.
اولین حدس
اولین حدسی که میزنیم خیلی مهمه. این کلمه اگر حروف مناسبی داشته باشه. میتونه وضعیت خیلی از حروف دیگرو برامون مشخص کنه. اگر خاطرتون باشه ما قبلاً دیتاستی از کلمات و امتیاز پرتکرار بودن حروفشون تهییه کردیم. حالا میتونیم ۵تا از کلماتی که بیشترین امتیاز رو دارن انتخاب کنیم. وقتی این کلمات رو استفاده کنیم، به خاطر اشتراک حرفشون با اکثر کلمات، وضعیت تقریباً بیشتر حروف رو مشخص میکنن.
words_df.sort_values("score", ascending=False).head(5)
خروجی این دستور طبق دیتاستی که ما آماده کردیم اینطوری میشه:
خب همونطور که میبینید چون حروف این ۵تا کلمه یکسانه. امتیاز حروفشون هم یکسانه. پس میتونیم یکبار دیگه هم اونارو بر اساس پرتکرار بودن کلمات مرتب کنیم:
words.sort_values(by="score", ascending=False).head(5).sort_values(by="count", ascending=False)
نکته: بعضی از این کلمات ممکنه در دایره واژگان بازی Wordle تعریف نشده باشه. من اونارو حذف نکردم و بهجاش در هر مرحله ۵ پیشنهاد به کاربر میدم تا از بینشون انتخاب کنه.
حدسهای بعدی
حالا که کلمه رو وارد بازی کردیم. بازی بهمون نتیجه رو اعلام میکنه. ماهم باید اونو در قالب کد در بیاریم:
incorrect_positions = defaultdict(set)
correct_letters = set()
incorrect_letters = set()
word_pattern = ["\w", "\w", "\w", "\w", "\w"]
- incorrect_positions: این یک دیکشنری است که کلید اون حرف و مقدارش فهرستی از جایگاههای اشتباه برای اون حرفه. حروفی که با رنگ زرد مشخص میشن باید جایگاهشون وارد این دیکشنری بشه.
- correct_letters: مجموعه حروفی که با رنگ زرد و سبز مشخص شدن.
- incorrect_letters: مجموعه حروفی که با رنگ خاکستری مشخص شدن.
- word_pattern: الگوی کلی کلمه. که در قدم اول یک کلمه ۵ حرفی رو نشون میده. این الگو با عبارات باقاعده (regex) نوشته میشه و در طول روند برنامه آپدیت میشه.
حالا کافیه روی دیتاستی که در اختیار داریم. با توجه به قواعدی که استخراج کردیم، یک جستجوی ساده انجام بدیم:
pattern = re.compile("".join(word_pattern))
filter_func = lambda w: all(l in w for l in correct_letters) \
and not any(l in w for l in incorrect_letters) \
and not any(w.index(l) in incorrect_positions[l] for l in w)
candidates = words_df[words_df.index.str.match(pattern) & words_df.index.map(filter_func)]
candidate_words = candidates.sort_values("count", ascending=False).head(5) \
.sort_values("score", ascending=False).index.to_list()
در اینجا تمامی شرایط بازی، مثل حروف درست و نادرست و جایگاههای نادرست رو لحاظ میکنیم و در کل کلمات ۵ حرفی جستجو میکنیم.
اما حواستون باشه که اینجا موقع مرتبسازی اول بر اساس count (تکرار کلمات) مرتب کردیم و بعد بر اساس score (مجموع تکرار حروف). دلیلشم اینه که معمولاً کلمه هدف این پازلها حروف مرسومه و با این روش انتخاب بهتری میکنیم. اما شما میتونید جایگاهشونو عوض کنید و تست کنید.
مثلا برای کلمه APPLE که در اول پست مثالشو آوردم. اطلاعات متغیرها به این صورت باید باشه:
Pattern: \w\wp\w\w
Correct Letters: {'l', 'p', 'a'}
Incorrect Letters: {'e'}
Incorrect Positions: {'a': {0}, 'p': {1}, 'l': {3}}
مقدار pattern داره میگه که ما دنبال کلمهای ۵ حرفی هستیم که حرف سوم اون p باشه.
پیادهسازی رابط گرافیکی
برای اینکه بتونیم راحتتر با این برنامه کار کنیم. من یک رابط گرافیکی با استفاده از Tkinter یکی از کتابخونههای داخلی پایتون نوشتم. در این پست به توضیح رابط گرافیکی نمیپردازم. کدهاش بسیار سرراست و سادهست و میتونید با یکبار خوندن متوجه بشید. اما خروجی به این صورت خواهد بود:
در هربار کلیک روی دکمه Guess یک فهرست از ۵ کلمه ۵ حرفی به شما نمایش داده میشه. روی هرکدوم کلیک کنید، مربع حروف پر میشه و بعد میتونید با کلیک روی مربعات رنگشون رو تغییر بدید. طبیعتاً این رنگ باید مطابق رنگی باشه که بازی Wordle به شما گفته. با کلیک بعدی روی Guess فهرست کلمات آپدیت میشه.
این هم نمونه فارسی برای بازی واجور:
سخن پایانی
حل کردن پازلها بدون هیچ کمکی خودش یه لذت و چالشه. اما خودکارسازی و ساخت ابزار برای حل پازل هم خودش یک نوع پازل دیگهست. پس از انجام این دسته از کارها لذت ببرید و چیزهای مختلف یاد بگیرید. اگر ایدهای برای بهتر کردن این کد داشتید، درخواستش رو به مخزن گیتهاب که در ادامه میذارم ارسال کنید.