Perusing the newsstands in New York you'll find newspapers, TV-Guides, magazines, and even books. In the magazine section you'll often find crosswords and books containing the puzzles you'll see in figure 1. The books contain pages and pages of puzzles with hidden words contained in the matrix of letters in which you can spend time going blind trying to find. However, since these puzzles haven't disappeared from the newsstand in recent years, I suspect they are quite popular. Well perhaps popular is a strong word for them. I suspect they are more of a way to kill time on the subway.
Figure 1 - Word Find Puzzle and Answers
I can't deny that in the past I have been guilty of trying a few of these word find puzzles myself, so I figured why not give it a go it .NET. The Word Find creator reads a MSDE database of Words and Categories. The design of this database is shown below (The sql creation script is included in the download):
Figure 2 - Database Schema of Word Find Reverse Engineered using WithClass
If we look at a snapshot of the server explorer in figure 3, you'll note that you can populate the word find database simply by adding words followed by a category id. Each word find puzzle hides the words in a contained in a particular category in the database.
Figure 3 - Server Explorer View of MSDE WordFind Database
The Word Find Application reads the database and hides the words in a 15 x 15 square letter matrix. The application allows you to print and print preview the puzzle as well as display an answer key. You can also switch categories and choose a new puzzle. The program will randomly generate positions and directions of words and place them in the matrix. Words can be in any of 8 directions in a combination of the following: horizontal, vertical, diagonal, forwards, and backwards.
The UML design of the classes in this program are shown in the WithClass UML diagram in figure 4. Believe it or not, most of the classes in this diagram, were generated by the framework and are database specific. The classes in yellow all work with the Category Dialog(used to select a category for the puzzle) in order to databind the CategoryName column of the Category table to the combobox of the dialog. In actuality, this is only a very small part of the functionality of the program, but its interesting to see the wrapper classes generated and the relationships between them.
Figure 4 - Part of the UML diagram of Word Find reverse engineered by WithClass
Although not the most object-oriented approach, most of the functionality of the Word Find is contained in the Form1 class. Words are read in from the database in a particular category and a rectangular matrix is populated with these words in random positions and directions. Then the rest of the matrix is filled with random letters. This part of the program can be seen in the NewBoard menu event handler shown in listing 1.
private void NewBoardMenu_Click(object sender, System.EventArgs e)
{
CategoryDialog dlg = new CategoryDialog();
if (dlg.ShowDialog() == DialogResult.OK)
{
// clear out the word and answer matrix
InitializeMatrix();
// clear the word array
_words.Clear();
// get the id of the category chosen from the database
int id = GetCategoryID(dlg.CategoryName);
// read all of the words in the category into the arraylist
_words = ReadWords(id);
// place the category name in the title
Text = "Word Find - " + dlg.CategoryName;
// place the words of this category into the matrix and
// answer matrix
PlaceWords(); // place all the words
// fill the rest of the matrix with random letters
FillTheRest(); // fill with random letters
// redraw the form with the filled matrix
Invalidate();
}
}
Listing 1 - NewBoard Menu Event Handler to Create a Word Find Puzzle Matrix
Great care needs to be taken to make sure that words placed do not overlap. Therefore the program will sit in a loop generating random positions and directions until it places a word in a spot that doesn't overlap another word or go off the boundaries of the matrix. The PlaceWords method is shown below in listing 2. The PlaceWord method called inside of PlaceWords, checks a flag to see if the word was able to be placed. If it wasn't placed, it tries to place it again:
private void PlaceWords()
{
// Construct the random number class with a seed
// based on time (default behavior)
Random rn = new Random();
// place each word inside the category
foreach (string p in _words)
{
String s = p.Trim();
bool placed = false;
// continue trying to place the word in
// the matrix until it fits
while (placed == false)
{
// generate a new random row and column
int nRow = rn.Next(_maxRow);
int nCol = rn.Next(_maxCol);
// generate a new random x & y direction vector
// x direction: -1, 0, or 1
// y direction -1, 0, or 1
// (although direction can never be 0, 0, this is null)
int nDirX = 0;
int nDirY = 0;
while (nDirX == 0 && nDirY == 0)
{
nDirX = rn.Next(3) - 1;
nDirY = rn.Next(3) - 1;
}
// try to place the word in the random row, column and x, y direction
placed = PlaceWord(s.ToUpper(), nRow, nCol, nDirX, nDirY);
}
}
}
Listing 2 - PlaceWords method for placing words of a category inside the Word Puzzle
Drawing the matrix is accomplished easily through GDI+. The paint event handler is overridden and calls the drawing method DrawWordFindBoard that draws the graphical representation of the word puzzle. This same method is used by the Print_Page event handler to print the Word Find Puzzle to the printer. The DrawWordFindBoard method calls the method PaintWords that actually paints the characters into the matrix. This method checks a flag called _showAnswers that determines whether or not to display the words contained inside the puzzle in red. The code for the PaintWords method is shown in listing 3 below:
void PaintWords(Graphics g)
{
// go through the entire character matrix and
// paint each letter onto the form scaled to
// a spaced position
for (int i = 0; i < _maxRow; i++)
{
for (int j = 0; j < _maxCol; j++)
{
if (_showAnswers && matrixAnswers[i,j] != ' ')
{
// if the answer key flag is checked
// draw the letters in bold red if they are
// contained in the answer matrix
g.DrawString(matrix[i,j].ToString(), boldFont, Brushes.Red, i*_spacer + _padding,j*_spacer+_padding);
}
else
{
// otherwise paint the letter in black
g.DrawString(matrix[i,j].ToString(), this.Font, Brushes.Black, i*_spacer + _padding,j*_spacer+_padding);
}
}
}
}
Listing 3 - PaintWords Method that Paints the Puzzle Letters into the Form
Improvements
One thing that would be nice to do in the word find puzzle is to place the words so they share letters. I suspect you would need to change the PlaceWords method slightly so that it occasionally seeks out letters to share before placing them. Also some of the answer keys seen on the newsstand tend to have a big ellipse circling the answers instead of bolding them in red. This saves on the cost of printing, because once you start printing puzzle books with red colored ink, your production cost goes up tremendously. (I suppose you could bold them in black though). Anyway, if you are in need of a mildly challenging pastime, run the program, print out the page, and lose yourself in the jumble of letters produced by C# and .NET.