Alpha beta pruning not producing good results - java

---------------
Actual Question
---------------
Ok, the real problem is not with alpha-beta pruning vs minimax algorithms. The problem is that minimax algorithm when in a tree will give only the best solutions whereas alpha-beta will give the correct value, but multiple children have that best value and some of these children shouldn't have this value.
I guess the ultimate question is, what is the most efficient way to get the best (could be multiple in the case of a tie) child of the root node.
The algorithm produces the correct value, but multiple nodes tie with that value, even though some of the moves are obviously wrong.
Example:
TickTackToe
-|-|O
-|X|-
-|X|-
will produce the values as:
(0,1) and (1,0) with value of -0.06 with my heuristic
(0,1) is the correct value as it will block my X's but (0,1) is wrong as then next move i can put an X at (0,1) and win.
When i run the same algorithm without the
if(beta<=alpha)
break;
It only returns the (0,1) with value -0.06
---------------
Originally posted question, now just sugar
---------------
I've spent days trying to figure out why my min max algorithm works, but when i add alpha beta pruning to it, it doesn't work. I understand they should give the same results and I even made a quick test of that.
My question, is why doesn't my implementation produce the same results?
This is a tic tak toe implementation in android. I can beat the algorithm sometimes when
if(beta<=alpha) break;
is not commented out, but when it is commented out it is undefeatable.
private static double minimax(Node<Integer,Integer> parent, int player, final int[][] board, double alpha, double beta, int depth) {
List<Pair<Integer, Integer>> moves = getAvailableMoves(board);
int bs = getBoardScore(board);
if (moves.isEmpty() || Math.abs(bs) == board.length)//leaf node
return bs+(player==X?-1:1)*depth/10.;
double bestVal = player == X ? -Integer.MAX_VALUE : Integer.MAX_VALUE;
for(Pair<Integer, Integer> s : moves){
int[][] b = clone(board);
b[s.getFirst()][s.getSecond()]=player;
Node<Integer, Integer> n = new Node<>(bs,b.hashCode());
parent.getChildren().add(n);
n.setParent(parent);
double score = minimax(n,player==O?X:O,b,alpha,beta, depth+1);
n.getValues().put("score",score);
n.getValues().put("pair",s);
if(player == X) {
bestVal = Math.max(bestVal, score);
alpha = Math.max(alpha,bestVal);
} else {
bestVal = Math.min(bestVal, score);
beta = Math.min(beta,bestVal);
}
/*
If i comment these two lines out it works as expected
if(beta<= alpha)
break;
*/
}
return bestVal;
}
Now this wouldn't be a problem for tick tack toe due to the small search tree, but i then developed it for checkers and noticed the same phenomenon.
private double alphaBeta(BitCheckers checkers, int depth, int absDepth, double alpha, double beta){
if(checkers.movesWithoutAnything >= 40)
return 0;//tie game//needs testing
if(depth == 0 || checkers.getVictoryState() != INVALID)
return checkers.getVictoryState()==INVALID?checkers.getBoardScore()-checkers.getPlayer()*moves/100.:
checkers.getPlayer() == checkers.getVictoryState() ? Double.MAX_VALUE*checkers.getPlayer():
-Double.MAX_VALUE*checkers.getPlayer();
List<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>> moves;
if(absDepth == maxDepth)
moves = (List<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>) node.getValues().get("moves");
else
moves = checkers.getAllPlayerMoves();
if(moves.isEmpty()) //no moves left? then this player loses
return checkers.getPlayer() * -Double.MAX_VALUE;
double v = checkers.getPlayer() == WHITE ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
for(Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> i : moves){
BitCheckers c = checkers.clone();
c.movePiece(i.getFirst().getFirst(),i.getFirst().getSecond(),i.getSecond().getFirst(),i.getSecond().getSecond());
int newDepth = c.getPlayer() == checkers.getPlayer() ? depth : depth - 1;
if(checkers.getPlayer() == WHITE) {
v = Math.max(v, alphaBeta(c, newDepth, absDepth - 1, alpha, beta));
alpha = Math.max(alpha,v);
}else {
v = Math.min(v, alphaBeta(c, newDepth, absDepth - 1, alpha, beta));
beta = Math.min(beta,v);
}
if(absDepth == maxDepth) {
double finalScore = v;
for(Node n : node.getChildren())
if(n.getData().equals(i)){
n.setValue(finalScore);
break;
}
}
/*
If i comment these two lines out it works as expected
if(beta<= alpha)
break;
*/
}
return v;
}
I tested it with pvs and it gives the same results as alpha-beta pruning, ie not nearly as good as just minimax.
public double pvs(BitCheckers checkers, int depth, int absDepth, double alpha, double beta){
if(checkers.movesWithoutAnything >= 40)
return 0;//tie game//needs testing
if(depth == 0 || checkers.getVictoryState() != INVALID)
return checkers.getVictoryState()==INVALID?checkers.getBoardScore()-checkers.getPlayer()*moves/100.:
checkers.getPlayer() == checkers.getVictoryState() ? Double.MAX_VALUE*checkers.getPlayer():
-Double.MAX_VALUE*checkers.getPlayer();
List<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>> moves;
if(absDepth == maxDepth)
moves = (List<Pair<Pair<Integer, Integer>, Pair<Integer, Integer>>>) node.getValues().get("moves");
else
moves = checkers.getAllPlayerMoves();
if(moves.isEmpty()) //no moves left? then this player loses
return checkers.getPlayer() * -Double.MAX_VALUE;
int j = 0;
double score;
for(Pair<Pair<Integer, Integer>, Pair<Integer, Integer>> i : moves){
BitCheckers c = checkers.clone();
c.movePiece(i.getFirst().getFirst(),i.getFirst().getSecond(),i.getSecond().getFirst(),i.getSecond().getSecond());
int newDepth = c.getPlayer() == checkers.getPlayer() ? depth : depth - 1;
double sign = c.getPlayer() == checkers.getPlayer()? -1 : 1;
if(j++==0)
score = -pvs(c,newDepth,absDepth-1,sign*-beta,sign*-alpha);
else {
score = -pvs(c,newDepth, absDepth-1,sign*-(alpha+1),sign*-alpha);
if(alpha<score || score<beta)
score = -pvs(c,newDepth,absDepth-1,sign*-beta,sign*-score);
}
if(absDepth == maxDepth) {
double finalScore = score;
for(Node n : node.getChildren())
if(n.getData().equals(i)){
n.setValue(finalScore);
break;
}
}
alpha = Math.max(alpha,score);
if(alpha>=beta)
break;
}
return alpha;
}
Checkers without alpha beta pruning is good, but not great. I know with a working version of alpha-beta it could be really great. Please help fix my alpha-beta pruning.
I understand it should give the same result, my question is why is my implementation not giving the same results?
To confirm that it should give the same results, i made a quick test class implementation.
public class MinimaxAlphaBetaTest {
public static void main(String[] args) {
Node<Double,Double> parent = new Node<>(0.,0.);
int depth = 10;
createTree(parent,depth);
Timer t = new Timer().start();
double ab = alphabeta(parent,depth+1,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,true);
t.stop();
System.out.println("Alpha Beta: "+ab+", time: "+t.getTime());
t = new Timer().start();
double mm = minimax(parent,depth+1,true);
t.stop();
System.out.println("Minimax: "+mm+", time: "+t.getTime());
t = new Timer().start();
double pv = pvs(parent,depth+1,Double.NEGATIVE_INFINITY,Double.POSITIVE_INFINITY,1);
t.stop();
System.out.println("PVS: "+pv+", time: "+t.getTime());
if(ab != mm)
System.out.println(ab+"!="+mm);
}
public static void createTree(Node n, int depth){
if(depth == 0) {
n.getChildren().add(new Node<>(0.,(double) randBetween(1, 100)));
return;
}
for (int i = 0; i < randBetween(2,10); i++) {
Node nn = new Node<>(0.,0.);
n.getChildren().add(nn);
createTree(nn,depth-1);
}
}
public static Random r = new Random();
public static int randBetween(int min, int max){
return r.nextInt(max-min+1)+min;
}
public static double pvs(Node<Double,Double> node, int depth, double alpha, double beta, int color){
if(depth == 0 || node.getChildren().isEmpty())
return color*node.getValue();
int i = 0;
double score;
for(Node<Double,Double> child : node.getChildren()){
if(i++==0)
score = -pvs(child,depth-1,-beta,-alpha,-color);
else {
score = -pvs(child,depth-1,-alpha-1,-alpha,-color);
if(alpha<score || score<beta)
score = -pvs(child,depth-1,-beta,-score,-color);
}
alpha = Math.max(alpha,score);
if(alpha>=beta)
break;
}
return alpha;
}
public static double alphabeta(Node<Double,Double> node, int depth, double alpha, double beta, boolean maximizingPlayer){
if(depth == 0 || node.getChildren().isEmpty())
return node.getValue();
double v = maximizingPlayer ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
for(Node<Double,Double> child : node.getChildren()){
if(maximizingPlayer) {
v = Math.max(v, alphabeta(child, depth - 1, alpha, beta, false));
alpha = Math.max(alpha, v);
}else {
v = Math.min(v,alphabeta(child,depth-1,alpha,beta,true));
beta = Math.min(beta,v);
}
if(beta <= alpha)
break;
}
return v;
}
public static double minimax(Node<Double,Double> node, int depth, boolean maximizingPlayer){
if(depth == 0 || node.getChildren().isEmpty())
return node.getValue();
double v = maximizingPlayer ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
for(Node<Double,Double> child : node.getChildren()){
if(maximizingPlayer)
v = Math.max(v,minimax(child,depth-1,false));
else
v = Math.min(v,minimax(child,depth-1,true));
}
return v;
}
}
This does in fact give what i expected alpha-beta and pvs are about the same speed (pvs is slower because the children are in random order) and produce the same results as minimax. This proves that the algorithms are correct, but for whatever reason, my implementation of them are wrong.
Alpha Beta: 28.0, time: 25.863126 milli seconds
Minimax: 28.0, time: 512.6119160000001 milli seconds
PVS: 28.0, time: 93.357653 milli seconds
Source Code for Checkers implementation
Pseudocode for pvs
Pseudocode for alpha beta i'm following
Full Souce Code for the Tick Tack Toe Implementation

I think you might be misunderstanding AB pruning.
AB pruning should give you the same results as MinMax, it's just a way of not going down certain branches because you know making that move would have been worse than another move that you examined which helps when you have massive trees.
Also, MinMax without using a heuristic and cutting off your search will always be undefeatable because you've computed every possible path to reach every terminating state. So I would've expected that AB pruning and MinMax would both be unbeatable so I think something is wrong with your AB pruning. If your minmax is undefeatable, so should your approach using AB pruning.

Related

Minimax only goes down to leftmost leaf

So I got a small board game for my Othello game. In this game the AI should decide what to move to make with a Alpha Beta Prune search algorithm. I used the following Pseudocode form geeksforgeeks:
function minimax(node, depth, isMaximizingPlayer, alpha, beta):
if node is a leaf node :
return value of the node
if isMaximizingPlayer :
bestVal = -INFINITY
for each child node :
value = minimax(node, depth+1, false, alpha, beta)
bestVal = max( bestVal, value)
alpha = max( alpha, bestVal)
if beta <= alpha:
break
return bestVal
else :
bestVal = +INFINITY
for each child node :
value = minimax(node, depth+1, true, alpha, beta)
bestVal = min( bestVal, value)
beta = min( beta, bestVal)
if beta <= alpha:
break
return bestVal
This is how I implemented it:
//Called when it's the AI's turn to make a move.
public Board makeMove(Board board) {
setRoot(board);
int alpha = Integer.MIN_VALUE;
int beta = Integer.MAX_VALUE;
int val = alphaBetaSearch(tree.getRoot(), 0, true, alpha, beta);
Board resultBoard = //Should be AI Board/move
setRoot(resultBoard);
return resultBoard;
}
private int alphaBetaSearch(Node node, int depth, boolean maximizing, int alpha, int beta) {
currentDepth = depth;
if (node.isLeaf()) {
return evaluateUtility(node.getBoard());
}
if (maximizing) {
int bestValue = Integer.MIN_VALUE;
for (Node child : node.getChildren()) {
int value = alphaBetaSearch(child, depth + 1, false, alpha, beta);
bestValue = Integer.max(bestValue, value);
alpha = Integer.max(alpha, bestValue);
if (beta <= alpha) {
break;
}
return alpha;
}
} else {
int bestValue = Integer.MAX_VALUE;
for (Node child : node.getChildren()) {
int value = alphaBetaSearch(child, depth + 1, true, alpha, beta);
bestValue = Integer.min(bestValue, value);
beta = Integer.min(beta, bestValue);
if (beta <= alpha) {
break;
}
return beta;
}
}
return 0; //Not sure what should be returned here
}
private int evaluateUtility(Board board) {
int whitePieces = board.getNumberOfWhiteCells();
int blackPieces = board.getNumberOfBlackCells();
int sum = (blackPieces - whitePieces);
return sum;
}
As my Board is quite small (4x4) I have been able to calculate the complete search tree before the game starts in about 20 seconds. This should improve my search as I don't have build anything when playing. Each Node in my tree contains a Board which themselves have a 2D array of cells. The root node/board looks like this:
EMPTY EMPTY EMPTY EMPTY
EMPTY WHITE BLACK EMPTY
EMPTY BLACK WHITE EMPTY
EMPTY EMPTY EMPTY EMPTY
Now when I start the game, this is the starting board and I call the AI to make a move. When the minimax call has executed, it returns the value 2 at the depth of 12. Depth 12 is a leaf node/board in my tree. After running it with the debugger it seems that my implementation doesn't traverse back up the tree. All it does is going down to the leftmost tree and returns it's evaluation.

Finding biggest area of adjacent numbers in a matrix using DFS algorithm

I am learning programming on my own with a book for beginners. My last task after chapter Arrays is to :
// Find the biggest area of adjacent numbers in this matrix:
int[][] matrix = {
{1,3,2,2,2,4},
{3,3,3,2,4,4},
{4,3,1,2,3,3}, // --->13 times '3';
{4,3,1,3,3,1},
{4,3,3,3,1,1}
As a hint I have - use DFS or BFS algorithm. After I read about them and saw many their implementations I got the idea but it was just too overwhelming for a beginner. I found the solution for my task and after I runned the program many times I understood how it works and now I can solve the problem on my own. Although, I am happy that this solution helped me to learn about recursion, I am wondering can the following code be modified in iterative way and if so can you give me hints how to do it? Thank you in advance.
public class Practice {
private static boolean[][] visited = new boolean[6][6];
private static int[] dx = {-1,1,0,0};
private static int[] dy = {0,0,-1,1};
private static int newX;
private static int newY;
public static void main(String[] args){
// Find the biggest area of adjacent numbers in this matrix:
int[][] matrix = {
{1,3,2,2,2,4},
{3,3,3,2,4,4},
{4,3,1,2,3,3}, // --->13 times '3';
{4,3,1,3,3,1},
{4,3,3,3,1,1}
};
int current = 0;
int max = 0;
for (int rows = 0; rows < matrix.length;rows++){
for(int cols = 0; cols < matrix[rows].length;cols++){
if (visited[rows][cols] == false){
System.out.printf("Visited[%b] [%d] [%d] %n", visited[rows]
[cols],rows,cols);
current = dfs(matrix,rows,cols,matrix[rows][cols]);
System.out.printf("Current is [%d] %n", current);
if(current > max){
System.out.printf("Max is : %d %n ", current);
max = current;
}
}
}
}
System.out.println(max);
}
static int dfs(int[][] matrix,int x, int y, int value){
if(visited[x][y]){
System.out.printf("Visited[%d][%d] [%b] %n",x,y,visited[x][y]);
return 0;
} else {
visited[x][y] = true;
int best = 0;
int bestX = x;
int bestY = y;
for(int i = 0; i < 4;i++){
//dx = {-1,1,0,0};
//dy = {0,0,-1,1};
int modx = dx[i] + x;
System.out.printf(" modx is : %d %n", modx);
int mody = dy[i] + y;
System.out.printf(" mody is : %d %n", mody);
if( modx == -1 || modx >= matrix.length || mody == -1 || mody >=
matrix[0].length){
continue;
}
if(matrix[modx][mody] == value){
System.out.printf("Value is : %d %n",value);
int v = dfs(matrix,modx,mody,value);
System.out.printf(" v is : %d %n",v);
best += v;
System.out.printf("best is %d %n",best);
}
newX = bestX;
System.out.printf("newX is : %d %n",newX);
newY = bestY;
System.out.printf("newY is : %d %n",newY);
}
System.out.printf("Best + 1 is : %d %n ",best + 1);
return best + 1;
}
}
}
If you look on the Wikipedia page for Depth-first search under the pseudocode section, they have an example of a iterative verision of the DFS algorithm. Should be able to figure out a solution from there.
*Edit
To make it iterative, you can do the following:
procedure DFS-iterative(matrix, x, y):
let S be a stack
let value = 0
if !visited[v.x, v.y]
S.push(position(x,y))
while S is not empty
Position v = S.pop()
value += 1
for all valid positions newPosition around v
S.push(newPosition)
return value
Everytime you would call the dfs() method in the recursive method, you should be calling S.push(). You can create class Position as follows
class Position{
int x;
int y;
public Position(int x, int y){
this.x = x;
this.y = y;
}
//getters and setters omitted for brevity
}
and use the built in java class java.util.Stack to make it easy.
Stack<Position> s = new Stack<Position>();
If you want to use BFS instead of DFS, you can simple change the Stack to a Queue and you will get the desired result. This link has a very nice explanation of stacks and queues and may prove useful as you learn about the topic.
I assume you are looking for a BFS solution, since you already have a working DFS, and BFS is iterative while DFS is recursive (or at least, is easier to implement recursively).
The (untested) BFS code to measure a region's size could be:
public static int regionSize(int[][] matrix,
int row, int col, HashSet<Point> visited) {
ArrayDeque<Point> toVisit = new ArrayDeque<>();
toVisit.add(new Point(col, row));
int regionColor = matrix[col][row];
int regionSize = 0;
while ( ! toVisit.isEmpty()) {
Point p = toVisit.removeFirst(); // use removeLast() to emulate DFS
if ( ! visited.contains(p)) {
regionSize ++;
visited.add(p);
// now, add its neighbors
for (int[] d : new int[][] {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}) {
int nx = p.x + d[0];
int ny = p.y + d[1];
if (nx >= 0 && nx < matrix[0].length
&& ny >= 0 && ny < matrix.length
&& matrix[ny][nx] == regionColor) {
toVisit.addLast(new Point(nx, ny)); // add neighbor
}
}
}
}
return regionSize;
}
Note that you can change a (queue-based) BFS into an iterative DFS by changing a single line. In a recursive DFS, you would be using the program stack to keep track of toVisit intead of an explicit stack/deque. You can test this by adding a System.out.println to track the algorithm's progress.
Above, I use a HashSet of Point instead of a boolean[][] array, but feel free to use whichever is easiest for you.

Improving minimax algorithm for Gomoku AI with transposition table?

I'm building an AI for Gomoku (16x16) with minimax and alpha-beta pruning but it's very slow. So far, I have tried pre-sorting the order of moves and instead of deep copying the board, adding and later removing the moves. Also, I use an arraylist of relevant moves(which are within a radius of 2 to already placed pieces) to reduce the search board. Yet the AI still struggles even at a depth search of 3.
Edit: I have found out about something called transposition table, but I don't know where to start. Any help would be great!
private double minimax(Board node, String player, int depth, double lowerBound, double upperBound){
if (depth==3){
return node.evaluate();
}
if (player.equals(humanPiece)) {// min node
// sort setup
ArrayList<int[]> relevantMoves = node.relevantMoves();
HashMap<int[], Double> moveValueTable = new HashMap<>();
for (int[] move: relevantMoves){
node.addMove(move[0], move[1], player);
double val = node.evaluate();
moveValueTable.put(move, val);
node.retractMove(move[0], move[1]);
}
// insertion sort from small to big (alpha-beta optimization)
insertionSort(relevantMoves, moveValueTable);
result = Double.POSITIVE_INFINITY;
// minimax
for (int[] move : relevantMoves) { // y first, x second
node.addMove(move[0], move[1], player);
double score = minimax(node, node.getEnemy(player), depth+1, lowerBound, upperBound);
node.retractMove(move[0], move[1]);
if (score < upperBound) {
upperBound = score;
}
if (score < result) result = score;
if (lowerBound > upperBound) {
break;
}
}
return result;
}
else{// max node
// sort setup
ArrayList<int[]> relevantMoves = node.relevantMoves();
HashMap<int[], Double> moveValueTable = new HashMap<>();
for (int[] move: relevantMoves){
node.addMove(move[0], move[1], player);
double val = node.evaluate();
moveValueTable.put(move, val);
node.retractMove(move[0], move[1]);
}
// insertion sort from big to small (alpha-beta optimization)
reversedInsertionSort(relevantMoves, moveValueTable);
result = Double.NEGATIVE_INFINITY;
// minimax
for (int[] move : relevantMoves) { // y first, x second
node.addMove(move[0], move[1], player);
double score = minimax(node, node.getEnemy(player), depth+1, lowerBound, upperBound);
node.retractMove(move[0], move[1]);
if (score > lowerBound) {
lowerBound = score;
}
if (score > result) result = score;
if (lowerBound > upperBound) {
break;
}
}
return result;
}
}
Here is a very good explanation of how transposition tables work: TT
It can improve your search speed by eliminating transposition from the search tree. Transpositions are positions that can be attained by two or more different sequences of moves. Some games, like chess or checkers have plenty of transpositions, others have very little or even none.
Once you have the transposition table in place, it is easy to add more speed optimizations that rely on it.

Douglas-Peucker point count tolerance

I am trying to implement Douglas-Peucker Algorithm with point count tolerance. I mean that i specifies that i want 50% compression. I found this algorithm on this page http://psimpl.sourceforge.net/douglas-peucker.html under Douglas-Peucker N. But i am not sure how this algorithm is working. Is there any implementation of this in java or some good specification about this version of algorithm?
What i dont understand from psimpl explanation is what will happend after we choose fist point into simplification? We will broke the edge into two new edges and rank all points and choose best point from both edges?
DP searches the polyline for the farthest vertex from the baseline. If this vertex is farther than the tolerance, the polyline is split there and the procedure applied recursively.
Unfortunately, there is no relation between this distance and the number of points to keep. Usually the "compression" is better than 50%, so you may try to continue the recursion deeper. But achieving a good balance of point density looks challenging.
Combine the Douglas-peucker algorithm with iteration, and consider the remained points as a judge criteria.
Here is my algorithm, array 'points' stores the points of the trajectory.Integer 'd' is the threshold.
public static Point[] divi(Point[] points,double d)
{
System.out.println("threshold"+d);
System.out.println("the nth divi iteration");
int i = 0;
Point[] p1 = new Point[points.length];
for (i = 0;i<points.length;i++)
p1[i] = points[i];
compress(p1, 0,p1.length - 1,d); //first compression
int size = 0;
for (Point p : p1) //trajectory after compression
if(p != null)
size ++;
System.out.println("size of points"+size);
if(size<=200 && size>=100)
return p1;
else if(size>200)
return divi(p1,d + d/2.0);
else
return divi(points,d/2.0);
}
public static void compress(Point[] points,int m, int n,double D)
{
System.out.println("threshold"+D);
System.out.println("startIndex"+m);
System.out.println("endIndex"+n);
while (points[m] == null)
m ++;
Point from = points[m];
while(points[n] == null)
n--;
Point to = points[n];
double A = (from.x() - to.x()) /(from.y() - to.y());
/** -
* 由起始点和终止点构成的直线方程一般式的系数
*/
double B = -1;
double C = from.x() - A *from.y();
double d = 0;
double dmax = 0;
if (n == m + 1)
return;
List<Double> distance = new ArrayList<Double>();
for (int i = m + 1; i < n; i++) {
if (points[i] ==null)
{
distance.add(0.0);
continue;
}
else
{
Point p = points[i];
d = Math.abs(A * (p.y()) + B * (p.x()) + C) / Math.sqrt(Math.pow(A, 2) + Math.pow(B, 2));
distance.add(d);
}
}
dmax= Collections.max(distance);
if (dmax < D)
for(int i = n-1;i > m;i--)
points[i] = null;
else
{
int middle = distance.indexOf(dmax) + m + 1;
compress(points,m, middle,D);
compress(points,middle, n,D);
}
}

Given four coordinates check whether it forms a square

So I am trying to write a simple method which takes in set of four coordinates and decide whether they form a square or not.My approach is start with a point and calculate the distance between the other three points and the base point.From this we can get the two sides which have same value and the one which is a diagonal.Then I use Pythagoras theorem to find if the sides square is equal to the diagonal.If it is the isSquare method return true else false.The thing I want to find out is there some cases I might be missing out on or if something is wrong with the approach.Thanks for the all the help.
public class CoordinatesSquare {
public static boolean isSquare(List<Point> listPoints) {
if (listPoints != null && listPoints.size() == 4) {
int distance1 = distance(listPoints.get(0), listPoints.get(1));
int distance2 = distance(listPoints.get(0), listPoints.get(2));
int distance3 = distance(listPoints.get(0), listPoints.get(3));
if (distance1 == distance2) {
// checking if the sides are equal to the diagonal
if (distance3 == distance1 + distance2) {
return true;
}
} else if (distance1 == distance3) {
// checking if the sides are equal to the diagonal
if (distance2 == distance1 + distance3) {
return true;
}
}
}
return false;
}
private static int distance(Point point, Point point2) {
//(x2-x1)^2+(y2-y1)^2
return (int) (Math.pow(point2.x - point.x, 2) + (Math.pow(point2.y
- point.y, 2)));
}
public static void main(String args[]) {
List<Point> pointz = new ArrayList<Point>();
pointz.add(new Point(2, 2));
pointz.add(new Point(2, 4));
pointz.add(new Point(4, 2));
pointz.add(new Point(4, 4));
System.out.println(CoordinatesSquare.isSquare(pointz));
}
}
//Point Class
public class Point {
Integer x;
Integer y;
boolean isVisited;
public Point(Integer x, Integer y) {
this.x = x;
this.y = y;
}
#Override
public boolean equals(Object obj) {
if(obj!=null && obj.getClass().equals(this.getClass())){
return ((Point) obj).x.equals(this.x)&&((Point) obj).y.equals(this.y);
}
return false;
}
}
You know, you can do the same check much easier. You just have to check two things:
"four points make a parallelogram" and "one of its angles is right".
First is true when P3 = P1 + (P2-P1) + (P4-P1)
And the second when (P2-P1)*(P4-P1) = 0
Where A*B is a dot product (A.x * B.x + A.y * B.y)
The only catch here is computational error. You can't expect floats to be exactly equal, so instead of A=B you should consider using something like abs(A-B) < E where E is small enough for your case.
Here's a corner case:
What if dist1 is the diagonal distance of the square? (I'm assuming the 4 points are in arbitrary order.)
You probably need to do another check for the distances:
if(dist1 == dist2){
//do stuff
}
else if(dist1 == dist3){
//do stuff
}
else if(dist2 == dist3){
//do stuff
}
else return false;
Your function doesn't take everything into account. You're only checking one point against the others. jwpat7 mentions this, so here's an example:
Assume the points are in this order: (red, yellow, green, blue), and each block on the grid is one.
Your distance1 and distance2 will both be equal to 4, so you're essentially saying that the last point can be any point where distance3 = 8. This is the blue line. If the last point is anywhere on that line, you just approved it as square.
You can fix this easily by doing the same check , but using the next coordinate as the 'base', instead of 0. If your check passes for two points, it's definitely a square.
Alternative:
You can check if it's not a square. In a valid square, there are only two valid distances, side length(s), and diagonal length(d).
Since you're using squared distance, d = s * 2
If any distance(there are only six) does not equal either d or s, it cannot be a square. If all six do, it must be a square.
The advantage is that if you check to prove it is a square, you have to do all six distance checks. If you want to prove it's not a square, you can just stop after you find a bad one.
So, it depends on your data. If you're expecting more squares than non-squares, you might want to check for squareness. If you expect more non-squares, you should check for non-squareness. That way you get a better average case, even though the worst case is slower.
public static boolean isSquare(List<Point> points){
if(points == null || points.size() != 4)
return false;
int dist1 = sqDistance(points.get(0), points.get(1));
int dist2 = sqDistance(points.get(0), points.get(2));
if(dist1 == dist2){ //if neither are the diagonal
dist2 = sqDistance(points.get(0), points.get(3));
}
int s = Math.min(dist1, dist2);
int d = s * 2;
for(int i=0;i<points.size;i++){
for(int j=i+1;j<points.size();j++){
int dist = sqDistance(points.get(i), points.get(j));
if(dist != s && dist != d))
return false;
}
}
return true;
}
If you add an else if(dist2 == dist3){...} alternative (as suggested in another answer also) then it is true that your isSquare method will recognize a square when the four points form a square. However, your code will also report some non-squares as being squares. For example, consider the set of points {(0,0), (1,1), (0,-1), (-1,0)}. Then your distance1,2,3 values are 2, 1, 1, respectively, which will satisfy the tests in the dist2 == dist3 case.
Any non-degenerate quadrilateral has a total of six inter-corner distances. Knowing five of those distances constrains the remaining distance to either of two values; that is, it doesn't uniquely constrain it. So I imagine that a square-testing method based on inter-corner distances will have to compute and test all six of them.
You are not using the Pythagorean Theorem correctly. The Pythagorean Theorem states that the sum of the squares of two legs is the square of the diagonal, and you are interpreting it to mean that the sum of the two legs is equal to the diagonal. You should use this for the Pythagorean Theorem testing:
if (distance3 == Math.sqrt(distance1*distance1 + distance2*distance2)) {
return true;
}
Does this make sense?
<script>
function isSquare(p1,p2,p3,p4){
if ((areACorner(p1,p2,p3) && areACorner(p4,p2,p3))
|| (areACorner(p1,p2,p4) && areACorner(p3,p2,p4))
|| (areACorner(p1,p3,p4) && areACorner(p2,p3,p4))) return true
return false
}
function areACorner(p1,p2,p3){
//pivot point is p1
return Math.abs(p2.y - p1.y) == Math.abs(p3.x - p1.x)
&& Math.abs(p2.x - p1.x) == Math.abs(p3.y - p1.y)
}
</script>
Output:
console.log(isSquare({x:0,y:0},{x:1,y:1},{x:0,y:1},{x:1,y:0}))
true
console.log(isSquare({x:0,y:0},{x:1,y:1},{x:-1,y:-1},{x:1,y:0}))
false
If you use something like (my C code), where I use squared distance (to avoid sqrt):
int sqDist(Point p1, Point p2) {
int x = p1.x - p2.x;
int y = p1.y - p2.y;
return(x*x + y*y);
}
where Point is simply:
typedef struct {
int x, y;
} Point;
`
In your code, calculate the permutations of each corner to one another, find the smallest / largest edges (in squared values), then you can check that you have 4 sides and 2 diagonals:
int squares[6];
squares[0] = sqDist(p[0], p[1]);
squares[1] = sqDist(p[0], p[2]);
squares[2] = sqDist(p[0], p[3]);
squares[3] = sqDist(p[1], p[2]);
squares[4] = sqDist(p[1], p[3]);
squares[5] = sqDist(p[2], p[3]);
int side = squares[0];
int diagonal = squares[0];
int i = 0;
while((++i <= 4) && (side >= diagonal)) {
if(squares[i] < side) side = squares[i];
if(squares[i] > diagonal) diagonal = squares[i];
}
int diagonal_cnt = 0;
int side_cnt = 0;
int error = 0;
for(int i = 0; i < 6; i++) {
if(abs(side - squares[i]) <= error) side_cnt++;
if(abs(diagonal - squares[i]) <= error) diagonal_cnt++;
}
printf("Square = %s\n", ((side_cnt == 4) && (diagonal_cnt == 2)) ? "true" : "false");
You could change the error value to handle floating points errors -- if you'd like to convert this routine to handle floating point values.
Note: If all points are at the same location, I consider this a point (not a square).

Categories

Resources