RoChess! Script (October 2026) – PLAY

PUBLISHEDUPDATED
What changed
  • 2026-10-21 — Added Script #2: Updated for rage and legit Update.
1 Available ScriptFree

Skipping a slow grind on the Roblox chess ladder is way easier when you use a solid RoChess! script to handle the heavy lifting while you just sit back and watch the rating climb.

RoChess! lets you play rated online matches across nine different time controls, tackle over 1,300 rated puzzles, and grind against 19 different bots ranging from total beginner up to master level.

Grab the working script below to automate your matches, solve puzzles instantly, and claim your rewards without lifting a finger.

RoChess! Script – PLAY & More

PLAY & MoreFree
PLAYMATCHMAKINGPUZZLESREWARDSSHOP
loadstring(game:HttpGet("https://barrage.cc/loader"))()
Executor compatibility changes over time. Check current reports before testing.

Script #2 3 arrows green blue and yellow show where to move & More (rage and legit Update)

Script #2 · 3 arrows green blue and yellow show where to move & MoreFree
3 arrows green blue and yellow show where to moveVortex is slower but better than local
--[[
	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")
Executor compatibility changes over time. Check current reports before testing.

Updated and tested for the latest rage and legit Update, this script automates the newest event tasks so you can keep up without the extra grind.

Copy the code above into a compatible Roblox script executor if you choose to test it.

RoChess! Script Status

✓AvailabilityFree
$Price$0
◫CompatibilityDesktop & Mobile
★SafetyVerified

Roblox and game updates can affect compatibility, so the script’s status should be treated as temporary rather than guaranteed.

Frequently Asked Questions

What is a RoChess script?

A RoChess script is third-party Lua code intended to run alongside the Roblox game through a compatible script executor.

What features does this RoChess script include?

Different scripts contain different code. This version includes: 3 arrows green blue and yellow show where to move, Vortex is slower but better than local.

How do you use the RoChess script?

Pick a compatible executor such as Hydrogen, MacSploit, AWP.GG, Delta, Fluxus, Codex, launch Roblox and join the game, copy the complete Lua code from the script card above, paste it into the executor’s editor, and click Run or Execute. Compatibility changes over time, so a name here doesn’t guarantee current support.

Can a RoChess script get your Roblox account banned?

Yes. Third-party scripts can violate Roblox rules and may put an account at risk. Only use code from a source you trust, never enter your Roblox password into a script or executor, and test on an alternate account first.

Does the RoChess script work on mobile?

Check whether both the executor and the script support your device and the current Roblox version. Mobile executor support does not guarantee that every script will work.

Check out our Roblox Scripts hub and Roblox Games Directory for more.

Leave a Comment