RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper - Verified script for RoChess!
Key Required

RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper

RoChess!smertni*qqsmertni*qq 3.4K views 9.8K executesUpdated Oct 20, 2026
Unrated
00
Lua Script•1150 lines (30598 chars)
--[[
	rocheese.luau - RoChess! engine panel + autopilot
	Luau, Real executor, client side.

	Uses the game's own modules instead of re-implementing chess:
		ReplicatedStorage.Chess.Engine       board, legal moves, UCI/SAN, FEN
		ReplicatedStorage.Chess.Bot          local search: analyse(fen,moves,sec,depth) / think(fen,moves,persona)
		ReplicatedStorage.Chess.VortexBot    server engine bridge: analyse / think
		ReplicatedStorage.ChessClient.App    live client state: session, board, onBoardMove, playMove

	Squares use Engine.algToIndex64 (bottom-up, a1 = 1, h8 = 64).
	Engine scores are always from WHITE's side of the board.

	Panel: top-right, drag it by the title.
	Arrows on the board:
		green  = best move      blue = second best      yellow = third best

	Presets:
		LEGIT  local engine, plays like a 1500 human (noise + blunders), 5-10s per move
		RAJ    server engine (Vortex), always the best move, plays fast

	Controls: STYLE, DELAY min/max, PLAY BEST/ELO, ENGINE LOCAL/VORTEX, AUTOPLAY,
	          PLAY NOW, COPY FEN, BOT GAME.
	Hotkeys: End auto | Home arrow | PageUp hide | PageDown source | Insert copy fen.
]]

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local LocalPlayer = Players.LocalPlayer
local Chess = ReplicatedStorage:WaitForChild("Chess")
local Client = ReplicatedStorage:WaitForChild("ChessClient")

local function req(mod)
	return require(mod)
end

local Engine = req(Chess:WaitForChild("Engine"))
local Bot = req(Chess:WaitForChild("Bot"))
local VortexBot = req(Chess:WaitForChild("VortexBot"))
local App = req(Client:WaitForChild("App"))

local GENV = getgenv()
GENV.__RoCheeseGen = (GENV.__RoCheeseGen or 0) + 1
local GEN = GENV.__RoCheeseGen

local CFG = {
	W = 344,
	H = 424,
	MARGIN = 14,
	TOP = 84,
	DEPTH = 16,
	SECONDS = 0.8,
	CHILD_DEPTH = 8,
	CHILD_SECONDS = 0.06,
	VK_SHIFT = 160,
	PROMO = { q = 5, r = 4, b = 3, n = 2 },
	ARROW_1 = Color3.fromRGB(129, 182, 76),
	ARROW_2 = Color3.fromRGB(96, 156, 240),
	ARROW_3 = Color3.fromRGB(240, 200, 70),
	DELAY_MIN = 0,
	DELAY_MAX = 20,
	DELAY_STEP = 0.5,
}

local PRESETS = {
	legit = {
		label = "LEGIT",
		source = "local",
		mode = "elo",
		elo = 1500,
		noise = 0.32,
		blunder = 0.06,
		delayMin = 5,
		delayMax = 10,
	},
	raj = {
		label = "RAJ",
		source = "vortex",
		mode = "best",
		elo = 1800,
		noise = 0,
		blunder = 0,
		delayMin = 0.5,
		delayMax = 1.5,
	},
}

local RUNTIME = GENV.__RoCheese or {}
RUNTIME.SHOW_ARROW = RUNTIME.SHOW_ARROW ~= false
RUNTIME.AUTO = RUNTIME.AUTO == true
RUNTIME.HUMANIZE = RUNTIME.HUMANIZE ~= false
RUNTIME.SOURCE = RUNTIME.SOURCE == "vortex" and "vortex" or "local"
RUNTIME.MODE = RUNTIME.MODE == "best" and "best" or "elo"
RUNTIME.ELO = tonumber(RUNTIME.ELO) or 1500
RUNTIME.NOISE = tonumber(RUNTIME.NOISE) or 0.32
RUNTIME.BLUNDER = tonumber(RUNTIME.BLUNDER) or 0.06
RUNTIME.DELAY_MIN = math.clamp(tonumber(RUNTIME.DELAY_MIN) or 5, CFG.DELAY_MIN, CFG.DELAY_MAX)
RUNTIME.DELAY_MAX = math.clamp(tonumber(RUNTIME.DELAY_MAX) or 10, CFG.DELAY_MIN, CFG.DELAY_MAX)
RUNTIME.STYLE = RUNTIME.STYLE == "raj" and "raj" or (RUNTIME.STYLE == "legit" and "legit" or "custom")
GENV.__RoCheese = RUNTIME

local ARROW_KEYS = { "rc1", "rc2", "rc3" }

local S = {
	ply = -1,
	sessionId = nil,
	best = nil,
	san = nil,
	score = 0,
	depth = 0,
	second = nil,
	third = nil,
	secondSan = nil,
	thirdSan = nil,
	busy = false,
	autoAt = 0,
	thinkAt = 0,
	status = "loading",
}
GENV.__RoCheeseState = S

local LIVE = _G.STATE

local function alive()
	if GENV.__RoCheeseGen ~= GEN then
		return false
	end
	if typeof(LIVE) == "table" and type(LIVE.alive) == "function" then
		return LIVE.alive()
	end
	return true
end

local function cleanup(fn)
	if typeof(LIVE) == "table" and type(LIVE.onCleanup) == "function" then
		LIVE.onCleanup(fn)
	end
end

-- board ------------------------------------------------------------------------------------

local function clearArrows()
	local board = App.board
	if not board or type(board.setArrow) ~= "function" then
		return
	end
	for _, key in ARROW_KEYS do
		pcall(function()
			board:setArrow(key, nil)
			board:setTint(key, nil)
		end)
	end
	-- older build of this script used a single arrow key
	pcall(function()
		board:setArrow("rocheese", nil)
		board:setTint("rocheese", nil)
	end)
end

local function drawArrow(key, uci, color, tint)
	local board = App.board
	if not board then
		return
	end
	local from = Engine.algToIndex64(uci:sub(1, 2))
	local to = Engine.algToIndex64(uci:sub(3, 4))
	if not from or not to then
		return
	end
	pcall(function()
		board:setArrow(key, from, to, color, 0.12)
		if tint then
			board:setTint(key, to, color, 0.26)
		end
	end)
end

-- engine ----------------------------------------------------------------------------------

local function buildEngine(startFen, moves)
	local engine = Engine.new(startFen)
	for _, uci in ipairs(moves) do
		local mv = engine:moveFromUci(uci)
		if not mv then
			return nil
		end
		engine:play(mv)
	end
	return engine
end

local function sanOf(engine, uci)
	if not engine or not uci then
		return nil
	end
	local want = string.lower(uci)
	for _, mv in engine:legalMoves() do
		local u = engine:moveToUci(mv)
		if u and string.lower(u) == want then
			return engine:moveToSan(mv)
		end
	end
	return nil
end

local function analyseBest(startFen, moves)
	if RUNTIME.SOURCE == "vortex" then
		local ok, res = pcall(VortexBot.analyse, startFen, moves, CFG.SECONDS, CFG.DEPTH)
		if ok and type(res) == "table" and res.best then
			return res
		end
	end
	local ok, res = pcall(Bot.analyse, startFen, moves, CFG.SECONDS, CFG.DEPTH)
	if ok and type(res) == "table" then
		return res
	end
	return nil
end

-- ranks every continuation of the position, so the 2nd and 3rd arrows have a real order
local function rankContinuations(startFen, moves, skipUci)
	local engine = buildEngine(startFen, moves)
	if not engine then
		return nil
	end
	local scored = {}
	for _, mv in engine:legalMoves() do
		local uci = engine:moveToUci(mv)
		if not skipUci or string.lower(uci) ~= string.lower(skipUci) then
			local child = Engine.new(startFen)
			local ok = pcall(function()
				child:playUci(uci)
			end)
			if ok then
				local cres = Bot.analyse(child:getFen(), {}, CFG.CHILD_SECONDS, CFG.CHILD_DEPTH)
				table.insert(scored, {
					uci = uci,
					san = engine:moveToSan(mv),
					score = tonumber(cres and cres.score) or 0,
				})
			end
		end
	end
	table.sort(scored, function(a, b)
		return a.score > b.score
	end)
	return scored
end

local function pickMove(session)
	if RUNTIME.MODE == "elo" then
		local ok, uci = pcall(Bot.think, session.startFen, table.clone(session.ucis), {
			rating = RUNTIME.ELO,
			noise = RUNTIME.NOISE,
			blunder = RUNTIME.BLUNDER,
			time = 0.6,
			depth = 14,
		})
		if ok and type(uci) == "string" then
			return uci
		end
	end
	return S.best
end

-- playing ---------------------------------------------------------------------------------

local function tapInput()
	if not RUNTIME.HUMANIZE then
		return
	end
	if type(keyclick) ~= "function" then
		return
	end
	if type(isrbxactive) == "function" and not isrbxactive() then
		return
	end
	pcall(keyclick, CFG.VK_SHIFT)
end

local function playUci(uci)
	local session = App.session
	if not session or session.over then
		return false, "no game"
	end
	if not session:isMyTurn() then
		return false, "not your turn"
	end
	local from = Engine.algToIndex64(uci:sub(1, 2))
	local to = Engine.algToIndex64(uci:sub(3, 4))
	if not from or not to then
		return false, "bad uci"
	end
	local promo = CFG.PROMO[uci:sub(5, 5)] or 0
	local before = #session.ucis
	tapInput()
	local ok = false
	pcall(function()
		-- same call a square click makes: local apply + packed move to the server
		ok = App.onBoardMove(from, to, promo, nil)
	end)
	if not ok or #session.ucis == before then
		pcall(function()
			App.playMove(uci)
		end)
	end
	return true, uci
end

local function humanDelay()
	local lo = math.min(RUNTIME.DELAY_MIN, RUNTIME.DELAY_MAX)
	local hi = math.max(RUNTIME.DELAY_MIN, RUNTIME.DELAY_MAX)
	if hi - lo < 0.2 then
		return lo
	end
	return lo + math.random() * (hi - lo)
end

local function autopilotTick(session, ply)
	if not RUNTIME.AUTO then
		return
	end
	if session.over or not session:isMyTurn() then
		return
	end
	if session.viewPly ~= ply or S.busy or not S.best then
		return
	end
	local now = os.clock()
	if now < S.thinkAt then
		return
	end
	local uci = pickMove(session)
	if type(uci) ~= "string" then
		return
	end
	task.spawn(function()
		if not alive() then
			return
		end
		if App.session == session and not session.over and session:isMyTurn() then
			playUci(uci)
		end
	end)
end

-- gui -------------------------------------------------------------------------------------

local gui = nil
local panel = nil
local rows = {}
local btns = {}
local dot = nil
local dragged = false
local lastEval = nil

-- forward declarations: the buttons are wired before these are defined
local toggleAuto, playNow, copyPosition, refreshButtons, syncDelay, applyPreset, startBotGame

local C_BG = Color3.fromRGB(17, 18, 22)
local C_IDLE = Color3.fromRGB(36, 39, 46)
local C_ON = Color3.fromRGB(52, 104, 60)
local C_RAJ = Color3.fromRGB(148, 54, 48)
local C_TXT = Color3.fromRGB(236, 238, 241)
local C_DIM = Color3.fromRGB(160, 166, 176)
local C_ACC = Color3.fromRGB(255, 170, 0)

local function mkText(parent, y, h, size, color, bold, x)
	local t = Instance.new("TextLabel")
	t.BackgroundTransparency = 1
	t.Size = UDim2.fromOffset(CFG.W - 28 - (x or 14), h)
	t.Position = UDim2.fromOffset(x or 14, y)
	t.Font = bold and Enum.Font.GothamBold or Enum.Font.Gotham
	t.TextSize = size
	t.TextColor3 = color
	t.TextXAlignment = Enum.TextXAlignment.Left
	t.TextYAlignment = Enum.TextYAlignment.Center
	t.TextTruncate = Enum.TextTruncate.AtEnd
	t.Parent = parent
	return t
end

local function mkButton(y, h, text, width, onClick)
	local b = Instance.new("TextButton")
	b.BackgroundColor3 = C_IDLE
	b.BorderSizePixel = 0
	b.AutoButtonColor = false
	b.Size = UDim2.fromOffset(width or (CFG.W - 28), h)
	b.Position = UDim2.fromOffset(14, y)
	b.Font = Enum.Font.GothamBold
	b.TextSize = 12
	b.TextColor3 = C_TXT
	b.Text = text
	b.Parent = panel
	local c = Instance.new("UICorner")
	c.CornerRadius = UDim.new(0, 6)
	c.Parent = b
	local s = Instance.new("UIStroke")
	s.Color = Color3.fromRGB(255, 255, 255)
	s.Thickness = 1
	s.Transparency = 0.85
	s.Parent = b
	b.MouseButton1Click:Connect(onClick)
	b.MouseEnter:Connect(function()
		s.Transparency = 0.45
	end)
	b.MouseLeave:Connect(function()
		s.Transparency = 0.85
	end)
	return b
end

local function setBtn(b, on, text, onColor)
	b.BackgroundColor3 = on and (onColor or C_ON) or C_IDLE
	b.Text = text
end

local function mkSeparator(y)
	local sep = Instance.new("Frame")
	sep.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	sep.BackgroundTransparency = 0.87
	sep.BorderSizePixel = 0
	sep.Size = UDim2.fromOffset(CFG.W - 28, 1)
	sep.Position = UDim2.fromOffset(14, y)
	sep.Parent = panel
	return sep
end

local function mkDot(x, y, color, size)
	local d = Instance.new("Frame")
	d.BackgroundColor3 = color
	d.BorderSizePixel = 0
	d.Size = UDim2.fromOffset(size or 9, size or 9)
	d.Position = UDim2.fromOffset(x, y)
	d.Parent = panel
	local c = Instance.new("UICorner")
	c.CornerRadius = UDim.new(1, 0)
	c.Parent = d
	return d
end

-- delay row: MIN / MAX steppers, every press is one step, no dragging ----------------------

local function mkDelayRow(y)
	local row = Instance.new("Frame")
	row.BackgroundTransparency = 1
	row.Size = UDim2.fromOffset(CFG.W - 28, 26)
	row.Position = UDim2.fromOffset(14, y)
	row.Parent = panel

	local title = Instance.new("TextLabel")
	title.BackgroundTransparency = 1
	title.Size = UDim2.fromOffset(42, 26)
	title.Text = "DELAY"
	title.Font = Enum.Font.GothamBold
	title.TextSize = 11
	title.TextColor3 = C_DIM
	title.TextXAlignment = Enum.TextXAlignment.Left
	title.Parent = row

	local values = {}

	local function stepper(x, name, getVal, setVal)
		local lab = Instance.new("TextLabel")
		lab.BackgroundTransparency = 1
		lab.Size = UDim2.fromOffset(28, 26)
		lab.Position = UDim2.fromOffset(x, 0)
		lab.Text = name
		lab.Font = Enum.Font.GothamBold
		lab.TextSize = 11
		lab.TextColor3 = C_DIM
		lab.TextXAlignment = Enum.TextXAlignment.Left
		lab.Parent = row

		local function smallButton(px, sign)
			local b = Instance.new("TextButton")
			b.BackgroundColor3 = C_IDLE
			b.BorderSizePixel = 0
			b.AutoButtonColor = false
			b.Size = UDim2.fromOffset(22, 22)
			b.Position = UDim2.fromOffset(px, 2)
			b.Font = Enum.Font.GothamBold
			b.TextSize = 13
			b.TextColor3 = C_TXT
			b.Text = sign
			b.Parent = row
			local c = Instance.new("UICorner")
			c.CornerRadius = UDim.new(0, 5)
			c.Parent = b
			local s = Instance.new("UIStroke")
			s.Color = Color3.fromRGB(255, 255, 255)
			s.Thickness = 1
			s.Transparency = 0.85
			s.Parent = b

			local holding = false
			local function step()
				setVal(getVal() + (sign == "+" and CFG.DELAY_STEP or -CFG.DELAY_STEP))
			end
			b.MouseButton1Down:Connect(function()
				holding = true
				step()
				task.spawn(function()
					task.wait(0.35)
					local reps = 0
					while holding and reps < 60 do
						step()
						reps += 1
						task.wait(0.05)
					end
				end)
			end)
			b.MouseButton1Up:Connect(function()
				holding = false
			end)
			b.MouseLeave:Connect(function()
				holding = false
			end)
			return b
		end

		local value = Instance.new("TextLabel")
		value.BackgroundTransparency = 1
		value.Size = UDim2.fromOffset(38, 26)
		value.Position = UDim2.fromOffset(x + 52, 0)
		value.Font = Enum.Font.GothamBold
		value.TextSize = 12
		value.TextColor3 = C_TXT
		value.TextXAlignment = Enum.TextXAlignment.Center
		value.Parent = row
		values[name] = value

		smallButton(x + 30, "-")
		smallButton(x + 92, "+")
	end

	stepper(58, "MIN", function()
		return RUNTIME.DELAY_MIN
	end, function(v)
		RUNTIME.DELAY_MIN = math.clamp(v, CFG.DELAY_MIN, RUNTIME.DELAY_MAX - CFG.DELAY_STEP)
		RUNTIME.STYLE = "custom"
		syncDelay()
		refreshButtons()
	end)

	stepper(176, "MAX", function()
		return RUNTIME.DELAY_MAX
	end, function(v)
		RUNTIME.DELAY_MAX = math.clamp(v, RUNTIME.DELAY_MIN + CFG.DELAY_STEP, CFG.DELAY_MAX)
		RUNTIME.STYLE = "custom"
		syncDelay()
		refreshButtons()
	end)

	local function layout()
		values.MIN.Text = string.format("%.1fs", RUNTIME.DELAY_MIN)
		values.MAX.Text = string.format("%.1fs", RUNTIME.DELAY_MAX)
	end

	layout()
	return layout
end

local function buildGui()
	local playerGui = LocalPlayer:WaitForChild("PlayerGui")
	local old = playerGui:FindFirstChild("RoCheesePanel")
	if old then
		old:Destroy()
	end
	gui = Instance.new("ScreenGui")
	gui.Name = "RoCheesePanel"
	gui.ResetOnSpawn = false
	gui.IgnoreGuiInset = true
	gui.DisplayOrder = 999
	gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	gui.Parent = playerGui

	panel = Instance.new("Frame")
	panel.Name = "Panel"
	panel.BackgroundColor3 = C_BG
	panel.BackgroundTransparency = 0.04
	panel.BorderSizePixel = 0
	panel.Size = UDim2.fromOffset(CFG.W, CFG.H)
	panel.Position = UDim2.fromOffset(1920 - CFG.W - CFG.MARGIN, CFG.TOP)
	panel.Active = true
	panel.Parent = gui

	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 12)
	corner.Parent = panel

	local stroke = Instance.new("UIStroke")
	stroke.Color = C_ACC
	stroke.Thickness = 1
	stroke.Transparency = 0.45
	stroke.Parent = panel

	rows.title = mkText(panel, 10, 22, 15, C_ACC, true)
	rows.title.Size = UDim2.fromOffset(CFG.W - 56, 22)

	dot = mkDot(CFG.W - 32, 17, C_DIM)

	rows.status = mkText(panel, 34, 16, 11, C_DIM)
	rows.eval = mkText(panel, 54, 20, 14, C_TXT, true)
	rows.best = mkText(panel, 76, 18, 13, C_TXT)
	rows.rest = mkText(panel, 94, 18, 12, C_DIM)
	rows.game = mkText(panel, 114, 18, 11, C_DIM)

	mkSeparator(136)

	local half = math.floor((CFG.W - 32) / 2)

	btns.legit = mkButton(146, 30, "STYLE  LEGIT", half, function()
		applyPreset("legit")
	end)
	btns.raj = mkButton(146, 30, "STYLE  RAJ", half, function()
		applyPreset("raj")
	end)
	btns.raj.Position = UDim2.fromOffset(18 + half, 146)

	syncDelay = mkDelayRow(182)

	btns.best = mkButton(214, 26, "PLAY  BEST", half, function()
		RUNTIME.MODE = RUNTIME.MODE == "best" and "elo" or "best"
		RUNTIME.STYLE = "custom"
		refreshButtons()
	end)
	btns.elo = mkButton(214, 26, "PLAY  ELO", half, function()
		RUNTIME.MODE = RUNTIME.MODE == "elo" and "best" or "elo"
		RUNTIME.STYLE = "custom"
		refreshButtons()
	end)
	btns.elo.Position = UDim2.fromOffset(18 + half, 214)

	btns.engineLocal = mkButton(246, 26, "ENGINE  LOCAL", half, function()
		RUNTIME.SOURCE = RUNTIME.SOURCE == "local" and "vortex" or "local"
		RUNTIME.STYLE = "custom"
		S.ply = -1
		refreshButtons()
	end)
	btns.engineVortex = mkButton(246, 26, "ENGINE  VORTEX", half, function()
		RUNTIME.SOURCE = RUNTIME.SOURCE == "vortex" and "local" or "vortex"
		RUNTIME.STYLE = "custom"
		S.ply = -1
		refreshButtons()
	end)
	btns.engineVortex.Position = UDim2.fromOffset(18 + half, 246)

	btns.auto = mkButton(280, 34, "AUTOPLAY   OFF", nil, function()
		toggleAuto()
	end)

	btns.play = mkButton(320, 26, "PLAY NOW", half, function()
		playNow()
	end)
	btns.copy = mkButton(320, 26, "COPY FEN", half, function()
		copyPosition()
	end)
	btns.copy.Position = UDim2.fromOffset(18 + half, 320)

	btns.botgame = mkButton(352, 26, "BOT GAME  (practice)", nil, function()
		startBotGame()
	end)

	mkSeparator(384)

	-- legend for the three arrows
	mkDot(14, 398, CFG.ARROW_1, 9)
	local l1 = Instance.new("TextLabel")
	l1.BackgroundTransparency = 1
	l1.Size = UDim2.fromOffset(80, 20)
	l1.Position = UDim2.fromOffset(27, 392)
	l1.Font = Enum.Font.Gotham
	l1.TextSize = 11
	l1.TextColor3 = C_TXT
	l1.Text = "1 best"
	l1.Parent = panel

	mkDot(112, 398, CFG.ARROW_2, 9)
	local l2 = Instance.new("TextLabel")
	l2.BackgroundTransparency = 1
	l2.Size = UDim2.fromOffset(90, 20)
	l2.Position = UDim2.fromOffset(125, 392)
	l2.Font = Enum.Font.Gotham
	l2.TextSize = 11
	l2.TextColor3 = C_TXT
	l2.Text = "2 second"
	l2.Parent = panel

	mkDot(212, 398, CFG.ARROW_3, 9)
	local l3 = Instance.new("TextLabel")
	l3.BackgroundTransparency = 1
	l3.Size = UDim2.fromOffset(90, 20)
	l3.Position = UDim2.fromOffset(225, 392)
	l3.Font = Enum.Font.Gotham
	l3.TextSize = 11
	l3.TextColor3 = C_TXT
	l3.Text = "3 third"
	l3.Parent = panel

	rows.hint = mkText(panel, 416, 16, 10, Color3.fromRGB(112, 118, 128))
	rows.hint.Text = "drag title | End auto | Home arrow | PgUp hide"

	-- drag the panel by the title
	local dragging = false
	local dragFrom = Vector2.zero
	local dragStart = UDim2.fromOffset(0, 0)
	rows.title.Active = true
	rows.title.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
			dragging = true
			dragged = true
			dragFrom = Vector2.new(input.Position.X, input.Position.Y)
			dragStart = panel.Position
		end
	end)
	rows.title.InputChanged:Connect(function(input)
		if not dragging then
			return
		end
		if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
			local cam = workspace.CurrentCamera
			local vp = cam and cam.ViewportSize or Vector2.new(1920, 1080)
			local delta = Vector2.new(input.Position.X - dragFrom.X, input.Position.Y - dragFrom.Y)
			local x = math.clamp(dragStart.X.Offset + delta.X, 0, math.max(0, vp.X - CFG.W))
			local y = math.clamp(dragStart.Y.Offset + delta.Y, 0, math.max(0, vp.Y - CFG.H))
			panel.Position = UDim2.fromOffset(x, y)
		end
	end)
	rows.title.InputEnded:Connect(function()
		dragging = false
	end)

	cleanup(function()
		if gui then
			gui:Destroy()
			gui = nil
			panel = nil
		end
	end)
end

refreshButtons = function()
	if not btns.auto then
		return
	end
	setBtn(btns.auto, RUNTIME.AUTO, RUNTIME.AUTO and "AUTOPLAY   ON" or "AUTOPLAY   OFF")
	setBtn(btns.legit, RUNTIME.STYLE == "legit", "STYLE  LEGIT", C_ON)
	setBtn(btns.raj, RUNTIME.STYLE == "raj", "STYLE  RAJ", C_RAJ)
	setBtn(btns.best, RUNTIME.MODE == "best", "PLAY  BEST")
	setBtn(btns.elo, RUNTIME.MODE == "elo", "PLAY  ELO " .. RUNTIME.ELO)
	setBtn(btns.engineLocal, RUNTIME.SOURCE == "local", "ENGINE  LOCAL")
	setBtn(btns.engineVortex, RUNTIME.SOURCE == "vortex", "ENGINE  VORTEX")
end

-- placement -------------------------------------------------------------------------------

local function shiftPanel(dx, dy)
	if not panel or not panel.Parent then
		return
	end
	local cam = workspace.CurrentCamera
	local vp = cam and cam.ViewportSize or Vector2.new(1920, 1080)
	local x = math.clamp(panel.Position.X.Offset + dx, 0, math.max(0, vp.X - CFG.W))
	local y = math.clamp(panel.Position.Y.Offset + dy, 0, math.max(0, vp.Y - CFG.H))
	panel.Position = UDim2.fromOffset(x, y)
	dragged = true
end

local function layoutPanel()
	if dragged or not panel or not panel.Parent then
		return
	end
	local cam = workspace.CurrentCamera
	local vp = cam and cam.ViewportSize or Vector2.new(1920, 1080)
	panel.Position = UDim2.fromOffset(vp.X - CFG.W - CFG.MARGIN, CFG.TOP)
end

-- actions ---------------------------------------------------------------------------------

toggleAuto = function()
	RUNTIME.AUTO = not RUNTIME.AUTO
	S.thinkAt = os.clock() + (RUNTIME.AUTO and humanDelay() or 0)
	S.status = RUNTIME.AUTO and "autopilot on" or "autopilot off"
	refreshButtons()
end

playNow = function()
	local session = App.session
	if not session then
		S.status = "no game running"
		return
	end
	if S.busy or not S.best then
		S.status = "engine still thinking"
		return
	end
	local uci = pickMove(session)
	if type(uci) ~= "string" then
		S.status = "no move available"
		return
	end
	local ok, err = playUci(uci)
	S.status = ok and ("played " .. tostring(err)) or tostring(err)
end

copyPosition = function()
	local session = App.session
	if not session then
		S.status = "no game running"
		return
	end
	local fen = session.engine:getFen()
	local sans = table.concat(session.sans, " ")
	pcall(setclipboard, fen .. "\n" .. sans)
	S.status = "fen + moves copied"
end

startBotGame = function()
	local ok, err = pcall(function()
		App.startBotGame("jasper", "white")
	end)
	S.status = ok and "practice game started" or ("bot game failed: " .. tostring(err))
end

applyPreset = function(name)
	local preset = PRESETS[name]
	if not preset then
		return
	end
	RUNTIME.STYLE = name
	RUNTIME.SOURCE = preset.source
	RUNTIME.MODE = preset.mode
	RUNTIME.ELO = preset.elo
	RUNTIME.NOISE = preset.noise
	RUNTIME.BLUNDER = preset.blunder
	RUNTIME.DELAY_MIN = preset.delayMin
	RUNTIME.DELAY_MAX = preset.delayMax
	S.ply = -1
	S.status = name .. " preset"
	if syncDelay then
		syncDelay()
	end
	refreshButtons()
end

local setDelay = function(lo, hi)
	lo = math.clamp(tonumber(lo) or RUNTIME.DELAY_MIN, CFG.DELAY_MIN, CFG.DELAY_MAX)
	hi = math.clamp(tonumber(hi) or RUNTIME.DELAY_MAX, CFG.DELAY_MIN, CFG.DELAY_MAX)
	if hi < lo then
		lo, hi = hi, lo
	end
	RUNTIME.DELAY_MIN = math.floor(lo / CFG.DELAY_STEP + 0.5) * CFG.DELAY_STEP
	RUNTIME.DELAY_MAX = math.floor(hi / CFG.DELAY_STEP + 0.5) * CFG.DELAY_STEP
	if RUNTIME.DELAY_MAX - RUNTIME.DELAY_MIN < CFG.DELAY_STEP then
		RUNTIME.DELAY_MAX = RUNTIME.DELAY_MIN + CFG.DELAY_STEP
	end
	RUNTIME.STYLE = "custom"
	if syncDelay then
		syncDelay()
	end
	refreshButtons()
end

-- hotkeys ---------------------------------------------------------------------------------

local function onKey(input, processed)
	if processed or UserInputService:GetFocusedTextBox() then
		return
	end
	local code = input.KeyCode
	if code == Enum.KeyCode.End then
		toggleAuto()
	elseif code == Enum.KeyCode.Home then
		RUNTIME.SHOW_ARROW = not RUNTIME.SHOW_ARROW
		if not RUNTIME.SHOW_ARROW then
			clearArrows()
		end
		refreshButtons()
	elseif code == Enum.KeyCode.PageUp then
		if gui then
			gui.Enabled = not gui.Enabled
		end
	elseif code == Enum.KeyCode.PageDown then
		RUNTIME.SOURCE = RUNTIME.SOURCE == "local" and "vortex" or "local"
		RUNTIME.STYLE = "custom"
		S.ply = -1
		refreshButtons()
	elseif code == Enum.KeyCode.Insert then
		copyPosition()
	end
end

local prevConn = GENV.__RoCheeseConn
if prevConn then
	pcall(function()
		prevConn:Disconnect()
	end)
end
local conn = UserInputService.InputBegan:Connect(onKey)
GENV.__RoCheeseConn = conn
cleanup(function()
	conn:Disconnect()
end)

-- eval bar -------------------------------------------------------------------------------
-- the game already ships a chess-style eval bar next to the board, it is just never
-- switched on during a normal game. Feeding it our score wakes it up.
local GAME_KINDS = {
	bot = true,
	online = true,
	puzzle = true,
	rush = true,
}

local function pushEval()
	local session = App.session
	local kind = session and session.kind
	if not session or not kind or not GAME_KINDS[kind] then
		lastEval = nil
		return
	end
	if S.busy or not S.best then
		return
	end
	local score = S.score
	if lastEval == score then
		return
	end
	lastEval = score
	local mate = nil
	if math.abs(score) >= 29900 then
		mate = math.max(1, math.floor((30000 - math.abs(score)) / 100 + 0.5))
	end
	pcall(function()
		App.setEval(score, mate)
	end)
end

-- panel refresh ---------------------------------------------------------------------------

local function formatClock(ms)
	if type(ms) ~= "number" then
		return "-"
	end
	ms = math.max(0, ms)
	local total = math.floor(ms / 1000)
	return string.format("%d:%02d", math.floor(total / 60), total % 60)
end

local function evalText(score)
	local abs = math.abs(score)
	if abs >= 29900 then
		local moves = math.max(1, math.floor((30000 - abs) / 100 + 0.5))
		return string.format("M%d %s", moves, score > 0 and "white" or "black")
	end
	local pawns = score / 100
	return string.format("%s%.2f", pawns >= 0 and "+" or "", pawns)
end

local function refreshPanel(session, ply)
	if not panel or not panel.Parent then
		return
	end

	rows.title.Text = "ROCHESS ENGINE"
	rows.status.Text = S.status
	rows.eval.Text = session
		and (S.busy and "EVAL ...      DEPTH ..." or string.format("EVAL %s      DEPTH %d", evalText(S.score), S.depth))
		or "EVAL -      DEPTH 0"
	if S.busy then
		rows.best.Text = "1 thinking..."
		rows.rest.Text = ""
	else
		rows.best.Text = S.best and string.format("1  %s   (%s)", S.san or "?", S.best) or "1  -"
		local parts = {}
		if S.second then
			table.insert(parts, string.format("2  %s", S.secondSan or S.second))
		end
		if S.third then
			table.insert(parts, string.format("3  %s", S.thirdSan or S.third))
		end
		rows.rest.Text = table.concat(parts, "     ")
	end
	local myClock, oppClock = "-", "-"
	if session and session.clocks and session.myColor then
		myClock = formatClock(session.clocks[session.myColor])
		oppClock = formatClock(session.clocks[-session.myColor])
	end
	rows.game.Text = session
		and string.format("%s | ply %d | clock %s/%s",
			session.myColor == 1 and (session.white and session.white.display or "white")
				or (session.black and session.black.display or "black"),
			ply,
			myClock,
			oppClock)
		or "no game"

	if session and session.kind == "idle" then
		rows.status.Text = "no game running - press BOT GAME or Play in the game"
	end

	if dot then
		if S.busy then
			dot.BackgroundColor3 = C_ACC
		elseif S.best then
			dot.BackgroundColor3 = CFG.ARROW_1
		else
			dot.BackgroundColor3 = Color3.fromRGB(200, 80, 70)
		end
	end

	pushEval()
end

-- main loop -------------------------------------------------------------------------------

buildGui()
refreshButtons()
S.status = "ready"

GENV.__RoCheese.api = {
	auto = toggleAuto,
	play = playNow,
	copy = copyPosition,
	move = shiftPanel,
	delay = setDelay,
	preset = applyPreset,
	state = function()
		return {
			ply = S.ply,
			best = S.best,
			san = S.san,
			second = S.second,
			third = S.third,
			score = S.score,
			depth = S.depth,
			status = S.status,
			auto = RUNTIME.AUTO,
			style = RUNTIME.STYLE,
			source = RUNTIME.SOURCE,
			mode = RUNTIME.MODE,
			delayMin = RUNTIME.DELAY_MIN,
			delayMax = RUNTIME.DELAY_MAX,
		}
	end,
}

task.spawn(function()
	while alive() do
		local session = App.session
		local ply = session and #session.ucis or -1
		local sid = session and session.id or nil

		if session and sid ~= S.sessionId then
			S.sessionId = sid
			lastEval = nil
			pcall(function()
				Bot.clearTT()
			end)
		end

		if session and ply ~= S.ply then
			S.ply = ply
			-- a new position means a fresh think time, exactly like a human
			S.thinkAt = os.clock() + humanDelay()
			S.best = nil
			S.san = nil
			S.second = nil
			S.third = nil
			S.secondSan = nil
			S.thirdSan = nil
			clearArrows()
			if not S.busy then
				S.busy = true
				local startFen = session.startFen
				local moves = table.clone(session.ucis)
				local wantPly = ply

				task.spawn(function()
					-- main search: the best move and the eval
					local res = analyseBest(startFen, moves)
					if not alive() then
						return
					end
					S.busy = false
					local live = App.session
					if not live or #live.ucis ~= wantPly then
						S.ply = -1
						return
					end
					if type(res) ~= "table" then
						S.status = "engine failed"
						return
					end
					S.best = res.best
					S.score = tonumber(res.score) or 0
					S.depth = tonumber(res.depth) or 0
					S.status = "engine ready"
					local engine = buildEngine(startFen, moves)
					S.san = sanOf(engine, res.best)

					-- second pass: rank the continuations for the 2nd and 3rd arrows
					task.spawn(function()
						local ranked = rankContinuations(startFen, moves, res.best)
						if not alive() then
							return
						end
						local live2 = App.session
						if not live2 or #live2.ucis ~= wantPly or not ranked then
							return
						end
						S.second = ranked[1] and ranked[1].uci or nil
						S.secondSan = ranked[1] and ranked[1].san or nil
						S.third = ranked[2] and ranked[2].uci or nil
						S.thirdSan = ranked[2] and ranked[2].san or nil
					end)
				end)
			end
		end

		if session then
			local live = RUNTIME.SHOW_ARROW and session.viewPly == ply and not session.over
			if live then
				if S.best then
					drawArrow(ARROW_KEYS[1], S.best, CFG.ARROW_1, true)
				end
				if S.second then
					drawArrow(ARROW_KEYS[2], S.second, CFG.ARROW_2, false)
				end
				if S.third then
					drawArrow(ARROW_KEYS[3], S.third, CFG.ARROW_3, false)
				end
			else
				clearArrows()
			end
			autopilotTick(session, ply)
		end

		refreshPanel(session, ply)
		layoutPanel()
		task.wait(0.12)
	end
end)

cleanup(clearArrows)

print("[rocheese] ready - STYLE LEGIT/RAJ, DELAY min/max, AUTOPLAY; arrows: green best / blue second / yellow third")

How to Execute RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper

Step 1: Copy Code: Click the Copy Code button in the viewer above to grab the clean Lua loadstring.

Step 2: Launch Executor: Open a compatible, unpatched executor. If you need one, check our Roblox Executors Directory.

Step 3: Join Experience: Launch Roblox and join RoChess!.

Step 4: Attach & Run: Attach/inject your executor into the Roblox process, paste the code into the script tab, and press Execute.

Created By

smertni*qq
smertni*qq
Script Creator
View Author Profile

Roblox Experience

RoChess!
RoChess!
By Roblox
Active Players:8.9K
Play on Roblox

Features & Information

Execute RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper. Key system required, created by smertni*qq, updated Oct 20, 2026. Tested and working on supported executors.
#Opensource#chess#rochess#autoplay#rage

Script Changelog (1)

@smertni*qqOct 20, 2026

1.0

Community Comments (0)

No comments yet for this script.

Frequently Asked Questions

How do I execute RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper in Roblox?

To run RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper, copy the Lua loadstring from the code viewer above, launch Roblox and join RoChess!, attach a working executor (such as Potassium, Solara, or Delta from our Executors directory), paste the code into your executor console, and click Execute.

Is RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper keyless or does it require an access key?

RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper requires an access key. You can retrieve the official key directly using the Get Script Key link provided above.

Is RoChess! Script – ROCHESS ENGINE auto play (rage and legit) + game helper currently safe and working?

This script is active and operational. Always test scripts using a secondary (alt) Roblox account to safeguard your main profile against anti-cheat bans.