9.15.3.1. Implementing The Pouring Puzzle

Time: 00:05:53 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides: PDF, PPTX
Review

The previous section developed a solution to the pouring puzzle using pictures, diagrams, and pseudocode. The current section implements the solution, transforming it into a functional program consisting of three files. The first two files, Glass.h and Glass.cpp, implement a Glass class forming a supplier, while the third, game.cpp, represents the client that uses it. Although this organization and most of the programming features are familiar, the program authentically demonstrates several object-oriented programming features.

The first feature is a static class variable named pours that counts the number of pouring operations that the player makes to solve the puzzle. A static variable is the preferred way to maintain the count and the only way that avoids a global variable. The Glass class makes the variable private, preventing clients from "cheating" by altering the stored value. However, the client may need to retrieve the count, so the second demonstrated feature is a static getter function. The definition of the getter function is unremarkable, but the syntax for calling it is notable.

The pour function demonstrates the remaining features: the effect of using an object as an implicit this argument versus as an explicit one, and a binary function that modifies both arguments (requiring the program to pass both using an INOUT technique). The function arbitrarily designates the this argument as the destination of the poured water and the explicit argument as the source. Given two Glass objects, g1 and g2, and the function call g1.pour(g2), pours or transfers water from g2 to g1. g1 is always passed to the function by pointer, but programmers can choose to pass g2 by pointer or by reference. The logic is the same in both cases, and the example presents both versions in parallel.

Source Code

 

Pass By Reference Pass By Pointer
#include "Glass.h"
#include <algorithm>
using namespace std;

int Glass::pours = 0;

void Glass::pour(Glass& source)
{
    pours++;

    int space = volume - amount;

    int transfer = min(space, source.amount);
    amount += transfer;
    source.amount -= transfer;
}
 
(a)


(b)

(c)

(d)

(e)

(f)
#include "Glass.h"
#include <algorithm>
using namespace std;

int Glass::pours = 0;

void Glass::pour(Glass* source)
{
    pours++;

    int space = volume - amount;

    int transfer = min(space, source->amount);
    amount += transfer;
    source->amount -= transfer;
}
int transfer =
    (space < source.amount) ? space : source.amount;
(g)
int transfer =
    (space < source->amount) ? space : source->amount;
if (space < source.amount)
{
    source.amount -= space;
    amount = volume;
    // amount += space;
}
else
{
    amount += source.amount;
    source.amount = 0;
}
(h)
if (space < source->amount)
{
    source->amount -= space;
    amount = volume;
    // amount += space;
}
else
{
    amount += source->amount;
    source->amount = 0;
}
Glass.cpp. The source code file performs two tasks: initializing the static pours variable and defining the pour function. The following figure demonstrates how the client calls the pour function and specifies the source and destination objects.
  1. The min function is prototyped in the <algorithm> header file.
  2. Programs must initialize static variables in source code (i.e., .cpp) files.
  3. Implementing the pour function as pass by reference or by pointer. The choice determines which selection operator the function uses to access the class's features.
  4. Whenever called, the function increments the pours counter.
  5. Each Glass object stores the glass's volume or capacity and the amount of water currently in it. Given these values, the program can calculate the third value characterizing a glass.
  6. The maximum amount of water the function can transfer from the source to the destination glass is the smallest value of the space in the destination and the amount in the source, calculated with the min function. The function transfers water by updating the amounts in both glasses.
  7. An alternate way of calculating how much water to transfer between glasses using the conditional operator rather than the min function.
  8. Another way of calculating how much water to transfer and updating the stored amount, replacing (f). (f), (g), (h), and the commented-out statement in (h) suggest that there are often many ways of performing an operation.

 

#include <iostream>
#include "Glass.h"
using namespace std;

int main()
{
    Glass    glasses[3] { Glass(3,0), Glass(5,0), Glass(8,8) };				// (a) - Reference
    //Glass* glasses[] { new Glass(3,0), new Glass(5,0), new Glass(8,8) };		// - Pointer

    while (glasses[1].getAmount() != 4 && glasses[2].getAmount() != 4)			// (b)
    //while (glasses[1]->getAmount() != 4 && glasses[2]->getAmount() != 4)
    {
        for (int i = 0; i < 3; i++)							// (c)
        {
            cout << "Glass " << i+1 << ": ";
            glasses[i].display();
            //glasses[i]->display();
        }


        int destination;

        cout << "Pour TO glass: <1, 2, or 3; or enter 4 to quit>: ";			// (d)
        cin >> destination;

        if (destination == 4)
            exit(0);

        int source;

        cout << "Pour FROM glass: <1, 2, or 3; or enter 4 to quit>: ";			// (e)
        cin >> source;

        if (source == 4)
            exit(0);


        if (source > 0 && source <= 3 && destination > 0 && destination <= 3)		// (f)
            glasses[destination - 1].pour(glasses[source - 1]);	 			// (g)
            //glasses[destination - 1]->pour(glasses[source - 1]);
        else
            cout << "0 < destination <= 3 AND 0 < source <= 3" << endl;
    }

    cout << "\n\nYou solved the puzzle in " << Glass::getPours() << " pours" << endl;	// (h)

    for (int i = 0; i < 3; i++)								// (i)
        glasses[i].display();
        //glasses[i]->display();

    return 0;
}
game.cpp. The client provides three Glass objects and the game logic to implement a simple game that loops until one glass contains 4 oz of water. Depending on how the Glass class implements the pour function, two implementations are possible. The example presents both versions: pass by reference first, followed by pass by pointer (the commented-out statement). Once the game instantiates the Glass objects, the only difference is the selection operator used to access the objects' features.
  1. Instantiates three Glass objects designed for compatibility with the pour function.
  2. Loops while none of the glasses contain 4 ounces. The player may end the game by "choosing glass 4."
  3. Prints the game's current state: the amount of water in each glass.
  4. prompts for and reads the destination glass.
  5. Prompts for and reads the source glass.
  6. The game refers to the glasses as 1, 2, and 3, but shifts the numbers to form proper array indexes. It's essential for programs to validate user input used as an array index to avoid indexing the array out of bounds. Continuing when source == destination provides a slight performance increase when a player attempts to pour water to and from the same glass.
  7. Transfers water from the source glass (in parentheses) to the destination (on the left side of the dot operator). The previous section describes how storing each object in an array simplifies calling the pour function.
  8. Prints the number of pouring operations the player made to solve the puzzle. The scope resolution operator binds static functions to classes rather than objects.
  9. Display the final puzzle state: the amount of water in each glass after the puzzle is solved.

Downloadable Glass Example Code

ViewDownloadComments
Glass.h Glass.h Pass by reference version
fraction.cpp fraction.cpp
calc.cpp calc.cpp
Glass.h Glass.h Pass by pointer version
fraction.cpp fraction.cpp
game.cpp game.cpp