XNA Game – Rotation – Discovering whether words have been made – Part 12

 

In this post I am going to go through the code that I have designed and written to detect if the player has made a word.  As stated in previous blog posts the player can make a word either vertically or horizontally.  The word has to have a minimum length.

The first thing that I needed to do was add a new property to the Square class CanUseInWord.  This is a bool that says whether that square can be used as a part of a word.  The reason for adding this is that I am currently thinking that I am going to have squares falling down from outside of the board, this will allow the player to see which squares will enter the board when they make a word.  I need a way to tell if the square can be used as a part of a word.  This will also allow me to put the game into a different mode that I have an idea about (more on that in later posts).  All of the squares that are selectable ie all of the ones that get filled with letters are eligible to be used in a word so currently I have set that property using the same logic.  I have obviously added a unit test for that in the board creation specs.

Next I need to design a collection to hold all of the possible words that the user can play.  This collection is hidden behind the interface IWordList which has one property Words which returns an IEnumerable<string>.  I have designed a factory to populate the word list, the default factory implementation populates the word list from a file.

Now that I can create a word list so I know all of the possible words a player can play, I need a way to check those words against every possible word in the board.  To do this I have designed an interface IWordChecker that has one method Check.  Check returns an IEnumerable<IWord> which is all of the words which have been found (if any) and it takes an IEnumerable<IEnumerable<Square>> (ie either all of the board’s columns or rows).  IWord is a simple interface that allows me to represent a collection of squares as a word.  It has two properties one which contains the string value that the list of squares represent and another which is the total value of those letters in points.

All of the above code is unit tested but I have left out the unit tests as they are straight forward.  If you wish to see them you can grab the code from github and check them out.  The interesting unit tests are the ones around checking for the existence of words in the board ie testing WordChecker.  I have designed 6 unit tests to do this:

1.  Check that if you have a set of squares that make a known word but all of the squares are squares in which the squares cant be used in a word then make sure that no words are returned.

2.  Check that a word is returned when a known word is in the list of squares that are passed in

3.  Check that two words are returned when two words are in the list of squares that are passed in (simulating two words being made in one row or column)

4.  Check that two words are returned when two words are in the list of squares and the words share letters e.g. HOUSEED should return HOUSE and SEED both words sharing the S and E

5.  Check that two words are returned when two words are in different lists in the lists of squares that are passed in (simulating that two words are made in two different rows or columns)

6.  Check that if there are no matching words in the lists that are passed in then no words are returned

To implement the code to check every possible word in the grid we first need to extract every possible word of a certain length.  We can do this with the following code

private IEnumerable<IWord> GetWordsOfLength(IEnumerable<Square> squares, int length)
{

    for (int i = 0; i + length <= squares.Count();  i++)
    {

        var currentWordSquares = squares.Skip(i).Take(length);

        if (currentWordSquares.All(s => s.CanUseInWord))
            yield return new Word(currentWordSquares);
    }
}

This private method works first by looping through all of the numbers from 0 to the total number of squares minus length of the words you are searching for.  Inside the loop the first thing we do is we skip to the starting square for the loop.  e.g. for the first iteration we will start at square 0.  The take statement then takes as many squares as we want this of course is the length of the word.  Next we check to make sure that all of those squares can be used in the word.  If all of those squares can be used in the word then we return that as a word from the grid.

public IEnumerable<IWord> Check(IEnumerable<IEnumerable<Square>> squares)
{
    var foundWords = new List<IWord>();

    foreach (var squareList in squares)
    {
        for (int j = GameConstants.MIN_WORD_LENGTH; j <= squareList.Count(); j++)
        {

            var words = GetWordsOfLength(squareList, j);

            foundWords.AddRange(words.Where(w => _wordList.Words.Contains(w.ToString())));

        }

    }

    return foundWords;
}

The method above completes the code for checking every word and passes all of the unit tests.  This code is pretty simple.  All we are doing is iterating around each list of squares ie basically looping around all of the rows or columns.  For each list of squares we loop around getting every word in that list of every length starting at the minimum word length and ending at the number of squares in the list (as obviously its impossible to have a word longer than that).  Once we have returned every possible word contained in that list all we have to do now is simply check to see if any of those words are in our word list and if so add them to the results.

That is all the code we need to implement word checking.  It’s good when a complex problem like this gets solved with a few simple lines of code.  It shows that we have broken our classes down and given them single responsibilities.  Because we have designed our unit tests upfront we have got confidence that our code works.  That is the beauty of TDD.

As always you can download the latest version of the code on Github.  Switch to the part12 branch to see the code at this point.  As always comments welcome.  Instructions on how to download the code from github or get in contact can be found on this page.

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s