---@module ChessDuels.Main -- Singleton guard that works whether or not the executor exposes `shared`. local globalEnv = getgenv and getgenv() or _G local ChessDuels = {} -- Services. local playersService = game:GetService("Players") local replicatedStorage = game:GetService("ReplicatedStorage") local userInputService = game:GetService("UserInputService") local virtualUser = game:GetService("VirtualUser") -- Constants. local DEPTH_OPTIONS = { 2, 3, 4, 5, 6, 7, 8 } local TIME_OPTIONS = { 0.3, 0.6, 1, 2, 3 } local DELAY_OPTIONS = { 0, 0.4, 1, 2, 3.5 } local POLL_INTERVAL = 0.25 local RETRY_AFTER = 3 -- State. local localPlayer = playersService.LocalPlayer local connections = {} local guiRoot = nil local running = true local flags = { autoPlay = false, autoRematch = false, autoClaim = true, antiAfk = true, } local depthIndex = 5 local timeIndex = 3 local delayIndex = 2 local Engine = nil local Bot = nil local actionRemote = nil local updateRemote = nil local currentGetter = nil local lastGame = nil local lastKey = nil local lastSentAt = 0 local suggestion = nil local statusLabel = nil local hintLabel = nil local analysing = false ---Require a game module under identity 2 and restore identity 8. ---@param module ModuleScript ---@return any local function safeRequire(module) setthreadidentity(2) local ok, result = pcall(require, module) setthreadidentity(8) return ok and result or nil end ---Resolve the game's engine, bot and remotes. local function loadGame() local chessFolder = replicatedStorage:WaitForChild("Chess", 10) if chessFolder then Engine = safeRequire(chessFolder:WaitForChild("Engine")) Bot = safeRequire(chessFolder:WaitForChild("Bot")) end local remotes = replicatedStorage:WaitForChild("ChessRemotes", 10) if remotes then actionRemote = remotes:WaitForChild("Action") updateRemote = remotes:WaitForChild("Update") end end ---Find GameView.current through the GC so joining mid-game still works. local function findCurrentGetter() if not getgc then return end for _, fn in ipairs(getgc(false)) do if typeof(fn) == "function" and not (isexecutorclosure and isexecutorclosure(fn)) then local okName, name = pcall(debug.info, fn, "n") local okSrc, source = pcall(debug.info, fn, "s") if okName and okSrc and name == "current" and type(source) == "string" and source:find("GameView") then currentGetter = fn return end end end end ---Return the newest known game payload. ---@return table? local function getGame() if currentGetter then local ok, game = pcall(currentGetter) if ok and type(game) == "table" and game.state then lastGame = game end end return lastGame end ---Set the status text in the HUD. ---@param text string local function setStatus(text) if statusLabel then statusLabel.Text = text end end ---Format a move as coordinate notation. ---@param move table ---@return string local function formatMove(move) return Engine.uci(move) end ---Search the current position and return the best move. ---@param game table ---@return table?, number?, number? local function searchBest(game) local state = Engine.deserialize(game.state) if #Engine.legalMoves(state) == 0 then return nil end local score, move, reached = Bot.analyze(state, DEPTH_OPTIONS[depthIndex], TIME_OPTIONS[timeIndex]) if not move then move = Engine.legalMoves(state)[1] end return move, score, reached end ---Send a move to the server exactly like the client does. ---@param move table local function playMove(move) if not actionRemote then return end actionRemote:FireServer("move", { from = move.from, to = move.to, promo = move.promo, }) lastSentAt = os.clock() end ---Process the current game once per poll tick. local function step() local game = getGame() if not game then setStatus("Waiting for a game...") return end if game.over then suggestion = nil lastKey = nil setStatus("Game over") if hintLabel then hintLabel.Text = "Best: -" end if flags.autoRematch and actionRemote and game.you ~= 0 then actionRemote:FireServer("rematch") end if flags.autoClaim and actionRemote then actionRemote:FireServer("claimReward") end return end if game.you == 0 then setStatus("Spectating") return end local state = Engine.deserialize(game.state) if state.turn ~= game.you then setStatus("Opponent thinking...") return end local key = tostring(game.id) .. ":" .. tostring(game.ply) if key == lastKey then -- Server may have rejected the move; retry after a grace period. if flags.autoPlay and suggestion and os.clock() - lastSentAt > RETRY_AFTER then playMove(suggestion) end return end if analysing then return end analysing = true lastKey = key setStatus("Calculating...") local move, score, reached = searchBest(game) analysing = false suggestion = move if not move then return end local shown = string.format("Best: %s (%+.2f, d%d)", formatMove(move), (score or 0) / 100, reached or 0) if hintLabel then hintLabel.Text = shown end setStatus("Your move") if flags.autoPlay then task.wait(DELAY_OPTIONS[delayIndex]) local fresh = getGame() if fresh and fresh.id == game.id and fresh.ply == game.ply and not fresh.over then playMove(move) end end end ---Poll loop. local function pollLoop() while running do local ok, err = pcall(step) if not ok then analysing = false warn("[ChessDuels] " .. tostring(err)) end task.wait(POLL_INTERVAL) end end ---Return a safe GUI parent. ---@return Instance local function getGuiParent() if gethui then local ok, hui = pcall(gethui) if ok and hui then return hui end end return localPlayer:WaitForChild("PlayerGui") end ---Build the native GUI. function ChessDuels.buildGui() local screenGui = Instance.new("ScreenGui") screenGui.Name = "ChessDuels_Menu" screenGui.ResetOnSpawn = false screenGui.Parent = getGuiParent() guiRoot = screenGui local frame = Instance.new("Frame") frame.Size = UDim2.fromOffset(270, 0) frame.AutomaticSize = Enum.AutomaticSize.Y frame.Position = UDim2.fromOffset(25, 100) frame.BackgroundColor3 = Color3.fromRGB(18, 20, 24) frame.BorderSizePixel = 0 frame.Parent = screenGui local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(50, 56, 68) stroke.Parent = frame local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 8) corner.Parent = frame local padding = Instance.new("UIPadding") padding.PaddingTop = UDim.new(0, 10) padding.PaddingBottom = UDim.new(0, 10) padding.PaddingLeft = UDim.new(0, 10) padding.PaddingRight = UDim.new(0, 10) padding.Parent = frame local layout = Instance.new("UIListLayout") layout.Padding = UDim.new(0, 4) layout.SortOrder = Enum.SortOrder.LayoutOrder layout.Parent = frame local order = 0 local function makeButton(text) order += 1 local button = Instance.new("TextButton") button.LayoutOrder = order button.Size = UDim2.new(1, 0, 0, 26) button.BackgroundColor3 = Color3.fromRGB(34, 38, 44) button.BorderSizePixel = 0 button.TextColor3 = Color3.fromRGB(230, 230, 230) button.Font = Enum.Font.GothamMedium button.TextSize = 12 button.Text = text button.Parent = frame local btnCorner = Instance.new("UICorner") btnCorner.CornerRadius = UDim.new(0, 4) btnCorner.Parent = button return button end local function makeLabel(text, size) order += 1 local label = Instance.new("TextLabel") label.LayoutOrder = order label.Size = UDim2.new(1, 0, 0, size or 18) label.BackgroundTransparency = 1 label.TextColor3 = Color3.fromRGB(160, 170, 185) label.Font = Enum.Font.GothamBold label.TextSize = 11 label.TextXAlignment = Enum.TextXAlignment.Left label.Text = text label.Parent = frame return label end local function addToggle(text, name) local button = makeButton("") local function refresh() local on = flags[name] button.Text = text .. ": " .. (on and "ON" or "OFF") button.BackgroundColor3 = on and Color3.fromRGB(35, 140, 75) or Color3.fromRGB(34, 38, 44) end button.MouseButton1Click:Connect(function() flags[name] = not flags[name] refresh() end) refresh() end local function addCycler(text, options, getIndex, setIndex, suffix) local button = makeButton("") local function refresh() button.Text = text .. ": " .. tostring(options[getIndex()]) .. (suffix or "") end button.MouseButton1Click:Connect(function() setIndex(getIndex() % #options + 1) refresh() end) refresh() end makeLabel("CHESS DUELS (OP) | RightShift").TextColor3 = Color3.new(1, 1, 1) statusLabel = makeLabel("Waiting for a game...") hintLabel = makeLabel("Best: -", 22) hintLabel.TextColor3 = Color3.fromRGB(120, 255, 160) hintLabel.TextSize = 14 makeLabel("ENGINE") addToggle("Auto-Play Best Move", "autoPlay") addCycler("Search Depth", DEPTH_OPTIONS, function() return depthIndex end, function(v) depthIndex = v end) addCycler("Think Time", TIME_OPTIONS, function() return timeIndex end, function(v) timeIndex = v end, "s") addCycler("Move Delay", DELAY_OPTIONS, function() return delayIndex end, function(v) delayIndex = v end, "s") makeButton("Play Best Move Now").MouseButton1Click:Connect(function() if suggestion then playMove(suggestion) end end) makeLabel("MATCH") addToggle("Auto Rematch", "autoRematch") addToggle("Auto Claim Rewards", "autoClaim") makeButton("Resign").MouseButton1Click:Connect(function() if actionRemote then actionRemote:FireServer("resign") end end) makeLabel("UTILITY") addToggle("Anti-AFK", "antiAfk") makeButton("Detach / Unload").MouseButton1Click:Connect(ChessDuels.detach) local dragging, dragStart, startPosition = false, nil, nil table.insert(connections, frame.InputBegan:Connect(function(input) if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end dragging = true dragStart = input.Position startPosition = frame.Position end)) table.insert(connections, userInputService.InputChanged:Connect(function(input) if not dragging or input.UserInputType ~= Enum.UserInputType.MouseMovement then return end local delta = input.Position - dragStart frame.Position = UDim2.new(startPosition.X.Scale, startPosition.X.Offset + delta.X, startPosition.Y.Scale, startPosition.Y.Offset + delta.Y) end)) table.insert(connections, userInputService.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 then dragging = false end end)) table.insert(connections, userInputService.InputBegan:Connect(function(input) if input.KeyCode == Enum.KeyCode.RightShift then frame.Visible = not frame.Visible end end)) end ---Clean up everything. function ChessDuels.detach() running = false for _, connection in ipairs(connections) do connection:Disconnect() end table.clear(connections) if guiRoot then guiRoot:Destroy() guiRoot = nil end globalEnv.ChessDuels = nil warn("[ChessDuels] Detached.") end ---Initialize. function ChessDuels.init() loadGame() if not (Engine and Bot and actionRemote) then error("Chess modules or remotes not found") end findCurrentGetter() -- Capture every game payload the server pushes. table.insert(connections, updateRemote.OnClientEvent:Connect(function(payload) if type(payload) == "table" and payload.state and payload.you ~= nil then lastGame = payload end end)) table.insert(connections, localPlayer.Idled:Connect(function() if not flags.antiAfk then return end virtualUser:CaptureController() virtualUser:ClickButton2(Vector2.zero) end)) ChessDuels.buildGui() task.spawn(pollLoop) warn("[ChessDuels] Loaded. Press RightShift to hide/show GUI.") end if globalEnv.ChessDuels then globalEnv.ChessDuels.detach() end globalEnv.ChessDuels = ChessDuels ---@param err string local function onInitializeError(err) warn("Failed to initialize.") warn(err) warn(debug.traceback()) ChessDuels.detach() end xpcall(ChessDuels.init, onInitializeError)