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)};
}