(Latest Revision: 08/28/2004)

Hints to Help You Write the Decoder Program


THE MAIN PROGRAM:

Your main program can, for example, consist of two basic steps:
  1. Call a function to read the input into an array.
  2. Execute a for-loop that outputs decoded rows from the array, one row at a time, last row first.
Just to make this discussion easier, let's assume that the main program calls a function named "ReadFile" to put the message into the array, and that it calls a function named "OutPutRow" each time it wants decode and output a row of the array.

Your program is required to read the message into an array. You can declare the array by starting the program out like this:
#include <iostream>

using namespace std ;

#define MAX_ROWS 21
#define MAX_COLS 71

typedef char bufferType [MAX_ROWS][MAX_COLS] ;

int main ()
{
    bufferType buffer ;
    ...
    return 0;
}
The declarations above give you an array of characters called "buffer" with 21 rows numbered 0..20 and 71 columns numbered 0..70. Since it is one of the inputs used by ReadFile, you would make buffer a parameter of ReadFile.

Let's assume you want ReadFile to put each line of the message into a new row of buffer. I heartily recommend you do that! It will make it easy to insure that the output of the program is formatted as well as the input.

You should design the second part of the main program so it only accesses the part of buffer that contains the message. For example, if the message is four lines long, the second part of the program should process four lines in buffer. However if the message is 10 lines long then the program should process ten lines in buffer.

To make this work, you will probably want ReadFile to count the number of lines in the message. Thus, the main program should have a variable with a name like "numLines" and numLines should be another parameter of ReadFile. ReadFile will count the number of lines in the message and return the value in the parameter numLines. When you write the loop that outputs the decoded message from buffer, you can use the value of numLines to determine which row of buffer to begin on.

Buffer should be a parameter of OutPutRow because it is something that OutPutRow uses as an input. Also, OutPutRow has to be told which row of buffer to work on, so the desired row number should be another parameter of OutPutRow.

When OutPutRow does its job it needs to process the message line stored in a certain row. It needs to start at the last character of the line. How will OutPutRow know where that character is located? Each line of buffer has slots enough for 70 characters (71 really, as I have defined it above).

One way to deal with this problem is to have ReadFile count the number of characters in each line of the message. You can declare an array of integers for storing the counts. Here's how the necessary declarations might look in your program:

#include <iostream>

using namespace std ;

#define MAX_ROWS 21
#define MAX_COLS 71

typedef char bufferType [MAX_ROWS][MAX_COLS] ;
typedef int lengthType [MAX_ROWS] ;

int main ()
{
    bufferType buffer ;
    lengthType length ;
    ...
    return 0;
}
You would make the "length" array a parameter of ReadFile. ReadFile would return "length" after storing the lengths of all the message lines there.

You would also have another parameter for OutPutRow: a single integer that represents the number of characters in the message line. That way when OutPutRow starts to do its job, it knows how many characters are in the message line it is working on, and so it knows which location in buffer to start with.

To summarize, we could get a workable main program if it called two functions, with prototypes something like this:

void ReadFile (bufferType buffer, lengthType length, int & numLines ) ;
void OutPutRow (bufferType buffer, int numChars, int rowNum) ;
HOW TO READ A FILE CHARACTER-BY-CHARACTER:

A programmer can use "cin statements" to read character data, but not if s/he wants the program to read the white space. "cin statements" skip over white space. They are not suitable for what we need to do in this program.

Instead consider this little program:


#include <iostream>

using namespace std ;

int main ()
{
  char curCh ;
      /* Outer loop stops when end-of-file is reached. */
  while (cin.peek() != EOF)
  {    /* Read and echo the current line. */

        /* Inner loop stops when end-of-line is reached. */
    while (cin.peek() != '\n')
    {      /* Read and echo the current character. */
       cin.get(curCh);
       cout.put(curCh) ;
    }
           /* Now move past the newline character
              and start a new line of output. */
     cin.get() ;
     cout << endl ;
  }
     return 0;
}
To get a good understanding of what this code does, save it as file mycat.cpp. Then compile and run it like this:


g++ mycat.cpp
a.out < foo
except, replace "foo" with the name of a text file you have in your directory. You can even use the name mycat.cpp. When the program runs it will just read and echo the file line-by-line and character-by-character. It will thus simply write the file to the screen, reproducing the line breaks and white space exactly as they occur in the file.

You can modify this little program to turn it in to a ReadFile function for your decoder program. You will have to take out the code that writes the characters to the screen, and you will have to change the input statements so that they put the characters into the appropriate slots in buffer instead of putting them into "curCh." You will also have to add some code to take care of counting the number of lines and counting the number of characters on each line.

The basic idea is to introduce two variables: rowNum, colNum. When the program reads one of the characters in the message, the call to cin.get looks like this:

cin.get(buffer[rowNum][colNum]) ;

This has the effect of reading the next character and placing it into buffer in a particular array slot determined by the row number rowNum and the column number colNum.

Of course for this to work, you have to insert code that insures that rowNum and colNum always have the correct values. rowNum has to be initialized before the outer loop starts. Each time before the inner loop starts, colNum has to be re-initialized and rowNum has to be incremented. Each time before a character is read into buffer, colNum has to be incremented.

HOW TO OUTPUT A ROW OF THE BUFFER:

You need to do array-indexing similar to what I discussed above under the subtitle: HOW TO READ A FILE CHARACTER-BY-CHARACTER. Here you need to output translations of characters that are in buffer. You can use a for-loop and a statement like:

cout << Translate(buffer[rowNum][colNum]) ;

Where "Translate" is a function you will have to write. The job of Translate is to input a character, and to return the decoded version of the character.

HOW TO DECODE A CHARACTER:

The function (I call it "Translate") that decodes a character can be very simple. First of all it can have an if-else statement that checks to see if the character is between 'A' and 'Z'. You just use the >= and <= operators to do that check.

If the character is not an upper case letter then the function just returns the character itself -- there's no need to decode. If the character is an upper case letter then basically you have to add 5 to the character and return that. There is one little wrinkle though. You have to "wrap around." You can't just add 5 to the letters 'V', 'W', 'X', 'Y', and 'Z'. You can treat those as a special case in the code if you want. It's also possible to create one formula that works on all the letters. I have a formula like that. It uses the operators: '-', '+', and '%'.

Instead of using the "magic number" 5 directly in the program, you should declare it as a constant at the beginning. For example, you could have all these "#defines" near the top of the source file:


#define MAX_ROWS 21
#define MAX_COLS 71
#define CODE_NUM 5
The idea is to use the name "CODE_NUM" in Translate, instead of using the specific number 5 there.

If you are "clever" in the way that you use the value of CODE_NUM in your decoder function then it will "decode" properly for any value of CODE_NUM between 1 and 25. You should then be able to change your decoder program into an encoder program just by changing the declaration:

 
#define CODE_NUM 5
To
 
#define CODE_NUM 21
(Notice that the decode-encode number pairs add up to 26.) For testing purposes, it's nice to be able to do that. Under those conditions, you can just create test messages in English (or your favorite language) and encode them with the version of the program that has CODE_NUM equal to 21. Then you can take the encoded messages and verify that the version of the program with CODE_NUM equal to 5 decodes them properly. This way, you don't have to do any "hand-encoding" when you want to make test files. (By the way, yes you have to make test files and test your program with them. If you don't test, or if you test merely by doing the example translation in the assignment sheet there will be a large penalty. You won't get full credit for testing unless you get complete code and data coverage.)