9.15.3.1. Pouring Puzzle: Pass-by-Reference

Time: 00:18:05 | Download: Large Small | Streaming | Slides + Dialog (PDF)

We explored the overall pouring puzzle problem in the last section and developed algorithms solving three of its main sub-problems:

  1. calculating how much water to pour from one glass to another
  2. how to manage the two glasses involved in the pouring operation
  3. how to sequence the game operations and determine when the puzzle is solved
Four main tasks remain to complete a programmed puzzle solution:
  1. specify the the Glass class, including the simple member functions
  2. fully implement the previously developed algorithms in C++
  3. complete the glasses array implementation
  4. convert the game operation logic diagram to a C++ program that uses the Glass class

The reference version of the Glass UML diagram:
Glass
-----------------------
-pours : int [underlined]
-volume : int
-amount : int
-----------------------
+Glass(a_volume : int, a_amount : int)
+getVolume() : int
+getAmount() : int
+display() : void
+getPours() : int [underlined]
+pour(source : Glass&) : void
Glass UML diagram: Pass-by-reference version. The first solution passes the source object to the pour function by reference. The diagram specifies that the data are private and operations are public. It further specifies that pours and getPours() are class features - belonging to the Glass class rather an instances of it.

Source Code

The pouring puzzle solution consists of three files. The first two files form a Glass supplier, while the third file represents the client. We begin programming the solution with the first supplier file, which implements the UML class diagram. Except for the pour function, the functions are small and simple enough that we implement them in the class specification. The second supplier file initializes the class variable pours and implements the pour function. Finally, the client file consists of main, where we create the array and implement the game logic.

#include "Glass.h"
#include <algorithm>		// for the min function
using namespace std;


int Glass::pours = 0;				// #1: initializes static data




// Pour water from the source glass to the destination or this glass.

void Glass::pour(Glass& source)			// pour from one glass to another
{
	pours++;				// #2: increment the number of moves

	int	space = volume - amount;	// #3: space available in the destination glass



	// #4: algorithm 1 ----------------------------------------------------------

	/*if (space < source.amount)
	{
		source.amount -= space;
		amount = volume;		// destination glass in now full
		// amount += space		// also works
	}
	else
	{
		amount += source.amount;
		source.amount = 0;		// source glass is now empty
	}*/



	// #5: algorithm 2 -----------------------------------------------------------

	int	transfer = min(space, source.amount);
	//int	transfer = (space < source.amount) ? space : source.amount;	// alternate

	amount += transfer;
	source.amount -= transfer;
}
Glass.cpp: Initializing the glasses array and implementing the pour function as pass-by-reference.
  1. Demonstrates the syntax necessary to initialize a static variable (the same syntax is used for both public and private variables). The pours variable tracks the number of times the pour function pours water from one glass to another; it acts as a move counter for the puzzle.
  2. Increment the number of pours (I've managed to solve the puzzle with six moves or pours but no fewer)
  3. Calculate the space available in the destination glass; note that when used without a specific reference to source that volume and amount belong to "this" or the source Glass object.
  4. If the remaining space in the destination glass is less than the water in the source glass, the space in the destination determines how much water to pour. Otherwise, the water in the source glass determines how much to pour.
  5. An alternate way of thinking about how much water to pour: pour the smaller of the space in the destination glass and the amount in the source glass.
/*
 * Implements a simple puzzle whose goal is to divide 8 ounces of water between
 * three glasses so that at least one glass contains exactly 8 ounces.
 * The three glasses have different capacities: 3 oz., 5 oz., and 8 oz.; the game
 * begins with the 8-oz. glass full and the other two glasses empty. Divide the
 * water by pouring it between glasses.
 */

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

int main()
{
	Glass	glasses[3] { Glass(3,0), Glass(5,0), Glass(8,8) };		// #1: create array

	/*
	 * Loops until at least one glass contains precisely 4 oz of water.
	 * Exits early if the player enters a 4 at any prompt.
	 */

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


		int destination;

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

		if (destination == 4)						// end the puzzle early
			exit(0);

		int source;

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

		if (source == 4)						// end the puzzle early
			exit(0);


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

	cout << "\n\nYou solved the puzzle in " <<
		Glass::getPours() << " pours" << endl;				// #4: the total # of pours

		for (int i = 0; i < 3; i++)					// #5:final puzzle state
glasses[i].display(); return 0; }
game.cpp: Implementing the game logic. The while loop keeps the puzzle running as long as none of the glasses contain 4 ounces. The loop also allows the puzzle solver to end the puzzle early by entering 4 for either the destination or the source glass. The main logic is as follows:
  1. Creates an array and three instances of Glass. We intentionally design the glasses array and the pour function for compatibility by creating glasses as an array of objects on the stack and pour as a pass-by-reference function.
  2. The current state of each glass is displayed, allowing the solver to choose the next move.
  3. The pour function transfers water from one glass to another. This operation demonstrates the advantage of managing the glasses as an array. The program represents the source and destination as numbers, which it converts into array indexes (the program numbers the glasses 1, 2, and 3, but C++ arrays are 0-indexed: 0, 1, and 2). Using an array of Glass objects allows us to have a single call to the pour function, where the destination and source arguments are determined algorithmically at the time of the call.
  4. Print the static variable pours, which counts how many steps were needed to solve the puzzle. pours is a static class variable, so it is accessed by a static class function. A static function is called with the class name and the scope resolution operator: Glass::getPours();.
  5. Display the final puzzle state: the amount of water in each glass after the puzzle is solved.

Downloadable Code