Project 0: Peg Solitaire - Fall 2024

There are no late submission folders for Project 0. You must turn it in on-time to receive credit.

Due: Tuesday Sep 10, before 9:00 pm

Objectives

  • Review the procedures to access the GL servers and to compile programs on GL.
  • Review C++ memory management (allocating and deallocating memory dynamically).
  • Use Valgrind to check for memory leaks.
  • Learn how to write test cases for a project.
  • Practice how to analyze and clarify a project's requirements.

Introduction

In this project, you will complete a C++ class. Furthermore, you will write a tester class and a test program and use Valgrind to check that your program is free of memory leaks. Finally, you will submit your project files on GL. If you have submitted programs on GL using shared directories (instead of the submit command), then the submission steps should be familiar.

In this project we develop the engine of a one player board game named Peg Solitaire. The origin of this game goes back to 1700 in Europe. The following excerpt from Wikipedia explains the game goals:

“The standard game fills the entire board with pegs (marbles) except for the central hole. The objective is, making valid moves, to empty the entire board except for a solitary peg in the central hole … A valid move is to jump a peg orthogonally (at right angles) over an adjacent peg into a hole two positions away and then to remove the jumped peg.”

Photo edited by WolfgangW. - Photo by Gnsin GFDL, CC BY-SA 3.0, Link

The game supports multiple shapes of board. In this project we implement the English and Diamond shapes. The English board has 33 holes, and it starts with 32 marbles. The Diamond board has 41 holes, and it starts with 40 marbles.

The Solitaire class implements the game engine including the required data structure. You are assigned the task to implement the Solitaire class. An object of the Solitaire class contains a 2D table. The following figure represents the main data structure in this project. It is a 2d structure storing the state of a game board.

Assignment

Step 1: Create your working directory

Create a directory in your GL account to contain your project files. For example, cs341/proj0.

Step 2: Copy the project files

You can right-click on the file linkes on this page and save-as in your computer. Then, transfer the files to your GL account using SFTP.

For this project, you are provided with the skeleton .h and .cpp files, a sample driver, and the output of driver program:

  • solitaire.h - The interface for the Solitaire class.
  • solitaire.cpp - The skeleton for the Solitaire class implementation.
  • driver.cpp - The sample driver/test program.
  • driver.pdf - The output of sample driver/test program.

Step 3: Complete the Solitaire class

You need to implement the class according to the requirements. For the requirements please refer to the Specifications, The Rules, and Additional Requirements sections on this page.

Step 4: Test your code

You need to test your implementation properly and adequately. For a description of testing please refer to the Testing section on this page. Moreover, a sample test function has been provided in the file driver.cpp for your reference.

Step 5: Check for memory leaks

Run your test program using Valgrind. For example, assuming you have compiled mytest.cpp, producting the executable mytest.out, run the command

   valgrind mytest.out 

If there are no memory leaks, the end of the output should be similar to the following:

   ==8613== 
            ==8613== HEAP SUMMARY:
            ==8613==     in use at exit: 0 bytes in 0 blocks
            ==8613==   total heap usage: 14 allocs, 14 frees, 73,888 bytes allocated
            ==8613== 
            ==8613== All heap blocks were freed -- no leaks are possible
            ==8613== 
            ==8613== For lists of detected and suppressed errors, rerun with: -s
            ==8613== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
          

The important parts are “in use at exit: 0 bytes” and “no leaks are possible.” The last line is also important as memory errors can lead to leaks.

Step 6: Link your shared directory

Please take this step as soon as you receive the notification indicating the shared submission folders are ready. Leaving this step to the last minute can prevent you from submitting your work on time. Please note:
  * No other method of submission will be accepted due to such a problem.
  * No late submission will be accepted due to such a problem.

Follow the instructions on the Project Submission page to make a symbolic link to the shared directory in your home directory.

Step 7: Submit your files

See the “What to Submit” section, below.

The Rules

The following rules are part of the project requirements.

  • Every cell stores an int number from the three possible values which are defined as const int variables OUT, HOLE, and MARBLE.
  • An English board presents 32 marbles and a hole in the center at the beginning of game. For the shaps of an English board please refer to the following figure.
  • A Diamond board presents 40 marbles and a hole in the center at the beginning of game. For the shaps of a diamond board please refer to the following figure.

By Júlio Reis by WolfgangW CC BY-SA 3.0, Link

Specifications

The class Solitaire has multiple member variables as the following list:

  • m_board is a 2D array which stores one of the allowed int values in every cell. The sizes of this data structure are specified by the member variables m_numRows and m_numColumns.

Solitaire::Solitaire(BOARDSHAPE board)
This is the constructor. It initializes all member variables and it allocates required memory if neccessary. If the user passes invalid parameters such as a non-supported shape the constructor creates an empty object.
Solitaire::~Solitaire()
This is the destructor and it deallocates the memory.
void Solitaire::clear()
This function deallocates all memory and re-initializes all member variables to default values. It clears the current object to an empty object.
bool play(pair<int,int> origin, pair<int,int> destination)
This function performs the requested move from the origin to the destination cell. If the requested move is valid the move is performed and the function returns true. Otherwise there will not be any changes to the board and the function returns false. The first member of a pair represents the row number and the second member represents the column number.
bool Solitaire::newBoard()
This function re-initializes the current object and returns true. If the object is empty the function returns false.
void Solitaire::changeBoard(BOARDSHAPE board)
This function creates a new Solitaire object if the asking shape is different from the current shape, otherwise it only re-initializes the object.
Solitaire::Solitaire(const Solitaire & rhs)
The copy constructor creates a deep copy of rhs. Reminder: a deep copy means the current object has the same information as rhs object, however, it has its own memory allocated. The new copy must be an exact copy of rhs.
void Solitaire::dumpBoard()
This function prints the board to the standard output. The implementation of this function is provided. You do not need to modify this function. This function is for debugging purposes. Do not call this function in the test program that you'll submit. The function prints the UTF characters \u2731 (BOLD ASTERISK) as a marble and \u23E3 (BENZENE RING WITH CIRCLE) to represent a hole. If you are using a Windows computer for development you may need to activate UTF characters since Windows shell does not support utf-8 encoding by default. The File windows.pdf provides instruction to activate the support of UTF characters.

Additional Requirements

  • The class declaration (Solitaire) in solitaire.cpp may not be modified in any way. No additional libraries may be used.
  • You are not allowed to use STL template containers in the Solitaire class except the pair class.
  • You are allowed to use STL template containers in your test functions.
  • The Solitaire class functions must be written in solitaire.cpp; in particular, they must not be written “in-line.”
  • Private helper functions may be added, but must be declared in the private section of the Solitaire class. There is a comment indicating where private helper fuction declarations should be written.
  • You should read through the coding standards for this course.

Testing

  • The sample driver.cpp file will not be accepted as a test file.
  • The name of the test file must be mytest.cpp.
  • The mytest.cpp file contains the Tester class, and the main function.
  • The test cases must be implemented in the Tester class, and must be called in the main function.
  • Every test function returns true or false to indicate the pass or fail.
  • A dump() function is provided for debugging purposes, visual inspections will not be accepted as test cases.

You must write and submit a test program along with the implementation of your project. To test your project class, you implement your test functions in the Tester class. The Tester class resides in your test file. You name your test file mytest.cpp. It is strongly recommended that you read the testing guidelines before writing test cases. A sample test program including Tester class, and sample test functions are provided in driver.cpp. You add your Tester class, test functions and test cases to mytest.cpp. You must write a separate function for every test case.

Any program should be adequately tested. Adequate testing includes test functions for normal cases, edge cases and error cases if any applies. The following list presents some examples for this project:

  • Trying to create a Solitaire object with a French board is an error case.
  • Trying to create a Solitaire object with an English board is a normal case.

The following presents a non-exhaustive list of tests for this project. Please note, you need to test your work adequately.

  • Test whether the constructor works correctly for an error case, e.g. it creates an empty object if the user tries to create an object with with a non-supported shape.
  • Test whether the constructor works correctly for a normal case, e.g. it creates memory and initilizes all member variables to the proper values.
  • Test whether newBoard(...) works correctly for an error case, e.g. it does not modify the object if the user tries to re-initialize an empty object, and it returns false.
  • Test whether newBoard(...) works correctly for an normal case, e.g. it re-initilizes all member variables to the proper values without allocating new memory, and it returns true.
  • Test whether changeBoard(...) is working correctly when called on the same board shape.
  • Test whether changeBoard(...) is working correctly when called in a different board shape
  • Test whether play(...) is working correctly for a correct move, i.e. a normal case.
  • Test whether play(...) is working correctly for a wrong move, i.e. an error case.
  • Test the copy constructor for an error case, e.g. trying to make a copy of an empty object.
  • Test the copy constructor for a normal case.

Sample Test Function

The following algorithm tests whether the copy constructor works correctly for a normal case:

  1. Create a normal object,
  2. Play a few moves,
  3. Create a copy of previous object using the copy constructor,
  4. Check whether m_board of original object and copy object point to different memory locations,
  5. Check whether the corresponding member variables of both objects carry the same values,
  6. Check whether all corresponding row pointers are different.

A sample implementation for a similar algorithm is provided in the driver.cpp file.

What to Submit

You must submit the following files to the proj0 submit directory:

  • solitaire.h
  • solitaire.cpp
  • mytest.cpp (Note: This file contains the declaration and implementation of your Tester class as well as all your test cases.)

If you followed the instructions in the Project Submission page to set up your directories, you can submit your code using the following command:

   cp solitaire.h solitaire.cpp mytest.cpp ~/cs341proj/proj0/

Grading Rubric

The following presents a course rubric. It shows how a submitted project might lose points.

  • Conforming to coding standards make about 10% of the grade.
  • Your test program is worth 50%. If you submit the sample driver program as your test program or no test program is submitted there will be 50% deduction.
  • Correctness and completeness of your test cases (mytest.cpp) make about 15% of the grade.
  • We have written test cases to test your submission without knowing anything about your code. Therefore, it is extremely important that your submission conforms to the specified requirements. Passing tests make about 30% of the grade.
  • There is a 5% deduction for every modification that we need to perform to compile and run your work. For example, if we need to rename your file from myTest.cpp to mytest.cpp the deduction will be applied.

If the submitted project is in a state that receives the deduction for all above items, it will be graded for efforts. The grade will depend on the required efforts to complete such a work.