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.

|simple array code
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?"&nbsp;":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.

|complex part
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)};
}
mistermind_arrays.txt · Last modified: 2021/05/18 04:21 by morey
 
Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki