5

Ok, consider this:

I have a big array containing arrays, -1, a and b.

The -1 means the field is empty:

var board = [
    [-1,-1, a],
    [-1,-1, b],
    [ b,-1, a]
]

Now i want to check smaller arrays agains this:

var solutions = [
    [
        [1, 1, 1]
    ],
    [
        [1],
        [1],
        [1]
    ],
    [
        [1],
        [0,1],
        [0,0,1]
    ],
    [
        [0,0,1],
        [0,1],
        [1]
    ]
]

To see if one existing value from board match the pattern in solutions.


Does a match any of pattern?
Does b match any of the pattern?


Can any of you see a better way than making a crazy nested loop:

var q,w,e,r,t,y;

q=w=e=r=t=y=0;

for( ; q < 3; q++ ) {
    for( ; w < 3; w++ ) {
        for( ; e < SOLUTIONS.length; e++ ) {
            .... and so on...
        }
    }
}

In this example I have used tic-tac-toe.

But i could be anything.

Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
  • I assume, for a tic-tac-toe, in the `solution` patterns you do not want to match zeros but empty cells. – akuhn Nov 23 '12 at 19:18
  • you could try to convert the arrays to 1 level deep to make the comparation easier. But I don't know any array shallower snippet... :( – ajax333221 Nov 24 '12 at 17:38

5 Answers5

3

What you can do is to compile the patterns for speed. The same way as same languages allow regular expressions to be compiled for speed.

function compile(pattern) {
    var code = "matcher = function(a) { return "
    var first = null
    for (var n = 0; n < pattern.length; n++) {
        for (var m = 0; m < pattern[n].length; m++) {
            if (pattern[n][m] == 0) continue
            var nm = "["+n+"]["+m+"]"
            if (first == null) {
                code += "a" + nm + " != -1";
                first = " && a" + nm + " == "
            }
            code += first + "a" + nm
      }
    }
    code += "; }";
    eval(code);
    return matcher
}

So what is this doing?

For example

    compile([[1],[0,1],[0,0,1]]).toString()

will create the following function

    "function (a) { return a[0][0] != -1 && a[0][0] == a[0][0] && a[0][0] == a[1][1] && a[0][0] == a[2][2]; }"

So how do you use it?

To match positions on your board use it as follows

var patterns = solutions.collect(function(each) { return compile(each); })
var matches = patterns.any(function(each) { return each(board); })

 

NB, the very last snipped above assumes you're using one of the many popular higher-order programming libraries, as for example lodash, to provide collect and any functions on the array prototype, if not use plain old for loops instead.

akuhn
  • 27,477
  • 2
  • 76
  • 91
0

No, you only do need three nested loops: One to loop over your patterns, and two to loop your two-dimensional playing field:

function checkPatterns(patterns, player, field) {
    pattern: for (var p=0; p<patterns.length; p++) {
        for (var i=0; i<patterns[p].length; i++)
            for (var j=0; j<patterns[p][i].length; j++)
                if (patterns[p][i][j] && player !== field[i][j])
                    continue pattern;
        // else we've matched all
        return p;
    }
    // else none was found
    return -1;
}
function getSolution(player)
    return SOLUTIONS[checkPatterns(SOLUTIONS, player, currentBOARD)] || null;
}

OK, you might need a fourth loop for the players (players.any(getSolution)), but that doesn't make it any crazier and might be inlined for only two players as well.

However, it might be easier than formulating "pattern arrays" to construct algorithms for the patterns themselves:

function hasWon(player, field) {
    vert: for (var i=0; i<field.length; i++) {
        for (var j=0; j<field[i].length; j++)
            if (field[i][j] !== player)
                continue vert;
        return "vertical";
    }
    hor: for (var j=0; j<field[0].length; j++) {
        for (var i=0; i<field.length; i++)
            if (field[i][j] !== player)
                continue hor;
        return "horizontal";
    }
    for (var i=0, l=true, r=true, l=field.length; i<l; i++) {
        l == l && field[i][i] === player;
        r == r && field[l-i-1][l-i-1] === player;
    }
    if (l || r)
        return "diagonal";
    return null;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

You can have your board to be a string:

 var board = 
   "-1,-1,a,
    -1,-1,b,
     b,-1,a"

and your solutions can be an array of strings (similar to the board)

var solutions = [ 
   "1,1,1,
    0,0,0,
    0,0,0"
    ,
   "1,0,0,
    0,1,0,
    0,0,1"

]

then for comparison, replace the -1 and b with 0s and a with 1s then simply compare the strings

this is much faster than having 10 different loop inside another loop

Ardavan Kalhori
  • 1,664
  • 1
  • 18
  • 32
  • That was my first thougt aswell, but that means that you need to define the pattern for each row. your solution wont match if the middle row is 1,1,1 unless you also add 0,0,0,1,1,1,0,0,0, to the match. This would work for 3x field, but expanding to 9x9 would give a lot of solutions. Also you need to make a copy of the playingfield for each check to do the replacements and since javascript makes references of array, you need to clone the array for each check, thus looping trough all rows and cols to create a new array (or use c = board.splice(0) for readability, not speed). – Hugo Delsing Nov 27 '12 at 23:16
0

You will always need loops to go trough it all. You can just make it easier to read and more flexible. The code below will work for any number of rows/cols larger then 1 and with a simple adjustment also for more then 2 players.

var board1 = [
[-1,-1, 'a'],
[-1,-1, 'b'],
['b',-1, 'a']
];
var board2 = [
['a','a', 'a'],
[-1,-1, 'b'],
['b',-1, 'a']
];
var board3 = [
[-1,'b', 'a'],
[-1,'b', 'b'],
['b','b', 'a']
];
var board4 = [
['a',-1, 'a'],
[-1,'a', 'b'],
['b',-1, 'a']
];

var solutions = [
[
    [1, 1, 1]
],
[
    [1],
    [1],
    [1]
],
[
    [1],
    [0,1],
    [0,0,1]
],
[
    [0,0,1],
    [0,1],
    [1]
]
];

function checkForWinner(playfield) {
    var sl = solutions.length; //solutions
    var bl = playfield.length; //board length
    var bw = playfield[0].length; //board width
    while(sl--) {
        //foreach solution
        var l = solutions[sl].length;

        if (l==1) {
            //horizontal
            //loop trough board length to find a match
            var tl = bl;
            while(tl--) {
                var pat = playfield[tl].join('')
                var r = checkRow(pat)
                if (r!==false)
                    return r;
            }
        } else {
            //vertical or diagonal
            var l1 = solutions[sl][0].length;
            var l2 = solutions[sl][1].length;

            if (l1==l2) {
                //vertical                  
                var tw = bw;
                while (tw--) {
                    //loop for each column  
                    var pat = "";   
                    var tl = l;
                    while(tl--) {
                        //loop for vertical
                        pat += playfield[tl][tw];
                    }

                    var r = checkRow(pat)
                    if (r!==false)
                        return r;
                }

            } else {
                //diagonal
                var pat = "";
                while(l--) {
                    //loop for vertical
                    var tw = solutions[sl][l].length;
                    while (tw--) {
                        //loop for horizontal                       
                        if (solutions[sl][l][tw]!=0)
                            pat += playfield[l][tw];
                    }
                }

                var r = checkRow(pat)
                if (r!==false)
                    return r;
            }
        }
    }
    return 'no winner';
}

function checkRow(pat) {
    if (!(pat.indexOf('a')>=0 || pat.indexOf('-1')>=0)) {
        //only b on row. player B won
        return 'B';
    }

    if (!(pat.indexOf('b')>=0 || pat.indexOf('-1')>=0)) {
        //only a on row. player A won
        return 'A';
    }

    return false;
}

console.log(checkForWinner(board1));
console.log(checkForWinner(board2));
console.log(checkForWinner(board3));
console.log(checkForWinner(board4));
Hugo Delsing
  • 13,803
  • 5
  • 45
  • 72
0

Very interesting question. +1 :) Here is my take on this.

Check my fiddle http://jsfiddle.net/BuddhiP/J9bLC/ for full solution. I'll try to explain the main points in here.

I start with a board like this. I've used 0 instead of -1 because its easier.

    var a = 'a', b = 'b';
    var board = [
       [a, 0, a],
       [b, b, b],
       [a, 0, a]
       ];

My Strategy is simple.

  1. Check if any of the rows has the same player (a or b), if so we have a winner.
  2. Else, Check if any of the columns has the same player
  3. Else, Check if diagonals has a player

Those are the three winning cases.

First I created a function which can take set of rows (Ex: [a,0,b]), and check if entire row contains the same value, and if that value is not zero (or -1 in your case).

checkForWinner = function () {
    lines = Array.prototype.slice.call(arguments);
    // Find compact all rows to unique values.
    var x = _.map(lines, function (l) {
        return _.uniq(l);
    });
    // Find the rows where all threee fields contained the same value.
    var y = _.filter(x, function (cl) {
        return (cl.length == 1 && cl[0] !== 0);
    });
    var w = (y.length > 0) ? y[0] : null;
    return w;
};

Here I take unique values in a row, and if I can find only one unique value which is not ZERO, the he is the winner.

If there is no winner in the rows, I then check for columns. In order to reuse my code, I use _.zip() method to transform columns into rows and then use the same function above to check if we have a winner.

var board2 = _.zip.apply(this, board);
winner = checkForWinner.apply(this, board2);

If I still don't find a winner, time to check the diagonals. I've written this function to extract two diagonals from the board as two rows, and use the same checkForWinner function to see if diagonals are dominated by any of the players.

extractDiagonals = function (b) {
    var d1 = _.map(b, function (line, index) {
        return line[index];
    });
    var d2 = _.map(b, function (line, index) {
        return line[line.length - index - 1];
    });
    return [d1, d2];
};

Finally this is where I actually check the board for a winner:

// Check rows
winner = checkForWinner.apply(this, board);
if (!winner) {
    var board2 = _.zip.apply(this, board);

    // Check columns, now in rows
    winner = checkForWinner.apply(this, board2);
    if (!winner) {
        var diags = extractDiagonals(board);
        // Check for the diagonals now in two rows.
        winner = checkForWinner.apply(this, diags);
    }
}

If any of you wonder why I use apply() method instead of directly calling the function, reason is apply() allows you to pass an array elements as a list of arguments to a function.

I believe this should work for 4x4 or higher matrics as well, although I did not test them.

I had very little time to test the solution, so please let me know if you find any errors.

BuddhiP
  • 6,231
  • 5
  • 36
  • 56
  • Downvote for hardwiring tic-tac-toe but not addressing OP’s general question of how to match any pattern against any board. – akuhn Nov 30 '12 at 05:16
  • hmm.. Really? :) OP seems to think I answered his question properly. Matching **any** pattern on **any** board would **NOT** be answerable in this forum, you'd rather need a book for that. OP wanted a tic-tac-toe fashion pattern matching (a single value in a row/column/diag) on any size board which this solution is fully capable of handling, and does it in much more simpler way. – BuddhiP Nov 30 '12 at 06:01