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));;
}
}
}
}