Home > database >  Is there a better way to infinitely error check other than recursion?
Is there a better way to infinitely error check other than recursion?

Time:01-01

I'm trying to make a Tic-Tac-Toe game with a custom board size. I want this to be very hard to break, so I use recursion to get the board measurements if the input is invalid or an error occurs. However, this doesn't seem very clean to me, and I was wondering if there's a better/more rusty way of achieving the same thing.

Code in main function

let board_size_str = get_board_size_string();
let (x_pos, width, height) = get_board_measurement(&board_size_str);

Functions

fn get_board_size_string() -> String {
    println!("Select the size of the board in the following format: 5x5 or 7x7");
    println!("The size can be from 3x3 to 30x30");
    print!("Size: ");
    std::io::stdout().flush().expect("Failed to flush stdout!");
    let mut board_size_str = String::new();
    std::io::stdin().read_line(&mut board_size_str).expect("Failed to read board size!");
    println!();

    board_size_str
}

fn get_board_measurement(board_size_str: &str) -> (usize, i64, i64) {
    let x_pos = get_x_pos(board_size_str);
    let width = get_board_width(board_size_str, x_pos);
    let height = get_board_height(board_size_str, x_pos);

    (x_pos, width, height)
}

fn get_x_pos(board_size_str: &str) -> usize {
    let x_pos_option = board_size_str.chars().position(|c| c == 'x');

    match x_pos_option {
        Some(x_pos) => x_pos,
        None => {
            println!("Board size must contain an x!");
            let board_size_str = get_board_size_string();
            get_x_pos(&board_size_str)
        }
    }
}

fn get_board_width(board_size_str: &str, x_pos: usize) -> i64 {
    let width_result = board_size_str[..x_pos].parse::<i64>();
    
    match width_result {
        Ok(width) => width,
        Err(_) => {
            println!("Invalid board width!");
            let board_size_str = get_board_size_string();
            get_board_width(&board_size_str, get_x_pos(&board_size_str))
        }
    }
}

fn get_board_height(board_size_str: &str, x_pos: usize) -> i64 {
    let height_result = board_size_str[x_pos   1..].trim().parse::<i64>();

    match height_result {
        Ok(height) => height,
        Err(_) => {
            println!("Invalid board height!");
            let board_size_str = get_board_size_string();
            get_board_height(&board_size_str, get_x_pos(&board_size_str))
        }
    }
}

CodePudding user response:

Just use an iterative loop?

fn get_x_pos(board_size_str: &str) -> usize {
    loop {
        let board_size_str = get_board_size_string();
        let x_pos_option = board_size_str.chars().position(|c| c == 'x');
        if let Some(x_pos) = x_pos_option {
            break x_pos
        }
    }
}

Though the structure is strange because a correct board size is a correct pattern ( 'x' ) so it's not like splitting that into three unrelated routines makes any sense, even if two of them do delegate the localisation of the x separator.

With your method you can input something like 52xkf, get an error, input 24x36, and I think you'll get a 52x36 board rather than the 24x36 you might expect, which is just odd. Would be a lot easier to just do the entire thing in a single pseudo-step:

fn parse_board_size() -> (usize, usize) {
    loop {
        let s = get_board_size_string();
        let Some((w_s, h_s)) = s.split_once('x') else { 
            // complain about a missing `x` here
            continue;
        };
        match (w_s.parse(), h_s.parse()) {
            (Ok(w), Ok(s)) => {
                // can add more validation here,
                // or as pattern guards
                return (w, s);
            }
            (Ok(_), Err(h_error)) => {
                // h was incorrect
            }
            (Err(w_error), Ok(_)) => {
                // w was incorrect
            }
            (Err(w_error), Err(h_error)) => {
                // both were incorrect
            }
        }
    }
}

Alternatively for the parsing if you don't care about custom-reporting each error case individually you can lean on Option e.g.

fn parse_board_size() -> (usize, usize) {
    loop {
        let s = get_board_size_string();
        let Some((w_s, h_s)) = s.split_once('x') else { 
            // complain about a missing `x` here
            continue;
        };
        if let Some(r) = w_s.parse().ok().zip(h_s.parse().ok()) {
            break r;
        }
        // report generic parsing error
    }
}
  • Related