The underlying code for [[https://jimmorey.com/js/master.html| 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)=>`
`).join(""), // html for a score array [3,1] #black and #whites (requires css styles) displayCode:(code)=> code.map((x)=>`
${x==-1?" ":colour[x]}
`).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| 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)}; }