using System; using System.Collections.Generic; using System.IO; using ProtoBuf.Serializers; namespace Boggle { public class BoggleList { /// /// Contains a hybrid tree that has been optimized for recursive search. /// private Dictionary _WordList = new Dictionary(27); //top level will store only 3 letter words or substrings. Declaring proper size makes insertion operation O(1) public Dictionary Wordlist { get { return _WordList; } } /// /// Contains a list of dictionaries that have been loaded, indexed by enum that contains dictionaries we support. /// private Dictionary> _LoadedDictionaries; public BoggleList() { //Initialize the dictionary containing the lists. _LoadedDictionaries = new Dictionary>(Enum.GetNames(typeof(SupportedLists)).Length); } /// /// Loads the list depending on the selected dictionary. /// /// Type of dictionary to load. /// /// /// A hybird tree containing the words with each level containing one alphabet (or QU) public void LoadList(SupportedLists dict, int boardSideLength, int minWordLength, string currentWorkingDirectory) { int maxWordLength = boardSideLength * boardSideLength + 1; string dictPath = this.GetSerializedDictPath(dict, minWordLength, maxWordLength, currentWorkingDirectory); if (_LoadedDictionaries.ContainsKey(dictPath)) { _WordList = _LoadedDictionaries[dictPath]; return; } //Load the seriazlized dictionary if it exists on the disk. if (SerializedDictExists(dictPath)) { _LoadedDictionaries.Add(dictPath, DeserializeDict(dictPath)); _WordList = _LoadedDictionaries[dictPath]; return; } _WordList = new Dictionary(27); string rawListPath = GetWordlistPath(dict); //true tells stream reader to auto detect encoding of the file. StreamReader rawList = new StreamReader(rawListPath, true); string word, substring; ListWord prevListWord; int startIndex; while ((word = rawList.ReadLine()) != null) { //no need to process the word if it cannot logically lie on the board. if (word.Length < minWordLength || word.Length > maxWordLength) continue; //First deal with the head node and then deal with following letters. startIndex = 0; word = word.ToUpper(); substring = word.Substring(0, 1); //if the string starts with Q and there is a 'U' following it, then put QU on tile and increment starting index accordingly. if (substring == "Q" && word[1] == 'U') { substring += "U"; startIndex++; } //If there is no space allocated for this letter at root level, allocate some. if (!_WordList.ContainsKey(substring)) { _WordList.Add(substring, new ListWord()); } prevListWord = _WordList[substring]; //fit the word in the tree structure for (int i = startIndex + 1; i < word.Length; i++) { //We are dealing with one letter at each level substring = word.Substring(i, 1); //But if the letter is Q and there is a U following it, then treat this as one letter. if (substring == "Q" && i + 1 < word.Length && word[i + 1] == 'U') { substring += "U"; //also increment the index as we have consumed the next letter. i++; } //Has this combination of letters appeared before? if (prevListWord.Next == null) { //if not, then make some space to store this combination prevListWord.Next = new Dictionary(27); //and store it. prevListWord.Next.Add(substring, new ListWord()); } //if it has, else if (!prevListWord.Next.ContainsKey(substring)) { //then just store it. prevListWord.Next.Add(substring, new ListWord()); } //Update the dictionary to point to current substring's last character/entry in the tree. prevListWord = prevListWord.Next[substring]; } //set the value of the leaf node to be true to point that we have completed the word. prevListWord.IsWord = true; } //Store it in the collection _LoadedDictionaries.Add(dictPath, _WordList); //Writes the dictionary just constructed to disk SerializeDict(_WordList, dictPath); } private string GetSerializedDictPath(SupportedLists list, int minWordLength, int maxWordLength, string currentWorkingDirectory) { string serializationFolder = string.Format("{0}/{1}", currentWorkingDirectory, Constants.SERIALIZATION_FOLDER); if (!Directory.Exists(serializationFolder)) { Directory.CreateDirectory(serializationFolder); } string filename = string.Format("{0}/{1}.{2}.{3}.{4}", serializationFolder, list, minWordLength, maxWordLength, Constants.SERIALIZED_FILE_SUFFIX ); //File creation is done by serialization function. No need to do it here. In case there is an error and we cannot //load the raw dictionary into our hybrid tree, we would be left with an empty and a useless file. return filename; } private bool SerializedDictExists(string serializedObjPath) { return File.Exists(serializedObjPath); } private void SerializeDict(Dictionary funkyDict, string serializationPath) { Stream serializationFile = File.Open(serializationPath, FileMode.CreateNew, FileAccess.Write); ProtoBuf.Serializer.Serialize >(serializationFile, funkyDict); serializationFile.Flush(); serializationFile.Close(); } private Dictionary DeserializeDict(string serializedObjPath) { Stream serializationFile = File.Open(serializedObjPath, FileMode.Open, FileAccess.Read); Dictionary funkyDict = ProtoBuf.Serializer.Deserialize>(serializationFile); serializationFile.Flush(); serializationFile.Close(); return funkyDict; } private string GetWordlistPath(SupportedLists list) { switch (list) { case SupportedLists.TWS: return Constants.TWS_FILE_PATH; case SupportedLists.ZINGARELLI: return Constants.ZINGARELLI_FILE_PATH; default: throw new ArgumentException(string.Format("Support for {0} wordlist has not been coded yet :(", list));; } } } }