Skip to content

Instantly share code, notes, and snippets.

@mLuby
Last active November 24, 2022 03:56
Show Gist options
  • Save mLuby/fa23b780b71a5f1b98fe312420a1052e to your computer and use it in GitHub Desktop.
Save mLuby/fa23b780b71a5f1b98fe312420a1052e to your computer and use it in GitHub Desktop.
Help travellers get from one side of a bridge to another with only a lantern to guide them. (Solution is random/brute force, not exhaustive/optimal.)
// Set up initial constraints.
GOAL_TOTAL_TIME = parseInt(process.argv.slice(2)[1]); // if optimal time unknown, set 0.
MAX_ATTEMPTS = parseInt(process.argv.slice(2)[2]); // I don't recommend going past 50000.
try { INITIAL_AT_RISK_PEOPLE = JSON.parse(process.argv.slice(2)[0]); } finally {
if (typeof INITIAL_AT_RISK_PEOPLE === 'undefined' || isNaN(GOAL_TOTAL_TIME) || isNaN(MAX_ATTEMPTS) || !Array.isArray(INITIAL_AT_RISK_PEOPLE)) {
console.log("\nInvalid command format. The command should look like this (space-sensitive):\n");
console.log(" node ZombieBridge.js [1,2,5,10] 17 1000\n");
return; // Return early since the command won't work.
} else console.log("INITIAL CONSTRAINTS:", {INITIAL_AT_RISK_PEOPLE,GOAL_TOTAL_TIME, MAX_ATTEMPTS}, "\n");
}
// Set up variables to track the best solution we find as well as how many attempts we've tried.
attemptNumber = 0;
bestSolutionSteps = [];
bestTotalTimeFound = Infinity // Will decrease over time as better solutions are found.
while (bestTotalTimeFound > GOAL_TOTAL_TIME && attemptNumber++ < MAX_ATTEMPTS) {
// Whe starting a new attempt we reset the time and steps and we move people and the lantern back
// to their starting positions.
timeUsed = 0;
stepsUsed = []
atRiskPeople = [...INITIAL_AT_RISK_PEOPLE];
safePeople = [];
isLanternSafe = false;
// Keep doing the following until everybody is safe.
while(atRiskPeople.length > 0) {
// If the lantern is on the "safe" side, pick one person to walk back to the "at risk" side,
// otherwise pick two people to walk from the "at risk" side to the "safe" side.
walkers = isLanternSafe ? pick(1, safePeople) : pick(2, atRiskPeople);
// Add the time of the slowest walker to the total time for this solution.
timeUsed = timeUsed + max(walkers);
// Store the text representing the step (and output it) so in case it's the best solution we
// can show it at the end.
stepsUsed.push(`${pad("#"+attemptNumber, "#"+MAX_ATTEMPTS)} @ ${pad(timeUsed, GOAL_TOTAL_TIME || "999")}min: \t ${pad(atRiskPeople, INITIAL_AT_RISK_PEOPLE)} \t AT RISK \t ${isLanternSafe ? '<' : '-'}-${pad(walkers, INITIAL_AT_RISK_PEOPLE, "-")}-${isLanternSafe ? '-' : '>'} \t SAFE \t ${safePeople}`);
console.log(stepsUsed[stepsUsed.length-1]);
// Move the walker(s) and the lantern to their new side.
walkers.forEach(walker => (isLanternSafe ? atRiskPeople : safePeople).push(walker));
isLanternSafe = !isLanternSafe;
}
// If this solution was better than the best solution we've found so far, save this one as the
// new best solution.
if (timeUsed < bestTotalTimeFound) {
bestTotalTimeFound = timeUsed;
bestSolutionSteps = stepsUsed;
}
console.log(""); // Add a carriage return/new line to visually separate each solution's steps.
}
// Now that we've either achieved the goal time or run out of attempts, output the best solution.
console.log("BEST SOLUTION FOUND:\n" + bestSolutionSteps.join("\n"));
// These are utility functions. Note that pick chooses n items _at random_.
function max (pair) { return Math.max(...pair); }
function pick (howMany, toRemoveFrom) { var results = []; while (results.length < howMany) { var indexToRemove = Math.floor(Math.random()*toRemoveFrom.length); var itemToRemove = toRemoveFrom[indexToRemove]; toRemoveFrom.splice(indexToRemove,1); results.push(itemToRemove); } return results; }
function pad(string, maxStr, char=" ") { while (String(string).length < String(maxStr).length) string = char + string; return string; }
@mLuby
Copy link
Author

mLuby commented Nov 24, 2022

Example output from running node ZombieBridge.js [1,2,5,10] 17 1000: (also runnable online here.)

    #1 @  5min: 	     1,10 	 AT RISK 	 -------2,5-> 	 SAFE 	 
    #1 @ 10min: 	     1,10 	 AT RISK 	 <--------5-- 	 SAFE 	 2
    #1 @ 20min: 	        1 	 AT RISK 	 ------10,5-> 	 SAFE 	 2
    #1 @ 25min: 	        1 	 AT RISK 	 <--------5-- 	 SAFE 	 2,10
    #1 @ 30min: 	          	 AT RISK 	 -------5,1-> 	 SAFE 	 2,10

    #2 @ 10min: 	      1,2 	 AT RISK 	 ------10,5-> 	 SAFE 	 
    #2 @ 20min: 	      1,2 	 AT RISK 	 <-------10-- 	 SAFE 	 5
    #2 @ 30min: 	        1 	 AT RISK 	 ------10,2-> 	 SAFE 	 5
    #2 @ 32min: 	        1 	 AT RISK 	 <--------2-- 	 SAFE 	 5,10
    #2 @ 34min: 	          	 AT RISK 	 -------1,2-> 	 SAFE 	 5,10

    #3 @  2min: 	     5,10 	 AT RISK 	 -------2,1-> 	 SAFE 	 
    #3 @  4min: 	     5,10 	 AT RISK 	 <--------2-- 	 SAFE 	 1
    #3 @ 14min: 	        2 	 AT RISK 	 ------10,5-> 	 SAFE 	 1
    #3 @ 15min: 	        2 	 AT RISK 	 <--------1-- 	 SAFE 	 10,5
    #3 @ 17min: 	          	 AT RISK 	 -------2,1-> 	 SAFE 	 10,5

bestSolution:
    #3 @  2min: 	     5,10 	 AT RISK 	 -------2,1-> 	 SAFE 	 
    #3 @  4min: 	     5,10 	 AT RISK 	 <--------2-- 	 SAFE 	 1
    #3 @ 14min: 	        2 	 AT RISK 	 ------10,5-> 	 SAFE 	 1
    #3 @ 15min: 	        2 	 AT RISK 	 <--------1-- 	 SAFE 	 10,5
    #3 @ 17min: 	          	 AT RISK 	 -------2,1-> 	 SAFE 	 10,5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment