First off, congratulations on your good design decision :)
A bunch of style comments.
0) As noted, use a string for reading in. When you do need a character array for something, you should see if you can allocate it on the stack rather than the heap. Here MAX_LINE_SIZE is a compile-time constant (presumably) and is not huge, so there's nothing stopping you from doing 'char newline[MAX_LINE_SIZE]', and not having to worry about deleting the memory yourself. (Remember, not having to worry about it is one of the reasons for using std::string in the first place.) But when you do have to allocate dynamically, you *must* make sure your deletion matches your allocation: delete[] for new[], delete for new. (And free for malloc, but don't use that in C++.)
1) Don't reuse your counter variables, and try to scope things according to where they are used.
2) Mark your constants as constants.
3) Prefer to pass complex arguments by reference - const reference if you don't need to change them.
4) Think about your interfaces. IntToString() can be generalized, assuming you're using stringstream techniques, to handle other types, by the magic of templating. FindAnchor should probably just seek the file to the desired point and return nothing (pass the stream by reference).
5) Mark your constants as const.
6) A function called "ReadIn()" should be actually reading into some container; do the output somewhere else. I've arranged to return the whole text as a string here. There may be better approaches depending on context. Normally, though, you want to separate the functionality of communicating with the user from the rest of the code.
7) Don't declare variables for simple expressions that are only used in one place.
8) Extract helper functions for repetitive sections of code.
// Warning: all off the top of my head, not compiled or tested.bool ReplaceAll(string& in, const string& what, const string& with) { // replace all occurrences of 'what' in 'in' with 'with', mutating the // supplied 'in'. Return whether any replacements occurred. // Instead of the 'i++' logic that was here, I just adjust the loop 'find'. // This is only a problem if "with" contains "what", but eh... This is // actually more robust, since the other version only guards against "with" // *starting with* "what". boolean found = false; for (int i = in.find(what); i != string::npos; i = in.find(what, i + with.size())) { found = true; in.replace(i, what.size(), with); } return found; // note the loop doesn't get to execute if the first find() fails}string ReadIn(PLAYER& player, const string& filename, int anchor, bool speech) { const string name = "<name>"; const string shorttitle = "<shorttitle>"; const string longtitle = "<longtitle>"; const string end = "<endread>"; const string nline = "\n"; const string blank = ""; ifstream in(filename.data()); SeekTo(in, "<a=" + boost::lexical_cast<string>(anchor) + ">"); string result; if (speech) { // Get name. We will constantly be appending to the result. // At the start, the result is empty, so we can read in there directly. getline(in, result, '$'); result.append(":"); } bool done = false; while (!done) { string line; // into which we read. getline(in, line, '$'); ReplaceAll(line, name, player.GetName()); ReplaceAll(line, longtitle, GetFullTitle(player)); // Why is GetName() a member function but GetFullTitle() a free function? // Also, you seem to have forgotten about <shorttitle> entirely? done = ReplaceAll(line, end, blank)); // If there was a line end, we're done at the end of this iteration. // Note that your version wouldn't emit the space on the last line of a // speech ;) if (speech) { result.append(" "); } result.append(line); result.append(nline); } // You don't have to .close() the stream; that is done automatically by the // destructor. return result;}