Why my minimax algorithm dosen't block my moves? - javascript

This is the Javascript code for my Tic-Tac-Toe algorithm:
function minimax(newGrid, depth, Player){
//debugger
const gameState = checkWin(newGrid,true);
if (gameState == false){
values = [];
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
const boardCopy = _.cloneDeep(newGrid);
if (boardCopy[i][j] !== '') continue;
boardCopy[i][j] = Player;
console.log(boardCopy);
const value = minimax(boardCopy, depth + 1, (Player === PLYR_TOKEN) ? COMP_TOKEN : PLYR_TOKEN);
values.push({
cost: value,
cell: {
i: i,
j: j
}
});
}
}
//debugger
if (Player === COMP_TOKEN){
const max = _.maxBy(values, (v) => {
return v.cost;
});
if( depth === 0 ){
return max.cell;
} else {
return max.cost;
}
}else{
const min = _.minBy(values, (v) => {
return v.cost;
});
if( depth === 0 ){
return min.cell;
} else {
return min.cost;
}
}
} else if (gameState === null){
return 0;
} else if (gameState === PLYR_TOKEN){
return depth - 10;
} else if (gameState === COMP_TOKEN){
return 10 - depth;
}
}
The problem with this "algorithm", "code" is simple: it doesn't block my moves Let's image this scenario:
X-> player
O-> MM algorithm
X - O
- X -
- - -
Normal, a perfect MiniMax algorithm should take this choice to block me from winning (lower case o is the new move):
X - O
- X -
- - o
The problem is that my code dose this (lower case o is the new move):
X - O
- X o
- - -
Why? I dont know, but I think it take's any chance it has to win, ignoring my moves, ignoring the fact that I'm one move away from winning.
To be honest with you, I don't really understand how this algorithm work's.
Other info: The main board is an two dimensional array and the result of the minimax function is an object with two proprieties (i,j) that represent coordinates on the main board.
const board = [
['','',''],
['','',''],
['','','']
];

So, when in doubt, comment ! I did it step by step, not stopping when I was stuck but delaying and going back and forth each time I understood something more.
//newGrid : the board
//depth : keep track of the "guessing level"
//Player : keep track of who's turn it would be
function minimax(newGrid, depth, Player){
//checking if the game ended
const gameState = checkWin(newGrid,true);
//if not
if (gameState == false){
values = [];
//for each cell in the grid
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
//we make a deep copy of the board
const boardCopy = _.cloneDeep(newGrid);
//if the cell isn't empty, we jump to the next one
if (boardCopy[i][j] !== '') continue;
//here we assign the Player to the cell (simulating a move from this player to this cell)
boardCopy[i][j] = Player;
//debugging
console.log(boardCopy);
//here go some recursivity, so we're putting our deepcopy with the simulated move, adding a depth level, and switching player
const value = minimax(boardCopy, depth + 1, (Player === PLYR_TOKEN) ? COMP_TOKEN : PLYR_TOKEN);
//since it was a recursive thing, please do imagine we get here at max depth BEFORE lesser depths, and then we'll climb back when each depth return its value to the previous one
//so here the first "value" going in "values" will be the first cell where we did not go through "if (gameState == false){" : first cell where the game ended (with its associated cost, more on that later)
values.push({
cost: value,
cell: {
i: i,
j: j
}
});
}
}
//when the loop ended
//if we're simulating a computer turn
if (Player === COMP_TOKEN){
//getting the "value" with max cost out of "values"
const max = _.maxBy(values, (v) => {
return v.cost;
});
//if we endend our recursivity (we climbed all the way back to depth 0) == we are on the actual grid with no simulation
if( depth === 0 ){
return max.cell; //return the cell (computer will play this cell)
} else {
return max.cost; //else return the cost (to put in the "values" list)
}
}else{ //if we're simulating a player turn, same thing but with the min
const min = _.minBy(values, (v) => {
return v.cost;
});
if( depth === 0 ){ //may not be useful if you always call minimax at depth 0 on computer turn
return min.cell;
} else {
return min.cost;
}
}
} else if (gameState === null){ //so, here we're simulating our endgame, a draw have a value of 0
return 0;
} else if (gameState === PLYR_TOKEN){ //a player win have a value of "depth-10" (the quicker he win, the lesser the result)
return depth - 10;
} else if (gameState === COMP_TOKEN){ //a computer win have a value of "10-depth" (the quicker he win, the greater the result)
return 10 - depth;
}
}
With that done, we have a better understanding on how the code work, and why it doesn't work like it's supposed to.
Indeed, on computer turn, it only check for what's the quickest move to win. I'm not 100% sure of my solution, but you could for example try to fix it that way :
if (Player === COMP_TOKEN){
//[...]
//if we endend our recursivity (we climbed all the way back to depth 0) == we are on the actual grid with no simulation
if( depth === 0 ){
const min = _.minBy(values, (v) => {
return v.cost;
});
if(min.cost>=-9)return min.cell; //if player win in the next turn, prevent it instead
else return max.cell; //else return the best cell for computer
}
//[...]
It's far from perfect (you could get him with a 2-move win for example) and it's not tested, but I hope it's clearer for you now.
When you're done with your code and it's working like you want, don't hesitate to post it on codereview.stackexchange to get optimisation advices.

Related

How to limit a number between several numbers (get the most nearest small number)? [duplicate]

Example: I have an array like this: [0,22,56,74,89] and I want to find the closest number downward to a different number. Let's say that the number is 72, and in this case, the closest number down in the array is 56, so we return that. If the number is 100, then it's bigger than the biggest number in the array, so we return the biggest number. If the number is 22, then it's an exact match, just return that. The given number can never go under 0, and the array is always sorted.
I did see this question but it returns the closest number to whichever is closer either upward or downward. I must have the closest one downward returned, no matter what.
How do I start? What logic should I use?
Preferably without too much looping, since my code is run every second, and it's CPU intensive enough already.
You can use a binary search for that value. Adapted from this answer:
function index(arr, compare) { // binary search, with custom compare function
var l = 0,
r = arr.length - 1;
while (l <= r) {
var m = l + ((r - l) >> 1);
var comp = compare(arr[m]);
if (comp < 0) // arr[m] comes before the element
l = m + 1;
else if (comp > 0) // arr[m] comes after the element
r = m - 1;
else // arr[m] equals the element
return m;
}
return l-1; // return the index of the next left item
// usually you would just return -1 in case nothing is found
}
var arr = [0,22,56,74,89];
var i=index(arr, function(x){return x-72;}); // compare against 72
console.log(arr[i]);
Btw: Here is a quick performance test (adapting the one from #Simon) which clearly shows the advantages of binary search.
var theArray = [0,22,56,74,89];
var goal = 56;
var closest = null;
$.each(theArray, function(){
if (this <= goal && (closest == null || (goal - this) < (goal - closest))) {
closest = this;
}
});
alert(closest);
jsFiddle http://jsfiddle.net/UCUJY/1/
Array.prototype.getClosestDown = function(find) {
function getMedian(low, high) {
return (low + ((high - low) >> 1));
}
var low = 0, high = this.length - 1, i;
while (low <= high) {
i = getMedian(low,high);
if (this[i] == find) {
return this[i];
}
if (this[i] > find) {
high = i - 1;
}
else {
low = i + 1;
}
}
return this[Math.max(0, low-1)];
}
alert([0,22,56,74,89].getClosestDown(75));
Here's a solution without jQuery for more effiency. Works if the array is always sorted, which can easily be covered anyway:
var test = 72,
arr = [0,56,22,89,74].sort(); // just sort it generally if not sure about input, not really time consuming
function getClosestDown(test, arr) {
var num = result = 0;
for(var i = 0; i < arr.length; i++) {
num = arr[i];
if(num <= test) { result = num; }
}
return result;
}
Logic: Start from the smallest number and just set result as long as the current number is smaller than or equal the testing unit.
Note: Just made a little performance test out of curiosity :). Trimmed my code down to the essential part without declaring a function.
Here's an ES6 version using reduce, which OP references. Inspired by this answer get closest number out of array
lookup array is always sorted so this works.
const nearestBelow = (input, lookup) => lookup.reduce((prev, curr) => input >= curr ? curr : prev);
const counts = [0,22,56,74,89];
const goal = 72;
nearestBelow(goal, counts); // result is 56.
Not as fast as binary search (by a long way) but better than both loop and jQuery grep https://jsperf.com/test-a-closest-number-function/7
As we know the array is sorted, I'd push everything that asserts as less than our given value into a temporary array then return a pop of that.
var getClosest = function (num, array) {
var temp = [],
count = 0,
length = a.length;
for (count; count < length; count += 1) {
if (a[count] <= num) {
temp.push(a[count]);
} else {
break;
}
}
return temp.pop();
}
getClosest(23, [0,22,56,74,89]);
Here is edited from #Simon.
it compare closest number before and after it.
var test = 24,
arr = [76,56,22,89,74].sort(); // just sort it generally if not sure about input, not really time consuming
function getClosest(test, arr) {
var num = result = 0;
var flag = 0;
for(var i = 0; i < arr.length; i++) {
num = arr[i];
if(num < test) {
result = num;
flag = 1;
}else if (num == test) {
result = num;
break;
}else if (flag == 1) {
if ((num - test) < (Math.abs(arr[i-1] - test))){
result = num;
}
break;
}else{
break;
}
}
return result;
}

Three Color Triangle

here is the question (https://www.codewars.com/kata/5a331ea7ee1aae8f24000175)
I've been searching for 2 days about this. I saw an essay (https://www.ijpam.eu/contents/2013-85-1/6/6.pdf). In codewars discussion, they say we can solve the question without using mat rules, if we design the code for complexity O(n).I did try that too but it doesnt work. I've tried my best but it didnt pass. Here is my code.
I did read this (Three colors triangles)
I wonder, is there any way to solve this without completely using Math ?
function triangle(row) {
let nextRow = []
let result = row.split("")
for(var j = 0; j < row.length-1; j++) {
nextRow= []
for(var i = 0; i < result.length - 1; i++) {
nextRow.push(nextColor(result[i],result[i+1]))
}
result = nextRow.slice(0)
}
return result.join("")
}
function nextColor(s1,s2) {
let colors = {"R": 1, "B": 2, "G": 3};
if(colors[s1] + colors[s2] == 6) return "G"
else if(colors[s1] + colors[s2] == 5) return "R"
else if(colors[s1] + colors[s2] == 4) return "B"
else if(colors[s1] + colors[s2] == 3) return "G"
else if(colors[s1] + colors[s2] == 2) return "R"
}
This solution solves it using < O(n2) with = O(n2) as a max. It uses two loops. One a while loop that ends so long as there is no new row to process. And an inner loop that iterates the colors of the current array.
This is not a solution to increase efficiency. It is a solution to show a non-efficient way with clarity on how the rules would work. Technically you could replace my use of "strings" with arrays, to increase efficiency, but that is not the point to illustrate on how an algorithm could work, albeit, inefficiently.
All other comments are in code comments:
function reduceTriangle( firstRow ) {
// from rules: You will be given the first row of the triangle as a string --> firstRow
let triangle = [];
// seed the triangle so we can loop it until the process is done
triangle.push( firstRow );
// lets loop this
let onRow = 0;
while (onRow < triangle.length) {
// lets determine the next generated row
// the rules given for adjacent colors are:
// Colour here: G G B G R G B R
// Becomes colour here: G R B G
// We'll also assume that order of the two-pattern doesn't matter: G B -> R, too! (from example)
let newRow = "";
for (let c = 0; c < triangle[onRow].length - 1; c++) {
let twoPattern = triangle[onRow].substring(c, c + 2); // GG, BG, etc.
// console.log(twoPattern);
let onePattern = false; // hmmm?
if (twoPattern == "RR" || twoPattern == "GG" || twoPattern == "BB") {
onePattern = twoPattern.substring(0, 1); // R || G || B
}
else {
if (twoPattern == "BG" || twoPattern == "GB") {
onePattern = "R"; // frome rules
}
else if (twoPattern == "RG" || twoPattern == "GR") {
onePattern = "B"; // frome rules
}
if (twoPattern == "BR" || twoPattern == "RB") {
onePattern = "G"; // frome rules
}
}
// hmmm cont'd:
if (onePattern !== false) {
newRow += onePattern;
}
}
if (newRow.length > 0) {
triangle.push( newRow.toString() ); // toString so we get a deep copy to append to triangle
}
// lets move to the next row, if none added, then the loop will end next cycle
onRow++;
}
return triangle;
}
console.log( reduceTriangle( "RRGBRGBB" ) );
console.log( reduceTriangle( "RBRGBRBGGRRRBGBBBGG" ) );

Search coordinates in a binary not working correctly

I need to insert a lot of coordinates in a binary tree and sometimes merge them. I'm testing the program for a while and have noted that the search function doesn't work sometimes.
In this example, I created two binary trees, merged them and searched for some coordinate, but the coordinates that are inserted in the tree, were not found:
First tree:
0 2
9 8
7 0
6 0
Second tree:
3 2
8 5
4 1
5 6
Merged trees:
3 2
8 5
4 1
5 6
0 2
9 8
7 0
6 0
VALUES 8 and 5 NOT FOUND
Can someone help me with this problem?
Here is the code:
function binarytree()
{
this.root = null;
this.add = function()
{
var node = {
x : j,
y : i,
left : null,
right : null
};
var current;
if (this.root == null) this.root = node;
else {
current = this.root;
while (1)
{
if (i < current.y || j < current.x) {
if (current.left == null) {
current.left = node;
break;
}
else current = current.left;
}
else if (i > current.y || j > current.x) {
if (current.right == null) {
current.right = node;
break;
}
else current = current.right;
}
else break;
}
}
}
this.search = function(tree, i, j) {
var found = false;
current = tree.root;
while (!found && current) {
if (i < current.y || j < current.x) current = current.left;
else if (i > current.y || j > current.x) current = current.right;
else found = true;
}
return found;
}
this.print = function(no)
{
if (no)
{
this.print(no.left);
this.print(no.right);
console.log(no.x, no.y);
}
}
}
function merge(tree, tree2) {
if (tree2.x < tree.x || tree2.y < tree.y) {
if (tree.left) {
this.merge(tree.left, tree2);
} else {
tree.left = tree2;
}
} else {
if (tree.right) {
this.merge(tree.right, tree2);
} else {
tree.right = tree2;
}
}
}
var i, j;
var tree = new binarytree();
var tree2 = new binarytree();
for (x = 0; x < 4; x++)
{
i = Math.floor(Math.random() * 10);
j = Math.floor(Math.random() * 10);
tree.add();
}
for (x = 0; x < 4; x++)
{
i = Math.floor(Math.random() * 10);
j = Math.floor(Math.random() * 10);
tree2.add();
}
console.log("First tree:");
tree.print(tree.root);
console.log("Second tree:");
tree2.print(tree2.root);
merge(tree.root,tree2.root);
console.log("Merged trees:");
tree.print(tree.root);
if (tree.search(tree, i, j) == true) console.log("FOUND VALUES " + j + " AND " + i);
else console.log("VALUES " + j + " AND " + i + " NOT FOUND");
As you already mentioned in your deleted question, you need to use KD trees for this, as a normal binary search tree is intended for 1-dimensional values, not 2-dimensional values.
There are several issues in your code:
The way you split points into 2 categories is not a transitive operation. Let's say you have two points in one tree: (0, 0) and (10, -5), then the second point will be stored in the left property of the root. This is because -5 is less than 0. Now we have a second tree with two points: (4, 4) and also (10, -5). The tree will have the same structure as the first for the same reasons. Your merge function will put the second tree in the right property of the first tree's root. This is because (4, 4) is considered "right" of (0, 0). Now notice how this is inconsistent: now we have a (10, -5) sitting both in the left and the right subtree of the merged tree! This happens because the way you compare points is not a transitive comparison.
The above point is the major problem, but also notice that if (i < current.y || j < current.x) will on average be true for 75% of the cases. This is a second reason why this comparison method is not the right one.
The way to compare 2D-points in a KD tree is to alternate comparisons with either the X-coordinate or the Y-coordinate. So at the top-level of the tree you would compare Y-coordinates to decide whether to go left or right, and on the next level you would compare X-coordinates. Then again one level deeper you would compare Y-coordinates again, ...etc.
Some other remarks:
Use class syntax
Create a constructor also for the node instances
Don't print in methods, instead define a generator to produce all values, and a toString method and print that string in your main program.
Avoid using global variables: your add method should take i and j as arguments, and you should declare variables always (you didn't use var current in search, nor for x and y in the main code): this is crucial if you want to write reliable code. Consider using "use strict" which will alert you about such problems.
add and search will use a similar way to navigate through the tree, so put the code that they have in common in a separate function, which we could call locate.
As far as I know, there is no way to merge KD trees the way you have imagined it, where you can decide to put the "rest" of the second tree under a leaf node of the first. So I would suggest to just iterate the nodes of the second tree and insert them one by one in the first.
Here is the suggested code:
"use strict"; // To help avoid all kinds of problems
class Node { // Use a class for creating node instances
constructor(i, j) {
this.x = i;
this.y = j;
this.left = null;
this.right = null;
}
* inorder() { // Generator to visit all nodes
if (this.left) yield * this.left.inorder();
yield [this.x, this.y];
if (this.right) yield * this.right.inorder();
}
}
class BinaryTree {
constructor() {
this.root = null;
}
locate(i, j) { // A common function to avoid code repetition
if (this.root == null) return [null, "root"];
let current = this.root;
let axis = "x";
let otherAxis = "y";
while (true) {
// alternate the axis:
[axis, otherAxis, i, j] = [otherAxis, axis, j, i];
if (i < current[axis]) {
if (current.left == null) return [current, "left"];
current = current.left;
} else if (i > current[axis] || j != current[otherAxis]) {
if (current.right == null) return [current, "right"];
current = current.right;
} else { // point is already in the tree
return [current, "equal"];
}
}
}
add(i, j) { // this method should have parameters
if (this.root == null) {
this.root = new Node(i, j); // Use constructor for creating node
} else {
const [current, side] = this.locate(i, j);
if (side !== "equal") {
current[side] = new Node(i, j);
}
}
}
search(i, j) {
const [_, side] = this.locate(i, j);
return side === "equal";
}
* inorder() {
if (this.root) yield * this.root.inorder();
}
mergeWith(otherTree) {
// Insert all the other nodes one by one:
for (let point of otherTree.inorder()) {
this.add(...point);
}
}
toString() { // Don't print, but produce string
return JSON.stringify(Array.from(this.inorder()));
}
}
const tree = new BinaryTree();
for (const point of [[0, 2], [9, 8], [7, 0], [6, 0]]) tree.add(...point);
const tree2 = new BinaryTree();
for (const point of [[3, 2], [8, 5], [4, 1], [5, 6]]) tree2.add(...point);
console.log("First tree:");
console.log(tree.toString());
console.log("Second tree:");
console.log(tree2.toString());
tree.mergeWith(tree2);
console.log("Merged trees:");
console.log(tree.toString());
// Check that all points are found:
for (const point of tree.inorder()) {
console.log(...point, tree.search(...point));
}

averagePair problem using multiple pointers as a solution

I'm trying to solve the following problem :
What I've come up with so far:
function averagePair(arr,tar){
if (arr.length < 2){
return false
}
let x = 0
for (var y = 1; y < arr.length; y++){
if ((arr[x] + arr[y]) / 2 == tar){
return true
}
else {
x++;
}
}
return false
}
I know this solution isn't correct, can someone explain why? It works for some cases but not all
There's a solution with O(1) additional space complexity and O(n) time complexity.
Since an array is sorted, it makes sense to have two indices: one going from begin to end (say y), another from end to begin of an array (say x).
Here's the code:
function averagePair(arr,tar){
// That's now included in for-loop condition
// if (arr.length < 2) {
// return false;
// }
let x = arr.length - 1;
for (var y = 0; y < x; y++) {
// Division may lose precision, so it's better to compare
// arr[x] + arr[y] > 2*tar
// than
// (arr[x] + arr[y]) / 2 > tar
while (y < x && arr[x] + arr[y] > 2*tar) {
x--;
}
if (x != y && arr[x] + arr[y] == 2*tar) {
return true;
}
}
return false;
}
It's kinda two-pointers technique: we'll decrease x until a[x] + a[y] > 2*tar for current loop iteration because we need to find the closest match. At the next for-loop iteration a[y] is greater or equal than the previous one, so it makes no sense to check if a[z] + a[y] == 2*tar for any z > x. We'll do this until indices aren't equal, which means there's no match.
You're only comparing adjacent elements, eg [0] vs [1], and [1] vs [2]. You also need to compare [0] vs [2] and so on. The simplest tweak would be to use a nested loop:
for (let x = 0; x < arr.length; x++) {
for (let y = 0; y < arr.length; y++) {
if (x !== y) {
// test arr[x] against arr[y]
But it'd be more elegant and less computationally complex (O(n) instead of O(n ^ 2)) to use a Set to keep track of what's been found so far:
const nums = new Set();
for (const num of arr) {
if (nums.has(tar - num)) {
return true;
} else {
nums.add(num);
}
}
function averagePair(arr,tar){
const nums = new Set();
for (const num of arr) {
if (nums.has(tar - num)) {
return true;
} else {
nums.add(num);
}
}
return false;
}
console.log(averagePair([-2, 3, 2], 0));
console.log(averagePair([-2, 3, 3], 0));

Levenshtein distance from index 0

I've been working through "The Algorithm Design Manual" section 8.2.1 Edit Distance by Recursion. In this section Skiena writes, "We can define a recursive algorithm using the observation that the last character in the string must either be matched, substituted, inserted, or deleted." That got me wondering, why the last character? This is true for any character based on the problem definition alone. The actual Levenshtein distance algorithm makes recursive calls from the back of the strings. Why? There's no reason you couldn't do the opposite, right? Is it just a simpler, more elegant syntax?
I'm flipping the algorithm around, so it iterates from the front of the string. My attempt is below. I know my implementation doesn't work completely (ex: minDistance("industry", "interest") returns 5 instead of 6). I've spent a couple hours trying to figure out what I'm doing wrong, but I'm not seeing it. Any help would be much appreciated.
var matchChar = (c,d) => c === d ? 0 : 1;
var minDistance = function(word1, word2) {
var stringCompare = function(s, t, i, j) {
if(i === s.length) return Math.max(t.length-s.length-1,0)
if(j === t.length) return Math.max(s.length-t.length-1,0)
if(cache[i][j] !== undefined) {
return cache[i][j]
}
let match = stringCompare(s,t,i+1,j+1) + matchChar(s[i], t[j]);
let insert = stringCompare(s,t,i,j+1) + 1;
let del = stringCompare(s,t,i+1,j) + 1;
let lowestCost = Math.min(match, insert, del)
cache[i][j] = lowestCost
return lowestCost
};
let s = word1.split('')
s.push(' ')
s = s.join('')
let t = word2.split('')
t.push(' ')
t = t.join('')
var cache = []
for(let i = 0; i < s.length; i++) {
cache.push([])
for(let j = 0; j < t.length; j++) {
cache[i].push(undefined)
}
}
return stringCompare(s, t, 0, 0)
}
The lines
if(i === s.length) return Math.max(t.length-s.length-1,0)
if(j === t.length) return Math.max(s.length-t.length-1,0)
look wrong to me. I think they should be
if(i === s.length) return t.length-j
if(j === t.length) return s.length-i

Categories

Resources