Boggle
It says on the assignment brief for this exercise to properly decompose the Boggle program, before starting work. Of course I didn’t. By this stage I was getting cocky and felt like recursion was getting easy. This problem has the capacity to produce hard-to-find bugs if you don’t prepare. My mistake was passing the x and y of the next square/current square, as a parameter. That doesn’t work- much better to get x and y from whatever you’re storing the path of tiles in. They tell you to split the execution between human and player turns, which I did, but I had the computer turn call two of the same functions. The first time I’ve re-used functions! I think if I’d done it flat-out it would have taken me maybe 3 days. Which gives me quite alot of respect for those kids at Stanford! Apparently the CS106B page has changed since I linked to it, so someone asked me to send the course files I’m working from. I’ll try and post them on this blog soon…
/** * File: boggle.cpp * ---------------- * Implements the game of Boggle. */ #include <cctype> #include <iostream> using namespace std; #include "simpio.h" #include "gwindow.h" #include "gboggle.h" #include "map.h" #include "grid.h" #include "random.h" #include "lexicon.h" const int kBoggleWindowWidth = 650; const int kBoggleWindowHeight = 350; const int kBoggleBoardSize = 4; const int kBoggleCubesSize = 16; const string kStandardCubes[16] = { "AAEEGN", "ABBJOO", "ACHOPS", "AFFKPS", "AOOTTW", "CIMOTU", "DEILRX", "DELRVY", "DISTTY", "EEGHNW", "EEINSU", "EHRTVW", "EIOSST", "ELRTTY", "HIMNQU", "HLNNRZ" }; const string kBigBoggleCubes[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" }; static void playGame(); static void welcome(); static void giveInstructions(); static bool responseIsAffirmative(const string& prompt); static void createBoard(Vector<Vector<string> >& cubes, Grid<char>& board); static void forceBoardConfig(Grid<char>& board); static bool isWord(string playerword, Lexicon& english); static bool notUsed(string playerword, Set<string>& usedwords); static bool longEnough(string playerword); static bool findStart(string playerword, Grid<char>& board); static bool findSquares(string playerword, Grid<char>& board, Vector<GPoint>& path, Set<GPoint>& usedsquares, Vector<int>& lastwordsize, string& found); static void nextSquare(string playerword, Grid<char>& board, Vector<GPoint>& path, Set<GPoint>& usedsquares, string& found, int dir); static void removeSquare(Vector<GPoint>& path, Set<GPoint>& usedsquares, Vector<int>& lastwordsize, string& found); static void highlightWord(Vector<GPoint>& path); static bool wordHasGrown(Vector<int>& lastwordsize, string& found); static void computerTurn(Grid<char>& board, Set<string>& usedwords, Lexicon& english); static bool findWords(Grid<char>& board, Vector<GPoint>& path, Vector<int>& lastwordsize, Set<GPoint>& usedsquares, Set<string>& foundwords, string& compfound, Set<string>& usedwords, Lexicon& english, bool endcheck); static void buildWord(Grid<char>& board, Vector<GPoint>& path, Set<GPoint>& usedsquares, Set<string>& foundwords, string& compfound, Set<string>& usedwords, Lexicon& english, int dir); static void processSquare(Vector<GPoint>& path, Set<GPoint>& usedsquares, Set<string>& foundwords, string& compfound, Set<string>& usedwords, Lexicon& english, string& temporary, GPoint& result); int main() { while (true) { playGame(); if (!responseIsAffirmative("Would you like to play again?")) { break; } } return 0; } static void playGame() { GWindow gw(kBoggleWindowWidth, kBoggleWindowHeight); initGBoggle(gw); welcome(); Lexicon english("EnglishWords.dat"); if (responseIsAffirmative("Do you need instructions? ")) { giveInstructions(); } Vector<Vector<string> > cubes; Grid<char> board(kBoggleBoardSize, kBoggleBoardSize); drawBoard(kBoggleBoardSize, kBoggleBoardSize); createBoard(cubes, board); Set<string> usedwords; cout << "I'll give you a chance to set up the board to your specification, which makes it easier to confirm your boggle program is working." << endl; if (responseIsAffirmative("Do you want to force the board configuration? ")) { forceBoardConfig(board); } else { createBoard(cubes, board); } for (int boardx = 0; boardx < kBoggleBoardSize; boardx++) { for (int boardy = 0; boardy < kBoggleBoardSize ; boardy++) { char letter = board.get(boardx, boardy); labelCube(boardx, boardy,letter); } } cout << endl; cout << "Ok, take all the time you want and find all the words you can! Signal that you're finished by entering an empty line." << endl; while (true) { cout << "Enter a word :"; string playerwordinit = getLine(); string playerword = toUpperCase(playerwordinit); if (playerword == "") { break; } if (isWord(playerword, english) && notUsed(playerword, usedwords) && longEnough(playerword)) { if (findStart(playerword, board)) { usedwords.add(playerword); } else { cout << "You can't make that word." << endl; } } } computerTurn(board, usedwords, english); } static 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; } static void giveInstructions() { cout << endl; cout << "The boggle board is a grid onto which I "; cout << "I will randomly distribute cubes. These "; cout << "6-sided cubes have letters rather than "; cout << "numbers on the faces, creating a grid of "; cout << "letters on which you try to form words. "; cout << "You go first, entering all the words you can "; cout << "find that are formed by tracing adjoining "; cout << "letters. Two letters adjoin if they are next "; cout << "to each other horizontally, vertically, or "; cout << "diagonally. A letter can only be used once "; cout << "in each word. Words must be at least four "; cout << "letters long and can be counted only once. "; cout << "You score points based on word length: a "; cout << "4-letter word is worth 1 point, 5-letters "; cout << "earn 2 points, and so on. After your puny "; cout << "brain is exhausted, I, the supercomputer, "; cout << "will find all the remaining words and double "; cout << "or triple your paltry score." << endl << endl; cout << "Hit return when you're ready..."; getLine(); } static bool responseIsAffirmative(const string& prompt) { while (true) { string answer = getLine(prompt); if (!answer.empty()) { switch (toupper(answer[0])) { case 'Y': return true; case 'N': return false; } } cout << "Please answer yes or no." << endl; } } static void createBoard(Vector<Vector<string> >& cubes, Grid<char>& board) { for (int i = 0; i < 16; i++) { Vector<string> onecube; for (int letter = 0; letter < 6; letter++) { string cube = kStandardCubes[i]; string side = cube.substr(letter, 1); onecube.add(side); } cubes.add(onecube); } for (int swap = 0; swap < cubes.size(); swap++) { int swap2 = randomInteger(swap, cubes.size() - 1); Vector<string> element1 = cubes.get(swap); Vector<string> element2 = cubes.get(swap2); cubes.set(swap2, element1); cubes.set(swap, element2); } Vector<string> selections; for (int x = 0; x < 16; x++) { Vector<string> cubethrow = cubes.get(x); int random = randomInteger(0, 5); string selection = cubethrow.get(random); selections.add(selection); } int selectionsindex = 0; for (int boardx = 0; boardx < kBoggleBoardSize; boardx++) { for (int boardy = 0; boardy < kBoggleBoardSize ; boardy++) { string position = selections.get(selectionsindex); char position2 = position[0]; board.set(boardx, boardy, position2); selectionsindex++; } } } static void forceBoardConfig(Grid<char>& board) { cout << "Enter a 16-character string to identify which letters you want on the cubes." << endl; cout << "The first 4 letters are the cubes on the top row from left to right, the next 4 letters are the second row, and so on." << endl; cout << "Enter the string: "; string playerselect = getLine(); cout << "This is the playerselect " + playerselect << endl; if (playerselect.size() < kBoggleCubesSize) { cout << "That string isnt long enough!" << endl; forceBoardConfig(board); } else if (playerselect.size() >= kBoggleCubesSize) { int stringindex = 0; for (int boardx = 0; boardx < kBoggleBoardSize; boardx++) { for (int boardy = 0; boardy < kBoggleBoardSize ; boardy++) { char select = playerselect[stringindex]; char upperselect = toupper(select); board.set(boardx, boardy, upperselect); stringindex++; } } } } static bool isWord(string playerword, Lexicon& english) { if (english.contains(playerword)) { return true; } else { cout << "That isn't a word!" << endl; } return false; } static bool notUsed(string playerword, Set<string>& usedwords) { if (usedwords.contains(playerword)) { cout << "You already used that word." << endl; return false; } return true; } static bool longEnough(string playerword) { if (playerword.size() < 4) { cout << "I'm sorry, but we have our standards." << endl; cout << "That word doesn't meet the minimum word length." << endl; return false; } return true; } static bool findStart(string playerword, Grid<char>& board) { char beginning = playerword[0]; char uppercase = toupper(beginning); for (int x = 0; x < kBoggleBoardSize; x++) { for (int y = 0; y < kBoggleBoardSize; y++) {// loop cycles through each space of the board looking for start letter char boardlocation = board.get(x, y); if (uppercase == boardlocation) { Vector<GPoint> path;// stores path of matched letters Vector<int> lastwordsize;// stores size of the word in the previous turn to check wordhasgrown Set<GPoint> usedsquares;// stores the usedsquares so they arent re-used in same word string found = playerword.substr(0, 1); lastwordsize.insert(0, 1); GPoint start(x, y); path.add(start); usedsquares.add(start); if (findSquares(playerword, board, path, usedsquares, lastwordsize, found)) { highlightWord(path); recordWordForPlayer(playerword, HUMAN); return true; } } } } return false; } static bool findSquares(string playerword, Grid<char>& board, Vector<GPoint>& path, Set<GPoint>& usedsquares, Vector<int>& lastwordsize, string& found) { if (playerword == found) {// Base case of found word matches the player input word return true; } if (found.size() == 0) {// if the found word goes back to empty, return false and cycle to the next start letter return false; } for (int dir = 0; dir < 8; dir++) {// loop of 8, one per possible direction nextSquare(playerword, board, path, usedsquares, found, dir);// returns GPoint of next square if (wordHasGrown(lastwordsize, found)) {// if the word has grown if (findSquares(playerword, board, path, usedsquares, lastwordsize, found)) {// recurse to new loop till its solved return true; } } } removeSquare(path, usedsquares, lastwordsize, found);// remove square after the loop and rewind to earlier decision return false; } static void nextSquare(string playerword, Grid<char>& board, Vector<GPoint>& path, Set<GPoint>& usedsquares, string& found, int dir) { GPoint newstart = path.get(path.size() - 1); int x = newstart.getX(); int y = newstart.getY(); int length = found.size(); char target = playerword[length]; if (dir == 0) {// dir is loop number from findsquares- direction if (board.inBounds(x + 1, y)) { char thisone = board[x + 1][y]; // for each cycle of the loop check a letter on the board around the source tile GPoint result(x + 1, y); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } if (dir == 1) { if (board.inBounds(x - 1, y)) { char thisone = board[x - 1][y]; GPoint result(x - 1, y); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } if (dir == 2) { if (board.inBounds(x, y + 1)) { char thisone = board[x][y + 1]; GPoint result(x, y + 1); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } if (dir == 3) { if (board.inBounds(x, y - 1)) { char thisone = board[x][y - 1]; GPoint result(x, y - 1); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } if (dir == 4) { if (board.inBounds(x - 1, y - 1)) { char thisone = board[x - 1][y - 1]; GPoint result(x - 1, y - 1); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } if (dir == 5) { if (board.inBounds(x + 1, y - 1)) { char thisone = board[x + 1][y - 1]; GPoint result(x + 1, y - 1); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } if (dir == 6) { if (board.inBounds(x + 1, y + 1)) { char thisone = board[x + 1][y + 1]; GPoint result(x + 1, y + 1); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } if (dir == 7) { if (board.inBounds(x - 1, y + 1)) { char thisone = board[x - 1][y + 1]; GPoint result(x - 1, y + 1); if (target == thisone && !usedsquares.contains(result)) { found = found + target; path.add(result); usedsquares.add(result); } } } } static void removeSquare(Vector<GPoint>& path, Set<GPoint>& usedsquares, Vector<int>& lastwordsize, string& found) { if (found.length() > 0) { GPoint erase = path.get(path.size() - 1); found.erase(found.length() - 1); int newwordsize = found.length(); lastwordsize.set(0, newwordsize); path.remove(path.size() - 1); usedsquares.remove(erase); } } static void highlightWord(Vector<GPoint>& path) { for (int i = 0; i < path.size(); i++) { GPoint update1 = path.get(i); int x = update1.getX(); int y = update1.getY(); highlightCube(x, y, true); } pause(200); for (int n = 0; n < path.size(); n++) { GPoint update2 = path.get(n); int x = update2.getX(); int y = update2.getY(); highlightCube(x, y, false); } } static bool wordHasGrown(Vector<int>& lastwordsize, string& found) {// int currentsize = found.length(); int lastsize = lastwordsize.get(0); if (lastsize >= currentsize) { lastwordsize.set(0, currentsize); return false; } else if (lastsize < currentsize){ lastwordsize.set(0, currentsize); return true; } } static void computerTurn(Grid<char>& board, Set<string>& usedwords, Lexicon& english) { Set<string> foundwords; for (int x = 0; x <= kBoggleBoardSize; x++) { for (int y = 0; y <= kBoggleBoardSize; y++) { string compfound; if (x < kBoggleBoardSize && y < kBoggleBoardSize) { char boardlocation = board.get(x, y); compfound = compfound + boardlocation; } Vector<GPoint> path;// stores path of matched letters Vector<int> lastwordsize;// stores size of the word in the previous turn to check wordhasgrown Set<GPoint> usedsquares;// stores the usedsquares so they arent re-used in same word bool endcheck = false;// during the loop send out false for recursive base case if (x == kBoggleBoardSize || y == kBoggleBoardSize) { endcheck = true;// hits the final extra loop and sends true } lastwordsize.insert(0, 1); GPoint start(x, y); path.add(start); usedsquares.add(start); if (findWords(board, path, lastwordsize, usedsquares, foundwords, compfound, usedwords, english, endcheck)) { break; } } } foreach (string word in foundwords) { recordWordForPlayer(word, COMPUTER); } } static bool findWords(Grid<char>& board, Vector<GPoint>& path, Vector<int>& lastwordsize, Set<GPoint>& usedsquares, Set<string>& foundwords, string& compfound, Set<string>& usedwords, Lexicon& english, bool endcheck) { if (endcheck == true) {// base case is the extra loop in computerturn that sends out endcheck bool return true; } if (compfound.size() == 0) { return false; } for (int dir = 0; dir < 8; dir++) { buildWord(board, path, usedsquares, foundwords, compfound, usedwords, english, dir); if (wordHasGrown(lastwordsize, compfound)) {// re-use wordhasgrown function if (findWords(board, path, lastwordsize, usedsquares, foundwords, compfound, usedwords, english, endcheck)) { return true; } } } removeSquare(path, usedsquares, lastwordsize, compfound);// re-use removesquare function return false; } static void buildWord(Grid<char>& board, Vector<GPoint>& path, Set<GPoint>& usedsquares, Set<string>& foundwords, string& compfound, Set<string>& usedwords, Lexicon& english, int dir) { GPoint newstart = path.get(path.size() - 1); int x = newstart.getX(); int y = newstart.getY(); string temporary = compfound; if (dir == 0) {// dir is loop number from findsquares- direction if (board.inBounds(x + 1, y)) { char thisone = board[x + 1][y]; // for each cycle of the loop check a letter on the board around the source tile temporary = temporary + thisone; GPoint result(x + 1, y); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } if (dir == 1) { if (board.inBounds(x - 1, y)) { char thisone = board[x - 1][y]; temporary = temporary + thisone; GPoint result(x - 1, y); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } if (dir == 2) { if (board.inBounds(x, y + 1)) { char thisone = board[x][y + 1]; temporary = temporary + thisone; GPoint result(x, y + 1); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } if (dir == 3) { if (board.inBounds(x, y - 1)) { char thisone = board[x][y - 1]; temporary = temporary + thisone; GPoint result(x, y - 1); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } if (dir == 4) { if (board.inBounds(x - 1, y - 1)) { char thisone = board[x - 1][y - 1]; temporary = temporary + thisone; GPoint result(x - 1, y - 1); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } if (dir == 5) { if (board.inBounds(x + 1, y - 1)) { char thisone = board[x + 1][y - 1]; temporary = temporary + thisone; GPoint result(x + 1, y - 1); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } if (dir == 6) { if (board.inBounds(x + 1, y + 1)) { char thisone = board[x + 1][y + 1]; temporary = temporary + thisone; GPoint result(x + 1, y + 1); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } if (dir == 7) { if (board.inBounds(x - 1, y + 1)) { char thisone = board[x - 1][y + 1]; temporary = temporary + thisone; GPoint result(x - 1, y + 1); if (usedsquares.contains(result) == false) { processSquare(path, usedsquares, foundwords, compfound, usedwords, english, temporary, result); } } } } static void processSquare(Vector<GPoint>& path, Set<GPoint>& usedsquares, Set<string>& foundwords, string& compfound, Set<string>& usedwords, Lexicon& english, string& temporary, GPoint& result) { if (english.containsPrefix(temporary) || english.contains(temporary)) { path.add(result); usedsquares.add(result); compfound = temporary; if (english.contains(compfound) && compfound.size() >= 4 && !usedwords.contains(compfound)) { foundwords.add(compfound); } } }

