Rust - Cryptoquote Solver

Kent West - kent.west@{that mail that swore to do no evil}

Cargo.toml
$ cargo add getchar
src/12Dec2023.txt
OYPAXSUZX AX UBXS SPTRG
OYPAXSUZX JYVF JV
OVRVNPZSV AS NG CADAFC
SYV RACYS BQ RBDV SB
SYBXV JYB FVVL AS UBXS.
--
PTSY OZPSVP XSZERVSBF
src/main.rs
use getchar::getchar;

use std::env::args;
use std::io::{stdout, Write};
use std::process::exit;

const SET_FG_RED: &str = "\x1b[31m";
const SET_FG_YELLOW: &str = "\x1b[33m";
const SET_FG_RESET: &str = "\x1b[0m";

fn main() {
    // Get input file from command-line, and possibly print help/usage info.
    let args: Vec<String> = args().collect();
    if args.len() < 2 {
        println!("Usage:\n\t{} <filename>", args[0]);
        println!("or");
        println!("\t<path>/cifer <filename>");
        println!("or");
        println!("\tcargo run -- <filename>");
        println!("\nAt any rate, we've gotta have a data file. It needs to be a");
        println!("plain-text file containing the cryptoquote that needs to be");
        println!("deciphered.  Enter \"+\" to quit program.");
        exit(1);
    }
    let filename = &args[1];

    // Read data from file into a String.
    let errmsg = format!(
        "\n\n{SET_FG_RED}ERROR reading \"{SET_FG_YELLOW}{}{SET_FG_RED}\".{SET_FG_RESET}\n\n",
        filename
    );
    let cryptoquote: String = std::fs::read_to_string(filename).expect(&errmsg);

    // Strip newlines from cryptoquote.
    let cryptoquote = cryptoquote.replace("\n", " ");

    // Convert it to a vector of chars.
    let cryptoquote: Vec<char> = cryptoquote.chars().collect();

    // Create a twin vector to hold the decrypted version (initially dashes).
    let mut decrypted_quote: Vec<char> = vec![];
    for ndx in 0..cryptoquote.len() {
      if cryptoquote[ndx] == ' ' || cryptoquote[ndx] == '.' {
            decrypted_quote.push(' ');
        } else {
            decrypted_quote.push('-');
        }
    }

    // And a vector for the match attempts.
    let mut alpha_matches: Vec<char> = vec![];
    for _alpha in 0..26 {
        alpha_matches.push('-');
    }

    let crypt_line = 3;
    let decrypt_line = crypt_line + 1;
    let change_line = crypt_line + 4;
    let alpha_line = crypt_line + 7;
    let match_line = crypt_line + 8;

    // Clear screen; print intro.
    clear_screen();
    goto(1, 1);
    println!("Decrypt this cryptoquote. (\"+\" to quit.)");

    // Print cryptoquote.
    goto(crypt_line, 1);
    for ndx in 0..cryptoquote.len() {
        print!("{}", cryptoquote[ndx]);
    }

    // Print alphabet, and matches.
    goto(alpha_line, 1);
    println!("ABCDEFGHIJKLMNOPQRSTUVWXYZ");

    loop {
        // Print the alpha-matches.
        goto(match_line, 1);
        for alpha in 0..26 {
            print!("{}", alpha_matches[alpha]);
        }

        // Print the decrypted line (as it progresses).
        goto(decrypt_line, 1);
        for ndx in 0..decrypted_quote.len() {
            print!("{}", decrypted_quote[ndx]);
        }

        // Get letter to change.
        goto(change_line, 1);
        println!("Enter letter to change:  ");
        goto(change_line, 25);
        let crypted_key = getchar().unwrap().to_ascii_uppercase();
        if crypted_key == '+' {
            break;
        }
        // Highlight that letter in the alpha-match line.
        let offset = crypted_key as usize - 'A' as usize;
        set_fg_color("yellow");
        goto(alpha_line, offset + 1); // Account for 1-indexed coords.
        println!("{}", crypted_key);

        // Get the new letter.
        goto(change_line, 1);
        println!("         Change it to: ");
        goto(change_line, 25);
        print!("{}", crypted_key);
        goto(change_line, 25);
        let decrypted_key = getchar().unwrap().to_ascii_uppercase();

        // Add this to the match line, turning off the highlight from earlier.
        set_fg_color("reset");
        goto(alpha_line, offset + 1); // Adjust for grid starting at 1,1 instead of 0,0.
        println!("{}", crypted_key);
        if decrypted_key == '+' {
            break;
        }
        alpha_matches[offset] = decrypted_key;
        for ndx in 0..cryptoquote.len() {
            if cryptoquote[ndx] == crypted_key {
                decrypted_quote[ndx] = decrypted_key;
            }
        }
    }

    println!("\n\nBye!\n\n");
} // end of main()

fn clear_screen() {
    println!("\x1b[2J");
}

fn goto(row: usize, col: usize) {
    print!("\x1b[{row};{col}H");
    stdout().flush().unwrap();
}

fn set_fg_color(color: &str) {
    match color.to_uppercase().as_str() {
        "RED" => println!("{SET_FG_RED}"),
        "YELLOW" => println!("{SET_FG_YELLOW}"),
        "RESET" | _ => println!("{SET_FG_RESET}"),
    };
}