Computer Science 3300
Fall 2013
Section 001
Programming Assignment 2

Assigned: Monday, September 16
First version due: Tuesday, October 2, 11:59pm
Second version due: Friday, October 25, 11:59pm


Read the entire assignment before you start working on it. It might appear very large and difficult at first. But if you do it the way that I have suggested you will only need to write roughly 250-300 nonblank lines. Just work your way forward, one step at at time.


Table of contents

  1. Background
  2. Program Requirements
  3. Sets
  4. Puzzles
  5. Getting a section of a puzzle
  6. Reading and writing puzzles
  7. Building and running your program
  8. Tactic 1
  9. The puzzle solver
  10. Debug prints
  11. Tactic 2
  12. Tactic 3
  13. Regression tests
  14. Notes on grading
  15. Submitting your work


Background

A Sudoku puzzle is a 9x9 grid. Some of the cells have digits in them, and some are empty. The grid naturally breaks into rows and columns, but it is also broken into nine 3x3 squares. For example, see here for sample puzzles. The goal is to fill in a digit into each of the empty cells, with the following requirements.

  1. Each digit must be from 1 to 9.
  2. Each row, column and square has all of the digits from 1 to 9 in it, each exactly once.


Program Requirements

Write a C++ program that reads a Sudoku puzzle and prints a solution to that puzzle. The puzzle is presented as 9 lines, with 9 nonblank characters per line. A digit represents an initially occupied cell, and a dash represents an empty cell. There can be empty lines, which are skipped, and there can be spaces in a line, which are also skipped. The input might be as follows.

1-- 489 --6
73- --- -4-
--- --1 295

--7 12- 6--
5-- 7-3 --8
--6 -95 7--

914 6-- ---
-2- --- -37
8-- 512 --4
You cannot assume that there are spaces or empty lines. So the input might also be as follows.
1--489--6
73-----4-
-----1295
--712-6--
5--7-3--8
--6-957--
9146-----
-2-----37
8--512--4

The output should consist of the original puzzle followed by the the solved puzzle, in a similar style. Please write the output with spaces and line breaks the way it was shown in the first input example, regardless of the original form of the input. For example, the output might be as follows.

The original puzzle:

1-- 489 --6
73- --- -4-
--- --1 295

--7 12- 6--
5-- 7-3 --8
--6 -95 7--

914 6-- ---
-2- --- -37
8-- 512 --4

Solution:

152 489 376         
739 256 841
468 371 295

387 124 659
591 763 428
246 895 713

914 637 582
625 948 137
873 512 964    

The puzzle was solved.

Sets

You will want to store a set of possible values for each cell. You can use the following implementation of sets. Get both files.

To use sets, include intset.h in your program, as follows.

  #include "intset.h"
(Notice the quotes instead of <...>. The quotes tell the compiler to look for intset.h in the directory that this file is in, while <...> tell the compiler to look in the standard library directory.)

You can use the following capabilities.

SetOfSmallInts

SetOfSmallInts is a type. Use it like any other type. A value of type SetOfSmallInts is a subset of {1,2,3,4,5,6,7,8,9}. (It does not need to be a proper subset, so it can be set {1,2,3,4,5,6,7,8,9}.) When you create a variable of type SetOfSmallInts, it is automatically initialized to hold an empty set.

emptySet

emptySet is an empty set. For example,
  SetOfSmallInts s;
  s = emptySet;
creates a variable s, and explicitly initializes it to hold an empty set. (The initialization is redundant, since s is initialized to an empty set automatically when it is created.)

isEmpty(s)

isEmpty(s) returns true if s is an empty set, and false otherwise.

rangeSet(x,y)

rangeSet(x,y) is a set {x, x+1, ..., y} holding all integers that are greater than or equal to x and less than or equal to y. For example, rangeSet(2,6) is set {2,3,4,5,6}. Notice that rangeSet(3,2) is {}, since there are no integers that are greater than or equal to 3 and less than or equal to 2. For example,
  SetOfSmallInts s;
  s = rangeSet(1,9);
makes set s = {1,2,3,4,5,6,7,8,9}.

singletonSet(x)

singletonSet(x) returns a set that contains only x. For example,
  SetOfSmallInts t;
  t = singletonSet(1);
makes t hold set {1}.

isSingleton(s)

isSingleton(s) returns true if set s holds exactly one number, and false otherwise.

onlyMember(s)

If s is a singleton set, then onlyMember(s) returns the member of s. If s is not a singleton set, then onlyMember(s) returns 0. For example,
  SetOfSmallInts s = singletonSet(3);
  int n = onlyMember(s);
makes n = 3.

smallest(s)

smallest(s) returns the smallest member of s, or 0 if s is an empty set. For example,
  SetOfSmallInts s = rangeSet(3,6);
  int n = smallest(s);
makes n = 3.

member(x,s)

member(x,s) returns true (1) if x is a member of set s, and false (0) otherwise. For example, if s is set {2,4}, then member(2,s) is true, but member(5,s) is false.

setUnion(s,t)

setUnion(s,t) returns the union of sets s and t. For example,
  SetOfSmallInts s, t, u;
  s = singletonSet(2);
  t = singletonSet(6);
  u = setUnion(s,t);
makes u hold set {2,6}.

setIntersection(s,t)

setIntersection(s,t) returns the intersection of sets s and t (the set of all numbers that are in both s and t). For example,
  SetOfSmallInts s,t,u;
  s = rangeSet(1,5);
  t = rangeSet(3,7);
  u = setIntersection(s,t);
makes u = {3,4,5}.

setDifference(s,t)

setDifference(s,t) returns the set of all numbers that are in s but not in t. For example,
  SetOfSmallInts s,t,u;
  s = rangeSet(1,5);
  t = rangeSet(3,7);
  u = setDifference(s,t);
makes u = {1,2}.

insert(x,s)

insert(x,s) returns the set that you get by adding x to set s. It does not change s. If x is already in s, insert(x,s) returns s. For example,
  SetOfSmallInts u,v;
  u = singletonSet(3);
  v = insert(5, u);
makes u = {3} and v = {3,5}. Notice that creating v does not change u.

remove(x,s)

remove(x,s) returns the set that you get by removing x from set s. It does not change s. If x is not in s then remove(x,s) returns s.

size(s)

size(s) returns the number of members that set s has. For example, size(rangeSet(2,5)) = 4.


Puzzles

A puzzle is a 9x9 array of sets. If you include definition

  typedef SetOfSmallInts Puzzle[9][9];
then you can use Puzzle as a type, indicating a 9x9 array of sets. If p has type Puzzle, then p[i][j] is the set stored at row i, column j of p. The rows and columns are numbered from 0 to 8, not from 1 to 9. So the upper left corner of puzzle p is p[0][0] and the lower right corner is p[8][8].

To get you going, here is a function that copies a puzzle. You can copy it into your program. (Just copy and paste. Do not copy it by hand! Be sure to copy the comment too, not just the function definition.) Read the function definition to see how it works.

/****************************************************************
 *                      copyPuzzle                              *
 ****************************************************************
 * Copy puzzle p into q.  For example, if p is a puzzle, then  *
 *    Puzzle q;                                                 *
 *    copyPuzzle(q, p);                                         *
 * stores a copy of puzzle p into q.                            *
 ****************************************************************/

void copyPuzzle(Puzzle q, Puzzle p)
{
  int i, j;
  for(i = 0; i < 9; i++) 
  {
    for(j = 0; j < 9; j++) 
    {
      q[i][j] = p[i][j];
    }
  }
}

Getting a Section of a Puzzle

You will find it useful to be able to extract a row, column or square of a puzzle, as an array of 9 sets. But you need to be able to change those sets, not just to look at them. So what you really want is a pointer back into the original array, so that any changes you make affect the original array. You can do that as follows. Copy the following into your program. (Just copy and paste. Do not copy it by hand! Copy the comments too.)

typedef SetOfSmallInts* PuzzleSection[9];

/****************************************************************
 *                      getRow                                  *
 ****************************************************************
 * Store the i-th row of puzzle p into puzzle section R.	*
 * The rows are numbered from 0 to 8.                           *
 *                                                              *
 * After doing this, the k-th set in row i is *(R[k]).          *
 * Do not omit *(...). The cells in the row are numbered        *
 * 0,1,...,8.                                                   *
 ****************************************************************/

void getRow(PuzzleSection R, Puzzle p, int i)
{
  int j;
  for(j = 0; j < 9; j++) 
  {
    R[j] = &(p[i][j]);
  }
}


/****************************************************************
 *                      getColumn                               *
 ****************************************************************
 * Store the j-th column of puzzle p into puzzle section R.     *
 * The columns are numbered from 0 to 8.		        *
 *                                                              *
 * After doing this, the k-th set in column j is                *
 * *(R[i]).  Do not omit *(...).  The cells in the              *
 * column are numbered 0,1,...,8.                               *
 ****************************************************************/

void getColumn(PuzzleSection R, Puzzle p, int j)
{
  int i;
  for(i = 0; i < 9; i++) 
  {
    R[i] = &(p[i][j]);
  }
}

/**************************************************************** 
 *                      getSquare                               *
 ****************************************************************
 * Store the k-th square of puzzle p into puzzle section R.     *
 * The squares are numbered as follows.	                        *
 *           0 1 2                                              *
 *           3 4 5                                              *
 *           6 7 8                                              *
 * For example, square 4 is the middle square in the puzzle.    *
 *                                                              *
 * After doing getSquare, the i-th set in the square is *(R[i]).*
 * Do not omit *(...). The cells in the square are numbered     *
 * 0,1,...,8, in the same pattern shown above for the squares   *
 * themselves.  For example *(R[3]) is the first position in    *
 * the second row of the square.                                *
 ****************************************************************/

void getSquare(PuzzleSection R, Puzzle p, int k)
{
  int i;
  for(i = 0; i < 9; i++) 
  {
    R[i] = &(p[k - k%3 + i/3][3*(k%3) + i%3]);
  }
}

Suppose that p has type Puzzle.

  PuzzleSection Sec;
  getSquare(Sec, p, 0);
makes Sec hold the square in the upper-left corner of puzzle p (square 0). The members of array Sec refer to cells in puzzle p, as follows.

Section entry Corresponds to
*(Sec[0]) p[0][0]
*(Sec[1]) p[0][1]
*(Sec[2]) p[0][2]
*(Sec[3]) p[1][0]
*(Sec[4]) p[1][1]
*(Sec[5]) p[1][2]
*(Sec[6]) p[2][0]
*(Sec[7]) p[2][1]
*(Sec[8]) p[2][2]

For example, statement

  *(Sec[4]) = setDifference(*(Sec[4]), s);
has the same effect as
  p[1][1] = setDifference(p[1][1], s);


Reading and Writing Puzzles

Start by writing three functions, one to read a puzzle and two to write a puzzle. Use the following headings.

void readPuzzle(Puzzle p)

Read a puzzle from the standard input (cin, if you use the iostream library) in the form shown above. Store it into p.

If variable c has type char, then

  cin >> c;
reads the next nonblank character and stores it into c. That is an easy way to read the input, one character at a time. What set should you store if you see a dash? Keep in mind that a dash means, as far as you know right now, any number from 1 to 9 might go in this cell.

void printPuzzle(Puzzle p)

Print puzzle p on the standard output (cout, if you use the iostream library), in a form suitable for the final output. If a set is a singleton set, write the number in that set. If it is not singleton, write '-'. If it is empty, write 0. You should be able to use this to show both the original puzzle and the solved puzzle.

void showPuzzle(Puzzle p)

Print puzzle p on the standard output, but in a form suitable for debugging. For each set, print all of the members of the set. Make the output look nice. Keep the columns aligned. For example, a puzzle might look as follows when printed using showPuzzle.

1         5         25        4         8         9         3         7         6                    
7         3         259       2         5         6         18        4         1                    
46        46        8         3         7         1         2         9         5                    
34        489       7         1         2         4         6         5         39                   
5         49        129       7         46        3         149       12        8                    
234       48        6         8         9         5         7         12        123                  
9         1         4         6         37        78        58        258       2                    
6         2         5         89        4         48        158       3         7                    
8         7         3         5         1         2         9         6         4                    
You can see that the first square contains
 {1}   {5}   {2,5}
 {7}   {3}   {2,5,9}
 {4,6} {4,6} {8}
Design showPuzzle so that, if it encounters an empty set, it shows 0 rather than showing nothing at all.

Also write a main program that reads a puzzle and prints it back out using both printPuzzle and showPuzzle. Make sure that all three of these functions appear to be working before moving on. Resist the temptation to move on to the rest before you are ready. Here are some puzzles for testing your program.


Building and running your program

To build your program, copy file Makefile to the directory containing your program. It contains a few targets to make. For example, command

make sudoku
compiles and links the program. Command
make run1
compiles and runs the program on puzzle1.txt. Command
make test
runs the program on puzzle1.txt, puzzle2.txt and puzzle3.txt.


Tactic 1

There are a few tactics that people (and computers) use to solve Sudoku puzzles. A simple and obvious tactic is to locate singleton sets, which are cells whose values are known. If K occurs in a singleton set, then K cannot occur in any other set in the same row, column or square. So remove it from them all.

A simple way to run this tactic is to do a loop from 0 to 8. At value j (0 to 8) run tactic 1 on row j, column j and square j. Do each of those by extracting a section of the puzzle and running tactic 1 on the section.

To run tactic 1 on a section, look at each set in the section. If set number i is {K}, then go through all indices, skipping index i, removing K from the set.

You will need to know whether doing tactic 1 has made any change to the puzzle. The functions should tell you that information.

So there are two functions to write.

bool tacticOneOnSection(PuzzleSection sec)

This function does tactic 1 on a given section, sec. It should return true if it made a change to the section and false if it did not make any change. (How can you tell if it made a change? Be careful. Removing K from a set that does not contain K does not make a change.)

bool tacticOne(Puzzle p)

This function does tactic 1 on every row, column and square of the puzzle. It must use tacticOneOnSection for each row, column or square. It should return true if it made a change to any of the sets.

Write a clear and precise contract for each of your functions.

Modify your main program so that it echoes the original puzzle then runs tactic 1 once on each row, column and square, and then shows the puzzle using showPuzzle. Note that the puzzle will probably not be solved yet. But check whether tactic 1 appears to be doing something sensible.


The Puzzle Solver

Now write another function (let's call it the solver) whose job is to find a solution to a given puzzle. The puzzle should be a parameter. Make it run tactic1 on every row, column and square repeatedly until tactic 1 does not make any more progress.

There are three possible outcomes of the solver.

  1. It is possible that all of the sets are singleton. That means a solution has been found.

  2. It is possible that the original puzzle did not have a solution. (Your solver should be prepared to deal with unsolvable puzzles.) When that happens, at least one of the sets in the puzzle is an empty set, indicating that no values can go in that cell.

  3. It is possible that neither of the above cases holds. That means there is more work to be done.

Make the solver return a value indicating which of those cases hold. So there are three possible return values. To handle that, we will use an enumerated type. Definition

  enum SolutionStatus {solved, unsolvable, working};
creates a new type, SolutionStatus, with three values, solved, unsolvable and working. Make your solver return a result of type SolutionStatus. For example, its heading would be
  SolutionStatus solver(Puzzle p)
To make the solver return value solved, say
  return solved;

You will want functions to test the status. Write a function that takes a puzzle and returns true if all of the sets are singleton. Write another function that takes a puzzle and returns true if there is an empty set in the puzzle.

Modify the main program so that it calls the solver. Make it print the solved puzzle, then report the status: either solved, or unsolvable, or still a work in progress.

Test your program. You should find that it solves puzzle1.txt completely, but cannot finish puzzle3.txt. Command

  ./sudoku <puzzle1.txt
runs the sudoku program with its input coming from file puzzle1.txt.


Debug Prints

Professional software designers write their programs for testing, right from the start. They do everything they can to make them work, but then presume that there will be problems. (There usually are.) The idea is not to wait until problems show up to think about testing. Think about it from the beginning.

One way to think about testing from the start is to build in tracing capabilities. If you write

  #ifdef DEBUG
    if(tracing) 
    {
      cout << "tactic 1: here is the initial puzzle\n";
      showPuzzle(p);
    }
  #endif
inside tactic 1, then you make tactic 1 show what it is doing. Of course, you do not always want it to do that. So you have two switches to turn it on or off.

  1. If you have written

      #define DEBUG
    
    then the part between #ifdef DEBUG and #endif will be compiled into the program. But if you do not write that, or write
      //#define DEBUG
    
    then the compiler will ignore the part between #ifdef DEBUG and #endif, and it will be as if that part were not part of the program at all.

  2. Variable tracing has type bool. If it is true, then the trace will be shown (assuming it has not been skipped over by the compiler.). If tracing is false, nothing is shown.

Using this idea, add debug prints to function tacticOne. Show the puzzle at the beginning and at the end of the function. But make sure that it can be turned off. Never print raw information, such as numbers. Always say which function is showing it, and what it represents.

When you turn in your program, do not remove the debug prints. The point is to leave them in the program so that they can be turned on when they are needed. But ensure that debug prints are turned off in the version that you submit.


Tactic 2

Some puzzles will still remain unsolved after tactic 1 has nothing more to contribute. But there is another commonly employed tactic that can help. Suppose that you look at all of the sets in a given row, and only one of those sets contains 2. Then, clearly, that cell must hold 2. So you change it to {2}, a singleton set.

You can do the same for every row, column and square, and for every number from 1 to 9. Write a function that does this to a section, by checking, for every number k from 1 to 9, whether there is just one set in the section that holds k. If so, it should make that set be the singleton set {k}. Then write another function that does tactic 2 on each row, column and square in a puzzle.

Your new functions should return true if they made a change and false if they did not make a change.

Modify your solver to work as follows. (This is a description of the algorithm, in pseudo-code, not a C++ program.)

  1. Loop (main loop)
       a. If all of the sets in the puzzle are singleton, return solved.

       b. If at least one of the sets is empty, return unsolvable.

       c. Run tactic 1 once on every row, column and
          square in the entire puzzle once.  Remember whether it
          made any progress.

       d. Run tactic 2 once on every row, column and
          square in the entire puzzle once.  Remember whether
          it made any progress.

       e. If neither tactic 1 nor tactic 2 made any progress, 
          then exit the main loop.  
     end loop

  2. Return working

Test this. On puzzle 1, it should return solved. Try it on puzzle 3. The program will not finish puzzle 3, and the solver should return working. If you turn on debug prints you can see the puzzle at every step.)

Do not move on until your program seems to be working so far. Do not take it for granted that it works. Look at what your program is doing with a critical eye. Are the tactics doing sensible things? Are your debug prints readable, so that you can tell?


Tactic 3

Many puzzles can be solved using only tactics 1 and 2. But some, such as puzzle 3, are more stubborn. We will end with a tactic that is guaranteed to finish the job, for every puzzle.

Suppose that, after running tactics 1 and 2 until they no longer help, there are still nonsingleton sets, but no empty sets. Then find one of the nonsingleton sets, and try each possible value for it.

Of course, some of your choices will not work. So you have to consider the possibility that a puzzle is not solvable, even if the original puzzle that you started with is solvable. Fortunately, you have already taken that into account.

Write a function that performs tactic 3 on a puzzle. It should return a result of type SolutionStatus. It can return either solved or unsolvable, but it will never return working, because it always reaches a conclusion.

Tactic 3 works roughly as follows, where p is the puzzle that it is working on.

  for i = 0, ..., 8
    for j = 0, ..., 8
      if p[i][j] is not a singleton set  

        comment: try values for set p[i][j]

        for k = 1, ..., 9
          if k is a member of p[i][j]
            Make a copy (q) of puzzle p.

            q[i][j] = {k}  (a singleton set -- this is where we try k)

	    Do a debug print showing that p[i][j] = k is being tried.

            Run the solver on q.  

            If the solver returned solved, then 
              Copy q back into p
              Return solved 
            end if

            Do a debug print explaining that p[i][j] = k did not work.
          end if 
        end for

        return unsolvable (none of the values for p[i][j] work, so
                           the puzzle has no solution)
      end if
    end for
  end for

It is important to work on a copy of the puzzle, so that the changes that you make will not ruin what you have. Also, notice that tactic 3 only tries values for the first non-singleton set that it encounters. If it cannot find a value to use for that set, there is no point in going ahead and looking for values for other sets. All it takes is one spot that cannot be filled in to make the puzzle unsolvable. That is why tactic 3 returns unsolvable if it finishes the k-loop without returning success.

Modify the solver too so that it uses tactic 3. Here is a sketch.

  1. Loop (main loop)
       a. If all of the sets in the puzzle are singleton, return solved.

       b. If at least one of the sets is empty, return unsolvable.
  
       c. Run tactic 1 once on every row, column and
          square in the entire puzzle once.  Remember whether it
          made any progress.

       d. Run tactic 2 once on every row, column and
          square in the entire puzzle once.  Remember whether
          it made any progress.

       e. If neither tactic 1 nor tactic 2 made any progress, 
          then exit the main loop.  
     end loop

  3. Run tactic 3 on the puzzle and return the same answer as
     tactic 3 returns.
Notice that you only try tactic 3 after tactics 1 and 2 have nothing more to contribute. The reason is that tactic 3 is relatively expensive, and you would prefer to use it only when the cheaper tactics 1 and 2 cannot help.

Note. The solver uses tactic 3, and tactic 3 uses the solver. That is allowed. It is called mutual recursion. But you will need to help the compiler, so that you can use a function before it is defined. If you want to use the solver before it is defined, copy the heading of the solver to an earlier place in the program, and write a semicolon at the end of the heading. That tells the compiler that the definition of the solver is coming, but lets you use the solver before you define it. (It is called a prototype.) For example, write

  SolutionStatus solver(Puzzle p);
somewhere before the occurrence of tactic 3, so that tactic 3 can use solver.

Test your program. Do not just presume that it works. Try it on an obviously unsolvable puzzle. Does it say that the puzzle is unsolvable?


Regression Tests

Software needs to be tested extensively. Any time you make a modification, you really should go back and redo all of your tests to make sure that you have not inadvertantly ruined something that used to work. But you do not want to do that by hand; that is far too much work. So you put together a suite of tests, called regression tests, that run automatically. You generally leave tests in the suite, even after they work, unless they are testing something that the software no longer supports.

The Makefile that I supplied contains a target called test so that command make test runs ./sudoku on each of puzzle1.txt, puzzle2.txt and puzzle3.txt. That is a small regression test.

(Regression tests are normally done with debugging turned off. You only turn debugging on when a test does not work and you want to find out why not. Ideally, each test checks its own work and you only get output shown if the test is incorrect. Yours won't do that. But if you did want to automate checking, you would run another program that captures the standard output from sudoku and checks it. For example, command

  ./sudoku <puzzle1.txt | checkpuzzle puzzle1.ans
sends the standard output of the sudoku program to the standard input of the checkpuzzle program. That is, checkpuzzle sees in its standard input just what sudoku writes to its standard output. You would write checkpuzzle to read the information, possibly along with a prewritten answer file (puzzle1.ans) and to check whether the two are identical.)


Notes on Grading

Here are some things to do and mistakes to avoid, with a range of points that you might lose (out of 100) for each one.

  1. Write your name, the course and the assignment number in a comment at the top of the program, along with the tab stop distance. (up to 2 points)

  2. Failure to compile (100 points). Your program is expected to compile. A program that has fatal compile errors will receive a grade of 0.

  3. Compiler warnings (up to 5 points). Your program should not have any compiler warnings.

  4. Failure to follow the design (up to 100 points). The design requires you two write certain functions. It also says what those functions must do. It is not acceptable to substitute a different design. If you use a completely different design you will receive no points.

    The design does not call for global variables. Do not use global variables. Using even one global variable means that you are not following the design, and can lead to a very high loss of points.

  5. Failure to write one or more tactics (up to 15 points each). You are expected to provide all three tactics, and to use them.

  6. Failure to use puzzle sections (up to 7 points). Do not write each tactic directly for a row, then for a column, then for a square. You end up duplicating ideas. That is a problem because it makes a program difficult to modify. Any modification needs to be done on each copy.

  7. Incorrect results (up to 30 points). The program should produce correct results.

  8. Violating the abstraction of the SmallSetOfInts type (up to 20 points). You can use type SmallSetOfInts and the functions described on this page, but that is all. If s has type SmallSetOfInts and your program mentions s.val, then you have violated the abstraction.

    Type SmallSetOfInt is an abstract data type. That means its implementation can be changed at any time. The only thing that is guaranteed about it is that it will support the advertised operations.

    I will compile your program with a modified definition of SmallSetOfInts. If you have violated the abstraction then your program will not compile with the modified intset.cpp and intset.h files.

  9. Poor or missing contracts (up to 12 points). Every function must have a clear and precise contract. A contract tells what a function does, not how it works. A contract tells precisely how each parameter affects what the function does, and refers to parameters by name.

  10. Functions do not do what they are supposed to do, or return incorrect results according to their contracts (up to 7 points per function). If a function is supposed to tell whether it made any progress, be sure that it truly does say whether it made progress. If you are sloppy you will find that you produce the wrong answer. In tactics 1 and 2, the function makes progress if it makes a change in any of the rows, columns or squares.

    Tactics 1 and 2 are supposed to work on every row, column and square. Do not cut them off before they have done their jobs. For example, do not stop tactic 1 just because you made a change in the first row. Keep going until all of the rows, columns and squares have been done.

  11. Crippled functions (up to 8 points). A crippled function is one that does not really do its job, but requires its caller to do part of the job.

    Of particular concern is the function that reads a puzzle. Make sure that it does the whole job. You should not make mistakes, putting incorrect values into the puzzle, expecting to compensate for them later.

  12. Poor indentation (up to 10 points). You are expected to indent your function definitions correctly. Minor problems will not lose much, but seriously bad indentation can lose 10 points.

  13. Failure to explain tabs (2 points). Be sure that you either do not use tabs in your program or that you tell where your tab stops are at the top of the program. Different editors are set up with different tab stops. For example, write

      // tab stops: every 4 characters
    
    If you do not know where the tab stops are, do not use tabs.

  14. No debug prints (up to 6 points). There should be debug prints in tactics 1 and 2 showing the puzzle before and after they run, and showing what their return value is. There should also be debug prints in tactic 3, as indicated in the sketch. The debug prints should not print raw information. They should explain which function is printing them and what the information is.

  15. Debug prints left on (up to 5 points). Turn off the debug prints. That is important, because with them turned on, your program does not do what it is required to do.

  16. Awkward loop initialization (up to 2 points). Initialization for a loop should be at the start of the loop, not someplace else. This rule is particularly important for nested loops. Look at the following.

      j = 0;
      k = 0;
      while(j < 9)
      {
        while(k < 9) 
        {
          ...
        }
      }
    
    The initialization of the loop that begins while(k < 9) is done at the beginning of the outer loop. That means it is only done the first time. To compensate for that, some programmers add another initialization at the end of the inner loop, for the next time around.
      j = 0;
      k = 0;
      while(j < 9)
      {
        while(k < 9) 
        {
          ...
        }
        k = 0;
      }
    
    But this is really making a mistake and then compensating for it, which is not the recommended way to write computer programs. Instead, put the initialization for the inner loop just before the inner loop, where it belongs.
      j = 0;
      while(j < 9)
      {
        k = 0;
        while(k < 9) 
        {
          ...
        }
      }
    

  17. Excessive inefficiency (up to 8 points). This program should solve a puzzle quickly. Each tactic should take the time to do its job, but it should not use excessively more time than is necessary.

  18. Long lines, margin comments (up to 5 points) Avoid long lines. As a rule of thumb, keep lines at most 80 characters long. Avoid margin comments (comments written in the right margin telling what the code to the left of them does). Those comments rarely say anything useful, and they make lines long and clutter the program. If you need to write a comment inside a function, write a paragraph comment telling what the next paragraph of code does.

  19. Kick-starting functions (up to 5 points). To use a function, just use it where its result is needed. Do not add an extra call. For example, do not write

      while(tacticOne(p))
      {
        tacticOne(p);
        ...
      }
    
    There is no need to get the program ready to call a function. If you already ran it, do not try to remind the compiler to run it. If you want to call a function once, call it once, not twice.

  20. Packing functions together (up to 1 point). Put a blank line between function definitions. There is no need to pack them together. That makes the program more readable.

    But also do not put huge blank sections in your program consisting of several blank lines in a row. A long space is two blank lines. Longer than that is excessive.

  21. system("pause") (up to 5 points). Some programmers write system("pause") at the end of main to compensate for a flaw in the design of a Windows development environment. Since that is a nonstandard thing, I must edit your program to get rid of it before I can test your program. I should not have to fix your program for any reason.


Submitting Your Work

You will turn in two versions of this program, unless your grade on version 1 is one that you are willing to keep. To submit a version of a program, run one of the following commands in a terminal window. I am assuming that your program is called sudoku.cpp. If it has a different name, substitute that name. Submit both your source program and your Makefile. Do not submit your executable program or test files (puzzle1.txt, etc.)

First version
~abrahamsonk/3300/bin/submit 2a sudoku.cpp
Second version
~abrahamsonk/3300/bin/submit 2b sudoku.cpp