۱۳ - ۱۱قدم دوم: ایجاد یه لیست لغات
برای وضوحِ بیشتر، از یه تایپ مستعار استفاده میکنیم تا بدونیم منظورمون از [String]
چیه. جلوتر یه نسخهی دیگه که با استفاده از newtype
باز هم خواناتر شده رو نشون میدیم. از گرامر ِ do
هم برای خوندن محتویاتِ دیکشنری به داخل یه متغیر به اسمِ dict
استفاده میکنیم. با استفاده از تابعِ lines
اون نوشته ِ گُنده رو به یه لیست از نوشته تبدیل میکنیم که هر کدوم از المانهاش یکی از خطها میشن. هر خط هم یک لغته، پس نتیجهی کارمون میشه WordList
:
type WordList = [String]
allWords :: IO WordList
allWords = do
dict <- readFile "data/dict.txt"
return (lines dict)
قبل از اینکه پیش بریم یه نگاه به lines
بندازیم. این تابع نوشتهها رو از جاهایی که خط ِ جدید شروع میشن جدا میکنه و یه لیست از نوشته برمیگردونه:
Prelude> lines "aardvark\naaron"
["aardvark","aaron"]
Prelude> length $ lines "aardvark\naaron"
2
Prelude> length $ lines "aardvark\naaron\nwoot"
3
Prelude> lines "aardvark aaron"
["aardvark aaron"]
Prelude> length $ lines "aardvark aaron"
1
اگه متوجه شده باشین، کاری که این تابع انجام میده مشابه تابعِ words
که نوشتهها رو از فاصلهها (که معمولاً بین لغات وجود دارن) جدا میکنه هست. البته words
نوشتهها رو از خط جدیدها هم جدا میکنه:
Prelude> words "aardvark aaron"
["aardvark","aaron"]
Prelude> words "aardvark\naaron"
["aardvark","aaron"]
در بخش بعدی از ساخت لیستِ لغات برای بازیمون، حد بالا و حد پایینِ طول لغاتمون رو تعیین میکنیم. هرطور دوست داشتین تغییرشون بدین:
minWordLength :: Int
minWordLength = 5
maxWordLength :: Int
maxWordLength = 9
حالا خروجیِ allWords
رو فیلتر میکنیم تا با شرایطِ بالا جور بشه. با این کار لیستِ کوتاهتری از لغات برای بازی بدست میاریم:
gameWords :: IO WordList
gameWords = do
aw <- allWords
return (filter gameLength aw)
where gameLength w =
let l = length (w :: String)
in l >= minWordLength
&& l < maxWordLength
یه جفت تابع هم لازم داریم که یه لغتِ تصادفی رو از لیستِ لغات بیرون بکشن. برای این کار از تابعِ randomRIO
که بالاتر گفتیم استفاده میکنیم. به تابعِ randomRIO
یه توپل از صفر (اولین اندیس ِ لیستِ لغات) و عددی معادلِ طولِ لیستِ لغات منهای یک میدیم. چرا منهای یک؟
باید یکی از طولِ لیستِ لغات کم کنیم چون length
از ۱ شروع به شمارش میکنه، اما اندیس ِ لیست از صفر شروع میشه. یه لیست به طولِ ۵ هیچ عضوی در اندیس ِ ۵ نداره – المانهاش در جایگاههای صفر تا ۴ قرار گرفتن:
Prelude> [1..5] !! 4
5
Prelude> [1..5] !! 5
*** Exception: Prelude.!!: index too large
در نتیجه برای گرفتنِ آخرین مقدارِ یه لیست، باید از اندیسی که برابرِ طولِ لیست منهای یک هست استفاده کنیم:
Prelude> let myList = [1..5]
Prelude> length myList
5
Prelude> myList !! length myList
*** Exception: Prelude.!!: index too large
Prelude> myList !! (length myList) – 1
5
با دو تابعِ بعدی، یه لغتِ تصادفی از لیستِ gameWords
که بالاتر درست کردیم بیرون میکشیم. اجمالی بخوایم بگیم، تابعِ randomWord
یه عددِ تصادفی برمبنای طول لیستِ لغات (wl
) پیدا میکنه، بعد لغتی که با اون عددْ اندیس ِ یکسانی داره رو انتخاب میکنه و یه IO String
برمیگردونه. با توجه به چیزهایی که از randomRIO
و اندیسها میدونین، انتظار داریم خودتون آرگومانِ توپل ِ randomRIO
رو بنویسین:
randomWord :: WordList -> IO String
randomWord wl = do
randomIndex <- randomRIO ( , )
-- جای خالی رو پر کنید ^^^^
return $ wl !! randomIndex
تابعِ دوم، randomWord'
، لیستِ gameWords
رو به تابعِ randomWord
بایند میکنه تا لغتِ تصادفی ای که میگیریم از لیستِ gameWords
باشه. توضیحِ کاملِ عملگر ِ >>=
به اسمِ بایند رو موکول میکنیم به فصلی که Monad
رو میگیم. فعلاً، مشابهِ چیزی که برای گرامر ِ do
گفتیم، بایند هم امکانِ ترکیب ِ اجراییهها به صورت تسلسلی رو میده، طوری که مقدار ایجاد شده از یکی، میشه آرگومان برای بعدی:
randomWord' :: IO String
randomWord' = gameWords >>= randomWord
حالا که یه لیست از لغات داریم، روی ساخت یه بازیِ تعاملی که از اون لیست استفاده میکنه متمرکز میشیم.