using System;
using System.Collections.Generic;
using System.IO;
namespace Boggle
{
///
/// Supported lists.
///
public enum BoggleLists
{
///
/// Scrabble dictionary for USA, Canada and Thailand
///
TWS,
///
/// Scrabble dictionary used world wide except in USA, Canada and Thailand.
///
//SOWPODS,
///
/// A huge dictionary found on official Internet Scrabble Club's website (http://www.isc.ro/en/commands/lists.html)
///
ZINGARELLI
}
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;
///
/// Determines if we have an absolute path
///
/// File that contains the list SORTED list of words. Path can be relative or absolute.
/// The absolute file path that we can use to access it.
private string GetAbsoluteFilePath(string filePath)
{
//check if the absolute file path exists.
if (!File.Exists(filePath))
{
//try checking if this is a relative file path and prepend current directory's path.
filePath = string.Format("{0}\\{1}", Directory.GetCurrentDirectory(), filePath);
if (!File.Exists(filePath))
{
filePath = string.Empty;
}
}
return filePath;
}
///
/// Curates the list for use by this solver. Basically culls out words that do not meet the criteria.
/// Criteria: Min_word_length < word_length < Max_word_length. Max_word_length = sideLength^2 + 1.
///
/// Path to file that contains the SORTED list of words.
/// It can be relative to current execution directory or absolute.
/// Length of the side of board in number of tiles.
/// Number of alphabets that can be there in the shortest word on the board.
private void CurateList(string filePath, int boardSideLength, int minWordLength)
{
//filePath = GetAbsoluteFilePath(filePath);
//true tells stream reader to auto detect encoding of the file.
StreamReader rawList = new StreamReader(filePath, true);
//Create a new file. Do not append anything.
string curatedFilePath = string.Format("{0}.{1}.{2}", filePath, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString());
StreamWriter curatedList = new StreamWriter(curatedFilePath, false);
string word;
//write out the words whose number of characters are between the shortest and largest provided length.
while ((word = rawList.ReadLine()) != null)
{
if (word.Length >= minWordLength && word.Length <= boardSideLength * boardSideLength + 1)
{
curatedList.WriteLine(word);
}
}
curatedList.Flush();
curatedList.Close();
rawList.Close();
}
public BoggleList()
{
//Initialize the dictionary containing the lists.
_LoadedDictionaries = new Dictionary>(Enum.GetNames(typeof(BoggleLists)).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 Dictionary LoadList(BoggleLists dict, int boardSideLength, int minWordLength, string listRootPath)
{
if (_LoadedDictionaries.ContainsKey(dict))
return _LoadedDictionaries[dict];
string filePath = string.Empty;
if (NeedsCuration(dict, minWordLength, listRootPath, ref filePath))
CurateList(filePath, boardSideLength, minWordLength);
_WordList = new Dictionary(27);
//true tells stream reader to auto detect encoding of the file.
StreamReader rawList = new StreamReader(filePath, true);
string word, substring;
ListWord prevListWord;
int startIndex;
int maxWordLength = boardSideLength * boardSideLength + 1;
while ((word = rawList.ReadLine()) != null)
{
//ignore the word if it cannot logically exist 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(dict, _WordList);
return _WordList;
}
private void SaveDictToDisk(Dictionary hierarchicalDict, string filename)
{
}
private Dictionary LoadDictFromDisk(string filename)
{
}
private bool DictExistsOnDisk(string filename)
{
}
private string DictFilenameOnDisk(int boardSideLength, int minWordLength)
{
}
///
/// Checks to see if the proper file exists that contains the appropriate words.
///
/// Type of list to check for.
/// Minimum number of LETTERS the word should contain.
/// Reference to path of file. If the list will need curation, it will store path to source list.
/// If list is there, it will store the path to the curated list.
/// Whether there is a need to run the curation procedure or not.
private bool NeedsCuration(BoggleLists list, int minWordLength, string wordlistContainer, ref string filePath)
{
bool needsCuration = false;
switch (list)
{
case BoggleLists.TWS:
filePath = GetAbsoluteFilePath(string.Format("{0}/{1}.{2}.{3}", filePath, Constants.TWS_FILE_PATH, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString()));
if (filePath == string.Empty)
{
filePath = string.Format("{0}/{1}", wordlistContainer, Constants.TWS_FILE_PATH);
needsCuration = true;
}
break;
case BoggleLists.ZINGARELLI:
filePath = GetAbsoluteFilePath(string.Format("{0}/{1}.{2}.{3}", filePath, Constants.ZINGARELLI_FILE_PATH, Constants.CURATED_FILE_SUFFIX, minWordLength.ToString()));
if (filePath == string.Empty)
{
filePath = string.Format("{0}/{1}", wordlistContainer, Constants.ZINGARELLI_FILE_PATH);
needsCuration = true;
}
break;
default:
throw new ArgumentException(string.Format("Support for {0} wordlist has not been coded yet :(", list));
}
return needsCuration;
}
}
}