Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Fallout 4 like hacking minigame, displays a list of words, one of which is the correct password.
- -- The player has 5 attempts to guess the correct password.
- -- The player can select a word by clicking on it twice, which will display the likeness of the selected word to the password.
- -- If the player selects the correct password, the game will display "Access Granted" and reboot the computer.
- -- If the player runs out of attempts, the game will display "LOCKED" and reboot the computer.
- -- Works best with a 4x2 monitor.
- -- Built for ComputerCraft in lua
- -- Settings
- local wordLength = 8
- local maxAttempts = 5
- local resetTimeAfterWin = 120
- local minSimilarity = 1
- local displayMatrixScreen = true
- local minSimilarWords = 2
- local minModerateSimilarWords = 3
- local doorSide = "bottom"
- -- Internal settings
- local garbleToLength = 16
- local linesToDisplay = 18
- local wordsToGenerate = 20
- local chanceOfWordPerLine = 30 -- % chance of a word appearing on a line
- -- Variables
- local attempts = 0
- local terminalLog = {}
- local terminalLogInput = ">"
- local cursorYPos = 1
- local password = ""
- local words = {}
- local wordCoordinates = {}
- local selectedWord = nil
- local lines = {}
- local tPixels = {}
- local cachedWords = {}
- -- Check if monitors are connected
- local monitor = peripheral.find("monitor")
- if not monitor then
- print("No monitor found")
- return
- end
- -- Setup matrix display
- local size = {monitor.getSize()}
- for x = 1, size[1] - 1 do
- tPixels[x] = {}
- for y = 1, size[2] do
- tPixels[x][y]=' '
- end
- end
- -- Put garbled text around a word, returns the word and the start and end positions of the word
- function garble(word)
- local chars = {
- "\"", "!", "@", "#", "%", "^", "&", "*", "(", ")", "_", "+", "-",
- "=", "{", "}", "[", "]", "|", ":", ";", "'", "<", ">", ",", ".", "?",
- "/", "`", "~"
- }
- -- Place our word in the middle of a garbled string
- local garbled = ""
- for i = 1, garbleToLength do
- garbled = garbled .. chars[math.random(1, #chars)]
- end
- if word == "" then
- return garbled
- end
- local start = math.random(1, garbleToLength - wordLength)
- garbled = string.sub(garbled, 1, start) .. word .. string.sub(garbled, start + wordLength + 1, garbleToLength)
- return garbled, start
- end
- function writeLine(message)
- monitor.setCursorPos(1, cursorYPos)
- monitor.write(message)
- cursorYPos = cursorYPos + 1
- monitor.setCursorPos(1, cursorYPos)
- end
- function shuffle(tbl)
- for i = #tbl, 2, -1 do
- local j = math.random(i)
- tbl[i], tbl[j] = tbl[j], tbl[i]
- end
- return tbl
- end
- -- Function to pulse a bundled cable signal [Custom]
- local function pulseBundledSignal(color)
- for i = 1, 8 do
- redstone.setBundledOutput(doorSide, color)
- sleep(0.2)
- redstone.setBundledOutput(doorSide, 0)
- sleep(0.2)
- end
- end
- -- Generate lines of text with garbled words
- -- Generate is separate from display so things don't change
- function generateLines()
- local lines = {}
- table.insert(lines, 1, "Welcome to TPDCO Industries (TM) Termlink")
- table.insert(lines, 2, "Password Required")
- table.insert(lines, 3, "Attempts Remaining: " .. string.rep("#", (maxAttempts - attempts)))
- table.insert(lines, 4, "")
- local line = ""
- local displayedWords = 0
- local linesDisplayed = 0
- wordCoordinates = {}
- local lineForPassword = math.random(1, linesToDisplay * 2)
- -- linesToDisplay * 2 as we display 2 lines at a time
- while linesDisplayed < (linesToDisplay * 2) do
- local randomNumber = decToHex(math.random(20000, 22000))
- local word = words[displayedWords + 1]
- local prefixLength = string.len(randomNumber) + 1 + string.len(line)
- -- Best way to ensure we definitely display the password, force it to be on a specific line
- if lineForPassword == linesDisplayed + 1 then
- word = password
- end
- -- If we dont have a word or we dont hit the chance or we're out of words, then don't display a word.
- -- Unless of course we're on the line that has the password
- if
- word == nil or ((math.random(1, 100) > chanceOfWordPerLine or displayedWords == #words) and linesDisplayed + 1 ~= lineForPassword) then
- local garbledWord, start = garble("")
- line = line .. randomNumber .. " " .. garbledWord .. " "
- else
- local garbledWord, start = garble(word)
- line = line .. randomNumber .. " " .. garbledWord .. " "
- displayedWords = displayedWords + 1
- wordCoordinates[word] = {prefixLength + start, #lines + 1}
- end
- linesDisplayed = linesDisplayed + 1
- if (linesDisplayed % 2) == 0 then
- table.insert(lines, line)
- line = ""
- end
- end
- return lines
- end
- -- Display the lines of text
- function display()
- resetDisplay()
- if #lines == 0 then
- lines = generateLines()
- end
- lines[3] = "Attempts Remaining: " .. string.rep("#", (maxAttempts - attempts))
- for i, line in ipairs(lines) do
- writeLine(line)
- end
- -- Display terminal log to the right - TODO: make this dynamic
- for i, terminalLine in ipairs(terminalLog) do
- local y = #lines - #terminalLog + i
- monitor.setCursorPos(50, y - 1)
- monitor.write(terminalLine)
- end
- monitor.setCursorPos(50, #lines)
- monitor.write(terminalLogInput)
- end
- -- Highlight the selected word using the x,y position
- function highlightSelectedWord()
- word = selectedWord
- if word == nil then
- return
- end
- x = wordCoordinates[word][1]
- y = wordCoordinates[word][2]
- monitor.setCursorPos(x + 1, y)
- monitor.setTextColor(colors.white)
- monitor.setBackgroundColor(colors.green)
- monitor.write(string.upper(word))
- monitor.setTextColor(colors.green)
- monitor.setBackgroundColor(colors.black)
- end
- -- Initial display setup
- function resetDisplay()
- -- Clear monitor
- monitor.clear()
- monitor.setCursorPos(1, 1)
- cursorYPos = 1
- -- Set monitor settings
- monitor.setTextScale(0.5)
- monitor.setTextColor(colors.green)
- monitor.setBackgroundColor(colors.black)
- end
- -- Convert decimal to hex
- function decToHex(int)
- local hex = string.format("%x", int)
- if string.len(hex) == 1 then
- hex = "0" .. hex
- end
- return "0x" .. string.upper(hex)
- end
- -- Download the wordlist from the internet, discard words that are too short or too long
- function downloadWordList()
- local url = "https://raw.githubusercontent.com/dolph/dictionary/master/popular.txt"
- local response = http.get(url)
- if response then
- local file = fs.open("wordlist", "w")
- local data = response.readAll()
- -- Remove all words shorter than 5 characters or longer than 9
- for word in string.gmatch(data, "%a+") do
- if string.len(word) >= 5 and string.len(word) <= 9 then
- file.write(word .. "\n")
- end
- end
- file.close()
- end
- end
- -- Does the table contain the value?
- function tableContainsItem(arr, val)
- for i, v in ipairs(arr) do
- if v == val then
- return true
- end
- end
- return false
- end
- -- Merge tables
- function tableMerge(...)
- local newTable = {}
- for i, t in ipairs({...}) do
- for j, v in ipairs(t) do
- table.insert(newTable, v)
- end
- end
- return newTable
- end
- -- Get similar words to the password
- -- Tries to return 2 very similar words, 3 somewhat similar words, and the rest random
- function getSimilarWords()
- local similarWords = {}
- local somewhatSimilarWords = {}
- local randomWords = {}
- local loopLimit = 50000
- local loopCount = 0
- while #similarWords < minSimilarWords and loopCount < loopLimit do
- local word = getRandomWordOfLength(wordLength)
- local sim = getStringSimilarity(password, word)
- if
- word ~= password
- and sim >= #password - 2
- and tableContainsItem(similarWords, word) == false
- then
- table.insert(similarWords, word)
- end
- loopCount = loopCount + 1
- end
- loopCount = 0
- while #somewhatSimilarWords < minModerateSimilarWords and loopCount < loopLimit do
- local word = getRandomWordOfLength(wordLength)
- local sim = getStringSimilarity(password, word)
- if
- word ~= password
- and sim >= #password - 5
- and tableContainsItem(similarWords, word) == false
- and tableContainsItem(somewhatSimilarWords, word) == false
- then
- table.insert(somewhatSimilarWords, word)
- end
- loopCount = loopCount + 1
- end
- loopCount = 0
- while (#randomWords + #somewhatSimilarWords + #similarWords) < wordsToGenerate do
- local word = getRandomWordOfLength(wordLength)
- local sim = getStringSimilarity(password, word)
- if
- word ~= password
- and sim > minSimilarity
- and tableContainsItem(similarWords, word) == false
- and tableContainsItem(somewhatSimilarWords, word) == false
- and tableContainsItem(randomWords, word) == false
- then
- table.insert(randomWords, word)
- end
- end
- return shuffle(tableMerge(similarWords, somewhatSimilarWords, randomWords))
- end
- -- Get the similarity between two strings (the number of characters that are the same)
- function getStringSimilarity(string1, string2)
- local difference = 0
- for i = 1, string.len(string1) do
- if string.sub(string1, i, i) ~= string.sub(string2, i, i) then
- difference = difference + 1
- end
- end
- return #string1 - difference
- end
- -- Get a random word of a specific length
- -- Caches the words in memory
- function getRandomWordOfLength(length)
- if #cachedWords == 0 then
- local file = fs.open("wordlist", "r")
- for line in file.readLine do
- table.insert(cachedWords, string.upper(line))
- end
- file.close()
- end
- local word = cachedWords[math.random(1, #cachedWords)]
- while string.len(word) ~= length do
- word = cachedWords[math.random(1, #cachedWords)]
- end
- return word
- end
- -- Get the word at the x,y coordinates
- function getWordAtCoordinates(x, y)
- for word, coordinates in pairs(wordCoordinates) do
- if x >= coordinates[1] and x <= coordinates[1] + string.len(word) and y == coordinates[2] then
- return word
- end
- end
- return nil
- end
- -- Print a box with text in the middle of the screen
- function printOverlay(texts)
- local numLines = #texts
- local width = 0
- for i = 1, numLines do
- width = math.max(width, #texts[i])
- end
- local screenCenterX = math.floor(size[1] / 2)
- local screenCenterY = math.floor(size[2] / 2)
- local boxHeight = numLines + 2
- local boxWidth = width + 2
- local boxX = screenCenterX - math.floor(boxWidth / 2)
- local boxY = screenCenterY - math.floor(boxHeight / 2)
- -- Print box
- for y = 0, boxHeight - 1 do
- for x = 0, boxWidth - 1 do
- monitor.setCursorPos(boxX + x, boxY + y)
- monitor.setBackgroundColor(colors.cyan)
- monitor.write(' ')
- end
- end
- -- Print text
- for i = 1, numLines do
- local text = texts[i]
- local textX = screenCenterX - math.floor(#text / 2)
- local textY = screenCenterY - math.floor(numLines / 2) + i - 1
- monitor.setCursorPos(textX, textY)
- monitor.setBackgroundColor(colors.cyan)
- monitor.setTextColor(colors.white)
- monitor.write(text)
- end
- monitor.setBackgroundColor(colors.black)
- monitor.setTextColor(colors.lime)
- end
- -- Render the matrix
- function matrixRender(overlayTexts)
- monitor.clear()
- monitor.setCursorPos(1, 1)
- if (displayMatrixScreen) then
- for y = 1, #tPixels[1] do
- monitor.setCursorPos(1, y)
- if y ~= 1 then
- monitor.write('')
- end
- for x = 1, #tPixels do
- monitor.setCursorPos(x, y)
- monitor.setTextColor(colors.lime)
- monitor.write(tPixels[x][y])
- end
- end
- end
- printOverlay(overlayTexts)
- monitor.setBackgroundColor(colors.black)
- monitor.setTextColor(colors.lime)
- end
- -- "Cycle the matrix" - This is the matrix effect
- function matrixCycle()
- for x = 1, #tPixels do
- for y = #tPixels[x], 2, -1 do
- tPixels[x][y] = (tPixels[x][y - 1] == ' ' and ' ') or ((tPixels[x][y] ~= ' ' and tPixels[x][y]) or string.char(math.random(32, 126)))
- end
- end
- end
- -- Start the matrix
- function matrixCreate()
- tPixels[math.random(1, #tPixels)][1] = string.char(math.random(32, 126))
- tPixels[math.random(1, #tPixels)][1] = ' '
- tPixels[math.random(1, #tPixels)][1] = ' '
- end
- -- Display the matrix for a specific amount of time
- function displayMatrix(seconds, overlayTexts)
- local loops = seconds * 10
- for i = 1, loops do
- matrixCycle()
- matrixCreate()
- matrixRender(overlayTexts)
- sleep(.1)
- end
- end
- function termLog(message)
- table.insert(terminalLog, message)
- if #terminalLog > linesToDisplay - 4 then
- table.remove(terminalLog, 1)
- end
- end
- function termLogInput(message)
- terminalLogInput = message
- end
- -- Check if wordlist file exists
- if not fs.exists("wordlist") then
- downloadWordList()
- end
- -- Pick a password and find similar words
- password = getRandomWordOfLength(wordLength)
- words = getSimilarWords()
- display()
- print(password)
- while true do
- event, side, x, y = os.pullEvent("monitor_touch")
- local selected = getWordAtCoordinates(x, y)
- if selected == nil then
- selectedWord = nil
- display()
- termLogInput(">")
- end
- if selected == selectedWord and selected ~= nil then
- -- Word selected
- termLog(">" .. selected)
- if selected == password then
- termLog(">Access Granted")
- -- Set the redstone signal out the back of the computer
- -- redstone.setOutput("back", true)
- pulseBundledSignal(colors.green)
- for s = 1, resetTimeAfterWin do
- displayMatrix(1, {"HACKED", "Reset in " .. (resetTimeAfterWin - s) .. "s"})
- end
- monitor.clear()
- pulseBundledSignal(colors.red)
- os.reboot()
- else
- termLog(">Entry Denied")
- termLog(">Likeness=" .. getStringSimilarity(password, selected))
- selectedWord = nil
- attempts = attempts + 1
- if attempts >= maxAttempts then
- for s = 1, 5 do
- displayMatrix(1, {"LOCKED", "Reset in " .. (resetTimeAfterWin - s) .. "s"})
- end
- monitor.clear()
- pulseBundledSignal(colors.red)
- os.reboot()
- end
- end
- end
- selectedWord = selected
- if selected ~= nil then
- termLogInput(">" .. selected)
- end
- display()
- highlightSelectedWord()
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement