bmwalter68

CA&B Alchemy

Feb 26th, 2022 (edited)
187
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 7.59 KB | None | 0 0
  1. local NUM_COLORS = 6
  2. local CODE_LENGTH = 4
  3.  
  4. -- kubejs:substrate_{type}
  5. local alchemy = {
  6.     ["igneous"] = { "andesite", "diorite", "granite", "cobblestone", "basalt", "gabbro"},
  7.     ["herbal"] = { "red", "orange", "yellow", "green", "blue", "magenta"},
  8.     ["volatile"] = { "blaze", "slime", "nether", "obsidian", "gunpowder", "prismarine" },
  9.     ["crystal"] = { "arcane", "apatite", "sulfur", "niter", "certus", "quartz"},
  10.     ["metal"] = { "zinc", "copper", "iron", "nickel", "lead", "gold" },
  11.     ["gem"] = { "cinnabar", "lapis", "sapphire", "emerald", "ruby", "diamond" }
  12. }
  13. --  ["chaos"] = { "igneous", "herbal", "volatile", "crystal", "metal", "gem" }
  14.  
  15. local deviceNames = {
  16.     chest = "metalbarrels:gold_tile_1",
  17.     slot1 = "moreminecarts:minecart_loader_te_0",
  18.     slot2 = "moreminecarts:minecart_loader_te_1",
  19.     slot3 = "moreminecarts:minecart_loader_te_2",
  20.     slot4 = "moreminecarts:minecart_loader_te_3",
  21.     unloader = "moreminecarts:minecart_unloader_te_0",
  22.     centrifuge = "thermal:machine_centrifuge_0"
  23. }
  24.  
  25. local devices = {}
  26.  
  27. local reagentSlots = {}
  28.  
  29. local function buildMinecraftName(short)
  30.     return string.format("kubejs:substrate_%s", short)
  31. end
  32.  
  33. local function wrapPeripherals()
  34.     devices["chest"] = peripheral.wrap(deviceNames["chest"])
  35.     devices["slots"] = {}
  36.     devices["slots"][1] = peripheral.wrap(deviceNames["slot1"])
  37.     devices["slots"][2] = peripheral.wrap(deviceNames["slot2"])
  38.     devices["slots"][3] = peripheral.wrap(deviceNames["slot3"])
  39.     devices["slots"][4] = peripheral.wrap(deviceNames["slot4"])
  40.     devices["unloader"] = peripheral.wrap(deviceNames["chest"])
  41.     devices["centrifuge"] = peripheral.wrap(deviceNames["chest"])
  42. end
  43.  
  44. -- For testing
  45. local code = {}
  46.  
  47. -- For testing
  48. local function getRandomCode()
  49.     local code = {};
  50.     local max = NUM_COLORS;
  51.     local min = 1;
  52.     local i, random;
  53.  
  54.     for i = 1, CODE_LENGTH do
  55.         random = math.floor(math.random(min, max))
  56.         table.insert(code, random)
  57.     end
  58.  
  59.     return code
  60. end
  61.  
  62. local function solveCode()
  63.     local combinations = {}
  64.     local candidateSolutions = {}
  65.     local currentGuess = {}
  66.     local nextGuesses = {}
  67.     local numGuesses = 0
  68.     local responsePegs = ""
  69.  
  70.     local function combinationRecursive(combinationLength, position, currentIn, elements)
  71.         local current = { table.unpack(currentIn) }
  72.         local i, element
  73.  
  74.         if position > combinationLength then
  75.             table.insert(combinations, current)
  76.             table.insert(candidateSolutions, current)
  77.             return
  78.         end
  79.  
  80.         for i, element in pairs(elements) do
  81.             current[position] = element;
  82.             combinationRecursive(combinationLength, position + 1, current, elements)
  83.         end
  84.  
  85.         return
  86.     end
  87.  
  88.     local function createSet()
  89.         local current = {}
  90.         local elements = {}
  91.         local i
  92.  
  93.         -- Reset the combination/solution tables
  94.         combinations = {}
  95.         candidateSolutions = {}
  96.  
  97.         for i = 1, CODE_LENGTH do
  98.             current[i] = 0
  99.         end
  100.  
  101.         for i = 1, NUM_COLORS do
  102.             table.insert(elements, i)
  103.         end
  104.  
  105.         combinationRecursive(CODE_LENGTH, 1, current, elements);
  106.     end
  107.  
  108.     local function doCodesMatch(code1, code2)
  109.         return table.concat(code1) == table.concat(code2)
  110.     end
  111.  
  112.     local function findCodeIndex(set, currentCode)
  113.         local index = 0
  114.         local i, entry
  115.  
  116.         for i, entry in pairs(set) do
  117.             if doCodesMatch(entry, currentCode) then
  118.                 index = i
  119.                 break
  120.             end
  121.         end
  122.  
  123.         return index
  124.     end
  125.  
  126.     local function removeCode(set, currentCode)
  127.         local index = findCodeIndex(set, currentCode)
  128.  
  129.         if index > 0 then
  130.             table.remove(set, index)
  131.         end
  132.     end
  133.  
  134.     local function checkCode(guessIn, codeIn)
  135.         local guess = { table.unpack(guessIn) }
  136.         local code = { table.unpack(codeIn) }
  137.         local result = ""
  138.         local i, j
  139.  
  140.         -- Count black pegs
  141.         for i = 1, CODE_LENGTH do
  142.             if guess[i] == code[i] then
  143.                 result = result .. "B"
  144.                 guess[i] = guess[i] * -1
  145.                 code[i] = code[i] * -1
  146.             end
  147.         end
  148.  
  149.         -- Count white pegs
  150.         for i = 1, CODE_LENGTH do
  151.             if code[i] > 0 then
  152.                 for j = 1, CODE_LENGTH do
  153.                     if code[i] == guess[j] then
  154.                         result = result .. "W"
  155.                         guess[j] = guess[j] * -1
  156.                         break
  157.                     end
  158.                 end
  159.             end
  160.         end
  161.  
  162.         return result;
  163.     end
  164.  
  165.     local function makeGuess(guess)
  166.         return checkCode(guess, code)
  167.     end
  168.  
  169.     local function pruneCodes(set, currentCode, currentResponse)
  170.         local index
  171.         local i, entry
  172.  
  173.         for i, entry in pairs(set) do
  174.             if currentResponse ~= checkCode(currentCode, entry) then
  175.                 table.remove(set, i)
  176.             end
  177.         end
  178.     end
  179.  
  180.     local function getMaxScore(inputMap)
  181.         local max = 0
  182.         local key, val
  183.  
  184.         for key, val in pairs(inputMap) do
  185.             if val > max then
  186.                 max = val
  187.             end
  188.         end
  189.  
  190.         return max
  191.     end
  192.  
  193.     local function getMinScore(inputMap)
  194.         local min = 0xffff
  195.         local key, val
  196.  
  197.         for key, val in pairs(inputMap) do
  198.             if val < min then
  199.                 min = val
  200.             end
  201.         end
  202.  
  203.         return min;
  204.     end
  205.  
  206.     local function minmax()
  207.         local result = {}
  208.         local scoreCount = {}
  209.         local score = {}
  210.         local max, min, i, j, key, val
  211.  
  212.         for i = 1, #combinations do
  213.             for j = 1, #candidateSolutions do
  214.  
  215.                 -- ComputerCraft watchdog will break if we don't churn the event queue periodically
  216.                 if os.epoch() % 5 == 0 then
  217.                     os.queueEvent("randomEvent")
  218.                     os.pullEvent()
  219.                 end
  220.  
  221.                 local pegScore = checkCode(combinations[i], candidateSolutions[j])
  222.                 if scoreCount[pegScore] ~= nil then
  223.                     scoreCount[pegScore] = scoreCount[pegScore] + 1
  224.                 else
  225.                     scoreCount[pegScore] = 1
  226.                 end
  227.             end
  228.  
  229.             max = getMaxScore(scoreCount)
  230.             score[combinations[i]] = max
  231.             scoreCount = {}
  232.         end
  233.  
  234.         min = getMinScore(score)
  235.  
  236.         for key, val in pairs(score) do
  237.             if val == min then
  238.                 table.insert(result, key)
  239.             end
  240.         end
  241.  
  242.         return result;
  243.     end
  244.  
  245.     local function getNextGuess(nextGuesses)
  246.         local i, guess, index
  247.  
  248.         for i, guess in pairs(nextGuesses) do
  249.             if findCodeIndex(candidateSolutions, guess) > 0 then
  250.                 return guess
  251.             end
  252.         end
  253.  
  254.         for i, guess in pairs(nextGuesses) do
  255.             if findCodeIndex(combinations, guess) > 0 then
  256.                 return guess
  257.             end
  258.         end
  259.  
  260.         return nil
  261.     end
  262.  
  263.     -- Create the master set of possible codes (1296 for six colors and four digit code)
  264.     createSet()
  265.  
  266.     -- Start with Knuth's initial guess
  267.     currentGuess = { 1, 1, 2, 2 }
  268.  
  269.     -- Profile runtime
  270.     local startTime = os.epoch()
  271.  
  272.     -- Guess until we win
  273.     while (true) do
  274.         -- Count number of guesses
  275.         numGuesses = numGuesses + 1
  276.  
  277.         -- Remove currentGuess from possible solutions
  278.         removeCode(combinations, currentGuess);
  279.         removeCode(candidateSolutions, currentGuess);
  280.  
  281.         -- Play the guess to get a response of colored and white pegs
  282.         responsePegs = makeGuess(currentGuess)
  283.  
  284.         -- Show our guess
  285.         print(string.format("%02d", numGuesses) .. ": " .. table.concat(currentGuess) .. " Result: " .. responsePegs)
  286.  
  287.         -- Check if we correctly guessed the code
  288.         if responsePegs == string.rep("B", CODE_LENGTH) then
  289.             print("    Correct code guessed in " .. numGuesses .. " guesses!")
  290.             break
  291.         end
  292.  
  293.         -- Remove any code from candidateSolutions that would not give the same response if it were the code
  294.         pruneCodes(candidateSolutions, currentGuess, responsePegs)
  295.  
  296.         -- Show current guess and result
  297.         print("    " .. #candidateSolutions .. " candidates remain")
  298.  
  299.         -- Calculate Minmax scores
  300.         nextGuesses = minmax()
  301.  
  302.         -- Select next guess
  303.         currentGuess = getNextGuess(nextGuesses)
  304.  
  305.         if currentGuess == nil then
  306.             error("ERROR: No possible combinations left!")
  307.         end
  308.     end
  309.  
  310.     -- Calculate runtime
  311.     local endTime = os.epoch()
  312.     print("Runtime: " .. (endTime - startTime) / 100000 .. "s")
  313. end
  314.  
  315. -- Seed the RNG
  316. -- math.randomseed(os.epoch())
  317.  
  318. -- Generate a random code
  319. -- code = getRandomCode()
  320. -- print("Code: " .. table.concat(code))
  321. -- solveCode()
  322.  
  323. wrapPeripherals()
Add Comment
Please, Sign In to add comment