Final Touches

This commit is contained in:
Landon Taylor 2025-04-18 13:18:48 -06:00
parent 7d98a638b4
commit 3ab839e543
9 changed files with 188 additions and 179 deletions

View File

@ -50,10 +50,9 @@ Contributions are welcome! Please follow the steps at https://gitmoss.fyi/GitMos
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
This project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details.
## Acknowledgments
## Notes
- Inspired by the need for critical thinking in AI education.
- Special thanks to the AI Applications in Education course team at Utah State University.
- Open-source contributors and the developer community.
- Special thanks to Seth Poulsen and his AI Applications in Education course at Utah State University.
- Portions of this work are produced by generative AI, but all content is manually reviewed and edited.

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::{files, learner::profile, printers::{self, clear_console, print_boxed, print_screen}, utilities::questions::{ask_mcq, ask_multi_select, ask_plaintext}, Module};
use crate::{files, learner::profile, printers::{self, clear_console, print_boxed, print_screen}, utilities::{generators::random_affirmation, questions::{ask_mcq, ask_multi_select, ask_plaintext}}, Module};
use super::units;
const REQUIRED_FOR_MASTERY: f64 = 0.1;
@ -97,7 +97,7 @@ pub fn inner_loop(unit: Module, learner: &mut profile::Learner) {
print_screen(&screen.text);
}
ScreenType::Mcq => {
let options: Vec<&str> = screen.options.as_ref().unwrap().iter().map(|s| s.as_str()).collect();
// let options: Vec<&str> = screen.options.as_ref().unwrap().iter().map(|s| s.as_str()).collect();
let mut options = screen.options.clone().unwrap();
options.push("Get hint".to_string());
let options: Vec<&str> = options.iter().map(|s| s.as_str()).collect();
@ -129,7 +129,7 @@ pub fn inner_loop(unit: Module, learner: &mut profile::Learner) {
}
}
ScreenType::Checkboxes => {
let options: Vec<&str> = screen.options.as_ref().unwrap().iter().map(|s| s.as_str()).collect();
// let options: Vec<&str> = screen.options.as_ref().unwrap().iter().map(|s| s.as_str()).collect();
let mut options = screen.options.clone().unwrap();
options.push("Get hint".to_string());
let options: Vec<&str> = options.iter().map(|s| s.as_str()).collect();
@ -139,7 +139,7 @@ pub fn inner_loop(unit: Module, learner: &mut profile::Learner) {
let selected_indices: Vec<usize> = answers.iter().filter_map(|answer| options.iter().position(|x| *x == *answer)).collect();
let correct = selected_indices.iter().all(|&index| correct_indices.contains(&index)) && selected_indices.len() == correct_indices.len();
if correct {
print_boxed("Correct!", printers::StatementType::CorrectFeedback);
print_boxed(&format!("Correct! {}", random_affirmation()), printers::StatementType::CorrectFeedback);
learner.update_progress(unit.clone(), true);
learner.progress.get_mut(&unit).unwrap().print_progress();
break;

View File

@ -90,12 +90,12 @@ impl Tracker {
self.milestones_completed as f64 / self.milestones as f64
}
pub fn get_milestones(&self) -> usize {
self.milestones
}
pub fn get_milestones_completed(&self) -> usize {
self.milestones_completed
}
// pub fn get_milestones(&self) -> usize {
// self.milestones
// }
// pub fn get_milestones_completed(&self) -> usize {
// self.milestones_completed
// }
pub(crate) fn print_progress(&self) {
let (cols, _) = size().unwrap();

View File

@ -1,8 +1,6 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use rand::{rng, seq::IndexedRandom};
use std::fs;
use std::path::Path;
// use rand::{rng, seq::IndexedRandom};
// use strum_macros::EnumIter;
/// Module defines the structure and data for the course units, including lessons and modules.
@ -40,16 +38,16 @@ impl Module {
Module::Appraisal(appraisal) => format!("data/appraisal/{}.json", appraisal.get_filename()),
}
}
pub(crate) fn random() -> Self {
let mut rng = rng();
let variants = [
Module::Logic(Logic::random()),
Module::Fallacy(Fallacy::random()),
Module::Bias(Bias::random()),
Module::Appraisal(Appraisal::random()),
];
variants.choose(&mut rng).unwrap().clone()
}
// pub(crate) fn random() -> Self {
// let mut rng = rng();
// let variants = [
// Module::Logic(Logic::random()),
// Module::Fallacy(Fallacy::random()),
// Module::Bias(Bias::random()),
// Module::Appraisal(Appraisal::random()),
// ];
// variants.choose(&mut rng).unwrap().clone()
// }
pub fn iter() -> impl Iterator<Item = Self> {
vec![
Module::Introduction,
@ -125,11 +123,11 @@ impl Logic {
Logic::BooleanAlgebra => "boolean_algebra".to_string(),
}
}
pub(crate) fn random() -> Self {
let mut rng = rng();
let variants = [Logic::LogicalOperations, Logic::BooleanAlgebra];
variants.choose(&mut rng).unwrap().clone()
}
// pub(crate) fn random() -> Self {
// let mut rng = rng();
// let variants = [Logic::LogicalOperations, Logic::BooleanAlgebra];
// variants.choose(&mut rng).unwrap().clone()
// }
}
/// Fallacy module, which includes various lessons related to logical fallacies and errors in reasoning.
@ -158,14 +156,14 @@ impl Fallacy {
Fallacy::InformalFallacy(informal) => format!("informal/{}", informal.get_filename()),
}
}
pub(crate) fn random() -> Self {
let mut rng = rng();
let variants = [
Fallacy::FormalFallacy(FormalFallacy::random()),
Fallacy::InformalFallacy(InformalFallacy::random()),
];
variants.choose(&mut rng).unwrap().clone()
}
// pub(crate) fn random() -> Self {
// let mut rng = rng();
// let variants = [
// Fallacy::FormalFallacy(FormalFallacy::random()),
// Fallacy::InformalFallacy(InformalFallacy::random()),
// ];
// variants.choose(&mut rng).unwrap().clone()
// }
}
/// Formal fallacies, which are errors in the structure of an argument.
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
@ -199,16 +197,16 @@ impl FormalFallacy {
FormalFallacy::QuantificationalFallacy => "quantificational".to_string(),
}
}
pub(crate) fn random() -> Self {
let mut rng = rng();
let variants = [
FormalFallacy::PropositionalFallacy,
FormalFallacy::ProbabilisticFallacy,
FormalFallacy::SyllogisticFallacy,
FormalFallacy::QuantificationalFallacy,
];
variants.choose(&mut rng).unwrap().clone()
}
// pub(crate) fn random() -> Self {
// let mut rng = rng();
// let variants = [
// FormalFallacy::PropositionalFallacy,
// FormalFallacy::ProbabilisticFallacy,
// FormalFallacy::SyllogisticFallacy,
// FormalFallacy::QuantificationalFallacy,
// ];
// variants.choose(&mut rng).unwrap().clone()
// }
}
/// Informal fallacies, which are errors in reasoning that do not involve the structure of the argument.
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
@ -272,26 +270,26 @@ impl InformalFallacy {
InformalFallacy::BeggingTheQuestion => "begging_the_question".to_string(),
}
}
pub(crate) fn random() -> Self {
let mut rng = rng();
let variants = [
InformalFallacy::PostHocErgoPropterHoc,
InformalFallacy::SlipperySlope,
InformalFallacy::TexasSharpshooter,
InformalFallacy::HastyGeneralization,
InformalFallacy::OverGeneralization,
InformalFallacy::NoTrueScotsman,
InformalFallacy::QuotingOutOfContext,
InformalFallacy::AdHominem,
InformalFallacy::TuQuoque,
InformalFallacy::Bandwagon,
InformalFallacy::StrawMan,
InformalFallacy::AdIgnorantiam,
InformalFallacy::SpecialPleading,
InformalFallacy::BeggingTheQuestion,
];
variants.choose(&mut rng).unwrap().clone()
}
// pub(crate) fn random() -> Self {
// let mut rng = rng();
// let variants = [
// InformalFallacy::PostHocErgoPropterHoc,
// InformalFallacy::SlipperySlope,
// InformalFallacy::TexasSharpshooter,
// InformalFallacy::HastyGeneralization,
// InformalFallacy::OverGeneralization,
// InformalFallacy::NoTrueScotsman,
// InformalFallacy::QuotingOutOfContext,
// InformalFallacy::AdHominem,
// InformalFallacy::TuQuoque,
// InformalFallacy::Bandwagon,
// InformalFallacy::StrawMan,
// InformalFallacy::AdIgnorantiam,
// InformalFallacy::SpecialPleading,
// InformalFallacy::BeggingTheQuestion,
// ];
// variants.choose(&mut rng).unwrap().clone()
// }
}
/// Bias module, which includes various lessons related to cognitive biases and their impact on reasoning.
@ -332,18 +330,18 @@ impl Bias {
Bias::BarnumEffect => "barnum_effect".to_string(),
}
}
pub(crate) fn random() -> Self {
let mut rng = rng();
let variants = [
Bias::ConfirmationBias,
Bias::TheHaloEffect,
Bias::FundamentalAttributionError,
Bias::InGroupBias,
Bias::DunningKrugerEffect,
Bias::BarnumEffect,
];
variants.choose(&mut rng).unwrap().clone()
}
// pub(crate) fn random() -> Self {
// let mut rng = rng();
// let variants = [
// Bias::ConfirmationBias,
// Bias::TheHaloEffect,
// Bias::FundamentalAttributionError,
// Bias::InGroupBias,
// Bias::DunningKrugerEffect,
// Bias::BarnumEffect,
// ];
// variants.choose(&mut rng).unwrap().clone()
// }
}
/// Appraisal module, which includes various lessons related to the evaluation and appraisal of arguments.
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
@ -371,12 +369,12 @@ impl Appraisal {
Appraisal::CounterArgument => "counter_argument".to_string(),
}
}
pub(crate) fn random() -> Self {
let mut rng = rng();
let variants = [
Appraisal::ConversionToPropositional,
Appraisal::CounterArgument,
];
variants.choose(&mut rng).unwrap().clone()
}
// pub(crate) fn random() -> Self {
// let mut rng = rng();
// let variants = [
// Appraisal::ConversionToPropositional,
// Appraisal::CounterArgument,
// ];
// variants.choose(&mut rng).unwrap().clone()
// }
}

View File

@ -60,16 +60,16 @@ impl Learner {
clear_console();
let (cols, _) = size().unwrap();
let dashes = "-".repeat((cols as usize));
let dashes = "-".repeat(cols as usize);
let dots = " ".repeat((cols as usize).saturating_sub(42));
println!("Progress Report for {}:", self.name);
println!("{}", dashes);
println!("{}", format!(" Unit {} | Mastery |", dots).bold());
let mut modules = units::Module::iter();
let modules = units::Module::iter();
for module in modules {
if let Some(progress) = self.progress.get(&module) {
if let Some(_) = self.progress.get(&module) {
self.progress.get(&module).unwrap().print_progress();
}
}

View File

@ -2,7 +2,7 @@ use std::{fs::File, io::Read};
use serde::de::DeserializeOwned;
use crate::{course::story, learner::profile::{self, Learner}};
use crate::{course::story, learner::profile::Learner};
// pub fn save_learner_progress(learner: &Learner, file: &str) -> Result<(), std::io::Error> {
// learner.clone().save_to_file(file)?;

View File

@ -69,7 +69,7 @@ const OFFENSIVE_TERMS: [&str; 20] = [
"jew", "slut", "tit", "phag"
];
const affirmations: [&str; 8] = [
const AFFIRMATIONS: [&str; 26] = [
"You're doing great!",
"Keep up the good work!",
"Fantastic effort!",
@ -78,12 +78,30 @@ const affirmations: [&str; 8] = [
"You're making progress!",
"Keep it up!",
"Great work!",
"You're unstoppable!",
"Amazing progress!",
"You're crushing it!",
"Keep shining!",
"You're a star!",
"Outstanding work!",
"You're making a difference!",
"You're an inspiration!",
"You're reaching for the stars!",
"Keep shining like a supernova!",
"You're out of this world!",
"You're orbiting success!",
"You're a cosmic force!",
"You're a shooting star!",
"You're exploring new galaxies!",
"You're a stellar achiever!",
"You're blazing a trail through the cosmos!",
"You're a beacon in the universe!",
];
pub fn random_affirmation() -> String {
let mut rng = rand::rng();
let index = rng.random_range(0..affirmations.len());
affirmations[index].to_string()
let index = rng.random_range(0..AFFIRMATIONS.len());
AFFIRMATIONS[index].to_string()
}
pub fn generate_name() -> String {
@ -109,15 +127,15 @@ pub fn generate_name() -> String {
}
pub fn random_unit_intro() -> String {
let mut rng = rand::rng();
let unit_intros: [&str; 5] = [
"It's time to learn about",
"Let's dive into",
"Get ready to explore",
"Prepare to discover",
"Let's embark on a journey to learn about",
];
let index = rng.random_range(0..unit_intros.len());
unit_intros[index].to_string()
}
// pub fn random_unit_intro() -> String {
// let mut rng = rand::rng();
// let unit_intros: [&str; 5] = [
// "It's time to learn about",
// "Let's dive into",
// "Get ready to explore",
// "Prepare to discover",
// "Let's embark on a journey to learn about",
// ];
// let index = rng.random_range(0..unit_intros.len());
// unit_intros[index].to_string()
// }

View File

@ -1,14 +1,12 @@
use crossterm::{execute, style::Stylize, terminal::{size, Clear, ClearType}};
use std::io::stdout;
use super::generators;
const DELAY_SECONDS: u64 = 1;
// const DELAY_SECONDS: u64 = 1;
#[derive(PartialEq)]
pub enum StatementType {
Default,
DelayExplanation,
// DelayExplanation,
Question,
GeneralFeedback,
CorrectFeedback,
@ -160,10 +158,6 @@ pub fn print_boxed(instruction: &str, statement_type: StatementType) {
StatementType::Question => {
return;
}
StatementType::DelayExplanation => {
wait_for_input_delay(DELAY_SECONDS);
print_boxed(&instruction, StatementType::Default);
}
_ => {
wait_for_input();
}
@ -171,29 +165,29 @@ pub fn print_boxed(instruction: &str, statement_type: StatementType) {
}
/// Prints a full-width ruler of designated characters
pub fn print_rule(character: char, padding: (bool, bool)) {
let (cols, _) = size().unwrap();
// pub fn print_rule(character: char, padding: (bool, bool)) {
// let (cols, _) = size().unwrap();
println!(
"{}{}{}",
if padding.0 { "\n" } else { "" },
character.to_string().repeat(cols as usize),
if padding.1 { "\n" } else { "" }
);
}
// println!(
// "{}{}{}",
// if padding.0 { "\n" } else { "" },
// character.to_string().repeat(cols as usize),
// if padding.1 { "\n" } else { "" }
// );
// }
/// Horizontally center the desired text on the screen
pub fn print_centered(text: &str) {
let (cols, _) = size().unwrap();
let text_width = text.len() as u16;
let start_col = (cols.saturating_sub(text_width)) / 2;
// pub fn print_centered(text: &str) {
// let (cols, _) = size().unwrap();
// let text_width = text.len() as u16;
// let start_col = (cols.saturating_sub(text_width)) / 2;
println!(
"\x1B[{}C{}",
start_col,
text
);
}
// println!(
// "\x1B[{}C{}",
// start_col,
// text
// );
// }
/// Wait for input from the user
pub fn wait_for_input() {
@ -213,51 +207,51 @@ pub fn wait_for_input() {
}
/// Wait for input from the user
pub fn print_title(title: &str) {
let (cols, _) = size().unwrap();
let text_width = title.len() as u16;
let start_col = (cols.saturating_sub(text_width)) / 2 + 1;
// pub fn print_title(title: &str) {
// let (cols, _) = size().unwrap();
// let text_width = title.len() as u16;
// let start_col = (cols.saturating_sub(text_width)) / 2 + 1;
println!(
"\x1B[{}C{}",
start_col,
title.bold()
);
// println!(
// "\x1B[{}C{}",
// start_col,
// title.bold()
// );
wait_for_input();
}
// wait_for_input();
// }
/// Wait for input from the user
pub fn wait_for_input_delay(seconds: u64) {
let prompt = format!("Remember to be mindful. Advance in {} seconds.", seconds);
let (cols, _) = size().unwrap();
let text_width = prompt.len() as u16;
let start_col = (cols.saturating_sub(text_width)) / 2 + 1;
// pub fn wait_for_input_delay(seconds: u64) {
// let prompt = format!("Remember to be mindful. Advance in {} seconds.", seconds);
// let (cols, _) = size().unwrap();
// let text_width = prompt.len() as u16;
// let start_col = (cols.saturating_sub(text_width)) / 2 + 1;
println!(
"\x1B[{}C{}",
start_col,
prompt.italic().dark_blue()
);
// println!(
// "\x1B[{}C{}",
// start_col,
// prompt.italic().dark_blue()
// );
std::thread::sleep(std::time::Duration::from_secs(DELAY_SECONDS));
// std::thread::sleep(std::time::Duration::from_secs(DELAY_SECONDS));
}
// }
/// Wait for input from the user
pub fn inform_delay(seconds: u64) {
let prompt = format!("Remember to be mindful. Answers appear in {} seconds.", seconds);
let (cols, _) = size().unwrap();
let text_width = prompt.len() as u16;
let start_col = (cols.saturating_sub(text_width)) / 2 + 1;
// pub fn inform_delay(seconds: u64) {
// let prompt = format!("Remember to be mindful. Answers appear in {} seconds.", seconds);
// let (cols, _) = size().unwrap();
// let text_width = prompt.len() as u16;
// let start_col = (cols.saturating_sub(text_width)) / 2 + 1;
println!(
"\x1B[{}C{}",
start_col,
prompt.italic().dark_blue()
);
// println!(
// "\x1B[{}C{}",
// start_col,
// prompt.italic().dark_blue()
// );
}
// }
pub fn unit_screen(title: &str) {
clear_console();

View File

@ -1,16 +1,16 @@
use inquire::{MultiSelect, Select, Text};
use crossterm::terminal;
use super::printers::{clear_console, inform_delay, print_boxed, StatementType};
use super::printers::{clear_console, print_boxed, StatementType};
const DELAY_SECONDS: u64 = 1;
// const DELAY_SECONDS: u64 = 1;
pub fn ask_mcq_delayed(question: &str, options: &[&str]) -> Option<String> {
print_boxed(question, StatementType::Question);
inform_delay(DELAY_SECONDS);
std::thread::sleep(std::time::Duration::from_secs(DELAY_SECONDS));
ask_mcq(question, options)
}
// pub fn ask_mcq_delayed(question: &str, options: &[&str]) -> Option<String> {
// print_boxed(question, StatementType::Question);
// inform_delay(DELAY_SECONDS);
// std::thread::sleep(std::time::Duration::from_secs(DELAY_SECONDS));
// ask_mcq(question, options)
// }
/// Print a quiz question in an attractive format and get the user's choice
pub fn ask_mcq(question: &str, options: &[&str]) -> Option<String> {