hello,

last time I made a fibonacci series generator in Rust and now I have made something different :)

use std::io;

fn main() {
    let mut input: String = String::new();
    let stdin = io::stdin();

    let x = rand::random::<u32>() % 101;
    let mut attempts = 0;

    loop {
        println!("Guess a number from 0 to 100:");
        stdin.read_line(&mut input);
        input = input.to_string().replace("\n", ""); // removing the \n
        let user_input: u32 = input.parse::<u32>().unwrap();
        if x == user_input {
            println!("You won! attempts: {attempts}");
            break;
        }
        else if x < user_input {
            println!("too big");
            attempts += 1;
        }
        else {
            println!("too small");
            attempts += 1;
        }
        input.clear()
    }
}

feel free to give me suggestion :)

  • SWW13@lemmy.brief.guru
    link
    fedilink
    arrow-up
    3
    ·
    2 days ago

    You shouldn’t use modulo to get a random number in a specific range (solution already in another comment). Reason is that numbers below 64 will be twice as likely as number 64-101 (in your example) due to how binary numbers and modulo works.

    This is obviously no real issue for this game, just keep in mind that you shouldn’t implement something like that (random ranges) yourself, this is especially true for crypto related things.

  • nous@programming.dev
    link
    fedilink
    English
    arrow-up
    21
    ·
    4 days ago

    The biggest/only real problem is the .unwrap(). This will cause the program to exit with an error message if the user inputs an invalid value. Which is not a great user experience. Better to reject the input and ask them again (aka start the loop again). You can do this with a match on the returned value of parse with a message and a continue on Err (good time to learn about enums and pattern matching as well).

    A minor improvement can be done to this:

            input = input.to_string().replace("\n", ""); // removing the \n
            let user_input: u32 = input.parse::<u32>().unwrap();
    

    By replacing it with:

            let user_input: u32 = input.trim().parse::<u32>().unwrap();
    

    Which has a few advantages:

    • it removes all white space, including tabs and spaces from both the start and end.
    • it requires no allocations.
    • it is one fewer lines while still being clear what it is doing.
    • no mutability needed (though input still needs to be mutable here in other cases this can be a benefit).

    These are more nitpicks than anything substantial - would lead to nicer code in more complex situations but negligible at best in this code base (only really pointing out to give you some things to think about):

    Since you break in the win condition the attempts += 1; can be moved out of the other branches at the same level as the input.clear().

    Rand has a gen_range function which can be used like:

    rand::thread_rng().gen_range::<u32>(0..=100);
    

    Though typically you would save the thread_rng() output to a local and reuse that throughout your program instead of creating one each time - here you only need one number so that matters less. The Rng trait has a lot more convenience function on it as well so it is generally worth using over just the random() function. Though in this case it makes very little difference overall here - typically you would want to opt for one of those methods instead so is worth knowing about.

    I dislike the name x as it is very ambiguous. IMO short letters should be used only in smaller scopes or when they represent some sort of mathematical value. Here I would use secret_number or secret_value or something more descriptive. Though in this case there is not a much better domain name here, more often there is.

  • FizzyOrange@programming.dev
    link
    fedilink
    arrow-up
    7
    ·
    4 days ago

    Some suggestions:

    1. Declare input in the loop. That limits scope which is basically always a good idea.
    2. Trim the newline like
    let input = input.trim();
    

    This is better in several ways:

    1. Simpler
    2. Doesn’t allocate a new string.
    3. Removes all whitespace at the start and end.
    4. It makes input immutable after that line.

    Also attempts is never read - you should have got a compiler warning about that.

    Otherwise, good work.

    • whoareu@lemmy.caOP
      link
      fedilink
      arrow-up
      3
      ·
      4 days ago

      Thank you for the suggestions.

      attempts is used to display total attempts when user wins the game so it is used in the program, I don’t see any warnings from compiler.

    • nous@programming.dev
      link
      fedilink
      English
      arrow-up
      2
      ·
      4 days ago

      Declare input in the loop. That limits scope which is basically always a good idea.

      There is good reason to not do this. Though in this case it won’t make much difference as performance is not an issue. But by having it outside the loop it allows the allocation of the string to be reused and in more complex programs is typically what you would want to do - allocate once outside the loop and reuse that inside.

      In this case waiting on user input is going to outweigh any performance benefit I would not call it out as bad practice either.

      • FizzyOrange@programming.dev
        link
        fedilink
        arrow-up
        2
        ·
        4 days ago

        Yes this is true, but it’s a little advanced for this level so I didn’t mention it. Probably better for beginners to focus on good code style than optimal performance.

    • whoareu@lemmy.caOP
      link
      fedilink
      arrow-up
      1
      ·
      4 days ago

      done :D

      use std::io;
      
      fn main() {
          let mut input: String = String::new();
          let stdin = io::stdin();
      
          let x = rand::random::<u32>() % 101;
          let mut attempts = 0;
      
          let mut user_inputs: Vec<u32> = Vec::new();
          loop {
              println!("Guess a number from 0 to 100:");
              stdin.read_line(&mut input);
              input = input.to_string().replace("\n", ""); // removing the \n
              let user_input: u32 = input.parse::<u32>().unwrap();
      	user_inputs.push(user_input);
              if x == user_input {
                  println!("You won! attempts: {attempts}");
      	    println!("Your inputs:");
      	    for input in user_inputs {
      		print!("{input} ");
      	    }
      	    println!("");
                  break;
              }
              else if x < user_input {
                  println!("too big");
                  attempts += 1;
              }
              else {
                  println!("too small");
                  attempts += 1;
              }
              input.clear()
          }
      }
      
      
        • whoareu@lemmy.caOP
          link
          fedilink
          arrow-up
          1
          ·
          4 days ago

          but to do that I have to use external deps? which I am not comfortable doing. (I am newbie :) )

          • pemptago@lemmy.ml
            link
            fedilink
            English
            arrow-up
            3
            ·
            3 days ago

            Nice work and congrats on your progress! Being new and uncomfortable with dependencies, this project might be a good opportunity to read and apply chapter 7 of the rust book if you haven’t already. Specifically 7.2 defining modules … and the sections that follow.

            As your projects increase complexity it’s really useful to shift chunks around (within main.rs and into other files/directories). Doing it now will make it easier to see the individual parts (vs waiting till a project has a lot going on). It might make dependencies feel less unfamiliar.

          • nous@programming.dev
            link
            fedilink
            English
            arrow-up
            4
            ·
            4 days ago

            Coloured text does not require a dep. It is just about printing the right colour codes like:

            const BLACK: &'static str = "\u{001b}[30m";
            const RED: &'static str = "\u{001b}[31m";
            const GREEN: &'static str = "\u{001b}[32m";
            const YELLOW: &'static str = "\u{001b}[33m";
            const BLUE: &'static str = "\u{001b}[34m";
            const MAGENTA: &'static str = "\u{001b}[35m";
            const CYAN: &'static str = "\u{001b}[36m";
            const WHITE: &'static str = "\u{001b}[37m";
            const CLEAR: &'static str = "\u{001b}[0m";
            
            fn main() {
                println!("{RED}red! {BLUE}blue!{CLEAR}");
            }
            

            The libraries just make it easier so you don’t need to remember or know what those codes mean.

            • lad@programming.dev
              link
              fedilink
              English
              arrow-up
              1
              ·
              3 days ago

              I thought, colour codes are platform dependent, will it work on windows

              I usually run things on Linux or macOS, but using a library (crate) may add portability, imo

              • nous@programming.dev
                link
                fedilink
                English
                arrow-up
                2
                ·
                3 days ago

                🤔 I think the vt100 protocols (where the escape code come from) predate windows and I think all modern terminals still use those as the base. So I think they are cross platform. From a quick search it looks like they are the same codes on windows. So I dont think the libraries are doing anything special for cross platform support.

                • lad@programming.dev
                  link
                  fedilink
                  English
                  arrow-up
                  1
                  ·
                  3 days ago

                  I see, so I was wrong then

                  Maybe I should try colour codes on windows when I get to it 😅 thanks for the info