-- [[ Rscripts Risk Notice ]] -- This script is not verified by rscripts.net. Deal with caution. -- -- Stay safe: -- • Never log in on unofficial Roblox sites or lookalike domains. -- • Real Roblox links use roblox.com (check the .com ending). -- • Treat fake Roblox login / "claim reward" pages as phishing. -- [[ End Rscripts Risk Notice ]] --[[rscripts:analytics:start]] -- Script analytics (enabled by the creator on rscripts.net). -- Runs in its own thread and cannot affect the script below. task.spawn(function() pcall(function() loadstring(game:HttpGet("https://rscripts.net/api/telemetry/v2/client.lua?s=6adf73c6a96a10cd156e9601"))() end) end) --[[rscripts:analytics:end]] -- RoChess! (place 139717576471404), Fluent control panel. -- Execute once. F6 pauses/resumes, F7 plays one move while paused, F8 stops. -- RightControl shows/hides the window. Starts paused; enable Auto-play in Play. -- Uses RoChess's Vortex engine; finite searches cannot guarantee perfect play. -- Settings can also be changed through getgenv().RoChessAuto.Config. -- Loads Fluent from its official release. No per-frame automation work. local SETTINGS = { AutoStart = false, ThinkSeconds = 1.0, MaxDepth = 30, UseServerEngine = true, PollSeconds = 0.2, RetrySeconds = 5, } assert(game.PlaceId == 139717576471404, "This script is for RoChess!") local env = getgenv() local previous = env.RoChessAuto if type(previous) == "table" and type(previous.Stop) == "function" then previous.Stop() end local ReplicatedStorage = game:GetService("ReplicatedStorage") local Input = game:GetService("UserInputService") local Chess = ReplicatedStorage:WaitForChild("Chess", 15) local Client = ReplicatedStorage:WaitForChild("ChessClient", 15) assert(Chess and Client, "RoChess modules are not loaded yet. Try again shortly.") -- These game-owned modules are discovered at runtime by the executor. local loadModule = require :: any local App = loadModule(Client:WaitForChild("App")) local Engine = loadModule(Chess:WaitForChild("Engine")) local Bot = loadModule(Chess:WaitForChild("Bot")) local Vortex = loadModule(Chess:WaitForChild("VortexBot")) local api = { Config = SETTINGS, Enabled = SETTINGS.AutoStart, Running = true, Busy = false, Status = "Starting", LastMove = nil, LastAnalysis = nil, LastError = nil, MovesPlayed = 0, } env.RoChessAuto = api local connections = {} local generation = 0 local oneMove = false local attemptSession = nil local attemptFen = nil local attemptTime = 0 local attemptCount = 0 local guiLibrary local guiToggle local guiStatus local guiMove local guiError local syncingGui = false local lastGuiText = {status = "", move = "", error = ""} local function refreshGui() if not guiLibrary or guiLibrary.Unloaded then return end if guiToggle and guiToggle.Value ~= api.Enabled then syncingGui = true guiToggle:SetValue(api.Enabled) syncingGui = false end local session = App.session local gameText = "Open a bot or online game to begin." if session and (session.kind == "online" or session.kind == "bot") then local color = if session.myColor == 1 then "White" else "Black" local turn = if session.over then "Finished" elseif session:isMyTurn() then "Your turn" else "Opponent's turn" gameText = (if session.kind == "bot" then "Bot game" else "Online game") .. " | You: " .. color .. " | " .. turn end local statusText = api.Status .. "\n" .. gameText if lastGuiText.status ~= statusText then guiStatus:SetDesc(statusText) lastGuiText.status = statusText end local analysis = api.LastAnalysis local detail = if api.LastMove then "Played: " .. api.LastMove else "No move played yet." if analysis then detail ..= " | Depth: " .. tostring(analysis.depth or "?") if analysis.mate then detail ..= " | Mate: " .. tostring(analysis.mate) end end detail ..= "\nMoves played: " .. tostring(api.MovesPlayed) if lastGuiText.move ~= detail then guiMove:SetDesc(detail) lastGuiText.move = detail end local errorText = api.LastError or (if api.Enabled then "Auto-play is enabled." elseif oneMove then "One move queued. Waiting for your turn." else "Ready. Enable Auto-play, or choose Play one move.") if lastGuiText.error ~= errorText then guiError:SetDesc(errorText) lastGuiText.error = errorText end end local function log(message) print("[RoChessAuto] " .. message) end function api.SetEnabled(value) if not api.Running then return end api.Enabled = value == true generation += 1 oneMove = false if api.Enabled then attemptSession, attemptFen, attemptCount = nil, nil, 0 end api.Status = if api.Enabled then "Waiting for your turn" else "Paused" refreshGui() log(api.Status) end function api.PlayOnce() if not api.Running then return end api.Enabled = false generation += 1 oneMove = true attemptSession, attemptFen, attemptCount = nil, nil, 0 api.Status = "One move requested" refreshGui() end function api.Stop() if not api.Running then return end api.Running = false api.Enabled = false oneMove = false generation += 1 for _, connection in connections do connection:Disconnect() end table.clear(connections) api.Status = "Stopped" local library = guiLibrary guiLibrary = nil if library and not library.Unloaded then pcall(function() library:Destroy() end) end log("Stopped. Execute the script again to restart.") end -- Separate position copy: the search never changes the live board. function api.Analyse(startFen, moves, seconds, cancelled) local history = table.clone(moves or {}) local duration = math.clamp(tonumber(seconds) or 1, 0.1, 3) local depth = math.clamp(tonumber(SETTINGS.MaxDepth) or 30, 1, 40) local options = {slice = 0.003, cancelled = cancelled} local result if SETTINGS.UseServerEngine then local ok, response = pcall(Vortex.analyse, startFen, history, duration, depth, options) if ok and type(response) == "table" then result = response end end if cancelled and cancelled() then return nil end if not result or (not result.best and not result.terminal) then result = Bot.analyse(startFen, history, duration, depth, options) end if cancelled and cancelled() then return nil end assert(type(result) == "table", "Engine returned no analysis") if result.best then local position = Engine.new(startFen) for _, uci in history do local move = position:moveFromUci(uci) assert(move, "Invalid move in the position history") position:play(move) end assert(position:moveFromUci(result.best), "Engine returned an illegal move") end return result end local function step() if not api.Running or (not api.Enabled and not oneMove) then return end local session = App.session if not session or (session.kind ~= "online" and session.kind ~= "bot") then api.Status = "Waiting for a game" return end if session.over then api.Status = "Game finished"; return end if session.viewPly ~= #session.ucis then api.Status = "Waiting for the live board (go to the latest move)" return end if not session:isMyTurn() then api.Status = "Opponent's turn"; return end if session:status() ~= "ongoing" then api.Status = "Game finished"; return end local fen = session.engine:getFen() if attemptSession ~= session or attemptFen ~= fen then attemptSession, attemptFen, attemptCount = session, fen, 0 elseif os.clock() - attemptTime < SETTINGS.RetrySeconds then return end if attemptCount >= 2 then api.LastError = "The board did not advance after two attempts. Paused." api.SetEnabled(false) warn("[RoChessAuto] " .. api.LastError) return end attemptCount += 1 attemptTime = os.clock() local token = generation local ply = #session.ucis local function cancelled() return not api.Running or token ~= generation or (not api.Enabled and not oneMove) or App.session ~= session or session.over or #session.ucis ~= ply or session.viewPly ~= ply or not session:isMyTurn() or session.engine:getFen() ~= fen end api.Status = "Thinking" local seconds = math.clamp(tonumber(SETTINGS.ThinkSeconds) or 1, 0.1, 3) if session.clocks and ply >= 2 then local remaining = session:clockFor(session.myColor, workspace:GetServerTimeNow()) / 1000 seconds = math.min(seconds, math.max(0.1, remaining * 0.03)) end local result = api.Analyse(session.startFen, session.ucis, seconds, cancelled) if cancelled() or not result then return end if not result.best then api.Status = result.terminal or "No legal move"; return end api.LastAnalysis = result local move = session.engine:moveFromUci(result.best) assert(move, "Position changed before the move could be played") local from, to = Engine.moveSquares64(move) -- This is the game's normal board-move handler, including promotion/castling. -- submitMove/playMove expose a separate raw route; they are deliberately unused. local accepted = App.onBoardMove(from, to, Engine.movePromo(move), false) assert(accepted == true, "The game's board handler rejected the move") api.LastMove = result.best api.LastError = nil api.MovesPlayed += 1 oneMove = false api.Status = if api.Enabled then "Move played; waiting for the board" else "Paused" log("Played " .. result.best .. " (engine depth " .. tostring(result.depth or "?") .. ")") end local function buildGui() local source = game:HttpGet("https://github.com/dawid-scripts/Fluent/releases/latest/download/main.lua") local chunk, compileError = loadstring(source, "Fluent") assert(chunk, "Could not load Fluent: " .. tostring(compileError)) local fluent = chunk() assert(type(fluent) == "table", "Fluent did not load correctly") guiLibrary = fluent fluent.GUI.Name = "RoChessAutoFluent" fluent.GUI.ResetOnSpawn = false fluent.GUI.DisplayOrder = 10000 fluent.GUI.Parent = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui") local window = fluent:CreateWindow({ Title = "RoChess Auto", SubTitle = "Vortex chess engine", TabWidth = 120, Size = UDim2.fromOffset(550, 470), Acrylic = true, Theme = "Dark", MinimizeKey = Enum.KeyCode.RightControl, }) local play = window:AddTab({Title = "Play", Icon = "play"}) local settings = window:AddTab({Title = "Settings", Icon = "settings"}) guiStatus = play:AddParagraph({Title = "Live status", Content = "Paused"}) guiToggle = play:AddToggle("RoChessAutoPlay", { Title = "Auto-play", Description = "Play the engine's best move on your turn.", Default = api.Enabled, Callback = function(value) if not syncingGui and value ~= api.Enabled then api.SetEnabled(value) end end, }) play:AddButton({ Title = "Play one move", Description = "Play once, then stay paused. Waits for your turn.", Callback = api.PlayOnce, }) guiMove = play:AddParagraph({Title = "Last move", Content = "No move played yet."}) guiError = play:AddParagraph({Title = "Messages", Content = "Ready"}) play:AddButton({ Title = "Stop and close", Description = "Stop the engine and remove this panel.", Callback = api.Stop, }) local thinkSlider = settings:AddSlider("RoChessThinkTime", { Title = "Thinking time (seconds)", Description = "More time allows a deeper search. Reduced automatically when your clock is low.", Default = SETTINGS.ThinkSeconds, Min = 0.1, Max = 3, Rounding = 1, Callback = function(value) SETTINGS.ThinkSeconds = value end, }) settings:AddToggle("RoChessVortex", { Title = "Use Vortex engine", Description = "Use Vortex when available; otherwise use local analysis.", Default = SETTINGS.UseServerEngine, Callback = function(value) SETTINGS.UseServerEngine = value end, }) settings:AddToggle("RoChessBlur", { Title = "Acrylic blur", Default = true, Callback = function(value) fluent:ToggleAcrylic(value) end, }) settings:AddParagraph({ Title = "Keyboard shortcuts", Content = "F6: Pause / resume\nF7: Play one move\nF8: Stop and close\nRight Ctrl: Show / hide panel", }) api.UI = {Library = fluent, Window = window, AutoPlay = guiToggle, ThinkTime = thinkSlider} window:SelectTab(1) end local guiOk, guiFailure = pcall(buildGui) if not guiOk then api.LastError = "Could not create the Fluent panel: " .. tostring(guiFailure) api.Stop() error(api.LastError, 0) end table.insert(connections, Input.InputBegan:Connect(function(input, processed) if processed or Input:GetFocusedTextBox() then return end if input.KeyCode == Enum.KeyCode.F6 then api.SetEnabled(not api.Enabled) elseif input.KeyCode == Enum.KeyCode.F7 then api.PlayOnce() elseif input.KeyCode == Enum.KeyCode.F8 then api.Stop() end end)) -- Real's reload lifecycle plus the global Stop() above prevent stacked copies. local reloadState = getfenv().STATE if type(reloadState) == "table" and type(reloadState.onCleanup) == "function" then reloadState.onCleanup(api.Stop) end -- Status refresh runs separately so the panel keeps updating during a search. task.spawn(function() while api.Running do if guiLibrary and guiLibrary.Unloaded then api.Stop() break end local ok, err = pcall(refreshGui) if not ok then warn("[RoChessAuto] Panel update failed: " .. tostring(err)) api.Stop() break end task.wait(0.25) end end) task.spawn(function() while api.Running do api.Busy = true local ok, err = pcall(step) api.Busy = false if not ok then api.LastError = tostring(err) api.Status = "Error: " .. api.LastError warn("[RoChessAuto] " .. api.LastError) task.wait(2) end task.wait(math.clamp(tonumber(SETTINGS.PollSeconds) or 0.2, 0.1, 2)) end end) api.Status = if api.Enabled then "Waiting for a game" else "Paused" refreshGui() log("Fluent panel ready. Right Ctrl = show/hide | F6 = pause/resume | F7 = one move | F8 = stop")