Advertisement
JustABigNerd

ComputerCraft Fallout 4 Hacking mini-game with Matrix BG

Feb 28th, 2025
9
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.91 KB | Gaming | 0 0
  1. -- Fallout 4 like hacking minigame, displays a list of words, one of which is the correct password.
  2. -- The player has 5 attempts to guess the correct password.
  3. -- The player can select a word by clicking on it twice, which will display the likeness of the selected word to the password.
  4. -- If the player selects the correct password, the game will display "Access Granted" and reboot the computer.
  5. -- If the player runs out of attempts, the game will display "LOCKED" and reboot the computer.
  6. -- Works best with a 4x2 monitor.
  7. -- Built for ComputerCraft in lua
  8.  
  9. -- Settings
  10. local wordLength = 8
  11. local maxAttempts = 5
  12. local resetTimeAfterWin = 10
  13. local minSimilarity = 1
  14. local displayMatrixScreen = true
  15. local minSimilarWords = 2
  16. local minModerateSimilarWords = 3
  17.  
  18. -- Internal settings
  19. local garbleToLength = 16
  20. local linesToDisplay = 18
  21. local wordsToGenerate = 20
  22. local chanceOfWordPerLine = 30 -- % chance of a word appearing on a line
  23.  
  24. -- Variables
  25. local attempts = 0
  26. local terminalLog = {}
  27. local terminalLogInput = ">"
  28. local cursorYPos = 1
  29. local password = ""
  30. local words = {}
  31. local wordCoordinates = {}
  32. local selectedWord = nil
  33. local lines = {}
  34. local tPixels = {}
  35. local cachedWords = {}
  36.  
  37. -- Check if monitors are connected
  38. local monitor = peripheral.find("monitor")
  39. if not monitor then
  40.     print("No monitor found")
  41.     return
  42. end
  43.  
  44. -- Setup matrix display
  45. local size = {monitor.getSize()}
  46.  
  47. for x = 1, size[1] - 1 do
  48.     tPixels[x] = {}
  49.     for y = 1, size[2] do
  50.         tPixels[x][y]=' '
  51.     end
  52. end
  53.  
  54. -- Put garbled text around a word, returns the word and the start and end positions of the word
  55. function garble(word)
  56.     local chars = {
  57.         "\"", "!", "@", "#", "%", "^", "&", "*", "(", ")", "_", "+", "-",
  58.         "=", "{", "}", "[", "]", "|", ":", ";", "'", "<", ">", ",", ".", "?",
  59.         "/", "`", "~"
  60.     }
  61.  
  62.     -- Place our word in the middle of a garbled string
  63.     local garbled = ""
  64.     for i = 1, garbleToLength do
  65.         garbled = garbled .. chars[math.random(1, #chars)]
  66.     end
  67.  
  68.     if word == "" then
  69.         return garbled
  70.     end
  71.  
  72.     local start = math.random(1, garbleToLength - wordLength)
  73.     garbled = string.sub(garbled, 1, start) .. word .. string.sub(garbled, start + wordLength + 1, garbleToLength)
  74.  
  75.     return garbled, start
  76. end
  77.  
  78. function writeLine(message)
  79.     monitor.setCursorPos(1, cursorYPos)
  80.     monitor.write(message)
  81.     cursorYPos = cursorYPos + 1
  82.     monitor.setCursorPos(1, cursorYPos)
  83. end
  84.  
  85. function shuffle(tbl)
  86.     for i = #tbl, 2, -1 do
  87.         local j = math.random(i)
  88.         tbl[i], tbl[j] = tbl[j], tbl[i]
  89.     end
  90.     return tbl
  91. end
  92.  
  93. -- Generate lines of text with garbled words
  94. -- Generate is separate from display so things don't change
  95. function generateLines()
  96.     local lines = {}
  97.  
  98.     table.insert(lines, 1, "Welcome to ROBCO Industries (TM) Termlink")
  99.     table.insert(lines, 2, "Password Required")
  100.     table.insert(lines, 3, "Attempts Remaining: " .. string.rep("#", (maxAttempts - attempts)))
  101.     table.insert(lines, 4, "")
  102.  
  103.     local line = ""
  104.     local displayedWords = 0
  105.     local linesDisplayed = 0
  106.     wordCoordinates = {}
  107.     local lineForPassword = math.random(1, linesToDisplay * 2)
  108.  
  109.     -- linesToDisplay * 2 as we display 2 lines at a time
  110.     while linesDisplayed < (linesToDisplay * 2) do
  111.         local randomNumber = decToHex(math.random(20000, 22000))
  112.         local word = words[displayedWords + 1]
  113.         local prefixLength = string.len(randomNumber) + 1 + string.len(line)
  114.  
  115.         -- Best way to ensure we definitely display the password, force it to be on a specific line
  116.         if lineForPassword == linesDisplayed + 1 then
  117.             word = password
  118.         end
  119.  
  120.         -- If we dont have a word or we dont hit the chance or we're out of words, then don't display a word.
  121.         -- Unless of course we're on the line that has the password
  122.         if
  123.             word == nil or ((math.random(1, 100) > chanceOfWordPerLine or displayedWords == #words) and linesDisplayed + 1 ~= lineForPassword) then
  124.             local garbledWord, start = garble("")
  125.             line = line .. randomNumber .. " " .. garbledWord .. " "
  126.         else
  127.             local garbledWord, start = garble(word)
  128.             line = line .. randomNumber .. " " .. garbledWord .. " "
  129.  
  130.             displayedWords = displayedWords + 1
  131.             wordCoordinates[word] = {prefixLength + start, #lines + 1}
  132.         end
  133.  
  134.         linesDisplayed = linesDisplayed + 1
  135.         if (linesDisplayed % 2) == 0 then
  136.             table.insert(lines, line)
  137.             line = ""
  138.         end
  139.     end
  140.  
  141.     return lines
  142. end
  143.  
  144. -- Display the lines of text
  145. function display()
  146.     resetDisplay()
  147.  
  148.     if #lines == 0 then
  149.         lines = generateLines()
  150.     end
  151.  
  152.     lines[3] = "Attempts Remaining: " .. string.rep("#", (maxAttempts - attempts))
  153.  
  154.     for i, line in ipairs(lines) do
  155.         writeLine(line)
  156.     end
  157.  
  158.     -- Display terminal log to the right - TODO: make this dynamic
  159.     for i, terminalLine in ipairs(terminalLog) do
  160.         local y = #lines - #terminalLog + i
  161.         monitor.setCursorPos(50, y - 1)
  162.         monitor.write(terminalLine)
  163.     end
  164.  
  165.     monitor.setCursorPos(50, #lines)
  166.     monitor.write(terminalLogInput)
  167. end
  168.  
  169. -- Highlight the selected word using the x,y position
  170. function highlightSelectedWord()
  171.     word = selectedWord
  172.  
  173.     if word == nil then
  174.         return
  175.     end
  176.  
  177.     x = wordCoordinates[word][1]
  178.     y = wordCoordinates[word][2]
  179.  
  180.     monitor.setCursorPos(x + 1, y)
  181.     monitor.setTextColor(colors.white)
  182.     monitor.setBackgroundColor(colors.green)
  183.     monitor.write(string.upper(word))
  184.     monitor.setTextColor(colors.green)
  185.     monitor.setBackgroundColor(colors.black)
  186. end
  187.  
  188. -- Initial display setup
  189. function resetDisplay()
  190.     -- Clear monitor
  191.     monitor.clear()
  192.     monitor.setCursorPos(1, 1)
  193.     cursorYPos = 1
  194.  
  195.     -- Set monitor settings
  196.     monitor.setTextScale(0.5)
  197.     monitor.setTextColor(colors.green)
  198.     monitor.setBackgroundColor(colors.black)
  199. end
  200.  
  201. -- Convert decimal to hex
  202. function decToHex(int)
  203.     local hex = string.format("%x", int)
  204.     if string.len(hex) == 1 then
  205.         hex = "0" .. hex
  206.     end
  207.     return "0x" .. string.upper(hex)
  208. end
  209.  
  210. -- Download the wordlist from the internet, discard words that are too short or too long
  211. function downloadWordList()
  212.     local url = "https://raw.githubusercontent.com/dolph/dictionary/master/popular.txt"
  213.     local response = http.get(url)
  214.     if response then
  215.         local file = fs.open("wordlist", "w")
  216.         local data = response.readAll()
  217.  
  218.         -- Remove all words shorter than 5 characters or longer than 9
  219.         for word in string.gmatch(data, "%a+") do
  220.             if string.len(word) >= 5 and string.len(word) <= 9 then
  221.                 file.write(word .. "\n")
  222.             end
  223.         end
  224.  
  225.         file.close()
  226.     end
  227. end
  228.  
  229. -- Does the table contain the value?
  230. function tableContainsItem(arr, val)
  231.     for i, v in ipairs(arr) do
  232.         if v == val then
  233.             return true
  234.         end
  235.     end
  236.     return false
  237. end
  238.  
  239. -- Merge tables
  240. function tableMerge(...)
  241.     local newTable = {}
  242.  
  243.     for i, t in ipairs({...}) do
  244.         for j, v in ipairs(t) do
  245.             table.insert(newTable, v)
  246.         end
  247.     end
  248.  
  249.     return newTable
  250. end
  251.  
  252. -- Get similar words to the password
  253. -- Tries to return 2 very similar words, 3 somewhat similar words, and the rest random
  254. function getSimilarWords()
  255.     local similarWords = {}
  256.     local somewhatSimilarWords = {}
  257.     local randomWords = {}
  258.     local loopLimit = 50000
  259.     local loopCount = 0
  260.  
  261.     while #similarWords < minSimilarWords and loopCount < loopLimit do
  262.         local word = getRandomWordOfLength(wordLength)
  263.         local sim  = getStringSimilarity(password, word)
  264.  
  265.         if
  266.             word ~= password
  267.             and sim >= #password - 2
  268.             and tableContainsItem(similarWords, word) == false
  269.         then
  270.             table.insert(similarWords, word)
  271.         end
  272.         loopCount = loopCount + 1
  273.     end
  274.  
  275.     loopCount = 0
  276.  
  277.     while #somewhatSimilarWords < minModerateSimilarWords and loopCount < loopLimit do
  278.         local word = getRandomWordOfLength(wordLength)
  279.         local sim = getStringSimilarity(password, word)
  280.  
  281.         if
  282.             word ~= password
  283.             and sim >= #password - 5
  284.             and tableContainsItem(similarWords, word) == false
  285.             and tableContainsItem(somewhatSimilarWords, word) == false
  286.         then
  287.             table.insert(somewhatSimilarWords, word)
  288.         end
  289.         loopCount = loopCount + 1
  290.     end
  291.  
  292.     loopCount = 0
  293.  
  294.     while (#randomWords + #somewhatSimilarWords + #similarWords) < wordsToGenerate do
  295.         local word = getRandomWordOfLength(wordLength)
  296.         local sim = getStringSimilarity(password, word)
  297.  
  298.         if
  299.             word ~= password
  300.             and sim > minSimilarity
  301.             and tableContainsItem(similarWords, word) == false
  302.             and tableContainsItem(somewhatSimilarWords, word) == false
  303.             and tableContainsItem(randomWords, word) == false
  304.         then
  305.             table.insert(randomWords, word)
  306.         end
  307.     end
  308.  
  309.     return shuffle(tableMerge(similarWords, somewhatSimilarWords, randomWords))
  310. end
  311.  
  312. -- Get the similarity between two strings (the number of characters that are the same)
  313. function getStringSimilarity(string1, string2)
  314.     local difference = 0
  315.     for i = 1, string.len(string1) do
  316.         if string.sub(string1, i, i) ~= string.sub(string2, i, i) then
  317.             difference = difference + 1
  318.         end
  319.     end
  320.  
  321.     return #string1 - difference
  322. end
  323.  
  324. -- Get a random word of a specific length
  325. -- Caches the words in memory
  326. function getRandomWordOfLength(length)
  327.     if #cachedWords == 0 then
  328.         local file = fs.open("wordlist", "r")
  329.         for line in file.readLine do
  330.             table.insert(cachedWords, string.upper(line))
  331.         end
  332.         file.close()
  333.     end
  334.  
  335.     local word = cachedWords[math.random(1, #cachedWords)]
  336.     while string.len(word) ~= length do
  337.         word = cachedWords[math.random(1, #cachedWords)]
  338.     end
  339.  
  340.     return word
  341. end
  342.  
  343. -- Get the word at the x,y coordinates
  344. function getWordAtCoordinates(x, y)
  345.     for word, coordinates in pairs(wordCoordinates) do
  346.         if x >= coordinates[1] and x <= coordinates[1] + string.len(word) and y == coordinates[2] then
  347.             return word
  348.         end
  349.     end
  350.     return nil
  351. end
  352.  
  353. -- Print a box with text in the middle of the screen
  354. function printOverlay(texts)
  355.     local numLines = #texts
  356.     local width = 0
  357.     for i = 1, numLines do
  358.         width = math.max(width, #texts[i])
  359.     end
  360.  
  361.     local screenCenterX = math.floor(size[1] / 2)
  362.     local screenCenterY = math.floor(size[2] / 2)
  363.     local boxHeight = numLines + 2
  364.     local boxWidth = width + 2
  365.     local boxX = screenCenterX - math.floor(boxWidth / 2)
  366.     local boxY = screenCenterY - math.floor(boxHeight / 2)
  367.  
  368.     -- Print box
  369.     for y = 0, boxHeight - 1 do
  370.         for x = 0, boxWidth - 1 do
  371.             monitor.setCursorPos(boxX + x, boxY + y)
  372.             monitor.setBackgroundColor(colors.cyan)
  373.             monitor.write(' ')
  374.         end
  375.     end
  376.  
  377.     -- Print text
  378.     for i = 1, numLines do
  379.         local text = texts[i]
  380.         local textX = screenCenterX - math.floor(#text / 2)
  381.         local textY = screenCenterY - math.floor(numLines / 2) + i - 1
  382.  
  383.         monitor.setCursorPos(textX, textY)
  384.         monitor.setBackgroundColor(colors.cyan)
  385.         monitor.setTextColor(colors.white)
  386.         monitor.write(text)
  387.     end
  388.  
  389.     monitor.setBackgroundColor(colors.black)
  390.     monitor.setTextColor(colors.lime)
  391. end
  392.  
  393. -- Render the matrix
  394. function matrixRender(overlayTexts)
  395.     monitor.clear()
  396.     monitor.setCursorPos(1, 1)
  397.  
  398.     if (displayMatrixScreen) then
  399.         for y = 1, #tPixels[1] do
  400.             monitor.setCursorPos(1, y)
  401.             if y ~= 1 then
  402.                 monitor.write('')
  403.             end
  404.             for x = 1, #tPixels do
  405.                 monitor.setCursorPos(x, y)
  406.                 monitor.setTextColor(colors.lime)
  407.                 monitor.write(tPixels[x][y])
  408.             end
  409.         end
  410.     end
  411.  
  412.     printOverlay(overlayTexts)
  413.  
  414.     monitor.setBackgroundColor(colors.black)
  415.     monitor.setTextColor(colors.lime)
  416. end
  417.  
  418. -- "Cycle the matrix" - This is the matrix effect
  419. function matrixCycle()
  420.     for x = 1, #tPixels do
  421.         for y = #tPixels[x], 2, -1 do
  422.             tPixels[x][y] = (tPixels[x][y - 1] == ' ' and ' ') or ((tPixels[x][y] ~= ' ' and tPixels[x][y]) or string.char(math.random(32, 126)))
  423.         end
  424.     end
  425. end
  426.  
  427. -- Start the matrix
  428. function matrixCreate()
  429.     tPixels[math.random(1, #tPixels)][1] = string.char(math.random(32, 126))
  430.     tPixels[math.random(1, #tPixels)][1] = ' '
  431.     tPixels[math.random(1, #tPixels)][1] = ' '
  432. end
  433.  
  434. -- Display the matrix for a specific amount of time
  435. function displayMatrix(seconds, overlayTexts)
  436.     local loops = seconds * 10
  437.  
  438.     for i = 1, loops do
  439.         matrixCycle()
  440.         matrixCreate()
  441.         matrixRender(overlayTexts)
  442.         sleep(.1)
  443.     end
  444. end
  445.  
  446. function termLog(message)
  447.     table.insert(terminalLog, message)
  448.  
  449.     if #terminalLog > linesToDisplay - 4 then
  450.         table.remove(terminalLog, 1)
  451.     end
  452. end
  453.  
  454. function termLogInput(message)
  455.     terminalLogInput = message
  456. end
  457.  
  458. -- Check if wordlist file exists
  459. if not fs.exists("wordlist") then
  460.     downloadWordList()
  461. end
  462.  
  463. -- Pick a password and find similar words
  464. password = getRandomWordOfLength(wordLength)
  465. words = getSimilarWords()
  466.  
  467. display()
  468.  
  469. print(password)
  470.  
  471. while true do
  472.     event, side, x, y = os.pullEvent("monitor_touch")
  473.     local selected = getWordAtCoordinates(x, y)
  474.  
  475.     if selected == nil then
  476.         selectedWord = nil
  477.         display()
  478.         termLogInput(">")
  479.     end
  480.  
  481.     if selected == selectedWord and selected ~= nil then
  482.         -- Word selected
  483.         termLog(">" .. selected)
  484.  
  485.         if selected == password then
  486.             termLog(">Access Granted")
  487.  
  488.             -- Set the redstone signal out the back of the computer
  489.             redstone.setOutput("back", true)
  490.  
  491.             for s = 1, resetTimeAfterWin do
  492.                 displayMatrix(1, {"HACKED", "Reset in " .. (resetTimeAfterWin - s) .. "s"})
  493.             end
  494.  
  495.             monitor.clear()
  496.             os.reboot()
  497.         else
  498.             termLog(">Entry Denied")
  499.             termLog(">Likeness=" .. getStringSimilarity(password, selected))
  500.             selectedWord = nil
  501.             attempts = attempts + 1
  502.             if attempts >= maxAttempts then
  503.  
  504.                 for s = 1, 5 do
  505.                     displayMatrix(1, {"LOCKED", "Reset in " .. (resetTimeAfterWin - s) .. "s"})
  506.                 end
  507.                 monitor.clear()
  508.                 os.reboot()
  509.             end
  510.         end
  511.     end
  512.  
  513.     selectedWord = selected
  514.  
  515.     if selected ~= nil then
  516.         termLogInput(">" .. selected)
  517.     end
  518.  
  519.     display()
  520.  
  521.     highlightSelectedWord()
  522. end
  523.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement