Boggle - Implementing Recursion

3.4k views Asked by At

So, I'm trying to figure out how to implement the recursive calls for a simulation of Boggle. There are two players: human and computer. When the human goes, s/he finds a words on the board (which was randomly shuffled), and the recursive call is supposed to check the validity. Basically, I have to loop through adjacent letters starting from index 0 of the word input, and crawl that way, checking each time to make sure the word exists or not. The problem is I don't exactly know how to do this. Here's my code thus far, and I've provided some pseudocode to further explain my intentions. Can anyone help?

#include <iostream>
#include <cctype>
#include <cmath>
#include "strlib.h"
#include "gboggle.h"
#include "graphics.h"
#include "grid.h"
#include "lexicon.h"
#include "random.h"
#include "simpio.h"
#include "Board.h"

using namespace std;

/* Constants */
const int MIN_WORD_COUNT = 4;
const int boardSize = 16;

const int BOGGLE_WINDOW_WIDTH = 650;
const int BOGGLE_WINDOW_HEIGHT = 350;

const string STANDARD_CUBES[16]  = {
   "AAEEGN", "ABBJOO", "ACHOPS", "AFFKPS",
   "AOOTTW", "CIMOTU", "DEILRX", "DELRVY",
   "DISTTY", "EEGHNW", "EEINSU", "EHRTVW",
   "EIOSST", "ELRTTY", "HIMNQU", "HLNNRZ"
};

const string BIG_BOGGLE_CUBES[25]  = {
   "AAAFRS", "AAEEEE", "AAFIRS", "ADENNN", "AEEEEM",
   "AEEGMU", "AEGMNN", "AFIRSY", "BJKQXZ", "CCNSTW",
   "CEIILT", "CEILPT", "CEIPST", "DDLNOR", "DDHNOT",
   "DHHLOR", "DHLNOR", "EIIITT", "EMOTTT", "ENSSSU",
   "FIPRSY", "GORRVW", "HIPRRY", "NOOTUW", "OOOTTU"
};

/* Function prototypes */

void welcome();
void giveInstructions();

void checkBoard( Vector<string> );
void shakeCubes( int, int, Vector<string> &, Grid<char> & );
void checkBoardLetters( Grid<char> );
void checkHumanWords( Set<string> );
void findHumanWordsOnBoard( Set<string> &, Grid<char> );
bool boardContainsHumanWord( Grid<char>, string );

void toupper(string& word);
bool askQuestion(string question);
void customConfiguration(int boardSize);
void shuffleBoard(int boardSize, const string cubes[]);
void humanTurn(Grid<char>& cubeLetters, Set<string> &alreadyUsedWords, Lexicon &dictionary);

/* Main program */

int main() {

    initGraphics(BOGGLE_WINDOW_WIDTH, BOGGLE_WINDOW_HEIGHT);
    welcome();
    giveInstructions();

    int rows = 4;
    int cols = 4;
    int boardSize = rows*cols;
    Vector<string> vec;
    Set<string> humanWords;
    Grid<char> cubeLetters( rows, cols );
    Lexicon dictionary("EnglishWords.dat");

    // Task 1: Copy constant array into a vector vec
    for( int i = 0; i < rows * cols; i++ )
    {
        vec.add( STANDARD_CUBES[ i ] );
    }

    string instructions = "Do you need instructions?";
    if (askQuestion(instructions))
        giveInstructions();

    string customConfig = "Do you want a custom configuration?";
    drawBoard( rows, cols );
    if (askQuestion(customConfig)){
        customConfiguration(boardSize);
    }
    else {
        shakeCubes( rows, cols, vec, cubeLetters );
    }
    // Task 2
    humanTurn( cubeLetters, humanWords, dictionary );

    // debug
    //checkHumanWords( humanWords );

    // Task 3
    findHumanWordsOnBoard( humanWords, cubeLetters );

    // debug
    //checkHumanWords( humanWords );


   //else {
      // shuffleBoard(boardSize,STANDARD_CUBES);
   //}
   return 0;
}

/*
 * Function: shakeCubes
 * Usage: shakeCubes( int, int, Vector<string> & );
 * -----------------
 * Randomize cubes and their sides.
 */

void shakeCubes( int rows, int cols, Vector<string> &vec, Grid<char> &cubeLetters )
{
    // Shuffle cubes
    srand ( time(NULL) );
    random_shuffle( vec.begin(), vec.end() );

    char c;
    int i = 0, j = 0;

    // Shuffle cube sides
    foreach( string s in vec )
    {
        c = s[ rand() % 6 ];
        labelCube( i, j, c );
        cubeLetters[ i ][ j ] = c;

        if( j == 3 )
        {
            i++;
            j = 0;
        }
        else
            j++;
    }
}


void humanTurn(Grid<char>& cubeLetters, Set<string> &alreadyUsedWords, Lexicon &dictionary) {
    cout << endl << "Find all the words you can."
    << endl << "Signal that you're finished by entering an empty line." << endl << endl;
    do {
        cout << "Enter a word: ";
        string word = getLine();

        if(word.empty())
            break; // the only way out of the do-while loop

        toupper(word);

        if(word.length() < MIN_WORD_COUNT) // word < min word length
        {
            cout << "I'm sorry, but we have our standards." << endl
            << "That word doesn't meet the minimum word length." << endl;
        } 
        else if(alreadyUsedWords.contains(word)) // word has already been entered
        {
            cout << "You've already found that word!" << endl;
        } 
        else if( dictionary.contains( word ) ) // word not in lexicon
        {
            alreadyUsedWords.add(word);
            recordWordForPlayer( word, HUMAN );
        }
        else
        {
            cout << "You can't make that word!" << endl;
        }
    } while(true);
}


/*
 * Function: checkBoard
 * Usage: checkBoard( Vector<string> );
 * -----------------
 * Print out current state of Boggle board.
 */

void checkBoard( Vector<string> vec )
{
    cout << endl;

    foreach(string s in vec)
    {
        cout << s << endl;
    }
}

void checkBoardLetters( Grid<char> cubeLetters )
{
    cout << endl;
    for( int i = 0; i < 4; i ++ )
    {
        for( int j = 0; j < 4; j++ )
        {
            cout << cubeLetters[i][j] << endl;
        }
    }
}

void checkHumanWords( Set<string> humanWords )
{
    cout << endl;
    foreach( string s in humanWords )
    {
        cout << s << endl;
    }

    if( humanWords.isEmpty() )
        cout << "human word set is empty" << endl;
}

// Does board contain word? RECURSIVE
bool boardContainsWord( Grid<char> cubeLetters, string word )
{

    foreach( char c in cubeLetters )
    for( int i = 0; i < cubeLetters.numRows(); i++ )
    {
        for( int j = 0; j < cubeLetters.numCols(); j++ )
        {
            if( cubeLetters[i][j] == word[0] )
            {
                if( word.length() == 1 )
                    return true;
                else
                    return boardContainsWord( cubeLetters, word.substr(1) );
            }
        }
    }

    return false;
}
/*
bool boardContainsLetter( Grid<char> cubeLetters, char letter )
{
    return false;
}

// Task 3: Reduce set of HumanWords to those that exist on board
void findHumanWordsOnBoard( Set<string> &humanWords, Grid<char> cubeLetters )
{
    foreach( string word in humanWords )
    {
        if( !boardContainsWord( cubeLetters, word ) )
           humanWords.remove( word );
    }
}
*/

/* *** Brainstorming and Under Construction ***
I need to save (x,y) coordinates and store in a Set collection class to describe path.

I need a recursive, boolean function that takes in string word, Grid board, and Set path to check valid words.
This recursion will check each adjacent letter (and can start from top left [row-1][col-1], and move around the chosen letter);
It also has to check whether it's inBounds before proceeding.


/*
 * Function: welcome
 * Usage: welcome();
 * -----------------
 * Print out a cheery welcome message.
 */

void welcome() {
   cout << "Welcome!  You're about to play an intense game ";
   cout << "of mind-numbing Boggle.  The good news is that ";
   cout << "you might improve your vocabulary a bit.  The ";
   cout << "bad news is that you're probably going to lose ";
   cout << "miserably to this little dictionary-toting hunk ";
   cout << "of silicon.  If only YOU had a gig of RAM..." << endl << endl;
}

/*
 * Function: giveInstructions
 * Usage: giveInstructions();
 * --------------------------
 * Print out the instructions for the user.
 */

void giveInstructions() {
   cout << endl;
   cout << endl << "The boggle board is a grid onto which I will randomly distribute" << endl
        << "dice.  These 6-sided dice have letters rather than numbers on the faces, " << endl
        << "creating a grid of letters on which you try to form words.  You go first, " << endl
        << "entering the words you find that are formed by tracing adjoining " << endl
        << "letters.  Two letters adjoin if they are next to each other horizontally, " << endl
        << "vertically, or diagonally. A letter can only be used once in the word. Words" << endl
        << "must be at least 4 letters long and can only be counted once.  You score points" << endl
        << "based on word length, a 4-letter word is worth one, 5-letters two, etc.  After your " << endl
        << "tiny brain is exhausted, I, the brilliant computer, will find all the remaining " << endl
        << "words in the puzzle and double or triple your paltry score." << endl << endl;
   cout << "Hit return when you're ready...";
   getLine();
}

//Function call to capitalize every letter in a string
void toupper(string& word) {
    for(int i = 0; i < word.size(); i++) {
       word[i] = toupper(word[i]);
    }
}

// A do-while loop to keep asking for a YES/NO answer to a given question
bool askQuestion(string question) {
    do {
        cout << question << " ";
        string answer = getLine();
        if(toupper(answer[0]) == 'N') return false;
        else if(toupper(answer[0]) == 'Y') return true;
    } while (cout << "Please answer yes or no." << endl);

    return 1;
}


// Requests user for a board configuration input for the given size board
// and labels the cubes with the corresponding input string
void customConfiguration(int boardSize) {
    cout << endl << "Enter a " << boardSize
         << "-character string to identify which letters you want on the cubes."
         << endl << "The first " << sqrt(double(boardSize))
         << " letters are the cubes on the top row from left to right "
         << endl << "next " << sqrt(double(boardSize))
         << " letters are the second row, etc."
         << endl << "Enter the string: ";
    string answer;
    do {
        answer = getLine();
        if(answer.size() >= double(boardSize)) break;
    } while (cout << "String must include " << boardSize
        << " characters! Try again: ");
    toupper(answer);
    int strIndex = 0;
    for (int row = 0; row < sqrt(double(boardSize)); row++){
        for (int col = 0; col < sqrt(double(boardSize)); col++){
            char answerSubStr = answer[strIndex];
            labelCube(row, col, answerSubStr);
            strIndex++;
        }
    }
}
1

There are 1 answers

1
Jerry Coffin On BEST ANSWER

Unless you need to (e.g., for homework) don't use recursion for this.

Instead, pre-process your word list into equivalents with the letters sorted. Build a multimap from sorted letters to the words you can represent with a group of letters.

When the player picks out a proposed word, sort the letters in that word, look that up in your map, and see if one of the results matches what they entered. That'll essentially always be such a small number of entries that you can do a simple string comparison for each.

Another possibility would be to build your word list into a trie. This will definitely be more work. It will almost certainly save memory, and probably make lookups minutely faster as well -- but unless you're going to run a boggle web site so the computer is going to play against thousands of people at once, that's unlikely to matter.