use std::{
io::stdout,
ops::ControlFlow,
sync::{
mpsc::{self, Receiver},
Mutex,
},
thread,
time::Duration,
};
use crossterm::{
cursor::{MoveTo, Show},
execute,
style::Stylize,
terminal::{disable_raw_mode, Clear, ClearType, LeaveAlternateScreen},
};
use rand::{seq::SliceRandom, thread_rng};
use crate::{
providers::{gists::GistProvider, repos::RepositoryProvider, GithubProvider},
terminal::Terminal,
Config, Result, ARGS, CONFIG,
};
pub const PROMPT: &str = "Which programming language is this? (Type the corresponding number)";
pub const LANGUAGES: [&str; 25] = [
"Assembly",
"Shell",
"C",
"C#",
"C++",
"CSS",
"Dart",
"Dockerfile",
"Go",
"Groovy",
"HTML",
"Java",
"JavaScript",
"Kotlin",
"Lua",
"MATLAB",
"PHP",
"PowerShell",
"Python",
"R",
"Ruby",
"Rust",
"SQL",
"Swift",
"TypeScript",
];
pub struct Game {
pub points: u32,
pub terminal: Terminal,
pub provider: Box<dyn GithubProvider>,
}
impl Drop for Game {
fn drop(&mut self) {
let _raw = disable_raw_mode();
let _leave = execute!(self.terminal.stdout, Show, LeaveAlternateScreen);
println!(
"\nYou scored {} points!",
self.points.to_string().green().bold()
);
if self.points > CONFIG.high_score {
if CONFIG.high_score > 0 {
println!(
"You beat your high score of {}!\n\nShare it: {}",
CONFIG.high_score.to_string().magenta().bold(),
"https://github.com/Lioness100/guess-that-lang/discussions/6"
.cyan()
.bold()
);
}
let new_config = Config {
high_score: self.points,
..CONFIG.clone()
};
let _config = confy::store("guess-that-lang", new_config);
}
}
}
impl Game {
pub fn new() -> Result<Self> {
let provider: Box<dyn GithubProvider> = match ARGS
.provider
.as_ref()
.unwrap_or(&String::from("repos"))
.as_str()
{
"gists" => Box::new(GistProvider::new()?),
"repos" => Box::new(RepositoryProvider::new()?),
_ => return Err("Invalid github provider (repos/gists)".into()),
};
Ok(Self {
points: 0,
terminal: Terminal::new()?,
provider,
})
}
#[must_use]
pub fn get_options(correct_language: &str) -> Vec<&str> {
let mut options = Vec::<&str>::with_capacity(4);
options.push(correct_language);
let mut thread_rng = thread_rng();
while options.len() < 4 {
let random_language = LANGUAGES.choose(&mut thread_rng).unwrap();
if !options.contains(random_language) {
options.push(random_language);
}
}
options.shuffle(&mut thread_rng);
options
}
pub fn start_new_round(&mut self, preloader: Option<Receiver<()>>) -> Result<ControlFlow<()>> {
let data = self.provider.get_code()?;
let width = Terminal::width()?;
let highlighter = self.terminal.get_highlighter(&data.language);
let code = match self.terminal.parse_code(&data.code, highlighter, &width) {
Some(code) => code,
None => return self.start_new_round(preloader),
};
let options = Self::get_options(&data.language);
if let Some(preloader) = preloader {
let _ = preloader.recv();
}
self.terminal
.print_round_info(&options, &code, &width, self.points)?;
let available_points = Mutex::new(100.0);
let (sender, receiver) = mpsc::channel();
thread::scope(|s| {
let display = s.spawn(|| {
self.terminal
.start_showing_code(&code, &available_points, receiver)
});
let input = s.spawn(|| {
let input = Terminal::read_input_char()?;
let sender = sender;
let _ = sender.send(());
if input == 'q' || input == 'c' {
Ok(ControlFlow::Break(()))
} else {
let result = self.terminal.process_input(
input.to_digit(10).ok_or("invalid input")?,
&options,
&data.language,
&available_points,
&mut self.points,
);
if let Ok(ControlFlow::Break(())) = result {
thread::sleep(Duration::from_millis(1500));
}
result
}
});
display.join().unwrap()?;
input.join().unwrap()
})
}
pub fn start_next_round(&mut self) -> Result<ControlFlow<()>> {
let (sender, receiver) = mpsc::channel();
thread::scope(|s| {
let handle = s.spawn(|| self.start_new_round(Some(receiver)));
thread::sleep(Duration::from_millis(1500));
let _clear = execute!(stdout().lock(), Clear(ClearType::All), MoveTo(0, 0));
let _ = sender.send(());
handle.join().unwrap()
})
}
}