function totalIt(sofar,next){ return sofar+next;} //used to sum up all the elements of an array (see reduce()) mister.score = function(code,attempt,range){ //score needs to use mister.counts so I put it here. let cCode = mister.counts(code,range); let cAttempt = mister.counts(attempt,range); let total = cCode.map((x,i)=> Math.min(x,cAttempt[i])).reduce(totalIt); // # unordered matches...uses colour counts let exact = code.map((x,i)=> x==attempt[i]?1:0).reduce(totalIt); // # exact matches return [exact,total-exact]; } mister.filter = function(list,fact,range){ return list.filter(x => mister.score(x,fact.code,range).map((s,i)=>Math.abs(s-fact.score[i])).reduce(totalIt) ==0) } mister.makePuzzle = function(size,range,guess=[],max=2){ // create a puzzle (the facts) for "size" holes with "range" number of colours // this is fragile code in that has a bunch of assumptions about the args.... // by having a few good guesses preloaded helps to not have boring facts first let answerA = mister.rand(size,range); //set one answer that will end up part of the solution space let list = mister.codeSet(size,range); let facts =[]; let cur = 0; while (list.length > max) { // the solution space needs to be reduced by adding more facts if (cur >= guess.length) guess.push([]); // add a blank guess to be filled if (guess[cur].length == 0) guess[cur] = mister.rand(size,range); // get a new guess let newFact = {"code":guess[cur],"score":mister.score(answerA, guess[cur],range)}; // other solutions must get the same scoring as answerA let revised = mister.filter(list, newFact ,range); // compute possible solutions with this new fact if (revised.length < list.length) { list = revised; facts.push(newFact); cur++; } else { guess[cur] = []; //didn't work...the new fact didn't reduce the solution space } } return {"facts":facts,"solns":list,"choices":Array.from({length:range},(_,i)=> i)}; }