I have a function (getCoeff()) which returns one-dimensional arrays. I try to use it to fill a two-dimensional array:
//set up an 3x3 array for A
A = new Array(3);
for (var i=0; i<3; i++) {
A[i] = new Array(3);
}
//fill it per row using getCoeff()
for (var i=0; i<3; i++) {
A[i] = getCoeff(i+1);
}
console.log(A);
console.log(getCoeff(1));
console.log(getCoeff(2));
console.log(getCoeff(3));
but I only get the first row filled and the other two remain empty:
[ [ -3, 2, -1 ], [ , , ], [ , , ] ]
[ -3, 2, -1 ]
[ 2, -3, 2 ]
[ 1, -1, 3 ]
As you can see the function returns the rows correctly but for some reason It doesnt work inside the loop.
On the other hand if I try something like this:
for (var i=0; i<3; i++) {
A[i] = [1,2,3];
}
console.log(A);
it works fine!
[ [ 1, 2, 3 ], [ 1, 2, 3 ], [ 1, 2, 3 ] ]
What's wrong with my code?!
Update:
My original full code before the edits:
var fs = require('fs');
var input = "LPinput.txt";
var c = new Array();
var A = new Array();
var b = new Array();
var Eqin = new Array();
var MinMax;
open(input);
console.log(c);
console.log(A);
console.log(b);
console.log(Eqin);
console.log(MinMax);
function open(filename) {
if (fs.existsSync(filename)) {
var data = fs.readFileSync(filename).toString().split("\n");
analyse(data);
} else {
console.log("ERROR: File doesnt exist!");
}
}
function analyse(data) {
//clean up whitespaces
for (i in data) {
data[i] = data[i].replace(/\s/g, '');
}
//check LP type & clean up
if (data[0].substring(0,3) == "max") {
MinMax = 1;
data[0] = data[0].replace("max","");
} else if (data[0].substring(0,3) == "min") {
MinMax = -1;
data[0] = data[0].replace("min","");
} else {
console.log("ERROR: Invalid format!");
return;
}
//check constraints format & clean up
if ( data[1].substring(0,4) != "s.t.") {
console.log("ERROR: Invalid format!");
return;
} else {
data[1] = data[1].replace("s.t.","");
}
//get variables
var variables = data[data.length-1].split(",");
var last = variables[variables.length-1];
variables[variables.length-1] = last.substring(0,last.indexOf(">"));
//get number of constraints
var constraints = data.length-2;
c = getCoeff(0);
//===============================
//I JUST NEED TO POPULATE A TO FINISH THIS
for (var i=0; i<constraints; i++) {
A[i] = getCoeff(i+1);
}
//===============================
for (var i=1; i<data.length-1; i++) {
var end = data[i].length;
var start = end;
while (data[i].charAt(start) != "=") {
start = start - 1;
}
b[i-1] = parseInt(data[i].substring(start+1,end));
if (data[i].charAt(start-1) == "<") {
Eqin[i-1]=-1;
} else if (data[i].charAt(start-1) == ">") {
Eqin[i-1]=1;
} else {
Eqin[i-1]=0;
}
}
function getCoeff(row) {
var coeff = new Array();
for (i in variables) {
var pos = data[row].indexOf(variables[i]);
if ((data[row].charAt(pos-1) == "+") || (pos-1 < 0)) {
coeff[i]=1;
} else if (data[row].charAt(pos-1) == "-") {
coeff[i]=-1;
} else if (data[row].charAt(pos-1) == "*") {
var end = pos-1;
var start = end;
while ( (start > -1) && (data[row].charAt(start) != "+") && (data[row].charAt(start) != "-") ) {
start = start - 1;
}
coeff[i] = parseInt((data[row].substring(start,end)));
}
}
return coeff;
}
}
LPinput.txt:
max 2*x1+x2-4*x3-15
s.t.-3*x1+2*x2-x3>=5
2*x1-3*x2+2*x3<=9
x1-x2+3*x3<=5
x1,x2,x3>=0
Update #2:
Console output:
[ 2, 1, -4 ]
[ [ -3, 2, -1 ] ]
[ 5, 9, 5 ]
[ 1, -1, -1 ]
1
It should be:
[ 2, 1, -4 ]
[ [ -3, 2, -1 ],[ 2, -3, 2 ],[ 1, -1, 3 ]]
[ 5, 9, 5 ]
[ 1, -1, -1 ]
1
Here is the real problem:
you are using an i variable in your outer scope.
for (var i=0; i<constraints; i++) {
A[i] = getCoeff(i+1);
}
When you go inside the getCoef you have this for loop
for (i in variables) {
and since you have not declared the i here, it uses the same i declared in the outer scope. After the first run of the for loop (which fills the first row of A), i is changed to "0" as opposed to the numeric value 0. Therefore the condition of the for loop is no longer valid and it exits the loop.
There you go!
In my case, I got last 2 populated. Anyways, if you run loop from 0, you better write
A[i] = getCoeff(i+1);
OR
you run that loop starting from 1 to less than equals 3.
In your code:
A = new Array(3);
you should declare varaibles, and initialising an array with a length is usually unnecessary. Also, variables starting with a capital letter are, by convention, reserved for construtors (though some use variable names in all capitals to represent constants).
Consider using an array initialiser:
var a = [];
for (var i=0; i<3; i++) {
Initialising arrays in the following loop is a bit useless as you replace them in the next loop:
a[i] = [];
}
In the next loop, i is needlessly declared again (there is no block scope in javascript). It is common to use a different variable in these cases (though re-using i has no ill effects, it's just not liked):
for (var j=0; j<3; j++) {
a[j] = getCoeff(j + 1);
}
So creating a getCoeff function that just returns an array of the value passed to it (purely for testing):
function getCoeff(v){
return [v,v,v];
}
"works":
console.log(a.join(' - ')); // 1,1,1 - 2,2,2 - 3,3,3
Related
Creating a tic tac toe game. I am trying to read columns and rows of the tic tac toe to check who won. I am using $scope.checkResults() to grab the columns and rows and pass them into the allTheSame function to check if the values are equal which is not working.
Here is the codepen link. http://codepen.io/theMugician/pen/ojJrRp
var app = angular.module("ticTacToe", []);
app.controller("MainCtrl", function($scope){
var cell = $(".square");
$scope.player = "";
$scope.AI = "";
// changed special chars to X and O as the if statement failed.
var cross = "✖";
var circle = "◯";
/*** Choose a shape ***/
$scope.choosePlayer = function(e) {
$scope.player = $(e.currentTarget).text();
$('.choose').css('top', '-2000px');
$('#wrapper').css('top', '-600px');
$('#wrapper').css('opacity', '1');
//these if statements failed before (AI was always empty)
if($scope.player === cross){
$scope.AI = circle;
}else if($scope.player === circle){
$scope.AI = cross;
}
}
/*** Shape Cells ***/
$scope.cells = [ { value: '' }, { value: '' }, { value: '' },
{ value: '' }, { value: '' }, { value: '' } ,
{ value: '' }, { value: '' }, { value: '' }
];
// made a ref to scope cells
$scope.emptyCells = $scope.cells;
/*** Make a move ***/
$scope.move = function(cell){
cell.value = $scope.player;
var round = 0;
/*** AI makes a move ***/
while(round < 1){
// filtered to get only available cells (for performance)
$scope.emptyCells = $scope.cells.filter(function(cell){
return cell.value === '';
});
// got random cell according to empty cells
var randomCell = $scope.emptyCells[Math.floor((Math.random()*($scope.emptyCells.length-1))+1)];
if(randomCell.value === "" ){
randomCell.value = $scope.AI;
round = 1;
}else{
round = 0;
}
}
$scope.checkResults();
};
//checks if values are the same
function allthesame(arr){
var L= arr.length-1;
while(L){
if(arr[L--]!==arr[L]) return false;
}
alert(arr[L].value + "is the winner");
}
//checks Columns and rows
$scope.checkResults = function(){
var allCells = $scope.cells;
// check rows
var cellRows = [];
while(allCells > 0){
cellRows.push(allCells.splice(0,3));
}
for(var i = 0; i < cellRows.length; i++){
allTheSame(cellRows[i]);
}
// check columns
var cellCols = [];
while(allCells > 0){
cellCols.push(allCells.splice(0));
cellCols.push(allCells.splice(3));
cellCols.push(allCells.splice(6));
}
while(cellCols > 0){
cellCols.push(cellCols.splice(0,3));
}
for(var i = 0; i < cellCols.length; i++){
allTheSame(cellCols[i]);
}
}
$scope.reset = function(){
$scope.cells = [ { value: '' }, { value: '' }, { value: '' },
{ value: '' }, { value: '' }, { value: '' } ,
{ value: '' }, { value: '' }, { value: '' }
];
}
});
So you have an array of 9 values and need to compare to 8 possible winning arrangements, 3 vertical, 3 horizontal and 2 diagonal.. an array iteration against the follow list of "winning" combinations might be what you want to do.
{0,1,2}
{3,4,5}
{6,7,8}
{0,3,6}
{1,4,7}
{2,5,8}
{0,4,8}
{2,4,6}
I was just on my way out the door I can post some code later but I think saving this list as a comparison item for your cells might be the easier method.
Here is a reference article I grabbed quickly: this should be easy to convert to javascript: http://www.codeproject.com/Articles/2400/Tic-Tac-Toe-in-C
Using Frodo's advice I came up with a solution which is a lot simpler than what I was doing before.
I stored all winning combinations into an array. Then I checked each winning combo to see if each cell's value in that combo matched. Simple as that.
var winningNums = [
[0,1,2],
[3,4,5],
[6,7,8],
[0,3,6],
[1,4,7],
[2,5,8],
[0,4,8],
[2,4,6]
];
//checks if values are the same
$scope.checkResults = function(){
var allCells = $scope.cells;
for(var i = 0; i < winningNums.length; i++){
var a = winningNums[i][0],b=winningNums[i][1],c=winningNums[i][2];
var cell1 = allCells[a].value, cell2 = allCells[b].value, cell3 = allCells[c].value;
if(cell1 == "" || cell2 == "" || cell3 == "" ){
break;
}
if(cell1 === cell2 && cell2 === cell3 ){
var winnerDiv = "<div><h1>" + cell1 + " is the winner</h1></div>";
$(
"#wrapper").append(winnerDiv);
}
}
}
This Answer Does not Use jQuery
Using an array similar to what's shown in the above 👆🏾answer, we can just use a few different array methods:
const WINNING_INDICES = [
[0, 1, 2],
[0, 3, 6],
[0, 4, 8],
[1, 4, 7],
[2, 5, 8],
[2, 4, 6],
[3, 4, 5],
[6, 7, 8],
];
function check4Winner(board, letter) {
return WINNING_INDICES.some(winningIndices =>
winningIndices.every(winningIndex => board[winningIndex] === letter),
);
}
console.log(check4Winner(['X', 'O', 'O', 'X', 'O', 'X', 'X', '', '']), "X");
Here, I have passed in a hard-coded board that's a flat array. It represents the nine squares on a Tic-Tac-Toe board.
Summarily, we want to see if there is at least one instance (some) where all the indices of a given element (winningIndices) of WINNING_INDICES, when used with board, match the given letter ("X").
This is just the function of my code to determine who won in the game. I also use the winning combination numbers in my code.
var i = -1;
var WinnerChecker = setInterval(() =>{
var p1 = 0, p2 = 0;
i = i<TileCombos.length ? ++i : 0
for(let j = 0; j<3; j++){
if(player[TileCombos[i][j]].innerHTML == chk[0].innerHTML){
p1 += 1;
}
if(player[TileCombos[i][j]].innerHTML == chk[1].innerHTML){
p2 += 1;
}
}
if(p1 == 3){
console.log('PLAYER 1 WON');
}
if(p2 == 3){
console.log('PLAYER 2 WON');
}
})
I get an object with partial results of match from database.
[Object { home1=4, away1=3, home2=4, away2=5, home3=6, away3=7, home4=6, away4=5, home5=3, away5=6}]
home1 it's a result of first part of home team,
away1 -> away team, home2 it's a result of second part of home team... etc etc
data in my case is each row, which i get from database.
In rows i have td with class: home1, home2, home3, away1, away2 and there are values of corresponding part of match.
I want to check if value is equal to what I got from database.
Something like this
if ($('.home1') === data[index].home1;
if($('.away2') === data[index].away2;
there should be some loop. I have no idea how to do this, I thought about an array
var array = [
{
home1: data[index].home1,
away1: data[index].away1
},
{
home2: data[index].home2,
away2: data[index].away2
},
{
home3: data[index].home3,
away3: data[index].away3
},
{
home4: data[index].home4,
away4: data[index].away4
},
{
home5: data[index].home5,
away5: data[index].away5
}
]
and then for loop:
for(var X=0; X<5;X++){
homeX == data[index].homeX
}
How can I increment name of variable by eval function? or is there any other solution? I'm very confused.
You can access object properties using operator []:
for(var i=0; i<array.length; i++)
{
var item = array[i];
var homePropertyName = 'home' + (i+1);
//now you can access homeX property of item using item[homePropertyName]
//e.g. item[homePropertyName] = data[index][homePropertyName]
}
Maybe you should use a little different structure which might fit your needs better, like this:
array = [
0: array [
"home": "Text for home",
"away": "Text for away"
],
1: array [
"home": "",
"away": ""
]
// More sub-arrays here
];
You can also initialize it with a for loop:
var array = new Array();
var i;
for (i = 0; i < 4; i++) {
array[i] = [
"home": "",
"away": ""
];
}
Or like this:
array[0]["home"] = "Text for home";
array[0]["away"] = "Text for away";
You can use this structure for the data-array also, and then use a for-loop to go through them both (like if you wish to find an element):
var result = NULL;
for (i = 0; i < array.length; i++) {
if ( (array[i]["home"] == data[index]["home"]) &&
(array[i]["away"] == data[index]["away"])
) {
// Found matching home and away
result = array[i];
break;
}
}
if (result != NULL) {
alert("Found match: " + result["home"] + " - " + result["away"]);
}
else {
alert("No match");
}
PS: Code is not tested, let me know if something is wrong.
you can access global properties in browser via window object like this (fiddle):
value1 = "ONE";
alert( window['value'+1] );
But it is not good design. You should look into how to properly format JSON object.
I have something like this:
for(var i=0; i<2; i++)
{
var item = ARR[i];
for(var x=0;x<5;x++){
var hPropertyName = 'home_p' + (x+1);
var aPropertyName = 'away_p' + (x+1);
item[hPropertyName] = ARR[i][hPropertyName];
item[aPropertyName] = ARR[i][aPropertyName];
}
and it works when i create an array:
var ARR = [
{
home_p1: 4,
away_p1: 5,
home_p2: 8,
away_p2: 9,
home_p3: 2,
away_p3: 1,
home_p4: 5,
away_p4: 3,
home_p5: 3,
away_p5: 2
},
{
home_p1: 6,
away_p1: 1,
home_p2: 1,
away_p2: 2,
home_p3: 3,
away_p3: 4,
home_p4: 5,
away_p4: 6,
home_p5: 3,
away_p5: 2
}
];
but I don't have to create an array, because i have to work on object which I get from database :
[Object { event_id=19328, home_result=3, away_result=2, home_p1=4, away_p1=3, home_p2=1, away_p2=2 ...... }]
I'm only interested in these parameters --> home_p , away_p
I want to push it to my array to looks like ARR. I think i should convert an object which I get to an array
If you are using string name for your attributes then you could try using template literals?
var someObject = {}
for(let i=0 ; i<values.length ; i++){
someObject[`home${i+1}`] = values[i];
}
and if you need it to be ES5 you could just use string concatenation. Below is a working example:
values = [1,2,3,4,5];
let someObject = {};
for(let i=0 ; i<values.length ; i++){
someObject[`value${i+1}`]=values[i];
}
console.log(someObject.value1);
console.log(someObject.value2);
console.log(someObject.value3);
console.log(someObject.value4);
console.log(someObject.value5);
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Delete from array in javascript
I have the following JSON object:
[id:84,id:92,id:123,id:2353]
How would I go about removing the item which the value is "123" using javascript?
or if I formatted the json as
[84, 92, 123, 2353]
How would it be removed in this case?
Assume you have this:
var items = [{ id: 84 }, { id: 92 }, { id: 123 }, { id: 2353 }];
var filtered = items.filter(function(item) {
return item.id !== 123;
});
//filtered => [{ id: 84 }, { id: 92 }, { id: 2353 }]
Supposing you actually have an object from a json in the json variable
for (key in json) {
if (json.hasOwnProperty(key) && json[key] == 123) {
delete json[key];
}
}
Shorter alternative would be:
var newArr = [{id:84}, {id:92}, {id:123}, {id:2353}].filter(function(a) {
return a.id != 123;
});
If you have this:
var arr = [{id:84}, {id:92}, {id:123}, {id:2353}]
To remove the item with value 123, you can do:
for(var i = 0; i < arr.length; i++) {
if(arr[i].id == 123) {
arr.splice(i, 1);
break;
}
}
function removeClass(obj, cls) {
var classes = obj.className.split(' ');
for(i=0; i<classes.length; i++) {
if (classes[i] == cls) {
classes.splice(i, 1);
i--; // (*)
}
}
obj.className = classes.join(' ');
}
var obj = { className: 'open menu menu' }
removeClass(obj, 'menu')
alert(obj.className)
You can use splice function, like this:
var data = [{id:84}, {id:92}, {id:123}, {id:2353}];
function remove(){
for(var i = 0, max = data.length; i < max; i++) {
var a = data[i];
if(a.id === 123) {
data.splice(i, 1);
break;
}
}
}
remove();
Seems like you want to avoid a loop. Assuming it's available, you can use .filter:
[{id:84},{id:92},{id:123},{id:2353}]
.filter(function (elem) { return elem.id !== 123; });
This technically does do a loop, but at least you don't have to look at it.
Assuming your "json" is really an array, like [84, 92, 123, 2353]:
var myString = "[84, 92, 123, 2353]";
var myArray = JSON.parse(myString);
var index = myArray.indexOf(123); // whatever value you are looking for
myArray.splice(index, 1);
http://jsfiddle.net/7vkK6/
Assuming I'm understanding your question and comments correctly you can do something like this:
var old_array = [{id: 84},...];
var new_array = [];
for(var i = 0, len = old_array.length; i++) {
if (old_array[i].id != 123) new_array.push(old_array[i]);
}
What you have currently is not JSON so I'll give you some different options.
If you have an Array arr = [84,92,123,2353] then
arr = arr.filter(function (x) {return x !== 123;}); // all occurrences
// OR
arr.splice(arr.indexOf(123), 1); // first occurrence only
If you have an Object obj = {"84": a, "92": b, "123": c, "2353": d}, a to d some expressions, then
delete obj['123']; // obj now {"84": a, "92": b, "2353": d}
1) JSON is a string, not an array or an object.
var json = "[1,2,3]";
2) Valid JSON NEEDS to be valid JS
var myJSObj = { 1,2,3 }, // broken
myJSArr = [ name : 1, name2 : 2 ]; // broken
3) If you have a JS Array, you can remove an element by using [].splice
var arr = [ 1, 2, 3, 4 ],
i = 0, l = arr.length,
test = 4;
for (; i < l; i += 1) {
if (arr[i] === test) { arr.splice(i, 1); } // remove 1 starting at i
}
4) If you have an object with named keys, you can use delete
var obj = { val : 1 };
delete obj.val;
What's the fastest/best way to compare two arrays and return the difference? Much like array_diff in PHP. Is there an easy function or am I going to have to create one via each()? or a foreach loop?
I know this is an old question, but I thought I would share this little trick.
var diff = $(old_array).not(new_array).get();
diff now contains what was in old_array that is not in new_array
Working demo http://jsfiddle.net/u9xES/
Good link (Jquery Documentation): http://docs.jquery.com/Main_Page {you can search or read APIs here}
Hope this will help you if you are looking to do it in JQuery.
The alert in the end prompts the array of uncommon element Array i.e. difference between 2 array.
Please lemme know if I missed anything, cheers!
Code
var array1 = [1, 2, 3, 4, 5, 6];
var array2 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var difference = [];
jQuery.grep(array2, function(el) {
if (jQuery.inArray(el, array1) == -1) difference.push(el);
});
alert(" the difference is " + difference); // Changed variable name
use underscore as :
_.difference(array1,array2)
var arrayDiff = function (firstArr, secondArr) {
var i, o = [], fLen = firstArr.length, sLen = secondArr.length, len;
if (fLen > sLen) {
len = sLen;
} else if (fLen < sLen) {
len = fLen;
} else {
len = sLen;
}
for (i=0; i < len; i++) {
if (firstArr[i] !== secondArr[i]) {
o.push({idx: i, elem1: firstArr[i], elem2: secondArr[i]}); //idx: array index
}
}
if (fLen > sLen) { // first > second
for (i=sLen; i< fLen; i++) {
o.push({idx: i, 0: firstArr[i], 1: undefined});
}
} else if (fLen < sLen) {
for (i=fLen; i< sLen; i++) {
o.push({idx: i, 0: undefined, 1: secondArr[i]});
}
}
return o;
};
/** SUBTRACT ARRAYS **/
function subtractarrays(array1, array2){
var difference = [];
for( var i = 0; i < array1.length; i++ ) {
if( $.inArray( array1[i], array2 ) == -1 ) {
difference.push(array1[i]);
}
}
return difference;
}
You can then call the function anywhere in your code.
var I_like = ["love", "sex", "food"];
var she_likes = ["love", "food"];
alert( "what I like and she does't like is: " + subtractarrays( I_like, she_likes ) ); //returns "Naughty"!
This works in all cases and avoids the problems in the methods above. Hope that helps!
In this way you don't need to worry about if the first array is smaller than the second one.
var arr1 = [1, 2, 3, 4, 5, 6,10],
arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
function array_diff(array1, array2){
var difference = $.grep(array1, function(el) { return $.inArray(el,array2) < 0});
return difference.concat($.grep(array2, function(el) { return $.inArray(el,array1) < 0}));;
}
console.log(array_diff(arr1, arr2));
if you also want to compare the order of the answer you can extend the answer to something like this:
Array.prototype.compareTo = function (array2){
var array1 = this;
var difference = [];
$.grep(array2, function(el) {
if ($.inArray(el, array1) == -1) difference.push(el);
});
if( difference.length === 0 ){
var $i = 0;
while($i < array1.length){
if(array1[$i] !== array2[$i]){
return false;
}
$i++;
}
return true;
}
return false;
}
The short version can be like this:
const diff = (a, b) => b.filter((i) => a.indexOf(i) === -1);
result:
diff(['a', 'b'], ['a', 'b', 'c', 'd']);
["c", "d"]
Array operations like this is not jQuery's strongest point. You should consider a library such as Underscorejs, specifically the difference function.
This should work with unsorted arrays, double values and different orders and length, while giving you the filtered values form array1, array2, or both.
function arrayDiff(arr1, arr2) {
var diff = {};
diff.arr1 = arr1.filter(function(value) {
if (arr2.indexOf(value) === -1) {
return value;
}
});
diff.arr2 = arr2.filter(function(value) {
if (arr1.indexOf(value) === -1) {
return value;
}
});
diff.concat = diff.arr1.concat(diff.arr2);
return diff;
};
var firstArray = [1,2,3,4];
var secondArray = [4,6,1,4];
console.log( arrayDiff(firstArray, secondArray) );
console.log( arrayDiff(firstArray, secondArray).arr1 );
// => [ 2, 3 ]
console.log( arrayDiff(firstArray, secondArray).concat );
// => [ 2, 3, 6 ]
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.
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.
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.
Check if any of the rows has the same player (a or b), if so we have a winner.
Else, Check if any of the columns has the same player
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.
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;
}
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
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));