The underlying code for mister relies on arrays. (btw how to use mistermind is somewhat explained in mistermind)
In Javascript, one way to encode the patterns is by using arrow functions.
const mister = { //object to hold combination functions useful for MisterMind blank:(n)=> Array.from({length:n},()=> -1), // an array of n blanks encoded with -1 values rand:(n,range)=> Array.from({length:n},()=> Math.floor(Math.random()*range)), // fills a parallel array with random vals codeSet:(n,range)=> Array.from({length:Math.pow(range, n)}, (_,i)=> Array.from(baseChange(i,n,range))), // complete set of codeds counts: function(code, range){ // count the number of each type in the range (used for scoring) let counts = Array.from({length:range},()=>0); code.forEach(x => counts[x]++); return counts; }, displayScore:(sc)=> Array.from({length:sc[0]+sc[1]},(_,i)=> (i>=sc[0]?0:1)).map((x)=>`<div class='score s${x}'></div>`).join(""), // html for a score array [3,1] #black and #whites (requires css styles) displayCode:(code)=> code.map((x)=>`<div class='peg p${(x>=0 ? x:"empty")}'>${x==-1?" ":colour[x]}</div>`).join("") // html for a peg array (requires css styles) }
Unpacking some the arrow functions requires a bit of practice. Here, Array.from() and .map() are used a lot in cools ways. The generator function with yield is also slick. mister.counts arrow function in its foreach() seems much less cool in comparison.
code | description |
---|---|
(n)=> Array.from({length:n},()=> -1) | produces [-1,-1,… ,-1], n negative ones |
(n,range)=> Array.from({length:n},()=> Math.floor(Math.random()*range)) | fills the array of length n with random numbers |
function* baseChange(num,n,base){ // generates the first "n" digits of "num" in base "base" for (let i = 0;i<n;i++){ yield num % base; num = Math.floor(num/base); } } | This generator function allows for the digits to be yielded one at a time |
(_,i)=> Array.from(baseChange(i,n,range))) | turns the index i into an array of n digits in base “range” |
(n,range)=> Array.from({length:Math.pow(range, n)}, (_,i)=> Array.from(baseChange(i,n,range))) | produces the digits for all requred indices |
(sc)=> Array.from({length:sc[0]+sc[1]},(_,i)=> (i>=sc[0]?0:1)) | for [3,2], an array of size 5 (3+2) three 1s and two 0s is produced [1,1,1,0,0] |
(code)=> code.map((x)=>/*__html for each entry based on x__*/).join("") | all the array elements' html joined together |
The rest is a bit more involved. An interesting piece uses reduce() to turn an array in to a number. In this case, the function totalIt() reduces an array to the sum of all the array entries. As a further example of map(), mister.score() has a good example of .map( (element, index)⇒ ).
The .filter() mechanism in Javascript is used mister.filter() to eliminate elements from a list which do not agree with a fact. The facts in this example refer to a code / score pair.
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)}; }