
Keyless

-- This file is licensed under the Mozilla Public License version 2.0
-- The performance is good enough so I thought that I may as well just release it for anyone to use.
local Players = game:GetService("Players")
local Web = game:GetService("HttpService")
local player = Players.LocalPlayer
local parent = player:WaitForChild("PlayerGui")
local frame = parent:WaitForChild("InGame"):WaitForChild("Frame")
local SUGGESTIONS = 6
local SOURCE = "https://raw.githubusercontent.com/dwyl/english-words/refs/heads/master/words_dictionary.json"
local dictionary = {}
local prefixCache = {}
local screen = Instance.new("ScreenGui")
screen.Name = "AutoCompleteSuggestions"
screen.ResetOnSpawn = false
screen.Parent = parent
local labels = {}
for i = 1, SUGGESTIONS do
local lbl = Instance.new("TextLabel")
lbl.Name = "Suggestion"..i
lbl.Size = UDim2.fromOffset(220, 28)
lbl.Position = UDim2.new(1, -230, 0, 10 + (i-1)*30)
lbl.BackgroundTransparency = 1
lbl.TextColor3 = Color3.new(1, 1, 1)
lbl.Font = Enum.Font.GothamBold
lbl.TextSize = 22
lbl.TextXAlignment = Enum.TextXAlignment.Right
lbl.Text = ""
lbl.Parent = screen
labels[i] = lbl
end
task.spawn(function()
local success, content = pcall(function()
return game:HttpGet(SOURCE)
end)
if not success then
warn("Failed to download dictionary")
return
end
local data = nil
local ok = pcall(function()
data = Web:JSONDecode(content)
end)
if not ok or type(data) ~= "table" then
warn("Dictionary decode failed")
return
end
for word in pairs(data) do
dictionary[#dictionary+1] = word:lower()
end
table.sort(dictionary)
end)
-- Basically just binary search
local function findRange(prefix)
local low, high = 1, #dictionary
local startIdx, endIdx
while low <= high do
local mid = math.floor((low + high) / 2)
if dictionary[mid]:sub(1, #prefix) < prefix then
low = mid + 1
else
high = mid - 1
end
end
startIdx = low
low, high = startIdx, #dictionary
while low <= high do
local mid = math.floor((low + high) / 2)
if dictionary[mid]:sub(1, #prefix) == prefix then
low = mid + 1
else
high = mid - 1
end
end
endIdx = high
if startIdx > endIdx then
return nil, nil
end
return startIdx, endIdx
end
-- Yes, I know this is a slightly autistic way of designing the UI.
local function getWord()
local wordFrame = frame:FindFirstChild("CurrentWord")
if not wordFrame or not wordFrame.Visible then
return ""
end
local letters = {}
local numbered = {}
for _, child in ipairs(wordFrame:GetChildren()) do
local idx = tonumber(child.Name)
if idx and child:FindFirstChild("Letter") then
numbered[#numbered+1] = {idx = idx, obj = child}
end
end
table.sort(numbered, function(a,b) return a.idx < b.idx end)
for _, entry in ipairs(numbered) do
local letter = entry.obj.Letter.Text
if type(letter) == "string" then
letters[#letters+1] = letter
end
end
return table.concat(letters):lower()
end
local lastInput = ""
local function update()
local prefix = getWord()
if prefix == lastInput then
return
end
lastInput = prefix
for i = 1, SUGGESTIONS do
labels[i].Text = ""
end
if prefix == "" then
return
end
local range = prefixCache[prefix]
local sIdx, eIdx -- Start and end indexes, sorry for the naming
if range then
sIdx, eIdx = range[1], range[2]
else
sIdx, eIdx = findRange(prefix)
if sIdx then
prefixCache[prefix] = {sIdx, eIdx}
if #prefixCache > 4 then
for k in pairs(prefixCache) do
prefixCache[k] = nil
break
end
end
end
end
if not sIdx then
return
end
local count = 0
for i = sIdx, eIdx do
if count >= SUGGESTIONS then break end
local w = dictionary[i]
if w and w ~= prefix then
count += 1
labels[count].Text = w
end
end
end
local display = frame:WaitForChild("CurrentWord")
display.ChildAdded:Connect(update)
display.ChildRemoved:Connect(update)



If you want to remove any words you can do so by modifying the dictionary JSON link with one of your own. This supports dictionary tier lists so if enough suggestions aren't found in the first one it'll go onto the next, this is useful when you want to mostly use a subset of words.
top tier programming
try do guess do drawing!