One of my favorite puzzle books, "Aha! Gotcha" by Martin Gardner, used the game of "Chuck a Luck" to illustrate some basic probability concepts. You can read the rules and history here.
In a nutshell, the player picks a number from one to six and then places a $1 bet and rolls three dice. If your number doesn't come up, you lose the dollar. If it does come up, you keep your dollar and win $1 for each die that shows your number.
A program which implemented these rules might look like this:
What number do you bet on? 3
You rolled: 4 6 2
You lose $1!
What number do you bet on? 2
You rolled: 2 1 5
You win $1!
What number do you bet on? 1
You rolled: 1 3 1
You win $2!
Gardner concocted a rationalization for a gambler who believed that the game favors the player, and then challenged the reader to show why the argument is faulty. But if you actually write such a program, you could show empirically the the game is stacked against you.
I suggest that you do this in four phases.
Phase one: Implement a program that has output similar to the above.
Phase two: Keep track of how much total money the player has won or lost.
Phase three: Ask the player how many games he wants to play automatically. Like this:
What number do you bet on? 1
How many games do you want to play? 10
[optional: display the outcome of each game]
...You lost a total of $3!
Phase four: Have your program play a very large number of games, and calculate the average amount you lost per game.
Let me know how it goes.
Well, right of the bat, you also have to deal with psuedo-random numbers vs. true random numbers. Will the approximation you calculate even be considered viable? What do you think?
ReplyDeleteThe fact that they're "pseudo-random" rather than actually random is not a problem -- check out this post for a discussion of why I think you're right but it ultimately doesn't matter.
ReplyDeleteThe real problem you're alluding to is that using random trials will never give you the same answer as the precise mathematical calculations. That's true, but it's also the case in scientific experiments. At best, experimenting can point you TOWARD the answer, and if the number of trials is large enough then the approximation is pretty close.
One of my points in writing this kind of program, though, is that games of chance use these tricky psychological hooks to make you not really "feel" the logic behind the math. Having a program show you empirically just how much you stand to lose is a good way of making the numbers seem more concrete.
Whew, this will be a challenge. I'll give it a shot (work schedule permitting), but I'm afraid it take a few days.
ReplyDeleteWell, I'm stuck. My problem is getting the user input. I'm searching and there seems to be multiple ways to do so, which is fine, but most of them appear to be dependent on whatever tool I'm using for coding, which I do not understand.
ReplyDeleteMy HeadStart Java book hands me a separate class called GameHelper.java using something called BufferedReader. Another tutorial website uses Scanner to get the input, except I can't type input anything, but that maybe because I'm not using Eclipse (?)
Anyway, I know the high-level process is explain the rules, get the user's choice from 1 to 6, create 3 dice variables and randomize their value, then compare each roll with the choice and present the results. I'll worry about the running pot totals later.
import java.util.Scanner;
public class AhaGotcha {
public static void main(String[] args) {
System.out.println("The rules of this game are simple.");
System.out.println("Pick a number from 1 to 6 and bet one dollar.");
System.out.println("I'll roll 3 dice. If any of them match your number,");
System.out.println("then you win a dollar for each match.");
System.out.println("But if none of the three match, you lose your dollar.");
int di1 = (int) (Math.random() * 6);
int di2 = (int) (Math.random() * 6);
int di3 = (int) (Math.random() * 6);
System.out.println("What number do you bet on? ");
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
System.out.println("You chose " + choice);
System.out.println("You rolled: " + di1 + " " + di2 + " " + di3);
if (choice == di1) {
// Compare variables and display results
}
//
}
}
Hi James,
ReplyDeleteBoth BufferedReader and Scanner are a standard part of the core Java language. I just tried your code out, and it definitely accepts my input, so maybe you aren't running your environment correctly. Try clicking the window where your text appears before typing, and if that doesn't work you should probably look in the documentation of your editor, because I can't imagine that it wouldn't take text input. If you still can't make it work, I recommend that you open up a command line window (the Windows "cmd.exe" program), navigate to the directory where your .class files are generated, and run it using "java AhaGotcha".
You're off to a good start. There are two suggestions that I would make as you proceed:
1. You have three separate variables representing the dice, di1, di2, and di3. The three variables always behave in the same way. That is exactly the sort of reason why arrays exist. Consider having a single variable called "dice" and reference them as "dice[0]", "dice[1]" and "dice[2]". Bonus points if you can roll all three dice in a "for" loop instead of writing a separate line for each one.
2. It's time for you to start learning how to split up your programs into multiple functions, instead of a single "main" function. Functions are ideal for dividing up different concerns within the program, grouping similar concepts together. For example, those five lines where you wrote out the instructions of the game? Those could all go in a function called "printInstructions()" and then your main function could just call that. It makes the code much more readable.
Expect another full post in praise of the utility of functions when this is over. :)
ReplyDeleteOkay, not sure why NetBeans wasn't accepting my input yesterday but it is today, so moving on.
ReplyDeleteI understand the need for breaking out pieces of the main method into several sub-methods, and have seen that before. I've got the Instructions separated now, but I'm still not clear on the proper syntax. First, the printInstructions part:
public class printInstructions {
void printRules() {
System.out.println("The rules of this game are simple.");
blah blah
}
}
I presume no class can be without a method? At first I simply threw in the five lines of printed instructions, and all I saw were errors. Only after adding 'void printRules();' did it clear up. So in layman's terms, I have created a class called printInstructions that contains a method called printRules, and the main method will call for printInstructions at the proper time. Correct?
Next, in the main method, we see this:
public class AhaGotcha {
public static void main(String[] args) {
printInstructions p = new printInstructions();
p.printRules();
//rest of code
}
}
Not sure what's happening here. I was expecting to simply say 'printInstructions();' or some such. But instead I appear to be creating an object based on the class printInstructions, giving it a name 'p' then telling 'p' to perform the printRules function. That feels like unnecessary steps, but I suppose there is a reason.
I'll continue to work on the rest as time permits.
Well, 2 steps forward and all that...
ReplyDeleteSo I'm taking your advice about using an array to create the 3 dice. In my main method I've got this:
Dice [] di = new Dice[3];
for (int i=0; i<3; i++) {
di[i] = new Dice();
}
The array is created and populated with three dice, but they don't do anything. Based on what I did previously with the printInstructions, I create another file called Dice.java with this:
public class Dice {
int roll;
public void roll() {
roll = (int) (Math.random() * 6);
}
}
I have no clue if that syntax is right. Can 'roll' be used over and over like that?
Back in main, I need to roll the dice:
for (int i=0; i<3; i++) {
di[i].roll();
}
When the output gets to this line:
System.out.println("You rolled: " + di[0] + " " + di[1] + " " + di[2]);
Then this is the output:
You rolled: Dice@1a758cb Dice@1b67f74 Dice@69b332
So I guess my formula is wonky somehow.
Sorry, I meant to approve your last comment and reply to it sooner, but failed to get around to it and then it dropped off my radar.
ReplyDeleteSo, one thing at a time. I suggested that you create a separate function for printing the instructions, but you went ahead and created an entire separate class, which is overkill. There is a time and place for making new classes, but we're not quite that advanced yet, so I'm going to go ahead and recommend that you stick to one program, one class until things get a little more hairy.
In grammatical terms, classes become objects represent nouns, or things -- concepts that are so independent of one another that which we wish to keep them separate from one another in the program. Methods or "functions" in non-object-oriented programs, represent verbs -- actions which the objects know how to perform.
In this case, the program is so simple that you would be well served by thinking of the entire thing as one class -- a chuck-a-luck game. We can visualize which parts of the game might be considered as independent objects, such as dice for example, and might be given their own classes if the program were to become more complex. But not now. We have one thing, a game, which knows how to take several kinds of actions:
1. Give instructions.
2. Take input.
3. Roll some dice.
4. Assess the score.
5. Keep track of your bankroll.
In your initial design, you crammed all of these into one function, and that's fine, but there's a rule of thumb that is useful to keep in mind: functions should be as small as you can reasonably make them, while grouping high level concepts together. So, any of the five things I just listed could potentially be spun off into its own function, but they are not yet distinctive enough that they should get their own class.
So in response to your first question, you were right that you were making it needlessly complicated. Here's how I would do it:
public class AhaGotcha {
public static void main(String[] args) {
printInstructions();
//rest of code
}
public static void printInstructions() {
System.out.println("The rules of this game are simple.");
//and so on
}
}
Clear?
For your second question, I will repeat my first recommendation. Keep your program in one class for now. Instead of creating a Dice class, and making an array of three of them, just use an array of three "int"s. Once you revert to that method, the problem you mentioned will go away.
ReplyDeleteHowever, it might be helpful to understand why you got that weird output, so here it is. When you said:
System.out.println("You rolled: " + di[0]...);
You were not telling your program to print a number anymore, but a class. Now, if the class has a "toString" method, then all is well, because it could print something you recognize as a text representation of that class: probably the "roll" number that you created. (Although "roll" is a verb, which makes it seem like it should be a method and not an internal number. You might use something like "value" instead if you insist on doing it that way.)
However, you didn't have a toString method on Dice. So when you told it to print the value of an object of type "Dice," it defaulted to the standard behavior, which is to print some information about the memory allocation of the object. That is almost never what you really want it to do.
Okay, keeping printInstructions() inside the class definitely makes more sense. So to that end, my code is thus:
ReplyDeleteimport java.util.Scanner;
public class AhaGotcha {
public static void main(String[] args) {
printInstructions();
System.out.println("What number do you bet on? ");
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
System.out.println("You chose " + choice);
// misc code
public static void printInstructions() {
System.out.println("The rules of this game are simple.");
//etc etc
}
}
And that all works as expected.
Now I'm stuck on generating the three dice and getting them to roll. To make an array would look something like this:
public static void Dice() {
Dice [] di = new Dice[3];
for (int i=0; i<3; i++) {
di[i] = new Dice();
}
}
But NetBeans complains that it cannot find symbol: class Dice. Which I don't understand, because my intention was to make a function, not a class.
"Dice [] di" means that you are declaring a variable called "di", which is an array of three "Dice" objects. But there is no such thing as a "Dice" object in your program, which explains why it is looking for class Dice.
ReplyDeleteWhat you really want to be doing is "int [] di = new int[3]".
Furthermore, remember that methods are behaviors, and name them accordingly. Rolling dice is a verb, not a noun, so it's confusing to have a method called Dice (as you declared it in writing "public static void Dice()"). In general methods should be named with words describing what the method is doing. For example, you might call this one "rollDice."
My apologies to any lurkers smacking their foreheads in frustration because of my blunderings...
ReplyDeleteSo I was thinking that calling an array variable by the name of 'int' would violate some kind of 'protected term' rule, in that the variable type and the variable name are both called 'int'. I wouldn't want to name a variable 'String' or 'Boolean' for the same reason. Is that not correct?
Adding your line 'int[] di = new int[3];' seems fine, but then I can't populate the array with objects. This command:
'di[0] = new int();'
throws the error, "Incompatible types. Found: 'int' Required: 'int[]'
It throws this error whether I put the array creation and population inside the main method or inside the rollDice() method (with a call for rollDice() inside main).
My apologies to any lurkers smacking their foreheads in frustration because of my blunderings...
ReplyDeleteDon't worry about it. I'm pretty sure everyone either wrote their own program and went away, or read a few replies and went away. (But in case the lurkers are here -- hi lurkers!)
You've got a few conceptual problems, and I'd like to recommend that you spend a little time carefully re-reading the chapter that introduces them. You aren't creating an array with the NAME int. You are creating an array where each item in the array is TYPE int. The array as a whole is named "di" according to your declaration, although I'd like it better if it had the full descriptive word "dice" which indicates unamibiguously what it's for. Let me break down this line for you.
"int[] di = new int[3]"
"int" -> This is an integer type
"[]" -> It is not just a single integer, but an array of many integers (size unspecified at first)
"di" -> the name of your variable
"= new int[3]" -> you have initialized your array, explicitly setting aside enough memory to hold three integers, which will represent your dice rolls.
"di[0] = new int();" throws an error. In fact this line is unnecessary. All you have to do to set the value of di[0] is say something like "di[0] = 6".
I'll explain why.
There are two kinds of things in Java: atomic, and objects. An atomic variable is just a single, predefined chunk of space set aside for a group of data, represented as 1s and 0s. If the type of variable you declare starts with a lowercase letter, odds are that it's an atomic type, by convention of the language. If it's an instance of a class, then the type starts with a capital letter.
So as you've already seen there is an "int" type and an "Integer" type. "int" is atomic; "Integer" is a class. This means that as soon as you declare that an "int" exists, the program uses up exactly as much memory as an int normally requires. But if you declare an Integer, it only sets aside a reference where a memory location might store the Integer later.
I realize this is confusing, so let me give you some concrete examples to demonstrate.
int x; // there is now a number called x
x = 3; // now the value is set to 3
Integer y; // there is now a POTENTIAL object called y. The object does not exist yet; its value is "null".
y = new Integer(3); // it is now an object with "3" as the value.
Integer z;
z = 3; // This is a little odd, because you are setting an object variable to an atomic value. However, it does work. This is because under the covers, the Java compiler is smart enough to initialize a new object, so the practical result is the same as in the "y" example. In older versions of Java this would have caused an error.
Integer a, b, c;
a = 1;
c = a+b; // This is an error! a is an object, but b is still null, and it's not legitimate to try to manipulate ANY null value with operations like adding.
int d, e, f;
d = 1;
f = d+e; // You might think this should be an error too, but it's not. The int e is created as soon as it is declared, and by Java convention it is initialized to zero. So f is now 1.
Pardon the interruption. I just thought I'd post my own solution, written in Haskell instead of Java, just for variety. Skip past my post to continue with Kazim's and James' educational tutorial! :D
ReplyDelete(I just wanted to play too)
I ran it for a million trials and got a loss of $80477.
[code]
module ChuckALuck (main) where
import System.Random
import Data.List
die_size = 6::Int
lost_cost = 1::Int
win_cost = 1::Int
rolls_per_trial = 3::Int
verbose = False
data Outcome =
Outcome
{
bet_on :: Int,
rolls :: [Int],
win_count :: Int
} deriving (Show)
outcome_new :: Int -> [Int] -> Int -> Outcome
outcome_new b rs w =
Outcome
{
bet_on = b,
rolls = rs,
win_count = w
}
combine_outcomes :: (Int,Int,Int) -> Outcome -> (Int,Int,Int)
combine_outcomes (money,wins,losses) Outcome{win_count=0} = (money - lost_cost,wins,losses+1)
combine_outcomes (money,wins,losses) Outcome{win_count=wc} = (money + wc*win_cost,wins+1,losses)
run_trial :: Int -> [Int] -> Maybe (Outcome,[Int])
run_trial number_to_bet_on die_rolls = Just (outcome,die_rolls')
where
(rolls,die_rolls') = splitAt rolls_per_trial die_rolls
win_count = length.elemIndices number_to_bet_on $ rolls
outcome = outcome_new number_to_bet_on rolls win_count
run_trials :: Int -> Int -> [Int] -> (Int, Int, Int, [Outcome])
run_trials trials number_to_bet_on die_rolls = (money, wins, losses, outcomes)
where
outcomes = take trials.unfoldr (run_trial number_to_bet_on) $ die_rolls
(money,wins,losses) = foldl combine_outcomes (0,0,0) outcomes
main :: IO ()
main = do
rnd_gen <- getStdGen
putStr "What number do you bet on? "
number_to_bet_on_str <- getLine
putStr "How many games do you want to play? "
trials_str <- getLine
let
number_to_bet_on = read number_to_bet_on_str
trials = read trials_str
die_rolls = map (\x -> x `mod` die_size + 1) (randoms rnd_gen)
(delta_money, wins, losses, outcomes) = run_trials trials number_to_bet_on die_rolls
if (verbose) then
putStrLn (show outcomes)
else
return ()
if (delta_money == 0) then
putStrLn "You broke even."
else if (delta_money > 0) then
putStrLn $ "You won a total of $" ++ (show delta_money) ++ "!"
else
putStrLn $ "You lost a total of $" ++ (show (-delta_money)) ++ "!"
return ()
[/code]
Nice. One of the interesting things about reading that is that I've never heard of Haskell, and I find that the best way to learn a new language is to read samples of somebody else's code, and then try putting together simple programs just like this one to test out features of the language. You find that while every language has its own style and naming conventions, you can usually read a program written in an unfamiliar language and get the broad strokes clearly.
ReplyDeleteOut of curiosity, is there a particular problem domain where Haskell is frequently used?
As a followup to my previous post, I apologize for not finding a way to format it correctly -- Haskell does have a peculiarity where indentation matters, so you'd have to fettle with that posted source code to get it to run. I recommend GHC.
ReplyDeleteWell, Haskell falls into the same language family as Lisp, Scheme, Clojure, and F#. It is a functional language, rather than an imperative one. But that doesn't say anything particular about its use case.
Its a pretty general-purpose language. I've used it to search Tic-Tac-Toe game trees, find shortest paths in graphs, and generate fractals. People have written games with it, run GUI libraries with it, simulated physics with it... I remember the Haskell User's Group in London did a 3d Tron Lightcycle thing on the inside of a torus or something of that nature.
Its a little hard to make a comparison. GHC, one of the most popular backbones for the language, is an interpreter so you can whip together programs and run it on the spot, so it might be a little like a Lisp interpreter in that respect. However, GHC also allows you to compile to actual native code, directly for some platforms, and via a C/C++ compiler on others. You'd do this for a high performance final build.
Its base libraries are powerful, but geared around data processing and manipulation and math, sort of like C and C++, rather than having piles of GUI and multimedia and web facilities like Java, but it has a large and growing online package service like Perl has its CPAN to give you facilities you might lack.
Haskell is one of the most strongly typed languages I've worked in. More so than C, C++, Java, and C#. By the time you get something to compile at all, it is likely you've got something at least partially working. Its pretty hard to have a bug that is type related. Given that it's a functional language, the programs tend to be expressed in a way analogous to mathematical proofs, which make it a little easier to reason about the robustness of your solution.
Functional languages are making a bit of a comeback partly because of there algebraic robustness and partly because their property of non-mutation of data makes them delicious to apply to multithreading. Clojure and F# are getting to be more popular. Worth exploring.
Haskell's history is a bit odd. Its a highly pure functional language, trying not to compromise the pure functional programming style for the sake of willy-nilly convenience. It shows itself to be an academic language for that reason. Yet somehow it survived what usually happens to academic languages in only having 5 programmers worldwide and falling into obscurity after a year. It has an interpreter for quick evaluation and proof of concept, and yet its one of few functional programming languages that have a compiler that generates native executables as output.
I might be better off learning F# from a practical point of view, if I wanted to stick in the functional programming paradigm, but I think I'm sucked in now. :D
Um, so.. uh... in short... I suppose Haskell is a functional programming answer to C++, but with a Perl-like module community, and a LISP-like interpreter on the side. In *principle* it would be a similar domain to C++.
I'd better stop before I sound less like an enthusiastic advocate and too much like a fanboy.
You've got a few conceptual problems, and I'd like to recommend that you spend a little time carefully re-reading the chapter that introduces them. You aren't creating an array with the NAME int. You are creating an array where each item in the array is TYPE int.
ReplyDeleteI think I understand what you're saying. I'll have to think on that some more.
"di[0] = new int();" throws an error. In fact this line is unnecessary. All you have to do to set the value of di[0] is say something like "di[0] = 6".
I'm not sure when I would ever hard-code the value of di[x] like that. Isn't the point to use the randomizer to generate new values every time the game is played?
I think I understand what you're saying. I'll have to think on that some more.
ReplyDeleteDon't just think about it on your own -- find the chapter in whatever book you're using that introduces arrays and read it carefully. Maybe the author will explain it better than me.
"di[0] = new int();" throws an error. In fact this line is unnecessary. All you have to do to set the value of di[0] is say something like "di[0] = 6".
Correct. So instead of "6", put in your random function. I only used that to show that you can directly set the value of di[0] without using the "new" command.
Part of the reason I told you to use "int" instead of "Integer" earlier is so we can just stay completely away from this whole quagmire of atomic variables versus objects.
Oy, I'm really confused. But as my motivational coach always says, this is the point right before you throws your hands up and give up!
ReplyDelete(I'm firing my motivational coach.)
So I feel like I'm understanding what you're saying, but I'm going backwards. Before you recommended breaking the steps into methods, I had a working set of dice that randomly rolled numbers. Now, using methods, I'm stuck again. Here's the code, and I've eliminated the printInstructions method since it works:
import java.util.Scanner;
public class AhaGotcha {
public static void main(String[] args) {
printInstructions();
System.out.println("What number do you bet on? ");
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
System.out.println("You chose " + choice);
rollDice();
System.out.println("You rolled: " + dice[0] + " " + dice[1] + " " + dice[2]);
public static void rollDice() {
int[] dice = new int[3];
for (int i=0; i<3; i++) {
dice[i] = (int) ((Math.random() * 6) + 1);
}
}
}
NetBeans complains about the line "System.out.println("You rolled: " + dice[0] + " " + dice[1] + " " + dice[2]);"
that it "Cannot find symbol: variable dice"
I thought the rollDice() method created the variable dice and populated them with a random number.
In other words, if I code this inside the main method:
int[] dice = new int[3];
dice[0] = (int) ((Math.random() * 6) + 1);
dice[1] = (int) ((Math.random() * 6) + 1);
dice[2] = (int) ((Math.random() * 6) + 1);
then the program runs flawlessly. But if I put those four lines into a method called rollDice() and call rollDice() within main, then I get the "Cannot find symbol: Variable dice" error. I do have "public static void" before rollDice(), just like I did with printInstructions, but I guess Java can't see the variables dice?
The problem you're running to is variable scope. This of it like this. You can declare a variable (like "dice") from anywhere, but that variable only has meaning if you're inside the same set of curly braces where you declared it.
ReplyDeleteSo now you have two function: a "rollDice" function, in which you define and initialize an integer array called "dice"; and a "main" function, which is separate and apart from rollDice, and cannot see the variables inside it.
Now, there are at least three solutions to this problem.
Solution 1: Declare "dice" as a static variable, available to the entire AhaGotcha class.
public class AhaGotcha
{
public static int[] dice;
...
If you do that then both methods can see it.
Solution 2: Do what you're doing, but make the dice value a return value from the rollDice method.
public static void main( String[] args )
{
int[] dice = rollDice();
...
}
public static int[] rollDice()
{
int[] dice = new int[3];
//do the rolling part
...
return dice;
}
If you use this technique, then the main method will retrieve the "dice" rolls as a result of rollDice being performed.
Method 3:
Similar to the above, but your function should only roll one die.
public static void main( String[] args )
{
int[] dice = new int[3];
dice[0] = rollDie();
dice[1] = rollDie();
dice[2] = rollDie();
...
}
public static int rollDie()
{
return ... // your die-rolling formula
}
The advantage of this method is that it separates the die rolling as a single function, to be called under a variety of situations which are not specific to this game.
Any one of these solutions is legitimate. The first is probably the easiest, while the last is probably the most generalized.
Cool. I feel like I've turned a corner. I went with your third method, since that seems more logical. I even threw the dice rolling into a for loop to save a line of code. So the function call is thus:
ReplyDeleteint[] dice = new int[3];
for (int i = 0; i < 3; i++)
{ dice[i] = rollDice(); }
System.out.println("You rolled: " + dice[0] + " " + dice[1] + " " + dice[2]);
And this works flawlessly.
I would like a brief explanation about something. I've got two functions here. printInstructions() is preceded by 'public static void' whereas rollDice() is preceded by 'public static int'. What's the difference?
So for the next step, I need to put some error-checking into the Scanner, since inputting any number or text is accepted, and that will cause problems when I later want to compare the user's choice with what the 3 dice rolled. Onwards....
A fine question!
ReplyDeleteUsually when you create a function, you are building a small, predictable machine into your program. You put some data in, and a result comes back out. Once the function is written, you don't necessarily think about the behavior of it again; you just remember that it does something you want.
You'll probably remember the concept of functions appearing in math as well. Here's an example:
f(x) = 2x
You take a number, x, drop it in your function, and you get out a number. f(1) = 2. f(2) = 4. f(17) = 34. Etc.
In your roll dice function you are not passing any information in, but you are still relying on something semi-predictable to come out: a random number guaranteed to be between 1 and 6.
What kind of thing does rollDice return? Not a string, not a character, not a boolean. It's an integer, every time. So we declare it as "int rollDice".
printInstructions, however, doesn't actually return any result. It just DOES something, reliably, prints some words on the screen. Because it doesn't require a return statement, the type is not "int" but "void" (nothing).
Awright, that makes sense. Thanks.
ReplyDeleteNow for the error catching. After I "ask" for the player to choose a number, I added this:
System.out.println("What number do you bet on? ");
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
if (choice < 1 || choice > 6) {
System.out.println("Sorry, choose a number between 1 and 6, please.");
} else
System.out.println("You chose " + choice);
That works okay if the player puts any any small number other than 1 through 6. However, if she types a letter, then the program crashes. Or if she types a huge number like 9,999,999,999, it crashes too. I've seen discussion about the value range of certain variables, that 'int' is 32 bits with a much larger range than, say, 'byte.'
But the solution is not to use a 'long' just in case the player leans on the number pad. The solution is to evaluate the choice and if it doesn't fall between 1 and 6, then point it out. My code does that up to a point, but I'm not sure how to word that. I know about &&, ||, =<, =>, and so on. I guess I need the code to say, "If choice is NOT =< 1 AND if choice is NOT =>6 then complain." Is this a case for nested if-else statements?
(Ooh, and I'll wager you'll recommend writing the complaint code as a separate function.)
Also, I need the code to stop and offer a redo, otherwise it complains about the bad choice, then continues on with the dice roll. So the program needs to say something like, "Try again?" accept either Y or N, then either start over or quit. I guess Scanner would work, but what sort of variable would I use to accept a single letter? Char?
There are some operations which, if you don't watch them for errors, they go asplode. Therefore, you should take a look at a concept called "Exceptions." You've seen them before. Every time your program crashes, you receive a big text dump which you probably don't read, but which contains statements like "SomethingSomethingException: [class name]".
ReplyDeleteYou might want to start reading them now, because the first line in that dump will generally tell you what kind of error occurred. Most of the time, if your program is written robustly, you shouldn't get an exception. But there are -- how should I put this? -- exceptions. For instance, when you call Scanner.nextInt(), you're trusting that the user is going to give you an integer, which is not always a justified assumption given that all users are idiots.
In fact, if you check the definition of the nextInt method, you will see that you can expect it to throw an "InputMismatchException" when the user lies to you about sending an integer.
So if you know an exception's coming (because all users are idiots), then what do you do with it? The try/catch block works like this:
try
{
// do some risky behavior
}
catch(AnExceptionYouMightGet e)
{
System.out.println("Hey, user! You're an idiot!");
}
So you see, the part in the try block MIGHT crash your program. If it does, it will bounce straight out of the try block, and do the stuff in the catch block. However, if the expected exception never occurs, then the catch block will not be run.
For example:
try {
int x = 0;
int y = 1;
System.out.println(x/y);
System.out.println("I don't see anything wrong with that!");
}
catch(DivideByZeroException e)
{
System.out.println("What have I done with my life?");
}
This program will NOT print a number, nor will it print "I don't see anything wrong with that." It will only print the error message.
(Ooh, and I'll wager you'll recommend writing the complaint code as a separate function.)
ReplyDeleteYou bet I do! Although actually, I recommend you write the input code as its own function, and you INCLUDE the error message as part of that. Validating input is really part of the same work as reading it, conceptually.
Also, I need the code to stop and offer a redo, otherwise it complains about the bad choice, then continues on with the dice roll. So the program needs to say something like, "Try again?" accept either Y or N, then either start over or quit. I guess Scanner would work, but what sort of variable would I use to accept a single letter? Char?
If you want a redo, you should be thinking "while loop." As in: while (invalidInput) { read the input again; }
A char would be fine; you can also use a string, and use the "equals" method to check if your string matches "y" or "n" (or "Y" or "N").
I meant y/x, of course, not x/y.
ReplyDeleteI'm really not wrapping my head around 'public', 'private' and what can see what where.
ReplyDeleteI add a line called getChoice(); and move the previous code to get the player's choice from within main to a separate function. My first crack at it is thus:
public static void getChoice() {
try {
System.out.println("What number do you bet on? ");
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
}
catch(InputMismatchException e) {
System.out.println("Sorry, choose a number between 1 and 6, please.");
}
System.out.println("You chose " + choice);
}
NetBeans is complaining about two things. First, 'cannot find symbol: class InputMismatchException'. Second, 'cannot find symbol: variable choice'.
I figured that InputMismatchException would be a built-in error or something. So NetBeans makes two hints. First, it offers to fix the 'cannot find symbol: class InputMismatchException' error by adding 'import java.util.InputMismatchException;' to the top of the code, which seems like overkill to import a single error message.
Second, NetBeans fixes the 'cannot find symbol: variable choice' error by editing the first line of the getChoice() method to 'public static void getChoice(int choice)'
I read this as, "Run the getChoice method and be expecting an integer to insert into the 'choice' variable.'
That makes those two red flags go away within the getChoice() method, but then, within main, I get the red flag on the getChoice(); line reading, 'method cannot be applied to given types. Required 'int'. Found 'no arguments'. Reason: actual and formal argument lists differ in length.'
So the lack of arguments included when I call getChoice() tells me that I'm going the wrong direction adding 'int choice' within the parens of the getChoice() method. I don't want getChoice() to act on arguments; I want it to get an integer variable from the user to compare it to the dice rolls.
This would be a heck of a lot easier if users just weren't idiots.
I'm really not wrapping my head around 'public', 'private' and what can see what where.
ReplyDeleteDon't concern yourself with that yet. Those are only important if you are using multiple classes, and you're not doing that yet.
'cannot find symbol: variable choice'
The problem you're running to is one of scope. When you declare a variable, essentially it only exists as long as you're working inside the same set of curly braces where you declared it. As soon as the curly braces close the block of code, the variable is erased from existence.
Example:
{
int i;
i = 3;
}
All is well.
{
int i;
}
i = 3;
That will not compile. That is because "i" stopped existing when the braces ended.
The solution is to move the definition of your variable to a higher level. So this would work:
public static void getChoice()
{
int choice = 0;
try
{
...
choice = scan.nextInt();
}
catch ( InputMismatchException e )
{
...
}
System.out.println( "You chose " + choice );
}
See it now? "choice" exists for the entire function, not just the block that is inside the "try" statement. Another solution would be to move the println statement inside the "try" block, so that choice would still be in scope.
There's one more order of business though... you probably don't want getChoice to return a void, because you want to do something like this:
(In main:) int choice = getChoice();
Therefore, you should make the function return an int value, and before the function ends, you should "return choice;".
This would be a heck of a lot easier if users just weren't idiots.
ReplyDeleteAh, words of wisdom. I think that's fodder for a new post.
Awwww, and the posts went all quiet. How went the Java program to explore this probability problem, James? :) I was thinking of rewriting my Haskell program to go more towards the user input version rather than running the batch inputs. It wouldn't change a whole bunch from what I already posted, but it might be a good side-by-side comparison to whatever you come up with.
ReplyDeleteGah, I am so close I can taste it.
ReplyDeleteSo I've been banging my head on this getChoice() method, in that I've got to allow for two types of user idiocy. One, they put in a number not between 1 and 6, and Two, they put in a non-number. Here's what I've got:
public static void getChoice() {
int choice = 0;
while (choice < 1 || choice > 6) {
try {
System.out.println("What number do you bet on? ");
Scanner scan = new Scanner(System.in);
choice = scan.nextInt();
}
catch(InputMismatchException e) {
System.out.println("Sorry, choose a number from 1 to 6, please.");
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
}
System.out.println("These are six-sided dice. Choose from 1 to 6, please.");
}
System.out.println("You chose " + choice);
}
This *almost* works as specified. The program prompts the user with "What number do you bet on?" If the user picks a number NOT between 1 and 6, then the output is:
These are six-sided dice. Choose from 1 to 6, please.
What number do you bet on?
And that's perfect. Explain the problem, and give the user a chance to try again. However, if the user types something like 'a', then I get this:
Sorry, choose a number from 1 to 6, please.
These are six-sided dice. Choose from 1 to 6, please.
What number do you bet on?
Not quite. Both messages are being displayed, when all I want to do is say, "These are six-sided dice, etc. What number do you bet on?"
Then if the user picks a legitimate number (1 - 6) we get this output:
These are six-sided dice. Choose from 1 to 6, please.
You chose 4
You rolled: 3 3 1
The user gets a mild scolding, but then everything after that runs perfectly.
I don't *think* multiple catch statements are called for here, since entering '7' won't throw an error for the compiler to catch. It's the while statement that needs to make sure 'count' is between 1 and 6, but I'm having trouble knowing where to insert the right error message.
I agree! You're very close. I see three problems. You've noticed two of them, so I'll refrain from commenting on the third until it becomes a problem. I will address the other two one at a time.
ReplyDeleteYou're right that you shouldn't have yet another "catch" statement to catch the errors. One kind of error throws an exception because it's "broken" input; the other kind of error is valid input, but does not meet the rules, and that does not cause an exception.
Here's what I suggest: take your "bad number" error statement, and move it inside the try block. like this:
choice = scan.nextInt();
System.out.println( "These are six-sided dice. Choose from 1 to 6, please." );
}
catch ...
Here's what will happen. If "choice" is not a number, it will immediately jump into the try block, skipping over the first error statement (wrong number) and moving straight to the second (not a number). On the other hand, if you enter a number 7, it will complete the try block and print the "wrong number" message.
However, this still doesn't address your other problem, which is that it scolds you even if you enter a GOOD number. To get around this, you're going to need to add an extra "if" statement. The check for a bad number has to come after the scan occurs, obviously, but the error check has to occur before printing the scold.
See that condition in your "while" block?
while ( choice < 1 || choice > 6 )
Before you commit to printing the "bad number" error, you should run an "if" to check if that same condition has been met.
Okay, one step at a time. Moving the "These are six-sided dice" scolding into the try block worked, but I'm not following the logic flow.
ReplyDeleteYou said, "If "choice" is not a number, it will immediately jump into the try block, skipping over the first error statement (wrong number) and moving straight to the second (not a number)."
Why would java jump *into* the try block when the request for a number and the code to capture the user's input is already in the try block? Reading this, it appears to me the code prints, "What number do you bet on?", Scanner gets the user's choice, then prints, "These are six-sided dice; try again, dummy." But that's not what happens. Picking a legitimate choice prints the same error scold, then continues on with the rolling of the dice.
The only thing I can think of is that I've jumped the gun with the 'while' block, in that I need to place 'while (choice < 1 || choice > 6)' inside the try block, instead of the other way 'round.
Why would java jump *into* the try block
ReplyDeleteMy mistake. You caught it.
I meant to say that it will jump into the catch block. It will jump *out of* the try block.
And for the other thing, I'm suggesting that you keep the
ReplyDelete"while (choice < 1 || choice > 6)"
but ALSO add
"if (choice < 1 || choice > 6)"
before you print the second error message.
Okay, then. The getChoice method is as follows:
ReplyDeletepublic static void getChoice() {
int choice = 0;
while (choice < 1 || choice > 6) {
try {
System.out.println("What number do you bet on? ");
Scanner scan = new Scanner(System.in);
choice = scan.nextInt();
if (choice < 1 || choice > 6) {
System.out.println("These are six-sided dice. Choose from 1 to 6, please.");
}
}
catch(InputMismatchException e) {
System.out.println("Sorry, choose a number from 1 to 6, please.");
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
}
}
System.out.println("You chose " + choice);
}
And this works as expected for all forms of input. The idiot user has finally been vanquished.
Now on to the next step, taking the user choice and comparing it to the dice rolls.
So the next part was easier than I thought:
ReplyDeleteint tally = 0;
for (int i = 0; i < 3; i++) {
if (choice == dice[i]) {
tally++;
}
}
if (tally == 0) {
System.out.println("Sorry, no matches, so you lose.");
}
else {
System.out.println("You have " + tally + " matches,");
System.out.println("so you win " + tally + " dollars. Congratulations.");
}
}
To get this to work, I had to move the declaration of int variable 'choice' from out of the getChoice() method and declare it just before the main method as 'private static int choice;'
But the program works. My next puzzle is to take care of the singluar/plural issue that I sometimes see in programs. If the user has one match, then the program announces that "You win 1 dollars," which is poor English.
I suppose I need to build in more if statements, and have one verbiage when tally == 1, and another when tally > 1.
Seems clunky, but that's the price to be paid to make a program not appear clunky, I suppose.
Looks great!
ReplyDeleteI'll bet if you had looked at that block of code you just wrote a few weeks ago, you would have said "That looks pretty complicated! How on earth did you come up with that?" But as you get more practice, what you gain is confidence in the right approach due to familiarity. Am I right?
There isn't much you can do to avoid using two different styles of verbiage, although you might consider this a cute trick so you don't have to write out the entire sentence. I think you can easily see what it does.
String dollar_value = tally_value == 1 ? "a buck" : tally+" dollars";
System.out.println("You win "+dollar_value+"!");
The expression "condition ? trueresult : falseresult" is a trick of the language that is basically equivalent to a function which return trueresult or falseresult based on the boolean value of condition. You can accomplish the same thing with a long if/else block, but it makes your code look prettier.
And of course, the clever thing about this is that by setting a string value before printing, you retain the parts of the sentence that won't change, and you don't have to write it twice.
Sorry, I put "tally_value" in my sample code when I just meant "tally."
ReplyDeleteHeh.
ReplyDeletehttp://abstrusegoose.com/387
So... I know there's always going to be the urge to keep tinkering with your program, but nothing is ever perfect, so is it time to move on to the next thing now?
ReplyDeleteWell it's your blog, so do what you want. I did want to add the option of running multiple entries which, as I recall, was the point of Gardner's illustration, in that what appears to be a favorable game will work against you over the long run. That's already been demonstrated by CodeWeaver's Haskell program.
ReplyDeleteBut if you come up with something else that sounds more fun I may chase that bit of shiny, ADD-style.
Oh sorry, I forgot that you hadn't done that yet.
ReplyDeleteIf it makes things easier, and you haven't already, feel free to look over CodeWeaver's program and see if you can get any ideas on it to speed things up.