I want to break out of loop when 0 occurs 4 times or 1 occurs 3 times (baseball balls and strikes)


fn main() {

    let mut at_bat = 1;

    'counting_up: loop {

        println!("At Bat = {}", at_bat);

       

        let mut pitch = rand::thread_rng().gen_range(0..2);

       

        println!("The pitch is a: {}", pitch);

       

            if at_bat == 27 {

                break 'counting_up;

            }

            pitch += 1;

        }

        at_bat += 1;

   

     println!("End of game. Outs = {}", at_bat);

}

Please check out this post for how to properly format your code:

Beyond that, please also fix the indentation of your code. You can do this easily by running rustfmt on it.

2 Likes

(I collapsed every section below so this post wasn't a mile long. Just click on each title above the links to see the section.)

How to get started on the Playground for sharing examples:

I copied your code and pasted into the Playground. I ran rustfmt on it (under Tools, upper-right side). I removed a few more blank lines out of personal preference. Then I clicked Run (red button, upper left). It said:

help: the following trait is implemented but not in scope; perhaps add a `use` for it:
    |
2   | use rand::Rng;
    |

The use sugggestion was clickable; I clicked it and it added the line to the code. I clicked Run again and it ran. Then I clicked Share (upper right) to get a link I could use in this forum.

Here's the resulting playground of your example.

Counting balls and strikes

There's a warning that pitch is assigned to but never read -- it's declared anew every time through the loop, but you increment it at the bottom of the loop. I'll just remove that for now.

Next, you want to consider 0 to be a ball and 1 to be a strike. If you're going to keep track of those, you'll need to store them some where. I'll add balls and strikes variables. The need to be declared outside of the loop, like at_bat, because you want to keep track of these values for multiple trips through your loop.

To keep track, you'll have to look at the value of the pitch. Here is what I suggest:

        match pitch {
            0 => balls += 1,
            1 => strikes += 1,
            _ => unreachable!(),
        }

You need that last part because the compiler doesn't understand that you can only have 0s and 1s at this point. We'll come back to this later.

To break the loop, I change the condition a little:

        if at_bat == 27 || balls == 4 || strikes == 3 {
            break 'counting_up;
        }

At this point, the compiler warns that pitch doesn't need to be mutable anymore, so I remove the mut on pitch.

Here's the code counting balls and strikes.

That was your question, but let's keep going a little further.

Ignore batter for now and clean up the loop

It looks like this program always ends with "two outs" now. And before we counted balls and strikes, it would run forever. What's going on? Well, the only way you were exiting the loop before is when at_bats was 27. But that variable starts with value 1, and you never change it until after the loop, where you add 1. That's why you were running forever before, and that's why you always end with "2 outs" now.

Let's just get rid of that for now. It's some sort of "todo" for later.

Also, when possible, it's more idiomatic to have the conditions of your loop be at the top. Here's what that looks like:

    while balls < 4 && strikes < 3 {
        // As before, without the `if ... { break }
    }

Here's the code after that cleanup.

This may be where you want to stop for now and play with things a bit more. But I'll add one more section where we start to think about encoding your logic into data structures.

Making and using a `Pitch` datastructure

It's a little odd to look at this output where every pitch is a 0 or a 1. You could change things to print out "Ball" when it's a 0 and "Strike" when it's a 1, but even better is to use data structures to encode such details for you. Things are much simpler once a ball is a Ball and a strike is a Strike, even though it takes some investment up front.

So here's the data structure I suggest:

#[derive(Debug, Copy, Clone)]
pub enum Pitch {
    Ball,
    Strike,
}

Every Pitch can be a Pitch::Ball or a Pitch::Strike, but only one thing at a time. The attributes at the top supply some code for you automatically, in this case:

  • Copy and Clone mean you can copy the values around freely like you can with integers
  • Debug lets you print out the values with the "{:?}" format. We can use this to avoid writing our own Display implementation for now.

Then you want to hide away the details of a random 0 meaning Pitch::Ball and a random 1 meaning Pitch::Strike, like so:

impl Pitch {
    pub fn random() -> Self {
        if rand::thread_rng().gen_range(0..2) == 0 {
            Pitch::Ball
        } else {
            Pitch::Strike
        }
    }
}

You can now call Pitch::random() to get a random pitch. Here's what the changes look like in the loop:

        let pitch = Pitch::random();
        match pitch {
            Pitch::Ball => balls += 1,
            Pitch::Strike => strikes += 1,
        }
        println!("The pitch is a: {:?}", pitch);

Note how

  • We're matching on Pitch::... now, not 0s and 1s
  • There is no unreachable!() anymore, because the compiler sees we've matched on every possible type of Pitch
  • We can print out the pitch with {:?} -- that means, show the Debug representation

And the output is nicer too:

The pitch is a: Ball
The pitch is a: Ball
The pitch is a: Ball
The pitch is a: Strike
The pitch is a: Ball
End of game.

Final playground for now.


I hope that gives you some things to play with. I recommend reading The Book if you're not already.

3 Likes

TIL

Thank you.

Much appreciated on what you shared here. I will enjoy working with suggestions later this evening.