9.15.3.2. Pouring Puzzle: Pass-by-Pointer

The second pouring puzzle solution implements the pour function using pass-by-pointer and creates the glasses array as an array of pointers on the heap with the new operator. Beyond these two small differences, the two solutions are the same.

Pointer 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-pointer version. The first solution passes the source object to the pour function by pointer. All data are private, and all operations are public. pours and getPours() are class features.

Source Code

/*
 * Implements a single glass used in a water-pouring puzzle.
 * Each instance has a volume and amount. The class tracks the number of pours with a static variable.
 */

#include <iostream>
using namespace std;

class Glass
{
	private:
		static	int	pours;		// number of times water is poured from 1 glass to another

		int	volume;			// how much water the glass can hold
		int	amount;			// how much water is in the glass now

	public:

		Glass(int a_volume, int a_amount) : volume(a_volume), amount(a_amount) {}
		int getVolume() { return volume; }
		int getAmount() { return amount; }
		void display() { cout << amount << " / " << volume << endl; }
		static int getPours() { return pours; }
		void pour(Glass* from);
};
Glass.h. As in the previous version, the data members, including the static variable, are private; the member functions are public. Except for the pour function, the class inlines all functions in the specification. The two "get" functions are examples of getter or accessor functions.
#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 "to" glass



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

	/*if (space < source->amount)
	{
		source->amount -= space;
		amount = volume;		// destination glass is 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: Initialize glasses & implement pour as pass-by-pointer.
  1. Demonstrates the syntax necessary to initialize a static variable (the same syntax is used for both public and private variables). pours tracks the number of times the player pours water from one glass to another and acts as a move counter for the puzzle.
  2. Increment the number of pours (I've managed to solve the puzzle with only six moves or pours)
  3. Calculate the space available in the destination glass; note that when used without a specific reference to the 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[] { new Glass(3,0), new Glass(5,0), new Glass(8,8) };	// #1: create & init array

	/*
	 * Loops until at least one glass contains precisely 4 oz of water.
	 * Exits early if the player enters 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 glass contents
		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 the destination or the source glass. The main logic is as follows:
  1. Creates an array of three Glass pointers and initializes them to point to three objects created dynamically on the heap with new. To better support the pass-by-pointer, we replace the array of Glass objects with an array of pointers. Each pointer points to an instance of the Glass class instantiated one at a time on the heap. Changing objects to pointers implies that the arrow operator must replace the dot operator whenever we access a function or variable in one of the Glass objects.
  2. Demonstrates how a for-loop can display all Glass objects; another advantage of using an array.
  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 denotes the source and destination glass objects as numbers, which the program easily 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 program algorithmically determines the destination and the source arguments at the time of the call.
  4. Print the static variable pours, which counts how many steps were needed to solve the puzzle. Note that pours "belongs" to the class as a whole, so the program accesses it with a static function, which also "belongs" to the class. Programs call static functions 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