This lab should help you with the concept of [indeterminite] looping while continuing to let you practice with fancy input.
After talking with not only those who play games with heavy dice usage but now talking with those who develop such dice-heavy games, you realize there is a gap in your 'dice stats' program. Players need to roll dice and some find the stats interesting or useful. Developers, on the other hand, actually want to see the stats, do not need the rolls, but also want to know what dice would create a certain range of values.
For instance, suppose a games developer wanted random values in the range [5..15] but does not know what dice-roll would produce such a range. You can not just tell a player, "pick a random value in this range." (They'd always pick the maximum, you see. Unless it would hurt them, then they'd always pick the minimum...*shrug*) You have to tell them the dice-roll to make!
You start from your min and max formulas from earlier for a dice roll of NdS+A:
min = N+A max = N*S+A
With a little math, you hit upon a strategy:
take the user's desired range: min..max; we'll assume the adjustment is 0 — a natural dice roll
divide the max by the min and check if the remainder is zero (0) and the quotient is a standard die size (4, 6, 8, 10, 12, 20, 100).
if so, you are done: {min}d{quotient}+{adjustment} is the roll to get the user's original [min..max] range.
if not, subtract 1 from both the max and the min and add 1 to an adjustment counter (which started at 0 — the additive identity for integers).
(before subtracting, you can also check to see if the quotient you arrived at with a 0 remainder divides evenly into a die size
if so, you can be done: {min}d{quotient}+{adjustment} is a roll to get the user's original [min..max] range.)
if you reach a min of 1 and you did not get a quotient that was a standard die size, either report failure...
or try starting back at the original range values and increasing the max and min by 1 each 'round' (instead of decreasing them; of course, reset the adjustment to 0 and decrease it by 1 each 'round').
stop and ask if the developer wants to keep searching at every -10-divisible adjustment (0, -10, -20, -30, etc.), though. (the negative search, your mathematical testing revealed, may never end.)
even better, ask the developer how many tests/rounds to do before stopping to ask. (i.e. the user enters the 10 for a 10-divisible stop — or whatever value they choose.)
For the example above [5..15]:
| | | | | even divide? | round | min | max | adj | max/min | die size? | action --------+------+------+------+----------+---------------+---------------------- 0 | 5 | 15 | 0 | 3 | yes, no | min--; max--; adj++ 1 | 4 | 14 | 1 | 3 (R2) | no, no | min--; max--; adj++ 2 | 3 | 13 | 2 | 4 (R1) | no, yes | min--; max--; adj++ 3 | 2 | 12 | 3 | 6 | yes, yes | DONE REPORT: roll 2d6+3 to get [5..15]
(Notice that this could also be 5d3 right off the bat if we were allowing non-standard die sizes. This is up to you depending on the options you choose.)
Or perhaps the user wants values [-1..20]:
| | | | | even divide? |
round | min | max | adj | max/min | die size? | action
--------+------+------+------+----------+---------------+----------------------
0 | -1 | 20 | 0 | NA | NA | try negative adj? yes
| | | | | even divide? |
round | min | max | adj | max/min | die size? | action
--------+------+------+------+----------+---------------+----------------------
0 | -1 | 20 | 0 | NA | NA | min++; max++; adj--
1 | 0 | 21 | -1 | NA | NA | min++; max++; adj--
2 | 1 | 22 | -2 | 22 | yes, no | min++; max++; adj--
3 | 2 | 23 | -3 | 11 (R1) | no, no | min++; max++; adj--
4 | 3 | 24 | -4 | 8 | yes, yes | DONE
REPORT: roll 3d8-4 to get [-1..20]
Or perhaps the desired range of values is [1..21]:
| | | | | even divide? |
round | min | max | adj | max/min | die size? | action
--------+------+------+------+----------+---------------+----------------------
0 | 1 | 21 | 0 | 21 | yes, no | try negative adj? yes
| | | | | even divide? |
round | min | max | adj | max/min | die size? | action
--------+------+------+------+----------+---------------+----------------------
0 | 1 | 21 | 0 | 21 | yes, no | min++; max++; adj--
1 | 2 | 22 | -1 | 11 | yes, no | min++; max++; adj--
2 | 3 | 23 | -2 | 7 (R2) | no, no | min++; max++; adj--
3 | 4 | 24 | -3 | 6 | yes, yes | DONE
REPORT: roll 4d6-3 to get [1..21]
Another developer would like a range of [4..12]:
| | | | | even divide? | round | min | max | adj | max/min | die size? | action --------+------+------+------+----------+---------------+---------------------- 0 | 4 | 12 | 0 | 3 | yes, no | min--; max--; adj++ 1 | 3 | 11 | 1 | 2 (R2) | no, no | min--; max--; adj++ 2 | 2 | 10 | 2 | 5 | yes, no | min--; max--; adj++ 3 | 1 | 9 | 3 | 9 | yes, no | try negative adj? yes | | | | | even divide? | round | min | max | adj | max/min | die size? | action --------+------+------+------+----------+---------------+---------------------- 0 | 4 | 12 | 0 | 3 | yes, no | min++; max++; adj-- 1 | 5 | 13 | -1 | 2 (R3) | no, no | min++; max++; adj-- 2 | 6 | 14 | -2 | 2 (R2) | no, no | min++; max++; adj-- 3 | 7 | 15 | -3 | 2 (R1) | no, no | min++; max++; adj-- 4 | 8 | 16 | -4 | 2 | yes, no | min++; max++; adj-- 5 | 9 | 17 | -5 | 1 (R8) | no, no | min++; max++; adj-- 6 | 10 | 18 | -6 | 1 (R8) | no, no | min++; max++; adj-- 7 | 11 | 19 | -7 | 1 (R8) | no, no | min++; max++; adj-- 8 | 12 | 20 | -8 | 1 (R8) | no, no | min++; max++; adj-- 9 | 13 | 21 | -9 | 1 (R8) | no, no | min++; max++; adj-- 10 | 14 | 22 | -10 | 1 (R8) | no, no | adj is -10, cont? no REPORT: cannot create [4..12] with standard dice
(Of course, the savy among you realize that [4..12] is really just 4d3. (And everyone who games knows that 'd3', although non-standard, can be done with a d6 — or a d12 — by simply using integer division or wrapping (aka integer modulo...plus 1). That is integer division of a d6 would give:
d6 roll: 1 2 3 4 5 6 d3 result: 1 1 2 2 3 3 (d6 / 2)
Versus wrapping of a d6 would give:
d6 roll: 1 2 3 4 5 6 d3 result: 1 2 3 1 2 3 (d6 % 3 + 1)
You could add checks for such non-standard die sizes if you like, but they are not required.)
(Another nice one is [3..5] which is d3+2.)
So, what are we proposing? A developer's version of your dice program that allows the user to enter a range to be produced and then produces a dice-roll that will produce such a range!
As an example, you might have the program interaction look something like (as usual, the parts in this color were typed by the user):
$ ./devdice.out
Welcome to the Developer's Dice Program!!!
Enter your desired range: [5..38]
Thank you!! Calculating... Done.
You can achieve a range of [5..38] by rolling 3d12+2.
Thank you for using the DDP!!
Endeavor to have an enclosed day!
$
How can you check if a division is 'even' or not? (Like "min evenly divides max"...)
How can you have a loop stop when one of two conditions is met? (Like "min == 1 or max/min is even and a die size" or like "max/min is even and a die size or adj/-10 is even and user wants to stop"... Oo! That was a pair of pairs, was not it?)
How can you check that a value is one of a group of values — not a range, but a set of discrete values? (Like max/min is one of 4, 6, 8, 10, 12, 20, 100...) There must be a simple and efficient way to decide if one value is equal to one or another of a set of unique, discrete-typed literally known values! Hmm...
How can you remember a truth value for later testing? (Like remembering if a division is even and remembering if a value is one of a group of values and then later using those truth values in a loop condition...)
Bonus:
How did the min and max formulas lead you to the ingenious solution of dividing the max and min values and 'reducing' by an adjustment?
Why do you sometimes have to go the other direction?
Why is this mathematically sound?
This assignment is (Level 3.5).
Add (Level 0.5) to allow the user to repeat dice determining with another range if so desired. (i.e. "Do you desire it? yes" or "Do you desire it? no")
Add (Level 3) to use functions wisely to break your program into manageable pieces! Some suggestions:
entry of a range
deciding if a number is a standard die size
asking the user a yes/no question
down-ward test
up-ward test
printing a die-roll
printing a range
printing all results
printing string messages (prompts, welcome, closing, etc.)
Add (Level 1) to place your down-ward (adjustment is decreased) and up-ward (adjustment is increased) testing functions and the die-roll printing function and the 'is it a standard die size' function into a 'dice' library — especially if you add them to the dice library you wrote before).
In fact, if you do add them to the library from the earlier program (yes, either one of those will do), add another (Level 0.5).
(Note that if you had either of those prior libraries, it would already contain a function to print a die-roll and so you can re-use that one instead of writing another. It still counts toward satisfying the above 'use functions' option.)
Also, you can add another (Level 1) if you use either default arguments or overloading to combine the two adjustment testing functions into a single function.
Add (Level 2) to recognize other possibilities like the 4d3 mentioned above. Let's see, that was max/min is even, not a die size, but max/min divides a die size evenly and the user says this is okay. Yeah...that's it...
Add (Level 1) to allow the user to enter their range with either [ ] or ( ). Recall from math that ( ) on an interval means non-inclusive and so you'll have to adjust the end-point they enter to account for that. Luckily we are always dealing with integers, so non-inclusive of 4 just means to move to 5 (if we are the min) or to move to 3 (if we are the max).
Always print your report in terms of an inclusive range — with [ ].
Add (Level 1) to allow the user to use either a comma, the two dots (as above), or a dash to separate their range end-points. For example, the 5 to 15 range might be [5..15] as before or [5,15] with a comma or [5-15] with a dash.
What happens when their range has negative values in it? (i.e. Will this cause trouble with the dash as separator idea?)
(Do not worry, though, they are not allowed to enter decimal values, so the '..' should not ever give trouble.)
Add (Level 2) to account for ranges that are entirely negative: [-10..-5]. (*ahem* d6-11)
| | | | | even divide? | round | min | max | adj | max/min | die size? | action --------+------+------+------+----------+---------------+---------------------- 0 | -10 | -5 | 0 | NA | NA | try negative adj? yes | | | | | even divide? | round | min | max | adj | max/min | die size? | action --------+------+------+------+----------+---------------+---------------------- 0 | -10 | -5 | 0 | 0 (R-5)| no, no | min++; max++; adj-- 1 | -9 | -4 | -1 | 0 (R-4)| no, no | min++; max++; adj-- 2 | -8 | -3 | -2 | 0 (R-3)| no, no | min++; max++; adj-- 3 | -7 | -2 | -3 | 0 (R-2)| no, no | min++; max++; adj-- 4 | -6 | -1 | -4 | 0 (R-1)| no, no | min++; max++; adj-- 5 | -5 | 0 | -5 | 0 | yes, no | min++; max++; adj-- 6 | -4 | 1 | -6 | 0 (R1) | no, no | min++; max++; adj-- 7 | -3 | 2 | -7 | 0 (R2) | no, no | min++; max++; adj-- 8 | -2 | 3 | -8 | -1 (R1) | no, no | min++; max++; adj-- 9 | -1 | 4 | -9 | -4 | yes, um... | min++; max++; adj-- 10 | 0 | 5 | -10 | ..... | ..... | adj is -10, cont? yes 10 | 0 | 5 | -10 | NA | NA | min++; max++; adj-- 11 | 1 | 6 | -11 | 6 | yes, yes | DONE REPORT: roll d6-11 to get [-10..-5]
Some of that checking could have been avoided if we simply skipped over all of those potential 'rolls' where the number of dice was negative, of course...
You can add (Level 0.5) to simply skip negative dice counts — and the 0 dice count — altogether.)
But, maybe someone would want us to report that -(1d6)-4 is also going to give [-10..-5]... (It may be interesting to note how the -1 can be factored out! -(1d6+4) would also give [-10..-5]!) (But definitely note that this is not actually a negative dice size or negative dice count — it is a negating of the dice roll result!)
Add (Level 1) to allow for negative dice by negating the dice part of the roll result.
Add (Level 1) to account for ranges accidentally entered in backwards: [15..5]. (*ahem* Morons...swap...much better.)
Add another (Level 1) to break this code into two re-usable functions — the detection and the swapping. (Hint: one will call the other and the 'main' — or some other function — will call just the 'outer' one.)
Add (Level 1) to give them a default value when entering things like the number of tries before you stop to ask if you should continue. (That is, if they simply hit Enter, you should set the default value. If they type a value and hit Enter, use the value they typed.)
(Hint: What would signal that they've hit Enter at all?)
(Hint 2: Should you do something before you prompt to make sure this 'signal' was in answer to the question you just asked?)
(Hint 2a: Perhaps it would be best if all input to your program were simply cleaned up after...)
(Hint 2b: Think about the fun we had with using getline() inside a simple yes/no loop...)
For instance:
How many negative adjustment trials before I stop and ask to go on? [10]
15
Would set the negative adjustment continue questions to go off at 0, -15, -30, ... Whereas:
How many negative adjustment trials before I stop and ask to go on? [10]
Would set the negative adjustment continue questions to go off at 0, -10, -20, ...as the default.
Add another (Level 1) to place this 'blank entry' support code into a re-usable function. (It might be re-usable in combination with the time entry program, for instance...*grin*)
Add (Level 0.5) to validate all of their yes/no entries. (i.e. do not let them by until they've entered [a word that begins with] either a 'y' or an 'n'.)
Add another (Level 1) to place this y/n validation into a re-usable function.
Add (Level 2) to protect all numeric input from translation failure as well as stupidity. (i.e. number of tries before asking to continue can not be 0 and also can not be a word; it can be negative, however: -10 is divisible by both 10 and -10 and so it will work fine.) (Range end-points can be any whole value but not words or decimals.)
For range end-points, give a clear message as to which end-point(s) had a problem. Always give a clear message as to what the user should do to fix the problem.
Add another (Level 1) to place the numeric validation code into a re-usable function. Remember that you can make the function more re-usable by returning to the caller with cin either clear() or set to failure when there was trouble. But you'll still be responsible for printing an appropriate message:
Enter your desired range: [n..38] Invalid minimum! Discarding the 38 maximum. Enter your desired range: [5..n] Invalid maximum! Discarding the 5 minimum. Enter your desired range: [m..M] Invalid minimum! Invalid maximum! Discarding whole entry!
(The question is, "Is it the me that wrote the validation function or the me that called it?" Interestingly, the answer is, "Both!") (Hint: Consider the 'invalid' part a validation response and the 'discarding' message a main-program level of information. As to the word following 'Invalid'...or even that part, too...I'm thinking a string argument might be appropriate...yeah...)
Add (Level 1) to place all of your input validation functions (the last three options, for instance) into a library. (These might even go along with functions you've created for another lab into a common library...)
Add (Level 2) to allow the user to choose if they want all possible combinations printed or just the first one found. (This'll be especially handy if you've done the non-standard dice (i.e. 4d3) option. Often d2 will be found and reported quickly even though there is a real die option that has yet to be found.)
For this, you'll also have to stop every 'x' tries to ask if you should continue. (Above this value was every 10 tries during the negative checks. If you did the option to let the user enter this value, for negative checks, you'll have to let them enter this one as well. Sorry, no extra levels this time...)
The option above (and this entire program discussion, really) are focused on rolling a single size of die but in multiples and with an adjustment added on. What if your user is willing to mix and match different sizes of dice in the same roll? Then the [5..15] we started out with, for instance, could be had by rolling d4+d8+3. (That is, roll a 4-sided die and an 8-sided die, add their pips, and then add 3.)
And [4..12] can be gotten with d4+d6+2. (So we kinda lied above when we said you couldn't get it with standard die sizes — you can, but it takes multiple sizes in a single roll!)
But the coolest one yet is 3d4+d12-3 gives us [1..21]. Pretty sweet!
Add (Level 4) to handle this kind of thing. (Hint: You'll find a vector of die sizes representing how many times that size die is in the roll handy...)
(Warning! Combining this option with the previous option is dangerous and potentially painful! But boy is it fun!)
(Double secret warning! Combining this option with non-standard die sizes is so harmful as to be considered illegal! Don't try this — anywhere!)
Just FYI, even though different ways are available to create the same spread of values with dice rolls, they are quite different probabilistically. For instance, here are the graphs of the probability density functions for the two ways to make [1..21]:
As you can see, the [5..17] portion of the range is quite different between the two sets of dice.
Add (Level 4) to allow the user to specify a list of die sizes to accept rather than just going with the standard sizes. (This must adapt to the non-standard option as well!) Doing this will turn nice simple branches into loops running along a vector of values. In fact, it is reminiscent of this other lab coming up in Portfolio III.
Speaking of the negative adjustment side, you start to wonder about the numbers in the example. It seems you could have detected mathematically that there was no hope quite a ways before the -10 adjustment. Also note that the user does not have all the information your program does inside — notice how bland the screen interaction would have been:
$ ./devdice.out Welcome to the Developer's Dice Program!!! Enter your desired range: [4..12] Thank you!! Calculating... Problem! We've hit a negative adjustment of -10 and still have not located a dice roll candidate. Should we continue the search? no *sigh* You cannot achieve a range of [4..12] by rolling standard die sizes. Thank you for using the DDP!! Endeavor to have an enclosed day! $
Add (Level 2) to add code to detect mathematically that there is no hope of coming up with a negative adjustment dice-roll instead of having to bother the user. (Unless of course, they want you to...)
Add (Level 1.5) to place this whole program into a menu system along with the either the adjusted dice statistics program or the dice statistics project version.
(Yes, you can still get the Base Level + Options for that lab/project as well — as extra credit — if you did not already hand it in for portfolio I — unless you hand in extra modifications as extra credit and do not try to claim the old levels from the first portfolio you already got as credit...um...yeah. But you'll have to hand the extra credit one in separately!)
Your menu should look something like this:
Dice Menu 1) Calculate dice Statistics 2) determine Roll for Range 3) Quit
Add (Level 1.5) to add a submenu for the extra questions you've had to ask in this lab (some in the main part and some in the options). It will look something like this (if all options are filled):
Options Menu 1) Detect that negative adjustments are Hopeless [NO] 2) Pause calculations every [10] tries to ask "go on?" 3) include All roll combinations [YES] 4) allow nonStandard dice sizes [NO] 5) allow die portion of roll to be Negated [NO] 6) Return to dice Menu
The parts in [square] brackets show the current setting for the options (do not auto-detect hopelessness of negative adjustments, do all combinations, stop every 10 tries to ask if you should continue or not, do not allow non-standard dice, do not allow negated rolls). The user should stay in the sub-menu until they choose option 6/R/M to return [control] to the main menu. (Note that 'control' can return to part of a program via flow control mechanisms — not just a function return!) That way the user can change multiple options before starting the next range test.
(Yes, you can still do the Enter for default entry value option above within this submenu. It's level counts separate — aka 'in addition to' — this one's.)
(Remember, you may not have all these options for them to set. It will depend on if you've done any/some/all of the options above.)
BTW, this will modify the main menu to:
Dice Menu 1) Calculate dice Statistics 2) determine Roll for Range 3) set roll-determining Options 4) Quit
(There were no option questions for the old dice lab or the dice project...)
Add (Level 2) to use a switch to good effect in your standard die size determining code. (This option is unaffected by that code being placed in a function. i.e. you still get this level as well as that one...)
In fact, you can take this option again...and again! Each time you take it, it's level will be cut in half. You can take it for using a switch for processing your menu. And you can take it a third time for the submenu option.
More options coming soon..?
If you did all above options, this lab could be worth as much as (Level 43.5).