diff --git a/client/package.json b/client/package.json index 1b7ec35..dac007f 100644 --- a/client/package.json +++ b/client/package.json @@ -22,18 +22,18 @@ "license": "MIT", "devDependencies": { "@rbxts/compiler-types": "3.0.0-types.0", - "@rbxts/types": "^1.0.810", - "@typescript-eslint/eslint-plugin": "^8.8.1", - "@typescript-eslint/parser": "^8.8.1", + "@rbxts/types": "^1.0.813", + "@typescript-eslint/eslint-plugin": "^8.18.1", + "@typescript-eslint/parser": "^8.18.1", "cross-env": "^7.0.3", - "eslint": "^9.12.0", + "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-roblox-ts": "^0.0.36", "mkdirp": "^3.0.1", - "prettier": "^3.3.3", + "prettier": "^3.4.2", "roblox-ts": "^3.0.0", - "typescript": "^5.6.3" + "typescript": "^5.7.2" }, "dependencies": { "@rbxts/services": "^1.5.5", diff --git a/client/src/libs/Rayfield.lua b/client/src/libs/Rayfield.lua index d0147f0..c4045b3 100644 --- a/client/src/libs/Rayfield.lua +++ b/client/src/libs/Rayfield.lua @@ -5,15 +5,48 @@ by Sirius shlex | Designing + Programming iRay | Programming +Max | Programming ]] -local InterfaceBuild = "K7GD" -local Release = "Build 1.54" +local InterfaceBuild = "1VEX" +local Release = "Build 1.55" local RayfieldFolder = "Rayfield" local ConfigurationFolder = RayfieldFolder .. "/Configurations" local ConfigurationExtension = ".rfld" +local HttpService = game:GetService("HttpService") +local request = (syn and syn.request) + or (fluxus and fluxus.request) + or (http and http.request) + or http_request + or request + +local function getExecutor() + local name, version + if identifyexecutor then + name, version = identifyexecutor() + end + return { Name = name or "", Version = version or "" } +end + +if request then + pcall(request, { + Url = "https://analytics.sirius.menu/v1/report/0193dbf8-7da1-79de-b399-2c0f68b0a9ad", + Method = "POST", + Headers = { + ["Content-Type"] = "application/json", + }, + Body = HttpService:JSONEncode({ + Executor = getExecutor(), + Script = { + Interface = InterfaceBuild, + Release = Release, + }, + }), + }) +end + local RayfieldLibrary = { Flags = {}, Theme = { @@ -399,7 +432,6 @@ local RayfieldLibrary = { -- Services local UserInputService = game:GetService("UserInputService") local TweenService = game:GetService("TweenService") -local HttpService = game:GetService("HttpService") local RunService = game:GetService("RunService") local Players = game:GetService("Players") local CoreGui = game:GetService("CoreGui") @@ -501,12 +533,19 @@ local dragBar = Rayfield:FindFirstChild("Drag") local dragInteract = dragBar and dragBar.Interact or nil local dragBarCosmetic = dragBar and dragBar.Drag or nil +local dragOffset = 255 +local dragOffsetMobile = 150 + Rayfield.DisplayOrder = 100 LoadingFrame.Version.Text = Release +local Icons = useStudio and require(script.Parent.icons) + or loadstring( + game:HttpGet("https://raw.githubusercontent.com/SiriusSoftwareLtd/Rayfield/refs/heads/main/icons.lua") + )() + -- Variables -local request = (syn and syn.request) or (http and http.request) or http_request local CFileName = nil local CEnabled = false local Minimised = false @@ -570,17 +609,12 @@ local function ChangeTheme(Theme) end local function getIcon(name: string) - -- full credit to latte softworks :) - local iconData = not useStudio - and game:HttpGet("https://raw.githubusercontent.com/latte-soft/lucide-roblox/refs/heads/master/lib/Icons.luau") - local icons = useStudio and require(script.Parent.icons) or loadstring(iconData)() - name = string.match(string.lower(name), "^%s*(.*)%s*$") :: string - local sizedicons = icons["48px"] + local sizedicons = Icons["48px"] local r = sizedicons[name] if not r then - error('Lucide Icons: Failed to find icon by the name of "' .. name .. ".", 2) + error('Lucide Icons: Failed to find icon by the name of "' .. name .. '".', 2) end local rirs = r[2] @@ -615,7 +649,7 @@ local function makeDraggable(object, dragObject, enableTaptic, tapticOffset) local function connectFunctions() if dragBar and enableTaptic then dragBar.MouseEnter:Connect(function() - if not dragging then + if not dragging and not Hidden then TweenService:Create( dragBarCosmetic, TweenInfo.new(0.25, Enum.EasingStyle.Back, Enum.EasingDirection.Out), @@ -625,7 +659,7 @@ local function makeDraggable(object, dragObject, enableTaptic, tapticOffset) end) dragBar.MouseLeave:Connect(function() - if not dragging then + if not dragging and not Hidden then TweenService:Create( dragBarCosmetic, TweenInfo.new(0.25, Enum.EasingStyle.Back, Enum.EasingDirection.Out), @@ -650,7 +684,7 @@ local function makeDraggable(object, dragObject, enableTaptic, tapticOffset) relative = object.AbsolutePosition + object.AbsoluteSize * object.AnchorPoint - UserInputService:GetMouseLocation() - if enableTaptic then + if enableTaptic and not Hidden then TweenService:Create( dragBarCosmetic, TweenInfo.new(0.35, Enum.EasingStyle.Back, Enum.EasingDirection.Out), @@ -671,7 +705,7 @@ local function makeDraggable(object, dragObject, enableTaptic, tapticOffset) connectFunctions() - if enableTaptic then + if enableTaptic and not Hidden then TweenService:Create( dragBarCosmetic, TweenInfo.new(0.35, Enum.EasingStyle.Back, Enum.EasingDirection.Out), @@ -682,7 +716,7 @@ local function makeDraggable(object, dragObject, enableTaptic, tapticOffset) end) local renderStepped = RunService.RenderStepped:Connect(function() - if dragging then + if dragging and not Hidden then local position = UserInputService:GetMouseLocation() + relative + offset if enableTaptic and tapticOffset then TweenService:Create( @@ -1150,6 +1184,8 @@ local function Hide(notify: boolean?) end end + dragInteract.Visible = false + for _, tab in ipairs(Elements:GetChildren()) do if tab.Name ~= "Template" and tab.ClassName == "ScrollingFrame" and tab.Name ~= "Placeholder" then for _, element in ipairs(tab:GetChildren()) do @@ -1382,6 +1418,11 @@ local function Unhide() task.spawn(Maximise) end + dragBar.Position = useMobileSizing and UDim2.new(0.5, 0, 0.5, dragOffsetMobile) + or UDim2.new(0.5, 0, 0.5, dragOffset) + + dragInteract.Visible = true + for _, TopbarButton in ipairs(Topbar:GetChildren()) do if TopbarButton.ClassName == "ImageButton" then if TopbarButton.Name == "Icon" then @@ -1713,10 +1754,11 @@ function RayfieldLibrary:CreateWindow(Settings) end end) - makeDraggable(Main, Topbar, false, { 255, 150 }) + makeDraggable(Main, Topbar, false, { dragOffset, dragOffsetMobile }) if dragBar then - dragBar.Position = useMobileSizing and UDim2.new(0.5, 0, 0.5, 150) or UDim2.new(0.5, 0, 0.5, 255) - makeDraggable(Main, dragInteract, true, { 255, 150 }) + dragBar.Position = useMobileSizing and UDim2.new(0.5, 0, 0.5, dragOffsetMobile) + or UDim2.new(0.5, 0, 0.5, dragOffset) + makeDraggable(Main, dragInteract, true, { dragOffset, dragOffsetMobile }) end for _, TabButton in ipairs(TabList:GetChildren()) do @@ -1729,27 +1771,32 @@ function RayfieldLibrary:CreateWindow(Settings) end if Settings.Discord and not useStudio then - if not isfolder(RayfieldFolder .. "/Discord Invites") then + if isfolder and not isfolder(RayfieldFolder .. "/Discord Invites") then makefolder(RayfieldFolder .. "/Discord Invites") end if - not isfile(RayfieldFolder .. "/Discord Invites" .. "/" .. Settings.Discord.Invite .. ConfigurationExtension) + isfile + and not isfile( + RayfieldFolder .. "/Discord Invites" .. "/" .. Settings.Discord.Invite .. ConfigurationExtension + ) then if request then - request({ - Url = "http://127.0.0.1:6463/rpc?v=1", - Method = "POST", - Headers = { - ["Content-Type"] = "application/json", - Origin = "https://discord.com", - }, - Body = HttpService:JSONEncode({ - cmd = "INVITE_BROWSER", - nonce = HttpService:GenerateGUID(false), - args = { code = Settings.Discord.Invite }, - }), - }) + pcall(function() + request({ + Url = "http://127.0.0.1:6463/rpc?v=1", + Method = "POST", + Headers = { + ["Content-Type"] = "application/json", + Origin = "https://discord.com", + }, + Body = HttpService:JSONEncode({ + cmd = "INVITE_BROWSER", + nonce = HttpService:GenerateGUID(false), + args = { code = Settings.Discord.Invite }, + }), + }) + end) end if Settings.Discord.RememberJoins then -- We do logic this way so if the developer changes this setting, the user still won't be prompted, only new users @@ -1793,8 +1840,7 @@ function RayfieldLibrary:CreateWindow(Settings) end if - isfile - and isfile( + isfile and isfile( RayfieldFolder .. "/Key System" .. "/" .. Settings.KeySettings.FileName .. ConfigurationExtension ) then @@ -2088,7 +2134,7 @@ function RayfieldLibrary:CreateWindow(Settings) { ImageTransparency = 1 } ):Play() task.wait(0.45) - game.Players.LocalPlayer:Kick("No Attempts Remaining") + Players.LocalPlayer:Kick("No Attempts Remaining") game:Shutdown() end KeyMain.Input.InputBox.Text = "" @@ -2573,7 +2619,7 @@ function RayfieldLibrary:CreateWindow(Settings) ColorPicker.HexInput.UIStroke.Color = SelectedTheme.InputStroke local opened = false - local mouse = game.Players.LocalPlayer:GetMouse() + local mouse = Players.LocalPlayer:GetMouse() Main.Image = "http://www.roblox.com/asset/?id=11415645739" local mainDragging = false local sliderDragging = false @@ -4353,6 +4399,8 @@ function RayfieldLibrary:CreateWindow(Settings) NewValue = math.floor(NewValue / SliderSettings.Increment + 0.5) * (SliderSettings.Increment * 10000000) / 10000000 + NewValue = math.clamp(NewValue, SliderSettings.Range[1], SliderSettings.Range[2]) + if not SliderSettings.Suffix then Slider.Main.Information.Text = tostring(NewValue) else @@ -4415,6 +4463,8 @@ function RayfieldLibrary:CreateWindow(Settings) end) function SliderSettings:Set(NewVal) + local NewVal = math.clamp(NewVal, SliderSettings.Range[1], SliderSettings.Range[2]) + TweenService:Create( Slider.Main.Progress, TweenInfo.new(0.45, Enum.EasingStyle.Exponential, Enum.EasingDirection.Out), @@ -4432,9 +4482,11 @@ function RayfieldLibrary:CreateWindow(Settings) } ):Play() Slider.Main.Information.Text = tostring(NewVal) .. " " .. (SliderSettings.Suffix or "") + local Success, Response = pcall(function() SliderSettings.Callback(NewVal) end) + if not Success then TweenService:Create( Slider, @@ -4458,6 +4510,7 @@ function RayfieldLibrary:CreateWindow(Settings) :Create(Slider.UIStroke, TweenInfo.new(0.6, Enum.EasingStyle.Exponential), { Transparency = 0 }) :Play() end + SliderSettings.CurrentValue = NewVal SaveConfiguration() end @@ -4608,6 +4661,7 @@ function RayfieldLibrary:IsVisible(): boolean end function RayfieldLibrary:Destroy() + hideHotkeyConnection:Disconnect() Rayfield:Destroy() end @@ -4686,7 +4740,7 @@ Topbar.Hide.MouseButton1Click:Connect(function() setVisibility(Hidden, not useMobileSizing) end) -UserInputService.InputBegan:Connect(function(input, processed) +hideHotkeyConnection = UserInputService.InputBegan:Connect(function(input, processed) if input.KeyCode == Enum.KeyCode.K and not processed then if Debounce then return @@ -4734,7 +4788,7 @@ function RayfieldLibrary:LoadConfiguration() if useStudio then config = - [[{"Toggle1adwawd":"false","Keybind1":"Q","InputExample":"","Slider1dawd":40,"ColorPicfsefker1":{"B":255,"G":255,"R":255},"Slidefefsr1":80,"dawdawd":"","ColorPicker1awd":{"B":255,"G":255,"R":255},"Dropdown1":["Ocean"]}]] + [[{"Toggle1adwawd":"false","Keybind1":"Q","InputExample":"","Slider1dawd":120,"ColorPicfsefker1":{"B":255,"G":255,"R":255},"Slidefefsr1":80,"dawdawd":"","ColorPicker1awd":{"B":255,"G":255,"R":255},"Dropdown1":["Ocean"]}]] end if CEnabled then @@ -4811,7 +4865,7 @@ if useStudio then }, }) - local Tab = Window:CreateTab("Tab Example", "badge-minus") -- Title, Image + local Tab = Window:CreateTab("Tab Example", "key-round") -- Title, Image local Tab2 = Window:CreateTab("Tab Example 2", 4483362458) -- Title, Image local Section = Tab2:CreateSection("Section") diff --git a/client/src/main.client.ts b/client/src/main.client.ts index ca9c97f..fd03eb8 100644 --- a/client/src/main.client.ts +++ b/client/src/main.client.ts @@ -31,7 +31,7 @@ findOrCreateFolder(CoreGui, "HighlightCache", "Folder") // create highlight cach const window = Rayfield.CreateWindow({ Name: "Chess", - LoadingTitle: "Loading 🔃", + LoadingTitle: "Chess ♟️", LoadingSubtitle: "By Haloxx", DisableBuildWarnings: false, diff --git a/server/Cargo.toml b/server/Cargo.toml index 5777987..a9c478f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "roblox-chess-script" -version = "0.1.4" +version = "0.1.5" edition = "2021" [dependencies] actix-web = "4" -serde = {version = "1.0.210", features = ["derive"]} -inline_colorization = "0.1.6" +serde = {version = "1.0.216", features = ["derive"]} rfd = "0.15.0" -sysinfo = "0.31.4" +sysinfo = "0.33.0" thousands = "0.2.0" shakmaty = "0.27.2" -log = "0.4.22" \ No newline at end of file +log = "0.4.22" +termcolor = "1.4.1" diff --git a/server/src/macros.rs b/server/src/macros.rs index d8c23cd..d2471a3 100644 --- a/server/src/macros.rs +++ b/server/src/macros.rs @@ -1,75 +1,10 @@ pub mod styled { - // #[macro_export] - macro_rules! styled_vec_print { - ($msg:expr, $styles:expr) => {{ - for style in $styles.iter() { - print!("{}", style); - } - print!("{}{color_reset}{style_reset}", $msg); - }}; - - ($msg:expr) => {{ - print!("{}", $msg); - }}; - } - - #[allow(unused_macros)] - macro_rules! styled_vec_println { - ($msg:expr, $styles:expr) => {{ - styled_vec_print!($msg, $styles); - println!(); - }}; - - ($msg:expr) => {{ - println!("{}", $msg); - }}; - } - - macro_rules! styled_print { - ($msg:expr, $($style:expr),*) => { - { - $( print!("{}", $style); )* - print!("{}{color_reset}{style_reset}", $msg); - } - }; - - ($msg:expr) => { - { - print!("{}", $msg); - } - }; - } - - macro_rules! styled_println { - ($msg:expr, $($style:expr),*) => { - { - crate::macros::styled::styled_print!($msg, $($style),*); - println!(); - // $( print!("{}", $style); )* - // println!("{}{color_reset}{style_reset}", $msg); - } - }; - - ($msg:expr) => { - { - // println!("{}", $msg); - styled_print!($msg); - println!(); - } - }; - } - /// A quicker way of doing `format!()` macro_rules! f { - ($($arg:tt)*) => { - format!($($arg)*) - }; -} + ($($arg:tt)*) => { + format!($($arg)*) + }; + } pub(crate) use f; - pub(crate) use styled_print; - pub(crate) use styled_println; - pub(crate) use styled_vec_print; - #[allow(unused_imports)] - pub(crate) use styled_vec_println; -} \ No newline at end of file +} diff --git a/server/src/main.rs b/server/src/main.rs index 59357a0..5bab15f 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,13 +8,12 @@ use std::{ }; use actix_web::{get, rt::time::Instant, web, App, HttpResponse, HttpServer, Responder}; -use inline_colorization::*; -use macros::styled::{f, styled_print, styled_println}; +use macros::styled::f; use rfd::FileDialog; use shakmaty::fen::Fen; -// use shakmaty::fen::Fen; use uci::Engine; use utils::{ + color_print::{CommonColors, Printer}, engine::{choose_engine_settings, initialize_engine}, SolveQueryParams, SolveResponse, }; @@ -27,7 +26,7 @@ struct AppState { #[actix_web::main] async fn main() -> std::io::Result<()> { - styled_println!("If anything goes wrong please try updating the app first. If that doesn't work, please report the issue on GitHub or DM me on Discord (in my github profile).\n", color_bright_cyan); + Printer::println("If anything goes wrong please try updating the app first. If that doesn't work, please report the issue on GitHub or DM me on Discord (in my github profile).\n", CommonColors::BrightCyan); let stockfish_path = choose_stockfish_file(); let (hash, threads, syzygy) = choose_engine_settings(); @@ -37,29 +36,25 @@ async fn main() -> std::io::Result<()> { set_stockfish_option(&engine, "MultiPV", "5"); set_stockfish_option(&engine, "Move Overhead", "0"); - styled_println!( - format!("\nStarting server at http://localhost:{PORT}\n"), - color_bright_green + Printer::println( + f!("\nStarting server at http://localhost:{PORT}\n"), + CommonColors::BrightGreen, ); let engine_data = web::Data::new(AppState { engine: Arc::new(Mutex::new(engine)), }); - HttpServer::new(move || { - App::new() - .app_data(engine_data.clone()) // Use the cloned Data instance - .service(solve) - }) - .bind(("127.0.0.1", PORT))? - .run() - .await + HttpServer::new(move || App::new().app_data(engine_data.clone()).service(solve)) + .bind(("127.0.0.1", PORT))? + .run() + .await } fn set_stockfish_option(engine: &Engine, option: &str, value: &str) { engine .set_option(option, value) - .unwrap_or_else(|e| styled_println!(f!("Failed to set option: {e}"))); + .unwrap_or_else(|e| Printer::println(f!("Failed to set option: {e}"), CommonColors::Red)); } fn choose_stockfish_file() -> String { @@ -70,17 +65,15 @@ fn choose_stockfish_file() -> String { .pick_file(); if stockfish_path.is_none() { - styled_println!( + Printer::println( "No file selected. Please select a file to continue.", - color_red + CommonColors::Red, ); let _ = Command::new("cmd.exe").arg("/c").arg("pause").status(); - // get_input("Press enter to exit...", None); exit(1); } - - styled_println!("File chosen successfully!\n", color_bright_green); + Printer::println("File chosen successfully!\n", CommonColors::BrightGreen); stockfish_path.unwrap().display().to_string() } @@ -89,15 +82,15 @@ fn choose_stockfish_file() -> String { async fn solve(data: web::Data, query: web::Query) -> impl Responder { let mut engine = data.engine.lock().unwrap(); - styled_print!("Received FEN", color_bright_magenta); + Printer::print("Received FEN", CommonColors::BrightMagenta); println!(": {}", query.fen); - styled_print!("Max Think Time", color_bright_magenta); + Printer::print("Max Think Time", CommonColors::BrightMagenta); println!(": {}", query.max_think_time.unwrap_or(100)); // Validate FEN if query.fen.parse::().is_err() { - styled_println!("Invalid FEN\n", color_red); + Printer::println("Invalid FEN\n", CommonColors::Red); return HttpResponse::BadRequest().json(SolveResponse { success: false, result: "Error: Invalid FEN".to_string(), @@ -108,7 +101,7 @@ async fn solve(data: web::Data, query: web::Query) - // Set position on engine if let Err(err) = engine.set_position(query.fen.as_str()) { - styled_println!(f!("Failed to set position - {err}\n"), color_red); + Printer::println(f!("Failed to set position - {err}\n"), CommonColors::Red); return HttpResponse::BadRequest().json(SolveResponse { success: false, result: f!("Error: Failed to set position - {}", err), @@ -123,7 +116,7 @@ async fn solve(data: web::Data, query: web::Query) - let best_move = match engine.bestmove_depth(17) { Ok(mv) => mv, Err(err) => { - styled_println!(f!("Failed to get best move - {err}\n"), color_red); + Printer::println(f!("Failed to get best move - {err}\n"), CommonColors::Red); return HttpResponse::BadRequest().json(SolveResponse { success: false, result: f!("Error: Failed to get best move - {}", err), @@ -133,10 +126,10 @@ async fn solve(data: web::Data, query: web::Query) - let duration = start.elapsed(); - styled_print!("Returned", color_bright_magenta); + Printer::print("Returned", CommonColors::BrightMagenta); println!(": {best_move}"); - styled_print!("Time Taken", color_bright_magenta); + Printer::print("Time Taken", CommonColors::BrightMagenta); println!(": {duration:?}\n"); // Return best move as JSON response diff --git a/server/src/uci/mod.rs b/server/src/uci/mod.rs index c5f1df4..125693e 100644 --- a/server/src/uci/mod.rs +++ b/server/src/uci/mod.rs @@ -5,10 +5,14 @@ use std::process::{Child, Command, Stdio}; use std::io::Write; use std::io::{self, Read}; -use std::fmt; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; +use std::{fmt, sync}; + +use log::{debug, info}; + +use self::EngineError::{Io, UnknownOption}; #[derive(Clone)] pub struct Engine { @@ -36,21 +40,58 @@ impl Engine { /// /// [`Engine`]: struct.Engine.html pub fn new(path: &str) -> Result { - let cmd = Command::new(path) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("Unable to run engine"); - - let res = Engine { - engine: Arc::new(Mutex::new(cmd)), - movetime: DEFAULT_TIME, - }; - - res.read_line()?; - res.command("uci")?; + // the amount of chatgpt stolen code is MASSIVE. you know what else is massive? + + let path = path.to_string(); // Move ownership of path into the thread + let (sender, receiver) = sync::mpsc::channel(); + + // Spawn a thread for the entire initialization process + thread::spawn(move || { + // Create the subprocess + let cmd = Command::new(path) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn(); + + if let Err(err) = cmd { + let _ = sender.send(Err(EngineError::Io(err))); + return; + } - Ok(res) + let process = cmd.unwrap(); + let engine = Engine { + engine: Arc::new(Mutex::new(process)), + movetime: DEFAULT_TIME, + }; + + // Execute the UCI command + let uci_result = engine.command("uci"); + match uci_result { + Ok(response) => { + if !response.trim_end().ends_with("uciok") { + let _ = sender.send(Err(EngineError::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "The selected file does not appear to be a UCI-compatible engine.", + )))); + println!("{}", response); + } else { + let _ = sender.send(Ok(engine)); + } + } + Err(err) => { + let _ = sender.send(Err(err)); + } + } + }); + + // Wait for the result with a timeout + match receiver.recv_timeout(Duration::from_secs(3)) { + Ok(result) => result, + Err(_) => Err(EngineError::Io(io::Error::new( + io::ErrorKind::TimedOut, + "Engine initialization timed out.", + ))), + } } /// Changes the amount of time the engine spends looking for a move @@ -314,9 +355,6 @@ pub enum EngineError { Message(String), } -use log::{debug, info}; - -use self::EngineError::{Io, UnknownOption}; impl fmt::Display for EngineError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { diff --git a/server/src/utils/color_print.rs b/server/src/utils/color_print.rs new file mode 100644 index 0000000..c0bbc98 --- /dev/null +++ b/server/src/utils/color_print.rs @@ -0,0 +1,65 @@ +use std::fmt::Display; + +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; + +pub enum CommonColors { + Red, + BrightGreen, + BrightCyan, + BrightMagenta, + BrightBoldYellow, +} + +impl From for ColorSpec { + fn from(val: CommonColors) -> Self { + match val { + CommonColors::Red => { + let mut color = ColorSpec::new(); + color.set_fg(Some(Color::Red)); + color + } + CommonColors::BrightGreen => { + let mut color = ColorSpec::new(); + color.set_fg(Some(Color::Green)).set_intense(true); + color + } + CommonColors::BrightCyan => { + let mut color = ColorSpec::new(); + color.set_fg(Some(Color::Cyan)).set_intense(true); + color + } + CommonColors::BrightMagenta => { + let mut color = ColorSpec::new(); + color.set_fg(Some(Color::Magenta)).set_intense(true); + color + } + CommonColors::BrightBoldYellow => { + let mut color = ColorSpec::new(); + color + .set_fg(Some(Color::Yellow)) + .set_intense(true) + .set_bold(true); + color + } + } + } +} + +pub struct Printer; +impl Printer { + pub fn print + Display>(msg: T, color: CommonColors) { + // setup + let mut std_stream = StandardStream::stdout(ColorChoice::Auto); + std_stream.set_color(&color.into()).unwrap(); + // printing + print!("{}", msg.as_ref()); + // write!(&mut std_stream, "{msg}").unwrap(); + // reset styles + std_stream.reset().unwrap(); + } + + pub fn println + Display>(msg: T, color: CommonColors) { + Self::print(msg, color); + println!(); + } +} diff --git a/server/src/utils/engine.rs b/server/src/utils/engine.rs index e0819bb..fd5cc71 100644 --- a/server/src/utils/engine.rs +++ b/server/src/utils/engine.rs @@ -1,20 +1,22 @@ use std::process::{exit, Command}; -use inline_colorization::*; use rfd::FileDialog; use sysinfo::System; use thousands::Separable; use crate::{ - macros::styled::{f, styled_println}, + macros::styled::f, uci::Engine, - utils::input::{get_input, get_int_input}, + utils::{ + color_print::{CommonColors, Printer}, + input::{get_input, get_int_input}, + }, }; pub fn choose_engine_settings() -> (Option, Option, String) { - styled_println!( + Printer::println( "Please leave the following options empty if you do not know what you are doing!", - color_red + CommonColors::Red, ); let sys = System::new_all(); @@ -32,18 +34,15 @@ pub fn choose_engine_settings() -> (Option, Option, String) { (free_mem as u64 / mb as u64).separate_with_commas(), ), true, - None, ); let threads = get_int_input( &f!("Enter threads amount\nTotal: {}", sys.cpus().len()), true, - None, ); let syzygy: String = { loop { - let answer = - get_input("Do you have a Syzygy tablebase? (Y\\n).", None).to_ascii_lowercase(); + let answer = get_input("Do you have a Syzygy tablebase? (Y\\n).").to_ascii_lowercase(); if answer.is_empty() || answer == "n" { break "".to_string(); @@ -62,9 +61,9 @@ pub fn choose_engine_settings() -> (Option, Option, String) { println!("No folder selected. Please try again."); } } else { - styled_println!( + Printer::println( "Invalid input. Please enter 'y' (yes), 'n' (no), or leave blank to skip.", - color_red + super::color_print::CommonColors::Red, ); } } @@ -79,17 +78,13 @@ pub fn initialize_engine( syzygy_path: &str, ) -> Engine { let engine = Engine::new(stockfish_path).unwrap_or_else(|err| { - styled_println!( - f!("Could not start engine: {}\n", err), - color_red, - "\n" - ); - styled_println!("Things to consider:", style_bold, color_bright_yellow); - styled_println!(" - Did you select the correct file for Stockfish?", style_bold, color_bright_yellow); - styled_println!(" - Did you make sure to enter valid settings?\n", style_bold, color_bright_yellow); - styled_println!( + Printer::println(f!("\nCould not start engine: {}\n", err), CommonColors::Red); + Printer::println("Things to consider:", CommonColors::BrightBoldYellow); + Printer::println(" - Did you select the correct file for Stockfish?", CommonColors::BrightBoldYellow); + Printer::println(" - Did you make sure to enter valid settings?\n", CommonColors::BrightBoldYellow); + Printer::println( "If you cannot figure out what went wrong, message me on Discord (on my GitHub) or leave an inssue on the repo\n", - color_bright_cyan + CommonColors::BrightCyan, ); let _ = Command::new("cmd.exe").arg("/c").arg("pause").status(); exit(1); diff --git a/server/src/utils/input.rs b/server/src/utils/input.rs index 3520f0b..dff9f6a 100644 --- a/server/src/utils/input.rs +++ b/server/src/utils/input.rs @@ -1,32 +1,27 @@ use std::io::{stdin, stdout, Write}; -use inline_colorization::*; +use super::color_print::{CommonColors, Printer}; -use crate::macros::styled::{f, styled_println, styled_vec_print}; - -pub fn get_input(message: &str, styles: Option>) -> String { +pub fn get_input(message: &str) -> String { println!(); // format let mut input = String::new(); - match styles { - Some(styles_vec) => styled_vec_print!(f!("{message}\n>"), styles_vec), - None => print!("{message}\n>"), - } + print!("{message}\n>"); stdout().flush().unwrap(); stdin().read_line(&mut input).unwrap(); input.trim().to_string() } -pub fn get_int_input(message: &str, allow_empty: bool, styles: Option>) -> Option { +pub fn get_int_input(message: &str, allow_empty: bool) -> Option { loop { - let input = get_input(message, styles.clone()); + let input = get_input(message); if allow_empty && input.is_empty() { return None; } if let Ok(number) = input.parse::() { return Some(number); } - styled_println!("Invalid input. Please enter a number.", color_red); + Printer::println("Invalid input. Please enter a number.", CommonColors::Red); } } diff --git a/server/src/utils/mod.rs b/server/src/utils/mod.rs index 0681c84..bccc845 100644 --- a/server/src/utils/mod.rs +++ b/server/src/utils/mod.rs @@ -1,5 +1,6 @@ pub mod engine; pub mod input; +pub mod color_print; use serde::{Deserialize, Serialize};