/////////////////////////////////////////////////////////////////////////////// // Crossword Puzzle Helper // // From Chapter 15 of: // "C++: The Core Language" by Gregory Satir and Doug Brown. // O'Reilly & Associates, Inc. Sebastopol, CA. 1995. /////////////////////////////////////////////////////////////////////////////// #include // to get exit() #include // to get strlen() and strcpy() #include // to get old-style input #include // to get new-style output #include // to get set_new_handler() // big trouble if any word in the dictionary // exceeds this ('\0' terminator included) #define maxwordsize 256 // These are now C++ keywords, take them out if your compiler has them. // If not, use these definitions. typedef int bool; #define true 1 #define false 0 // length of hexword generalized // You could have a puzzle with a different length, but we haven't seen any. #define hex 6 // error exit void oops(char *msg) { cerr << msg << "\n"; exit(1); } // called by new if insufficient memory void dearth_o_mem() { oops("out of memory"); } /////////////////////////////////////////////////////////////////////////////// // String: wrapper class for array of characters /////////////////////////////////////////////////////////////////////////////// class String { public: String(); // default constructor ~String(); // destructor void operator=(String &s); // assignment operator String(String &s); // copy constructor void set(char *s); // set from char* char *s(); // return pointer to string char c(int n); // return nth char int length(); // return length of string void print(ostream *os); // print string to output stream bool read(FILE *is); // read string from old-style input private: char *str; // pointer to string int lth; // length of string }; // default constructor - initialize object String::String() { str = 0; // make nothing for set() to deallocate set(""); // set() copies empty string } // destructor - free object memory String::~String() { delete[] str; // deallocate string } // assignment operator - define meaning of operator=() for this class void String::operator=(String &s) { if (this == &s) return; // don't assign to self set(s.str); // set() makes a copy } // copy constructor - initialize object as copy of another object String::String(String &s) { str = 0; // make nothing for set() to deallocate set(s.str); // set() makes a copy } // set value from string void String::set(char *s) { delete[] str; // deallocate old string lth = strlen(s); // set length of new string str = new char[lth+1]; // alloc space for string and terminator strcpy(str, s); // copy string and terminator } // return pointer to string char *String::s() { return str; // return the pointer } // return nth character of string char String::c(int n) { // check to make sure n is inside string if (n < 0 || n >= lth) oops("string index error"); return str[n]; // return the char } // return length of string int String::length() { return lth; // return the length } // print string to output stream void String::print(ostream *os) { *os << str; // op<<() already knows how to print char* } // overload << for String ostream &operator<<(ostream &os, String &s) { s.print(&os); // call our print member function return os; // so you can write: cout << a << b } // read string from old-style input // This routine uses fscanf() directly into a buffer, which is unsafe. // If the input file has a word that's too long it will write outside // the buffer. The code to do this right, typically using a getc() // and checking the buffer size with each character, is left as an // exercise for the reader because it contains nothing new for C++. bool String::read(FILE *is) { char buf[maxwordsize]; // temp buffer int n = fscanf(is, "%s", buf); // read word into buffer if (n != 1) return false; // EOF or other failure set(buf); // set String to word return true; // successful return } /////////////////////////////////////////////////////////////////////////////// // Rule: class for rules that decide if a word matches some criterion. // This class defines the interface for the acceptance rule. // It has no default for accepts() so all the rules that can be instantiated // are derived from this. /////////////////////////////////////////////////////////////////////////////// class Rule { public: // test word and return true if it meets the criterion virtual bool accepts(String word) = 0; }; /////////////////////////////////////////////////////////////////////////////// // CwRule: class for crossword puzzle rules /////////////////////////////////////////////////////////////////////////////// class CwRule : public Rule { public: // member function declared in base class bool accepts(String word); // set the rule void set(String cmd); private: // where to store the rule String cword; }; // Test a word to see if it matches a crossword puzzle rule. // The rule is stored as a String with ?'s where any character // can match, and the rest of the characters must match exactly. bool CwRule::accepts(String dword) { int i, dlth = dword.length(); // The word must match the rule length exactly if (dlth != cword.length()) return false; // loop through each character in the rule for (i = 0; i < dlth; i++) { // get the character in the rule char c = cword.c(i); // word doesn't match if characters don't match, // unless rule character is a ? if (c != '?' && c != dword.c(i)) return false; } // word matches if all the characters match return true; } // set crossword puzzle rule void CwRule::set(String cmd) { cword = cmd; // uses assignment operator from String class } /////////////////////////////////////////////////////////////////////////////// // HwRule: class for hexword puzzle rules /////////////////////////////////////////////////////////////////////////////// class HwRule : public Rule { public: // member function declared in base class bool accepts(String word); // set the rule void set(String cmd); private: // where to store the rule String hword; }; // Test a word to see if it matches a hexword puzzle rule. // The rule is stored as a String with ?'s where any character // can match, and the rest of the characters must match exactly. // Unlike the crossword puzzle word, the hexword puzzle word doesn't // have to match the size of the rule. The word must be exactly // 6 characters long, and the rule must match consecutive characters // in the dictionary word, going in either direction, possibly // wrapping around the end or beginning. bool HwRule::accepts(String dword) { int d, h; // dictionary word length must be 6 if (dword.length() != hex) return false; // For every starting point d in the dictionary word, // we scan forward and backward from d to see if the rule matches. // Instead of d going from 0 to 6 it goes from 6 to 12 so we // can add or subtract (to go forward or backward) then take the // modulo 6 to get a character position in the dictionary word. // If d started at 0 it would go negative and we don't trust // modulo of negative numbers. for (d = hex; d < 2 * hex; d++) { // at each new starting point of the dictionary word, // assume the forward and backward searches are successful so far bool fwd = true, bwd = true; int hlth = hword.length(); // scan the rule, and fwd and bwd in the dictionary word for (h = 0; h < hlth; h++) { // get rule character char c = hword.c(h); // fail fwd at this d if it doesn't match exactly, unless ? fwd = fwd && (c == '?' || c == dword.c((d+h)%hex)); // fail bwd at this d if it doesn't match exactly, unless ? bwd = bwd && (c == '?' || c == dword.c((d-h)%hex)); // optimization - give up on this d if fwd and bwd have already failed if (!fwd && !bwd) break; } // optimization - match if fwd or bwd matched at any d if (fwd || bwd) return true; } // failed if neither fwd nor bwd matched for any d return false; } // set hexword puzzle rule void HwRule::set(String cmd) { hword = cmd; // uses assignment operator from String class } /////////////////////////////////////////////////////////////////////////////// // Scanner: Class to scan the dictionary searching for words that match // a given rule. /////////////////////////////////////////////////////////////////////////////// class Scanner { public: // default constructor Scanner(); // destructor ~Scanner(); // set the dictionary file void dict(String name); // scan the dictionary for a given rule void scan(Rule *the_rule); private: // assignment operator void operator=(Scanner &s); // copy constructor Scanner(Scanner &s); // store pointer to dictionary file stream here FILE *dictionary; }; // default constructor - initialize object Scanner::Scanner() { // no dictionary to start with dictionary = 0; } Scanner::~Scanner() { // close dictionary if it's open if (dictionary != 0) fclose(dictionary); } // set dictionary file void Scanner::dict(String name) { // open the file for old-style reading dictionary = fopen(name.s(), "r"); // error if it won't open for reading if (dictionary == 0) oops("can't open dictionary"); } // scan the dictionary with a rule void Scanner::scan(Rule *the_rule) { String word; // make sure we have a dictionary if (dictionary == 0) oops("no dictionary to search"); // the dictionary needs to be rewound after the first scan rewind(dictionary); // read a word from the dictionary while (word.read(dictionary)) { // check if the word matches the rule if (the_rule->accepts(word)) { // print it if it does cout << word << "\n"; } } } /////////////////////////////////////////////////////////////////////////////// // mainline code /////////////////////////////////////////////////////////////////////////////// // The input loop. Pass it an instantiation of a scanner. void input_loop(Scanner *the_scanner) { String cmd; // command word goes here CwRule cr; // crossword rule goes here HwRule hr; // hexword rule goes here // read in typed-in commands while (cmd.read(stdin)) { // word beginning with q means quit (or quite enough) if (cmd.c(0) == 'q') break; // check for one of our rules switch (cmd.c(0)) { // c for crossword rule case 'c': // read next word into a String if (!cmd.read(stdin)) return; // set the crossword rule with it cr.set(cmd); // scan the dictionary with the crossword rule the_scanner->scan(&cr); break; // h for hexword rule case 'h': // read next word into a String if (!cmd.read(stdin)) return; // set the hexword rule with it hr.set(cmd); // scan the dictionary with the hexword rule the_scanner->scan(&hr); break; // otherwise it's an error default: cout << "unrecognized command\n"; break; } } } // The main function. Parameter argv[1] should be dictionary file name. int main(int argc, char**argv) { // we want new to exit on memory exhaustion set_new_handler(dearth_o_mem); // keep things in sync while we mix the stdio and iostream libraries ios::sync_with_stdio(); // the scanner we use for scanning the dictionary Scanner the_scanner; // String for dictionary name String dictName; argc--; argv++; if (argc != 1) oops("must specify a single dictionary"); // set dictionary name String from command argument dictName.set(*argv); // give dictionary name to scanner the_scanner.dict(dictName); // pass scanner to input loop input_loop(&the_scanner); // exit when input loop is done return 0; }