Hi there,
- What do you want to achieve? I want to create a puzzle flow algorithm in Lua, which can be used to solve puzzles like flow-based games where you connect colored dots with lines, ensuring that no lines overlap, and all dots are connected.
- What is the issue? I have never created such an algorithm before and don’t know where to start. I’m looking for guidance on how to approach this problem in Lua.
- What solutions have you tried so far? I have attempted to search for solutions and resources on the internet, including the Developer Hub, but I couldn’t find anything that specifically addresses my case or provides a step-by-step guide on how to create a puzzle flow algorithm in Lua.
Heres the code that I am working with currently, Its a piece of code I found on the internet while looking for this exact problem. Only problem with this code is I do not have any idea how the data they use is generated
Script Found on the Internet
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Color Palette
local ColorPalette = {
Index = 1,
BaseColors = {
Color3.new(1, 0, 0),
Color3.fromRGB(0, 120, 255),
Color3.fromRGB(189, 0, 255),
Color3.fromRGB(255, 154, 0),
Color3.fromRGB(1, 255, 31),
Color3.fromRGB(227, 255, 0)
},
Reset = function(self)
self.Index = 1
end
}
local RandomGenerator = Random.new()
function ColorPalette.NextColor(self)
if #self.BaseColors <= self.Index then
return Color3.fromHSV(RandomGenerator:NextNumber(), 1, 1)
end
self.Index = self.Index + 1
return self.BaseColors[self.Index]
end
-- Flow UI
local FlowUI = {
IsOpen = false,
Maid = nil,
OnFilled = nil
}
local function CreateGrid(width, height)
local grid = {}
for y = 1, height do
grid[y] = {}
for x = 1, width do
grid[y][x] = 0
end
end
return grid
end
function FlowUI.SetGrid(self, flowId, gridData)
self.FlowId = flowId
self.Grid = gridData
local gridHeight = #gridData
local gridWidth = #gridData[1]
self.GridClean = CreateGrid(gridWidth, gridHeight)
for y = 1, gridHeight do
for x = 1, gridWidth do
self.GridClean[y][x] = gridData[y][x]
end
end
ColorPalette:Reset()
self.Colors = {}
end
function FlowUI.ResetAlpha(self, value)
assert(value)
local gridWidth = #self.Grid[1]
for y = 1, #self.Grid do
for x = 1, gridWidth do
if self.Grid[y][x] == value then
self.Grid[y][x] = self.GridClean[y][x]
end
end
end
end
function FlowUI.Reset(self)
if self.Grid == nil then
return false
end
local gridWidth = #self.Grid[1]
for y = 1, #self.Grid do
for x = 1, gridWidth do
self.Grid[y][x] = self.GridClean[y][x]
end
end
end
FlowUI.Colors = {}
local FlowGui = script.FlowGui
function FlowUI.Draw(self)
local gridHeight = #self.Grid
local gridWidth = #self.Grid[1]
for y = 1, gridHeight do
for x = 1, gridWidth do
local cellId = ("%d.%d"):format(x, y)
local cell = FlowGui.Board.Inner:FindFirstChild(cellId)
if cell == nil then
cell = Instance.new("TextLabel")
cell.Name = cellId
cell.BorderSizePixel = 0
cell.BackgroundTransparency = 0
cell.TextSize = 20
cell.TextColor3 = Color3.new(0.1, 0.1, 0.1)
cell.Size = UDim2.new(1 / gridWidth * 0.5, 0, 1 / gridHeight * 0.5, 0)
cell.AnchorPoint = Vector2.new(0.5, 0.5)
cell.Position = UDim2.new((x - 1 + 0.5) / gridWidth, 0, (y - 1 + 0.5) / gridHeight, 0)
cell.Parent = FlowGui.Board.Inner
end
local cellValue = self.Grid[y][x]
if cellValue >= 0 then
local cellColor = self.Colors[cellValue]
if cellColor == nil then
cellColor = ColorPalette:NextColor()
self.Colors[cellValue] = cellColor
end
cell.BackgroundColor3 = cellColor
else
cell.BackgroundColor3 = Color3.new(0.1, 0.1, 0.1)
end
local textValue = self.Grid[y][x]
if textValue >= 0 then
cell.Text = tostring(textValue)
else
cell.Text = ""
end
end
end
end
function FlowUI.GetCoordFromMouse(self, mousePosition)
local boardAbsolutePosition = FlowGui.Board.AbsolutePosition
local boardAbsoluteSize = FlowGui.Board.AbsoluteSize
local x = math.floor(#self.Grid[1] * (mousePosition.X - boardAbsolutePosition.X) / boardAbsoluteSize.X) + 1
local y = math.floor(#self.Grid * (mousePosition.Y - boardAbsolutePosition.Y) / boardAbsoluteSize.Y) + 1
local value = self.Grid[y][x]
return x, y, value
end
local LocalPlayer = game:GetService("Players").LocalPlayer
local Maid = require(ReplicatedStorage.Maid)
local DirectionX = {0, 1, 0, -1}
local DirectionY = {-1, 0, 1, 0}
local UserInputService = game:GetService("UserInputService")
function FlowUI.Show(flowUI)
assert(flowUI.IsOpen == false)
flowUI.IsOpen = true
FlowGui.Parent = LocalPlayer.PlayerGui
flowUI.Maid = Maid.new()
local startX, startY, currentColor, startColor, endColor, drawing = nil, nil, nil, nil, nil, false
print(flowUI)
flowUI.Maid:GiveTask(FlowGui.Board.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
local x, y, cellValue = flowUI:GetCoordFromMouse(input.Position)
local grid = flowUI.Grid
local valid = x >= 1 and x <= #grid[1] and y >= 1 and y <= #grid and cellValue >= 0
if not valid then
return
end
startX, startY, currentColor = x, y, cellValue
startColor, endColor = startX, startY
flowUI:ResetAlpha(currentColor)
flowUI:Draw()
drawing = true
end
end))
local function OnInputEnded(input)
if not drawing then
return false
end
drawing = false
if startX == endColor and startY == startColor then
flowUI:ResetAlpha(currentColor)
else
local isValid = false
for i = 1, 4 do
local newX, newY = startX + DirectionX[i], startY + DirectionY[i]
local grid = flowUI.Grid
local valid = newX >= 1 and newX <= #grid[1] and newY >= 1 and newY <= #grid
if valid and flowUI.GridClean[newY][newX] == currentColor and (newX ~= endColor or newY ~= startColor) then
isValid = true
end
end
if not isValid then
flowUI:ResetAlpha(currentColor)
else
if not flowUI.OnConnection then
error("No OnConnection function set!")
end
flowUI.OnConnection()
end
end
flowUI:Draw()
end
flowUI.Maid:GiveTask(FlowGui.Board.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
OnInputEnded(input.Position)
end
end))
local function OnInputChanged(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
local x, y, cellValue = flowUI:GetCoordFromMouse(input.Position)
if x == startX + 1 then
local valid = y == startY
if x == startX - 1 then
valid = valid or y == startY
if x == startX then
valid = valid or y == startY + 1
valid = valid or y == startY - 1
end
elseif x == startX then
valid = valid or y == startY + 1
valid = valid or y == startY - 1
end
if not valid then
return false
end
if x ~= startX or y ~= startY then
flowUI.Grid[y][x] = currentColor
startX, startY = x, y
flowUI:Draw()
end
end
end
end
flowUI.Maid:GiveTask(FlowGui.Board.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
OnInputChanged(input)
end
end
)
)
flowUI.Maid:GiveTask(FlowGui.Board.Reset.MouseButton1Down:Connect(function()
flowUI:Reset()
flowUI:Draw()
end
)
)
flowUI.Maid:GiveTask(
FlowGui.Board.Cancel.MouseButton1Down:Connect(
function()
flowUI:Hide()
end
)
)
FlowUI:Draw()
if UserInputService:GetLastInputType() == Enum.UserInputType.Gamepad1 then
local Humanoid = LocalPlayer.Character:WaitForChild("Humanoid")
flowUI.Maid:GiveTask(
Humanoid.Changed:connect(
function()
Humanoid.Jump = false
end
)
)
local cursorX, cursorY, cursorLabel = 1, 1, nil
local function UpdateCursor()
local gridHeight, gridWidth = #flowUI.Grid, #flowUI.Grid[1]
if cursorX < 1 then
cursorX = 1
end
if gridWidth < cursorX then
cursorX = gridWidth
end
if cursorY < 1 then
cursorY = 1
end
if gridHeight < cursorY then
cursorY = gridHeight
end
if cursorLabel then
cursorLabel.Size = UDim2.new(1 / gridWidth * 0.5, 0, 1 / gridHeight * 0.5, 0)
end
local cell = FlowGui.Board.Inner:FindFirstChild((("%d.%d"):format(cursorX, cursorY)))
assert(cell)
cell.Size = UDim2.new(1 / gridWidth * 0.75, 0, 1 / gridHeight * 0.75, 0)
OnInputChanged(cell.AbsolutePosition + cell.AbsoluteSize * 0.5)
end
flowUI.Maid:GiveTask(
UserInputService.InputBegan:Connect(
function(input)
if input.UserInputType == Enum.UserInputType.Gamepad1 then
if input.KeyCode == Enum.KeyCode.DPadUp then
cursorY = cursorY - 1
UpdateCursor()
return
end
if input.KeyCode == Enum.KeyCode.DPadDown then
cursorY = cursorY + 1
UpdateCursor()
return
end
if input.KeyCode == Enum.KeyCode.DPadLeft then
cursorX = cursorX - 1
UpdateCursor()
return
end
if input.KeyCode == Enum.KeyCode.DPadRight then
cursorX = cursorX + 1
UpdateCursor()
return
end
if input.KeyCode == Enum.KeyCode.ButtonA then
local cell = FlowGui.Board.Inner:FindFirstChild((("%d.%d"):format(cursorX, cursorY)))
local x, y, cellValue = flowUI:GetCoordFromMouse(cell.AbsolutePosition + cell.AbsoluteSize * 0.5)
local grid = flowUI.Grid
local valid = x >= 1 and x <= #grid[1] and y >= 1 and y <= #grid
if not valid then
return
end
if cellValue < 0 then
return
end
startX, startY, currentColor = x, y, cellValue
startColor, endColor = startX, startY
flowUI:ResetAlpha(currentColor)
flowUI:Draw()
drawing = true
end
end
end
)
)
flowUI.Maid:GiveTask(
UserInputService.InputEnded:Connect(
function(input)
if input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.ButtonA then
local cell = FlowGui.Board.Inner:FindFirstChild((("%d.%d"):format(cursorX, cursorY)))
OnInputEnded(cell.AbsolutePosition + cell.AbsoluteSize * 0.5)
end
end
)
)
UpdateCursor()
end
end
local eventHandler = nil
local eventManager = nil
return {
Init = function(params)
eventHandler = params.Event
eventManager = params.em
function FlowUI.OnConnection()
print("Connection established.")
eventHandler:FireServer("m5z4m5of", FlowUI.FlowId, FlowUI.Grid)
end
function eventManager.ShowGrid(p24, p25)
FlowUI:SetGrid(p24, p25)
FlowUI:Show(FlowUI)
end
function eventManager.HideGrid(p26)
if FlowUI.IsOpen and (p26 == nil or FlowUI.FlowId == p26) then
FlowUI:Reset()
FlowUI:Hide()
end
end
return eventManager
end
}
Maid Script (REQUIRED)
local Maid = {} -- Create a table named 'Maid'
local TaskManager -- Declare a local variable 'TaskManager' to be defined later
do
-- Define a set of utility functions in a table
TaskManager = {
GiveTask = function(maid, task)
local taskCount = #maid.Tasks + 1 -- Calculate the index for the new task
maid.Tasks[taskCount] = task -- Add the task to the Maid's list
return taskCount -- Return the index of the task
end,
DoCleaning = function(maid)
local tasks = maid.Tasks
for index, task in pairs(tasks) do
if type(task) == "function" then
task() -- Execute the task if it's a function
elseif typeof(task) == "RBXScriptConnection" then
task:disconnect() -- Disconnect the connection if it's an RBXScriptConnection
else
task:Destroy() -- Destroy the task object if it's neither a function nor RBXScriptConnection
end
tasks[index] = nil -- Remove the task from the list
end
end
}
-- Alias Destroy function as DoCleaning
TaskManager.Destroy = TaskManager.DoCleaning
-- Create a metatable for the Maid
local MaidMetatable = {
__index = function(maid, key)
if TaskManager[key] then
return TaskManager[key] -- Use TaskManager functions if available
else
return maid.Tasks[key] -- Otherwise, access tasks by key
end
end,
__newindex = function(maid, key, value)
local tasks = maid.Tasks
if value == nil then
if type(tasks[key]) ~= "function" and tasks[key] then
if typeof(tasks[key]) == "RBXScriptConnection" then
tasks[key]:disconnect() -- Disconnect an RBXScriptConnection if it exists
else
tasks[key]:Destroy() -- Destroy the task object if it's not a function or RBXScriptConnection
end
end
else
tasks[key] = value -- Assign a new task to the specified key
end
end
}
-- Define a function to create a new Maid object
function TaskManager.new()
return setmetatable({
Tasks = {}, -- Initialize an empty table for tasks
Instances = {} -- Initialize an empty table for instances (not used in the provided code)
}, MaidMetatable)
end
end
-- Alias MakeMaid and new functions to Maid
Maid.MakeMaid = TaskManager.new
Maid.new = TaskManager.new
return Maid -- Return the Maid table as the module
How it should look, again this is my failed attempt at trying to do this
The GUI Structure is looking like this screenshot below, if u are a scripter u can guess how the UI is looking after the Puzzle Flow Initialized
Model of the UI
puzzle.rbxm (13.9 KB)
Additional Details: I’m specifically interested in understanding the logic and steps required to implement a puzzle flow algorithm in Lua. I’m not asking for a complete script or system design but rather seeking guidance on how to approach this problem and where to start my coding process.