Returning to the Word Search Creator

Returning to the Word Search Creator

By now you've learned all the skills you need to create the word find builder program that debuted at the beginning of this chapter. The program is stored in three files. First, the user enters a word list and puzzle information into an HTML page. This page will call the main wordFind.php program, which analyzes the word list, creates the puzzle, and prints it out. Finally, the user will have the opportunity to print an answer key, which is created by a simple PHP program.

Getting the Puzzle Data from the User

The wordFind.html page is the user's entry point into the word find system. This page is a standard HTML form with a number of basic elements in it.

<html>
<head>
       <title>Word Puzzle Maker</title>
</head>
<body>
<center>
<h1>Word Puzzle Maker</h1>

<form action = "wordFind.php"
      method = "post">
<h3>Puzzle Name</h3>
<input type = "text"
       name = "name"
       value = "My Word Find">
height: <input type = "text"
             name = "height"
             value = "10"
             size = "5">
width: <input type = "text"
             name = "width"
             value = "10"
             size = "5">
<br><br>

<h3>Word List</h3>
<textarea rows=10 cols=60 name = "wordList"></textarea>
<br><br>
Please enter one word per row, no spaces
<br>
<input type="submit" value="make puzzle">
</form>
</center>
</body>
</html>

The action property of the form points to the wordFind.php program, which is the primary program in the system. Notice that I used the post method to send data to the program. This is because I expect to be sending large strings to the program, and the get method allows only small amounts of data to be sent to the server.

The form features basic text boxes for the puzzle name, height, and width. This data will be used to determine how the puzzle is built. The wordList text area is expected to house a list of words, which will be used to create the puzzle.

Setting up the Response Page

The bulk of the work in the wordFind system happens in the wordFind.php page. This program has a small amount of HTML to set the stage, but the vast bulk of this file is made up of PHP code.

<html>
<head>
<title>
Word Find
</title>
</head>

<body>

<?
// word Find
// by Andy Harris, 2003
// for PHP/MySQL programming for the Absolute Beginner
// Generates a word search puzzle based on a word list
// entered by user. User can also specify the size of
// the puzzle and print out an answer key if desired

Notice the comments at the beginning of the code. Since the code for this program is a little bit more involved than most of the programs you have seen before in this book, I have decided to comment it more carefully. It's a really good idea to add comments to your programs so you can more easily determine what they do. You'll be amazed how little you understand your own code after you've been away from it a couple of days. Good comments can make it much easier to maintain your code, and make it easier for others to fix and improve your programs later. My comments here basically lay out the plan for this program.

Working with the Empty Data Set

For testing purposes, I wrote the word find PHP page before I worried about the HTML. For that reason, I simply added in default values for a word list and for the other main variables that determine the board's layout (height, width, and name). In a production version of the program, I don't expect the PHP code will ever be called without an HTML page, but I left the default values in place so you could see how they work.

if ($wordList == NULL){
  //make default puzzle
  $word = array(
    "ANDY",
    "HEATHER",
    "LIZ",
    "MATT",
    "JACOB"
    );
  $boardData = array(
    width => 10,
    height => 10,
    name => "Generic Puzzle"
  );

This code builds two arrays, which define the entire program. The $word array holds the list of words to hide in the puzzle, and the $boardData array is an associative array holding critical information about how the board is to be created.

Of course, these are not the values I expect to use, because most of the time this program will be called from an HTML form which will generate the values. The next section of code fills up these variables if the program is called from the appropriate form.

Building the Main Logic for the Program

The main logic for the program begins by retrieving the word list and puzzle parameters from the user's form. Then it tries to convert the list into an array. This type of text analysis is sometimes called parsing.

The program then repeatedly tries to build the board until it succeeds. Once the program has successfully created the board, it creates an answer key, then adds in the random letters with the addFoils() function. Finally, the program prints out the completed puzzle.

} else {
  //get puzzle data from HTML form
  $boardData = array(
    width => $width,
    height => $height,
    name => $name
  );

  //try to get a word list from user input
  if (parseList() == TRUE){
    $legalBoard = FALSE;

    //keep trying to build a board until you get a legal result
    while ($legalBoard == FALSE){
      clearBoard();
      $legalBoard = fillBoard();
    } // end while

    //make the answer key
    $key = $board;
    $keyPuzzle = makeBoard($key);

    //make the final puzzle
    addFoils();
    $puzzle = makeBoard($board);

    //print out the result page
    printPuzzle();

  } // end parsed list if
} // end word list exists if

As you look over this code, you should be able to tell the general flow of the program even if you don't understand exactly how things will happen. The main section of a well-defined program should give you a bird's eye view of the action. Most of the details are delegated to functions. Most of the remaining chapter is devoted to explaining how these functions work. Try to make sure you've got the basicgist of the program's flow; then you'll see how all of it is done.

Parsing the Word List

One important early task involves analyzing the word list that comes from the user. The word list comes as one long string separated by newline (\n) characters. The parseList() function converts this string into an array of words. It has some other important functions too, including converting each word to upper case, checking for words that will not fit in the designated puzzle size, and removing unneeded carriage returns.

function parseList(){
  //gets word list, creates array of words from it
  //or return false if impossible

  global $word, $wordList, $boardData;

  $itWorked = TRUE;

  //convert word list entirely to upper case
  $wordList = strtoupper($wordList);

  //split word list into array
  $word = split("\n", $wordList);

  foreach ($word as $currentWord){
    //take out trailing newline characters
    $currentWord = rtrim($currentWord);

    //stop if any words are too long to fit in puzzle
    if ((strLen($currentWord) > $boardData["width"]) &&
        (strLen($currentWord) > $boardData["height"])){
      print "$currentWord is too long for puzzle";
      $itWorked = FALSE;
    } // end if

  } // end foreach
  return $itWorked;
} // end parseList

The first thing I did was use the strtoupper() function to convert the entire word list into upper case letters. Word search puzzles always seem to use capital letters, so I decided to convert everything to that format.

The long string of characters with newlines is not a useful format for our purposes, so I converted the long string into an array called $word. The split() function works perfectly for this task. Note that I split on the string "\n". This is the newline character, so it should convert each line of the text area into an element of the new $word array.

The next task was to analyze each word in the array with a foreach loop. When I tested this part of the program, it became clear that sometimes the trailing newline character was still there, so I used the rtrim() function to trim off any unnecessary trailing white space.

If the user enters a word that is larger than the height or width of the puzzle board, it will be impossible to create the puzzle, so I check for this situation by comparing the length of each word to the height and width of the board. Note that if the word is too long, I simply set the value of the $itWorked variable to FALSE. Earlier in this function, I initialized the value of $itWorked to TRUE. By the time the function is finished, $itWorked will still contain the value TRUE if all the words were small enough to fit in the puzzle. If any of the words were too large to fit, the value of $itWorked will be false, and the program will not proceed.

Clearing the Board

The word search uses a crude but effective technique to generate legal game boards. It creates random boards repeatedly until it finds one that is legal. While this may seem like a wasteful approach, it is much easier to program than many more sophisticated attempts, and produces remarkably good results for simple problems such as the word search puzzle.

IN THE REAL WORLD
Start example

Although this program does use a "brute force" approach to find a good solution, you'll see a number of ways the code is optimized to make a good solution more likely. One example of this you've seen already is the way the program stops if one of the words is too long to fit in the puzzle. This prevents a long processing time while the program tries to fit a word in the puzzle when it cannot be done. As you go through the code, you'll see a number of other places where I do some work to steer the algorithm towards good solutions and away from pitfalls. Because of these efforts, you'll find that the program is actually pretty good at finding word search puzzles unless there are too many words or the game board is too small.

End example

The game board will often be re-created several times during one program execution. I needed a function that could initialize the game board or clean it out easily. The game board is stored in a two-dimensional array called $board. When the board is "empty," each cell will contain the period (.) character. I chose this convention because it would still give me something visible in each cell, and would give a character that represents an empty cell. The clearBoard() function sets or resets the $board array so that it contains a period in every cell.

function clearBoard(){
  //initialize board with a . in each cell
  global $board, $boardData;

  for ($row = 0; $row < $boardData["height"]; $row++){
    for ($col = 0; $col < $boardData["width"]; $col++){
      $board[$row][$col] = ".";
    } // end col for loop
  } // end row for loop
} // end clearBoard

This code is the classic nested for loop so common to two-dimensional arrays. Note that I used for loops rather than foreach loops because I was interested in the indices of the loops. The outer for loop steps through the rows. Inside each row loop, another loop steps through each column. I assigned the value "." to the $board array at the current $row and $col locations. Eventually, every cell in the array will contain the value "."

TRICK?

I determined the size of the for loops by referring to the $boardData associative array. Although there are a number of ways I could have done this, I chose the associative array for several reasons. The most important is clarity. It's easy for me to see by this structure that I'm working with the height and width related to board data information. Another advantage of using an associative array in this context is convenience. Since the height, width, and board name are all stored in the $boardData array, I could make a global reference to the $boardData variable and all its values would come along. It's like having three variables for the price of one.

Filling the Board

Of course, the purpose of clearing the board is to fill it in with the words from the word list. This happens in two stages. The fillBoard() function controls the entire process of filling up the whole board, but the details of adding each word to the board are relegated to the addWord() function (which you'll see next.)

The board is only complete if each word is added correctly. Each word is added only if each of its letters is added without problems. The program repeatedly calls fillBoard() as often as necessary to get a correct solution. Each time fillBoard() runs, it may call addWord() as many times as necessary until each word is added. The addWord() function in turn keeps track of whether it is able to successfully add each character to the board.

The general plan of the fillBoard() function is to generate a random direction for each word, then tell the addWord() function to place the specified word in the specified direction on the board. The looping structure for the fillBoard() function is a little unique, because the loop could exit in two different ways. If any of the words cannot be placed in the requested manner, the puzzle generation will stop immediately, and the function will return the value FALSE. However, if the entire word list is successfully placed on the game board, the function should also stop looping, but should report the value TRUE. There are a number of ways to achieve this effect, but I prefer often to use a special Boolean variable for this purpose. Boolean variables are variables meant to contain only the values true and false. Of course, PHP is pretty easy-going about variable types, but your can make a variable act like a Boolean simply by assigning it only the values TRUE or FALSE. In the fillBoard() function, look at how the $keepGoing variable is used. It is initialized to TRUE, and the function's main loop keeps running as long as this is the case. However, the two conditions that can cause the loop to exit (the addWord() function failed to place a word correctly, or the entire word list has been exhausted) both cause the $keepGoing variable to become FALSE. When this happens, the loop will stop, and the function will shortly exit.

function fillBoard(){
  //fill board with list by calling addWord() for each word
  //or return false if failed

  global $word;
  $direction = array("N", "S", "E", "W");
  $itWorked = TRUE;
  $counter = 0;
  $keepGoing = TRUE;
  while($keepGoing){
    $dir = rand(0, 3);
    $result = addWord($word[$counter], $direction[$dir]);
    if ($result == FALSE){
      //print "failed to place $word[$counter]";
      $keepGoing = FALSE;
      $itWorked = FALSE;
    } // end if
    $counter++;
    if ($counter >= count($word)){
      $keepGoing = FALSE;
    } // end if
  } // end while
  return $itWorked;

} // end fillBoard

The function begins by defining an array for directions. At this point, I decided only to support placing words in the four cardinal directions, although it would be easy enough to add diagonals. (Hey, that sounds like a dandy end-of-chapter exercise!) The $direction array holds the initials of the four directions I have decided to support at this time. The $itWorked variable is a Boolean which reports whether the board has been successfully filled. It is initialized to TRUE. If the addWord() function fails to place a word, the value of $itWorked will be changed to FALSE.

The $counter variable will be used to count which word I'm currently trying to place. I increment the value of $counter each time through the loop. When $counter is larger than the size of the $word array, the function has successfully added every word, and can exit triumphantly.

To choose a direction, I simply created a random value between 0 and 3, and referred to the associated value of the $direction array.

The last line of the function returns the value of $itWorked. The fillBoard() function is called by the main program repeatedly until it succeeds. This success or failure is reported to the main program by returning the value of $itWorked.

Adding a Word

The fillBoard() function handles the global process of adding the word list to the game board, but the process of adding each word to the board is performed by the addWord() function. This function expects two parameters, the word to add, and a direction.

The function cleans up the word, and renders slightly different service based on which direction the word will be placed. It places each letter of the word in an appropriate cell while preventing it from being placed outside the boundary of the game board. It also checks to make sure that the cell does not currently house some other letter from another word (unless that letter happens to be the one the function is already trying to place). The function may look long and complex at first, but when you look at it more closely, you'll find that it's extremely repetitive.

function addWord($theWord, $dir){
  //attempt to add a word to the board or return false if failed
global $board, $boardData;

//remove trailing characters if necessary
$theWord = rtrim($theWord);

$itWorked = TRUE;

switch ($dir){
  case "E":
    //col from 0 to board width - word width
    //row from 0 to board height
    $newCol = rand(0, $boardData["width"] - 1 - strlen($theWord));
    $newRow = rand(0, $boardData["height"]-1);

    for ($i = 0; $i < strlen($theWord); $i++){
      //new character same row, initial column + $i
      $boardLetter = $board[$newRow][$newCol + $i];
      $wordLetter = substr($theWord, $i, 1);

      //check for legal values in current space on board
      if (($boardLetter == $wordLetter) ||
          ($boardLetter == ".")){
        $board[$newRow][$newCol + $i] = $wordLetter;
      } else {
        $itWorked = FALSE;
      } // end if
    } // end for loop
    break;

  case "W":
    //col from word width to board width
    //row from 0 to board height
    $newCol = rand(strlen($theWord), $boardData["width"] -1);
    $newRow = rand(0, $boardData["height"]-1);
    //print "west:\tRow: $newRow\tCol: $newCol<br>\n";

    for ($i = 0; $i < strlen($theWord); $i++){
      //check for a legal move
      $boardLetter = $board[$newRow][$newCol - $i];
      $wordLetter = substr($theWord, $i, 1);
      if (($boardLetter == wordLetter) ||
          ($boardLetter == ".")){
        $board[$newRow][$newCol - $i] = $wordLetter;
      } else {
        $itWorked = FALSE;
      } // end if
    } // end for loop
    break;

  case "S":
    //col from 0 to board width
    //row from 0 to board height - word length
    $newCol = rand(0, $boardData["width"] -1);
    $newRow = rand(0, $boardData["height"]-1 - strlen($theWord));
    //print "south:\tRow: $newRow\tCol: $newCol<br>\n";

    for ($i = 0; $i < strlen($theWord); $i++){
      //check for a legal move
      $boardLetter = $board[$newRow + $i][$newCol];
      $wordLetter = substr($theWord, $i, 1);
      if (($boardLetter == $wordLetter) ||
          ($boardLetter == ".")){
        $board[$newRow + $i][$newCol] = $wordLetter;
      } else {
       $itWorked = FALSE;
      } // end if
      } // end for loop
      break;

    case "N":
      //col from 0 to board width
      //row from word length to board height
      $newCol = rand(0, $boardData["width"] -1);
      $newRow = rand(strlen($theWord), $boardData["height"]-1);

      for ($i = 0; $i < strlen($theWord); $i++){
        //check for a legal move
        $boardLetter = $board[$newRow - $i][$newCol];
        $wordLetter = substr($theWord, $i, 1);
        if (($boardLetter == $wordLetter) ||
            ($boardLetter == ".")){
          $board[$newRow - $i][$newCol] = $wordLetter;
        } else {
         $itWorked = FALSE;
        } // end if
      } // end for loop
      break;

  } // end switch
  return $itWorked;
} // end addWord

The main focus of the addWord() function is a switch structure based on the word direction. The code inside each switch branch is similar in its general approach.

Closely Examining the East Code

It's customary in Western languages to write from left to right, so the code for "E," which indicates "write towards the East" is probably the most natural to understand. I'll explain how that code works, then I'll show you how the other directions differ. Here's the code fragment that attempts to write a word in the Easterly direction:

case "E":
      //col from 0 to board width - word width
      //row from 0 to board height
      $newCol = rand(0,
                $boardData["width"] - 1 - strlen($theWord));
      $newRow = rand(0, $boardData["height"]-1);

      for ($i = 0; $i < strlen($theWord); $i++){
        //new character same row, initial column + $i
        $boardLetter = $board[$newRow][$newCol + $i];
        $wordLetter = substr($theWord, $i, 1);

        //check for legal values in current space on board
        if (($boardLetter == $wordLetter) ||
            ($boardLetter == ".")){
          $board[$newRow][$newCol + $i] = $wordLetter;
        } else {
          $itWorked = FALSE;
        } // end if
      } // end for loop
      break;

Determining Starting Values for the Characters

Essentially, the code steps through the word one letter at a time, placing each letter in the next cell to the right. I could have chosen any random cell and checked to see when the code got outside the range of the board, but this would have involved some complicated and clunky code. A more elegant solution is to carefully determine what the range of appropriate starting cells are, and only choose cells within that range. For example, if I'm placing the word "elephant" (with 8 letters) from left to right in a puzzle with a width of ten, the only legal columns would be columns zero and one (remember, computers usually start counting at zero). If I'm placing "elephant" in the same puzzle but from right to left, the last two columns (8 and 9) are the only legal options. Once I recognized this fact, I had to figure out how to encode this idea so it could work with any size words in any size puzzle.

IN THE REAL WORLD
Start example

By far the most critical part of this code is the comments at the beginning. Even though I'm a reasonably experienced programmer, it's easy enough to get confused when you start solving problems of any reasonable complexity. Just to remind myself, I placed these comments to explain to myself exactly what the parameters of this chunk of code are.

I referred back to these comments many times while I was writing and debugging the code. If I hadn't given myself clear guidance on what I was trying to do, I would have gotten so lost I probably wouldn't have been able to write the program.

End example

To figure out where to place each word, I need a random value for the row and column, but that random value must be within an appropriate range based on the word length and board width. By trial and error and some sketches on a white board, I determined that $boardData["width"] - 1 is the largest column in the game board, and strlen($theWord) is the length of the current word in characters. If I subtract the word length from the board width, I'll get the largest legal starting value for a left-to-right placement. That's how I got the slightly scary formula

$boardData["width"] - 1 - strlen($theWord)

The smallest legal starting value for this kind of placement is zero, because column zero will always work when you're going right-to-left and when the word is the same size or smaller than the puzzle (which has already been established).

In an Eastward placement, the row number doesn't matter, because any row in the puzzle will be legal, as all letters will be placed on the same row.

Once I know the largest and smallest legal starting places for the word, I can randomly generate that starting point knowing that the entire word can be placed there legally as long as it doesn't overlap any other words.

I used a for loop to pull one character at a time from the word using the substr() function. Each character is placed at the same row as the starting character, but at a column offset by the position in the word. Repeating the "elephant" example, if the starting position chosen is column 1, the character "E" will be placed in column 1, because "E" is at the 0th position in the word "elephant," and 1 + 0 = 1. When the counter ($i) gets to the letter "L," it will have the value 1, so it will be placed in column 2, and so on.

If the formula for choosing the starting place and the plan for placing subsequent letters in place work correctly, it will be impossible to add a letter outside the puzzle board. However, another bad thing could happen if a character from a previously placed word is in a cell that the current word wants. The code checks the current cell on the game board to see its current status. If the cell contains the value ".", it is empty, and the new character can be freely placed there. If the cell contains the value that the current word wants to place in the cell, the program can likewise continue without interruption. However, if the cell contains any other character, the loop will need to exit, and the program will need reset the board and try again. This is done by setting the value of $itWorked to FALSE.

Printing in the Other Directions

Once you understand how to print words when the direction is East, you'll see that the other directions are similar. However, for each direction, I needed to figure out what the appropriate starting values were, and what cell to place each letter in. The table below summarizes these value

A little explanation of Table 5.2 is in order. Within the table, I identified the minimum and maximum column for each direction, as well as the minimum and maximum row. This was actually easiest to figure out by writing some examples out on graph paper. The placement of each letter is based on the starting row and column, with i standing for the position of the current letter within the word. So, in direction W, I'd put the letter at position 2 of my word into the randomly chosen starting row, but at the starting column minus two. This will cause the letters to be printed from right to left. Work out the other examples on graph paper so you can see how they worked out.

Table 5.2: SUMMARY OF PLACEMENT DATA
?

E

W

S

N

min Col

0

word width

0

0

max Col

board width - 1 - word width

board width -1

board width - 1

board width - 1

min Row

0

0

0

word width

max Row

board height -1

board height - 1

board height - 1 - word width

board height - 1

letter col

start + i

start - i

start

start

letter row

start

start

start + i

start - i

IN THE REAL WORLD
Start example

This is exactly where computer programming becomes mystical for most people. Up to now, you've probably been following so far, but this business of placing the characters has a lot of math in it, and you didn't get to see me struggle with it. It might look to you as if I just knew what the right formulas were. I didn't. I had to think about it carefully withoutthe computer turned on. I got out a whiteboard (my favorite programming tool) and some graph paper, and tried to figure out what I meant mathematically when I said "write the characters from bottom to top." This is hard, but you can do it. The main thing beginners forget to do is turn off the computer. Get out some paper and figure out what it is you're trying to tell the computer to do. Then you can start writing code. You'll still get it wrong (at least I did). But, if you've written down your strategy, you can compare what you expected to happen with what did happen, and you're likely to be able to solve even this kind of somewhat mathematical problem.

End example

Making a Puzzle Board

By the time the fillBoard() function has finished calling addWord() to add all the words, the answer key is actually complete. Each word is in place, and any cell that does not contain one of the words still has a period. The main program will copy the current $board variable over to the $key array. The answer key is now ready to be formatted into a form the user can use. However, rather than writing one function to print out the answer key and another function to print out the finished puzzle, I wrote one function that takes the array as a parameter and creates a long string of HTML code placing that puzzle in a table.

function makeBoard($theBoard){
  //given a board array, return an HTML table based on the array
  global $boardData;
  $puzzle = "";
  $puzzle .= "<table border = 0>\n";
  for ($row = 0; $row < $boardData["height"]; $row++){
    $puzzle .= "<tr>\n";
    for ($col = 0; $col < $boardData["width"]; $col++){
      $puzzle .= " <td width = 15>{$theBoard[$row][$col]}</td>\n";
    } // end col for loop
    $puzzle .= "</tr>\n";
  } // end row for loop
  $puzzle .= "</table>\n";
  return $puzzle;
} // end printBoard;

Most of the function deals with creating an HTML table, which will be stored in the variable $puzzle. Each row of the puzzle corresponds begins by building an HTML <tr> tag, and creates a <td></td> pair for each element in the table.

TRAP?

Sometimes PHP has trouble correctly interpolating two-dimensional arrays. If you find that an array is not being correctly interpolated, you can try two things. First, you can simply surround the array reference in braces as I did in the code in makeBoard() Also, you can forego interpolation and use concatenation instead. For example, you could have built each cell with the following code:

$puzzle .= "<td> width = 15>" . $theBoard[$row][$col] . "</td>\n";

Adding the Foil Letters

The puzzle itself can be easily derived from the answer key. Once the words in the word list are in place, all it takes to generate a puzzle is to replace the periods in the puzzle with some other random letters. I call these other characters "foil letters" because it is their job to foil the user. This is actually quite easy compared to the process of adding the words.

function addFoils(){
  //add random dummy characters to board
  global $board, $boardData;
  for ($row = 0; $row < $boardData["height"]; $row++){
    for ($col = 0; $col < $boardData["width"]; $col++){
      if ($board[$row][$col] == "."){
        $newLetter = rand(65, 90);
        $board[$row][$col] = chr($newLetter);
      } // end if
    } // end col for loop
  } // end row for loop
} // end addFoils

The function uses the standard pair of nested loops to cycle through each cell in the array. For each cell that contains a period, it generates a random number between 65 and 90. These numbers correspond to the ASCII numeric codes for the capital letters. I then used the chr() function to retrieve the letter that corresponds to that number, and stored the new random letter in the array.

Printing Out the Puzzle

The last step in the main program is to print results to the user. So far, all the work has been done behind the scenes. Now it is necessary to produce an HTML page with the results. The printPuzzle() function performs this duty. The actual puzzle and answer key tables have already been formatted as HTML by the printBoard() function. The puzzle HTML is stored in the $puzzle variable, and the answer key is stored in $keyPuzzle.

function printPuzzle(){
  //print out page to user with puzzle on it

  global $puzzle, $word, $keyPuzzle, $boardData;
  //print puzzle itself

  print <<<HERE
  <center>
  <h1>{$boardData["name"]}</h1>
  $puzzle
  <h3>Word List</h3>
  <table border = 0>

HERE;

  //print word list
  foreach ($word as $theWord){
    print "<tr><td>$theWord</td></tr>\n";
  } // end foreach
  print "</table>\n";
  $puzzleName = $boardData["name"];

  //print form for requesting answer key.
  //send answer key to that form (sneaky!)
  print <<<HERE
  <br><br><br><br><br><br><br><br>
  <form action = "wordFindKey.php"
        method = "post">
  <input type = "hidden"
         name = "key"
         value = "$keyPuzzle">
  <input type = "hidden"
         name = "puzzleName"
         value = "$puzzleName">

  <input type = "submit"
         value = "show answer key">
  </form>
  </center>

HERE;

} // end printPuzzle

This function mainly deals with printing out standard HTML from variables that have been created during the program's run. The name of the puzzle is stored in $boardData["name"]. The puzzle itself is simply the value of the $puzzle variable. I printed the word list by the simple expedient of a foreach loop creating a list from the $word array.

The trickiest part of the code is working with the answer key. It would be easy enough to print the answer key directly on the same HTML page. In fact, this is exactly what I did as I was testing the program. However, the puzzle won't be much fun if the answer is right there, so I allowed the user to press a button to get the answer key. The key is related only to the currently generated puzzle. If the same word list were sent to the wordfind program again, it would likely produce a different puzzle with a different answer. The secret is to store the current answer key in a hidden form element and pass this element to another program. I created a form with two hidden fields. I stored the name of the puzzle in a field called puzzleName and the entire HTML of the answer key in a field called key. When the user presses the Submit key, it will call a program called wordFindKey.

IN THE REAL WORLD
Start example

Passing the answer key to another program was a kind of dirty trick. It worked for a couple of reasons. First, since the key field is hidden and the form sends data through the post method, the user is unlikely to know that the answer to the puzzle is literally under his nose. Since I expect this program mainly to be used by teachers who would print out the puzzle anyway, this is fine. Even without the secrecy concerns, it would be necessary to pass the key data by post because it is longer than the 256 characters allowed by the get method.

Sending the HTML-formatted answer key to the next program made the second program quite simple, but there is another advantage to this approach: It is very difficult to send entire arrays through form variables, but by creating the HTML table, all the data in the array was reduced to one string value, which can be easily passed to another program through a form.

End example

Printing Out the Answer Key

The wordFindKey program is actually very simplistic, because all the work of generating the answer key was done by the word find program. All this program has to do is retrieve the puzzle name and answer key from form variables and print them out. Since the key has even been formatted as a table, there is no need for any kind of heavy lifting by the wordFindKey program.

<!doctype html public "-//W3C//DTD HTML 4.0 //EN">
<html>
<head>
<title>Word Find Answer Key</title>
</head>
<body>

<?
//answer key for word find
//called from wordFind.php

print <<<HERE
<center>
<h1>$puzzleName Answer key</h1>
$key
</center>

HERE;
?>
</body>
</html>