CMPS 144 Spring 2020
Prog. Assg. #4: Taxman
Due: 11:59:59pm, Sunday April 19

The Taxman Game

Taxman is a solitaire game in which the player tries to "earn" more income than what must be "paid" in taxes. For some positive integer N chosen at the beginning of the game, each integer in the range [1..N] eventually is earned as income or is paid in taxes.

A number is referred to as being "active" if it has not yet been earned as income or paid in taxes. The game begins with all the numbers in the range [1..N] being active. On each turn, the player chooses an active number k that has at least one proper divisor1 that is also active. As a result of that choice, k is credited to the player as income and all its active proper divisors are paid as taxes. All these proper divisors, as well as k itself, become inactive.

When there remain no active numbers having at least one active proper divisor, all remaining active numbers are paid as "residual" taxes and the game ends.


Java Classes

You are provided with three Java classes one of which, Taxman, you are to complete and submit.

An appropriate abstraction to employ in developing software to play the Taxman game is that of set and, more specifically, set of integers. That explains why the SetOfInt class plays a role here. You will notice that the Taxman class has three instance variables that, respectively, represent the set of active numbers, the set of numbers earned as income, and the set of numbers paid in taxes. It is natural, then, for these variables to be of a type defined by a class whose instances represent sets of integers. During each turn of the game, one active number becomes an income and one or more active numbers become taxes.

Regarding the SetOfInt class, take note of the fact that the methods union(), intersection(), and minus() (all of which you should find useful to employ in the Taxman class) are generators rather than mutators. This is a common point of confusion among novice programmers, who often use a generator2 as though it were a mutator.

To illustrate the difference, suppose that setS and setT are variables of type SetOfInt representing the sets S and T, respectively. If you want to effect a change causing variable setS to represent the set S − T, the right and wrong ways to do it are shown on the left and right, respectively:

setS = setS.minus(setT); setS.minus(setT);
CorrectWRONG!!

Recall that the values of variables of non-primitive types are actually references/pointers to objects, not objects themselves. What the assignment statement on the left does is to effect this change:

               +----------------------+
     +---+     | instance of SetOfInt |
setS | *-+---->|  representing set S  |
     +---+     +----------------------+

     +---+     +----------------------+
setT | *-+---->| instance of SetOfInt |
     +---+     |  representing set T  |
               +----------------------+
               +----------------------+
               | instance of SetOfInt |
               |  representing set S  |
               +----------------------+

               +----------------------------+
     +---+     | (new) instance of SetOfInt |
setS | *-+---->|    representing set S-T    |
     +---+     +----------------------------+

     +---+     +----------------------+
setT | *-+---->| instance of SetOfInt |
     +---+     |  representing set T  |
               +----------------------+
BEFOREAFTER

The call to the minus() method generates the new instance of SetOfInt, and by virtue of it being assigned to setS, that variable's value changes to become a reference/pointer to the new object. The old object, which represents the set S, becomes "garbage" (unless it so happened that another program variable still points to it). If minus() is called, but its result is not assigned to a variable (or passed as a parameter to some method), then the object generated by the method immediately becomes garbage (and hence there was no point in calling it).

Notice that the SetOfInt class includes a method called iterator() that, as suggested by its name, returns an object that can iterate through/over the elements of a set. You may find this method useful. See the sumOf() method in Taxman for an example of how such an object is employed.


Program/User Dialog

The following shows an interaction between a user and the PlayTaxman application. User input is in boldface.

Welcome to the Taxman Game.
Enter the maximum income amount: 18
Income: []
Taxes: []
Current score:
  Income: 0; Taxes Paid: 0

Active numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Choose a number to add as income: 17
17 added to income.
Income: [17]
Taxes: [1]
Current score:
  Income: 17; Taxes Paid: 1

Active numbers: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18]

Choose a number to add as income: 15
15 added to income.
Income: [15, 17]
Taxes: [1, 3, 5]
Current score:
  Income: 32; Taxes Paid: 9

Active numbers: [2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18]

Choose a number to add as income: 14
14 added to income.
Income: [14, 15, 17]
Taxes: [1, 2, 3, 5, 7]
Current score:
  Income: 46; Taxes Paid: 18

Active numbers: [4, 6, 8, 9, 10, 11, 12, 13, 16, 18]

Choose a number to add as income: 12
12 added to income.
Income: [12, 14, 15, 17]
Taxes: [1, 2, 3, 4, 5, 6, 7]
Current score:
  Income: 58; Taxes Paid: 28

Active numbers: [8, 9, 10, 11, 13, 16, 18]

Choose a number to add as income: 18
18 added to income.
Income: [12, 14, 15, 17, 18]
Taxes: [1, 2, 3, 4, 5, 6, 7, 9]
Current score:
  Income: 76; Taxes Paid: 37

Active numbers: [8, 10, 11, 13, 16]

Choose a number to add as income: 16
16 added to income.

No choices remain.
Before paying residual taxes:
Income: [12, 14, 15, 16, 17, 18]
Taxes: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Current score:
  Income: 92; Taxes Paid: 45

Active numbers: [10, 11, 13]

After paying residual taxes:
Income: [12, 14, 15, 16, 17, 18]
Taxes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13]
Current score:
  Income: 92; Taxes Paid: 79

Active numbers: []

You won!


Footnote

[1] For positive integers k and m, m is said to be a proper divisor of k if m<k and m is a divisor of k (i.e., k % m == 0).

[2] A generator is a method that returns (a reference to) a new object of its class. Actually, you have been using generators since early in the CS 1 course, as the java.lang.String.substring() method is one. This explains why it makes sense to use str = str.substring(low,high) as a statement but not str.substring(low,high).