Alpha-beta pruning with clone doesnt work Java - java

Wanna simulate the next move for my AI in my othello game, but instead of just returning the next move, it makes all the moves on the original board instead of just simulating on a clone and the game ends.
public class GameState implements Cloneable{
private Node[][] board; // Game board
private int scorePlayer, scoreAI; // Player scores ( will start at 0 )
private ArrayList<Node> validNodes; // List holding all nodes possible to add pieces to
/**
* Creates the game state
*/
public GameState(){
// create size x size board
this.board = new Node[Setting.BOARD_SIZE][Setting.BOARD_SIZE];
validNodes = new ArrayList<>();
scorePlayer = 0;
scoreAI = 0;
protected GameState clone() {
return new GameState(this);
}------------------------ CLONE METHOD----------------
public int search(GameState board, Player player, int alpha, int beta, int depth, ScoreEval function) {
int record = Integer.MIN_VALUE;
Node maxMove = null;
int result;
GameState subBoard = board.clone();
if (depth <= 0 || board.getValidMoves().size()==0) {
record = function.evaluate(board, player);
} else {
ArrayList<Node> possibleMoves = board.getValidMoves();
if (!possibleMoves.isEmpty()) {
for (int i =0; i<possibleMoves.size();i++) {
Node nod = possibleMoves.get(i);
subBoard = board.clone();
subBoard.setPiece(nod.x,nod.y, player.type);
if(player.type==Setting.TILE_AI){
result = -search(subBoard, aiAss1.Controller.pHum, alpha, beta, depth - 1, function);
}
else{
result = -search(subBoard, aiAss1.Controller.pAI, alpha, beta, depth - 1, function);
}
if (result > record) {
record = result;
maxMove = nod;
}
}
} else {
record = -search(subBoard, player, alpha, beta, depth - 1, function);
}
}
bestMove = maxMove;
return record;
}

Try breaking down you logic into parts, putting each in a separate method. Then write a unit test for each part, and check that each part does what you want.
This is how you should write software.

Related

Boolean is set true without ever getting to the line setting the boolean true

A normal level (picture 1)
The squares are tiles and the red dots are vertices (picture 2)
What I'm trying to make is a set of blocks (grey things) which can be destroyed (removed) so water can go trough them. As shown in the picture this works most of the time, some blocks have already been removed and water is flowing trough the gaps. The problem is that it doesn't work all the time. Sometimes when a block is removed water doesn't fill the gap and there comes a hole in the water.
The code
public class WaterManager(){
//level is the map, it is the parent containing the tile mesh and the water
public Node level;
Mesh water = new Mesh();
Geometry geo;
Vector3f[] vertices;
//subdivision is used so the mesh is more detailed, a subdivision of 2 means there are 9 vertices per tile (cube) as shown on picture 2
public int subdivision = 2;
//The amount of vertices on the x and z-axis
int xVertices, zVertices;
//The size of one tile, this is 10 in my case
float cubeSize;
//The amount of tiles on the x and z-axis
int xCubes, zCubes;
List<Integer> triangles = new ArrayList<>();
List<Vector3f> emptyCubes = new ArrayList<>();
List<Vector3f> newEmptyCubes = new ArrayList<>();
Dictionary<Vector3f, Tile> tiles = new Hashtable<Vector3f, Tile>();
public void Loop(Vector3f startPos){
newEmptyCubes.clear();
emptyCubes.clear();
boolean loop = true;
List<Vector3f> temp = new ArrayList<>();
//This is the first check to get all the connected blocks that should be filled with water
//including the block clicked on
GetConnectedEmptyTiles(startPos, newEmptyCubes);
//If there is more then 1 block which should be filled than loop through all the blocks
//and get the ones connected to those that should be filled with water
if (newEmptyCubes.size() > 1){
do{
for(Vector3f cube : newEmptyCubes){
GetConnectedEmptyTiles(cube, temp);
}
emptyCubes.addAll(newEmptyCubes);
newEmptyCubes.clear();
if (temp.size() <= 0)
loop = false;
newEmptyCubes.addAll(temp);
temp.clear();
}while(loop);
} else emptyCubes.addAll(newEmptyCubes);
//This creates the mesh
PaintEmptyTiles();
}
private void GetConnectedEmptyTiles(Vector3f pos, List<Vector3f> list){
//The blocks connected to a given posistion including the given position
Vector3f[] connectedTilesPlusPos = new Vector3f[]{
new Vector3f(pos.x + cubeSize, pos.y, pos.z),
new Vector3f(pos.x - cubeSize, pos.y, pos.z),
new Vector3f(pos.x, pos.y, pos.z + cubeSize),
new Vector3f(pos.x, pos.y, pos.z - cubeSize),
pos
};
//If the removed block is not connected to water then it shouldn't be filled
//with water
boolean connectedToWater = false;
List<Vector3f> tempList = new ArrayList<>();
//Check if the given position exists
if (tiles.get(pos) != null){
System.out.println("tile has water: " + tiles.get(pos).water);
//Loop through all the connected blocks including the given position
//and if they should be filled with water than add them to tempList
for(Vector3f tile : connectedTilesPlusPos){
if (tiles.get(tile) != null){
//Check if there is a block at the position (tile), if there
// is not than it can be filled with water
if (tiles.get(tile).tileObj == null){
//Check if the position already has water in it **(this is were it goes wrong)**
if (tiles.get(tile).water == false){
tempList.add(tile);
tiles.get(tile).water = true;
} else connectedToWater = true; //If there is already water attached to the
//the given position then it is connected to water
}
}
}
if (!connectedToWater)
tempList.clear();
else list.addAll(tempList);
}
}
private void PaintEmptyTiles(){
for(Vector3f cube : emptyCubes){
MakeTriangles(cube);
}
MakeMesh();
}
public void MakeTriangles(Vector3f pos){
//Gets an array of vertices that are used to create water at a given position as shown in picture 2
Vector3f[] verticesToUse = GetVerticesForPosition(pos);
List<Integer> indexes = new ArrayList<>();
for(int i = 0; i < vertices.length; i++){
for(Vector3f vert : verticesToUse){
if (vertices[i].equals(vert)){
indexes.add(i);
}
}
}
int xVerticesSize = rowSize(indexes, tiles.get(pos));
int zVerticesSize = (int)indexes.size() / xVerticesSize;
int verticesLenght = (int)Math.round(Math.sqrt(vertices.length));//The amount of vertices in one row.
int i = 0;
for(int x = 0; x < xVerticesSize; x++){
for (int z = 0; z < zVerticesSize; z++){
int vert = indexes.get(i);
if (indexes.contains(vert + verticesLenght + 1)){
//Creates a quad, since the water is build from small quads
triangles.add(vert);
triangles.add(vert + 1);
triangles.add(vert + verticesLenght + 1);
triangles.add(vert + verticesLenght + 1);
triangles.add(vert + verticesLenght);
triangles.add(vert);
}
i++;
}
}
}
private Vector3f[] GetVerticesForPosition(Vector3f pos){
List<Vector3f> tempVertices = new ArrayList<>();
for (Vector3f vertex : vertices) {
if (vertex != null){
float distance = DistanceOnXAndZ(pos, vertex);
float roundedDistance = roundedTo(distance, cubeSize / subdivision);
if (distance < cubeSize || (roundedDistance <= cubeSize && vertex.y <= -100 )){
tempVertices.add(vertex);
}
}
}
Vector3f[] returnVertices = new Vector3f[tempVertices.size()];
returnVertices = tempVertices.toArray(returnVertices);
return returnVertices;
}
//This calculates the distance from a point to another point but only using the x and z-axis
private float DistanceOnXAndZ(Vector3f origin, Vector3f target){
float originX = origin.x;
float originZ = origin.z;
float targetX = target.x;
float targetZ = target.z;
float xDistance = targetX - originX;
float zDistance = targetZ - originZ;
float distance = (float)Math.sqrt((xDistance * xDistance) + (zDistance * zDistance));
return distance;
}
private void MakeMesh(){
water.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
int[] tri = new int[triangles.size()];
for(int i = 0; i < triangles.size(); i++){tri[i] = triangles.get(i);}
water.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(tri));
water.updateBound();
geo = new Geometry("Water", water);
Material mat = new Material(assetManager,
"Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geo.setMaterial(mat);
level.attachChild(geo);
}
}
//The tile Object
public class Tile {
//not used anymore, but not deleted yet
public enum CellType {
LEFTORRIGHTSIDE, OTHER
}
public CellType cellType;
//The cube the Tile contains, if there is no cube then this is null
public Spatial tileObj;
//True if the tile contains water, false if not
public boolean water = false;
public Tile(CellType type, Spatial obj){
//Nothing is done with cellType
cellType = type;
tileObj = obj;
}
}
The reason
The reason this occurs is still unknown, but I think it has something to do with the water boolean in Tile already set to true even when it has a cube (grey thing) in it.
Things to know
tiles is a list containing all the positions that contain a tile, if it contains a block then tileObj is not null. If the position contains water then the water boolean should be true.
PaintEmptyTiles(), in Loop() is a function that creates the water mesh
When a block is destroyed by clicking on it, the Loop() function is called and the tileObj in Tile is set to null in the tiles list
A tile is a square with, containing either a grey block, water or nothing.

Othello - Algorithm for finding the available options to play

I'm trying to develop the game Othello using Java and I'm struggling with the implementation of finding the available moves the player has(not computer).
For example I'm player 1, and I'm playing with the white pieces,
Check if the button is empty or not. (I'm using buttons as tiles)
Check if there're any neighbors of the opposite color.
If there is, continue checking every direction there's an opposite color until
If we reach a boundary - return false.
If we reach our color - turn all the pieces to my color.
I'm struggling implementing 3. and 5.
How can I Iterate through all the directions ( maximum of 8 directions if I'm in the inner part of the board ), and how can I can advance on checking the colors in each direction?
I thought about implementing all the 8 directions for the inner board, and then implementing all the possibilities in the outer board and checking edge options which is VERY not efficient and I don't want to code like that.
You don't have to look on the code, I'm trying to figure out how to approach it (thinking about 2 for loops), but here's the function and the whole code below: (every button has an icon - black/white piece)
private void checkLegalPlay(int row, int col) {
if(playerNum == 0){ //Black player
if(row > 0 && row < 7 && col > 0 && col < 7){ //Inner board -
//not good, i want from any point of the board
for(int i = col-1; i != 0; i--)
if(squares[row][i].getIcon() != null){
if(squares[row][i].getIcon() == blackPiece){
//Advance until boundary - return false
//Advance if there're black pieces
//If we get to white piece, turn all to
// white pieces
}
}
}
}
}
It's already almost 300 lines of code, so I prefer to give a link if you really want to see what I've done so far: -deleted-
Software development is an art of abstraction. You should try to develop a skill to see similarities between pieces of logic and to abstract them away. For example, to check if the move is legal you have to iterate from the cell in different directions applying the same check logic. Moreover, check of a move and applying move (flipping pieces) share the same iteration logic. So let's abstract it away, i.e. let's separate iteration from logic we do inside iteration:
private static final int SIZE = 8;
static boolean isValidPos(int pos) {
return pos >= 0 && pos < SIZE;
}
static class Point {
public final int row;
public final int col;
public Point(int row, int col) {
this.row = row;
this.col = col;
}
}
private static final Point[] ALL_DIRECTIONS = new Point[]{
new Point(1, 0),
new Point(1, 1),
new Point(0, 1),
new Point(-1, 1),
new Point(-1, 0),
new Point(-1, -1),
new Point(0, -1),
new Point(1, -1),
};
interface CellHandler {
boolean handleCell(int row, int col, Icon icon);
}
void iterateCells(Point start, Point step, CellHandler handler) {
for (int row = start.row + step.row, col = start.col + step.col;
isValidPos(row) && isValidPos(col);
row += step.row, col += step.col) {
Icon icon = squares[row][col].getIcon();
// empty cell
if (icon == null)
break;
// handler can stop iteration
if (!handler.handleCell(row, col, icon))
break;
}
}
static class CheckCellHandler implements CellHandler {
private final Icon otherIcon;
private boolean hasOtherPieces = false;
private boolean endsWithMine = false;
public CheckCellHandler(Icon otherIcon) {
this.otherIcon = otherIcon;
}
#Override
public boolean handleCell(int row, int column, Icon icon) {
if (icon == otherIcon) {
hasOtherPieces = true;
return true;
} else {
endsWithMine = true;
return false;
}
}
public boolean isGoodMove() {
return hasOtherPieces && endsWithMine;
}
}
class FlipCellHandler implements CellHandler {
private final Icon myIcon;
private final Icon otherIcon;
private final List<Point> currentFlipList = new ArrayList<Point>();
public FlipCellHandler(Icon myIcon, Icon otherIcon) {
this.myIcon = myIcon;
this.otherIcon = otherIcon;
}
#Override
public boolean handleCell(int row, int column, Icon icon) {
if (icon == myIcon) {
// flip all cells
for (Point p : currentFlipList) {
squares[p.row][p.col].setIcon(myIcon);
}
return false;
} else {
currentFlipList.add(new Point(row, column));
return true;
}
}
}
private boolean checkLegalPlay(int row, int col) {
ImageIcon otherIcon = (playerNum == 0) ? whitePiece : blackPiece;
Point start = new Point(row, col);
for (Point step : ALL_DIRECTIONS) {
// handler is stateful so create new for each direction
CheckCellHandler checkCellHandler = new CheckCellHandler(otherIcon);
iterateCells(start, step, checkCellHandler);
if (checkCellHandler.isGoodMove())
return true;
}
return false;
}
ALL_DIRECTIONS represents all 8 directions you can navigate. iterateCells method accepts some direction and navigates through it till it hits either empty cell or a border. For each non-empty cell handleCell of the passed CellHandler is called. So now your checkLegalPlay becomes simple: implement CheckCellHandler and iterate through all possible directions to see if we can flip in that direction. Implementing actual flip logic is actually very similar: just implement FlipCellHandler and use it similarly. Note that you can also abstract "current player" away by explicitly passing myIcon and otherIcon to handlers.

Negamax-search implementation not working when player can move twice in a row

I'm trying to implement Negamax search for a game called Nine Men's Morris in Java.
If a player has three pieces in a row (here called a mill), he removes a opponent's piece (the 'additional' move) before switching turns.
Additionally, there is a set piece phase and a move piece phase, after all initial pieces have been placed.
My implementation looks like this:
public int[] negamaxSet(int depth, int alpha, int beta, int color) {
if (depth == 0 || board.isGameOver()) {
return new int[] { color * evaluateBoard(color};
}
int stonesSet = color == -1 ? board.blackStonesSet : board.whiteStonesSet;
// set piece phase
if (stonesSet < Game.initialPieces) {
List<Piece> moves = board.getEmpty();
int bestValue = Integer.MIN_VALUE;
int bestMoveX = -1;
int bestMoveY = -1;
for (Piece piece : moves) {
Piece move = new Piece(color, piece.x, piece.y);
board.setPiece(move);
int value[] = null;
//Player made Mill, move again
if(board.checkMill(move)){
value = negamaxRemove(depth - 1, alpha, beta, color);
}
//normal move, switch turn
else {
value = negamaxSet(depth - 1, -beta, -alpha, -color);
value[0] = -value[0];
}
if (value[0] > bestValue) {
bestValue = value[0];
bestMoveX = move.x;
bestMoveY = move.y;
}
if (value[0] > alpha) {
alpha = value[0];
}
board.revertLastMove();
// if (alpha >= beta)
// break;
}
return new int[] { bestValue, bestMoveX, bestMoveY };
} else {
//move phase
List<Piece> moves = board.getPiecesByColor(color);
int bestValue = Integer.MIN_VALUE;
int bestMoveX = -1;
int bestMoveY = -1;
int bestMoveX2 = -1;
int bestMoveY2 = -1;
for (Piece piece : moves) {
List<Piece> adjPieces = board.getAdjacentEmtpy(piece);
for(Piece adjPiece : adjPieces){
Piece newFrom = new Piece(color, piece.x, piece.y);
Piece newTo = new Piece(color, adjPiece.x, adjPiece.y);
board.movePiece(newFrom, newTo);
int[] value = null;
//Player made Mill, move again
if(board.checkMill(newTo, false)){
value = negamaxRemove(depth - 1, alpha, beta, color);
} else {
value = negamaxSet(depth - 1, -beta, -alpha, -color);
value[0] = -value[0];
}
if (value[0] > bestValue) {
bestValue = value[0];
bestMoveX = newFrom.x;
bestMoveY = newFrom.y;
bestMoveX2 = newTo.x;
bestMoveY2 = newTo.y;
}
if (value[0] > alpha) {
alpha = value[0];
}
board.revertLastMove();
// if (alpha >= beta)
// break;
}
}
return new int[] { bestValue, bestMoveX, bestMoveY, bestMoveX2, bestMoveY2 };
}
}
It is probably advisable to not change the basic Negamax algorithm and encapsulate setting a stone and moving a stone in one operation to not distinguish between the two in the algorithm itself, but from my understanding it should still work like this.
The function negamaxRemove is basically the same as negamaxSet but without checking for a mill (not possible) and looking for a piece to remove.
Is it correct to call negamaxRemove with the same parameters as the calling function and not switching the sign (thereby maximizing again)?
Somehow the AI player does not prevent the opponent from forming a mill (but forms one himself if possible).
Is the algorithm correct like this and I should look for the error elsewhere in the code?
Or did I misinterpreted how Negamax should work?
(I commented out alpha-beta pruning so setting alpha or beta wrongly wouldn't make a difference here)
I would really appreciate some pointers!
I've implemented this game. Change your definition of a move from "performs action, awarded another move" to "performs multipart action". Then instead of having to make 2 "moves", you just end up with moves looking like from: 3, to: 0, remove: 17, from: 3, to: 0, remove 19, etc. For moves that do not remove a piece, you simply set remove to -1.

Does this clockwise method work for polygons?

public class SimplePolygon {
protected int n; // number of vertices of the polygon
protected Point2D.Double[] vertices; // vertices[0..n-1] around the polygon
// boundary
protected SimplePolygon(int size) {
n = size;
this.vertices = new Point2D.Double[size];
}
protected SimplePolygon() {
n = 0;
}
public static SimplePolygon getNewPoly(Point2D.Double[] vertex) {
int size = vertex.length; // TODO: replace this line with your code
SimplePolygon p = new SimplePolygon(size);
// TODO: populate p.vertices[0..size-1] from input file
for(int i = 0; i < vertex.length; i++){
Point2D.Double[] pArray = p.vertices;
pArray[i] = vertex[i];
}
return p;
}
public int getSize() {
return n;
}
public Point2D.Double getVertex(int i) throws IndexOutOfBoundsException {
Point2D.Double u = null;
try{
u = vertices[i];
}catch(IndexOutOfBoundsException e){
e.printStackTrace();
}
return u;
}
public static double delta(Point2D.Double a, Point2D.Double b,
Point2D.Double c) {
double val = (a.getX()*b.getY()*1) + (a.getY()*1*c.getX())
+ (1*b.getX()*c.getY()) - (a.getY()*b.getX()*1)
- (a.getX()*1*c.getY()) - (1*b.getY()*c.getX());
return val;
}
Hi, I am trying to implement the "delta" method, but I have no way in telling if it's correct. The method says it returns "twice the signed area of oriented triangle." The instructions given were a little iffy on how we're suppose to calculate
I saw the matrix and a little bit of research showed cross product would work, but now I feel I would need to create a helper method that would determine if the three points are clockwise, counter clockwise, or colinear, which I have some idea on how to do. I just need help in determining if my delta method is correct.

How to sort a collection of points so that they set up one after another?

I have an ArrayList which contains coordinates of points:
class Point
{
int x, y;
}
ArrayList<Point> myPoints;
of such an image for example:
The problem is that these points are set chaotically in the ArrayList and I would like to sort them so that 2 points lying next to each other on the image are also one after another in the ArrayList. I can't come up with some good idea or algorithm to solve such a sorting... Are there some known methods of solving such problems?
edit:
The shape cannot cross over itself and let's assume that only shapes looking similarly like this can occur.
My thinking is that you first need a mathematical definition of your ordering. I suggest (Note, this definition wasn't clear in the original question, left here for completeness):
Starting with placing any point in the sequence, then perpetually append to the sequence the point closest to the current point and that hasn't already been appended to the sequence, until all points are appended to the sequence.
Thus with this definition of the ordering, you can derive a simple algorithm for this
ArrayList<point> orderedList = new ArrayList<point>();
orderedList.add(myList.remove(0)); //Arbitrary starting point
while (myList.size() > 0) {
//Find the index of the closest point (using another method)
int nearestIndex=findNearestIndex(orderedList.get(orderedList.size()-1), myList);
//Remove from the unorderedList and add to the ordered one
orderedList.add(myList.remove(nearestIndex));
}
The above is pretty universal (regardless of the algorithm for finding the next point). Then the "findNearestIndex" method could be defined as:
//Note this is intentially a simple algorithm, many faster options are out there
int findNearestIndex (point thisPoint, ArrayList listToSearch) {
double nearestDistSquared=Double.POSITIVE_INFINITY;
int nearestIndex;
for (int i=0; i< listToSearch.size(); i++) {
point point2=listToSearch.get(i);
distsq = (thisPoint.x - point2.x)*(thisPoint.x - point2.x)
+ (thisPoint.y - point2.y)*(thisPoint.y - point2.y);
if(distsq < nearestDistSquared) {
nearestDistSquared = distsq;
nearestIndex=i;
}
}
return nearestIndex;
}
Update:
Since the question was revised to largely adopt the definition I used, I took out some of the caveats.
Here is a possible solution for you: our goal is to construct a path that visits each of points in your list exactly once before it loops back. We can construct paths recursively: we can pick any point from the original list as our starting point and make a trivial path that consists only of a single node. Then we can extend an already constructed path by appending a point that we haven't visited yet.
Then we assume that we can find a good order for the original list of points by making sure by choosing the path that has the smallest length. Here, by length I don't mean number of points in the path, but the total sum of the Euclidian distance between each pair of adjacent points on the path.
The only problem is: given such a path, which point should we append next? In theory, we'd have to try out all possibilities to see which one leads to the best overall path.
The main trick that the code below employs is that it uses the following heuristic: in each step where we have to append a new point to the path constructed so far, pick the point that minimizes the average distance between two adjacent points.
It should be noted that it would be a bad idea to include in this the "loop distance" between the last point on the path and the first point: as we keep adding points, we move away from the first path point more and more. If we included the distance between the two end points, this would severely affect the average distance between all adjacent pairs, and thus hurt our heuristic.
Here's a simple auxiliary class to implement the path construction outlined above:
/**
* Simple recursive path definition: a path consists
* of a (possibly empty) prefix and a head point.
*/
class Path {
private Path prefix;
private Point head;
private int size;
private double length;
public Path(Path prefix, Point head) {
this.prefix = prefix;
this.head = head;
if (prefix == null) {
size = 1;
length = 0.0;
} else {
size = prefix.size + 1;
// compute distance from head of prefix to this new head
int distx = head.x - prefix.head.x;
int disty = head.y - prefix.head.y;
double headLength = Math.sqrt(distx * distx + disty * disty);
length = prefix.length + headLength;
}
}
}
And here's the actual heuristic search algorithm.
/**
* Implements a search heuristic to determine a sort
* order for the given <code>points</code>.
*/
public List<Point> sort(List<Point> points) {
int len = points.size();
// compares the average edge length of two paths
Comparator<Path> pathComparator = new Comparator<Path>() {
public int compare(Path p1, Path p2) {
return Double.compare(p1.length / p1.size, p2.length / p2.size);
}
};
// we use a priority queue to implement the heuristic
// of preferring the path with the smallest average
// distance between its member points
PriorityQueue<Path> pq = new PriorityQueue<Path>(len, pathComparator);
pq.offer(new Path(null, points.get(0)));
List<Point> ret = new ArrayList<Point>(len);
while (!pq.isEmpty()) {
Path path = pq.poll();
if (path.size == len) {
// result found, turn path into list
while (path != null) {
ret.add(0, path.head);
path = path.prefix;
}
break;
}
loop:
for (Point newHead : points) {
// only consider points as new heads that
// haven't been processed yet
for (Path check = path; check != null; check = check.prefix) {
if (newHead == check.head) {
continue loop;
}
}
// create new candidate path
pq.offer(new Path(path, newHead));
}
}
return ret;
}
If you run this code on the sample points of your question, and then connect each adjacent pair of points from the returned list, you get the following picture:
This is not a Sort algorithm - it is more of a rearrangement to minimise a metric (the distance between consecutive points).
I'd attempt some kind of heuristic algorithm - something like:
Pick three consecutive points a, b, c.
If distance(a,c) < distance(a,b) then swap(a,b).
Repeat from 1.
It should be possible to calculate how many times you should need to cycle this to achieve a minimal arrangement or perhaps you could detect a minimal arrangement by finding no swaps during a run.
You may need to alternate the direction of your sweeps rather like the classic optimisation of bubble-sort.
Added
Experiment shows that this algorithm doesn't work but I've found one that does. Essentially, for each entry in the list find the closest other point and move it up to the next location.
private static class Point {
final int x;
final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "(" + x + "," + y + ")";
}
public double distance(Point b) {
int dx = x - b.x;
int dy = y - b.y;
// Simple cartesian distance.
return Math.sqrt(dx * dx + dy * dy);
}
}
// Sample test data - forms a square.
Point[] points = new Point[]{
new Point(0, 0),
new Point(0, 1),
new Point(0, 2),
new Point(0, 3),
new Point(0, 4),
new Point(0, 5),
new Point(0, 6),
new Point(0, 7),
new Point(0, 8),
new Point(0, 9),
new Point(1, 9),
new Point(2, 9),
new Point(3, 9),
new Point(4, 9),
new Point(5, 9),
new Point(6, 9),
new Point(7, 9),
new Point(8, 9),
new Point(9, 9),
new Point(9, 8),
new Point(9, 7),
new Point(9, 6),
new Point(9, 5),
new Point(9, 4),
new Point(9, 3),
new Point(9, 2),
new Point(9, 1),
new Point(9, 0),
new Point(8, 0),
new Point(7, 0),
new Point(6, 0),
new Point(5, 0),
new Point(4, 0),
new Point(3, 0),
new Point(2, 0),
new Point(1, 0),};
public void test() {
System.out.println("Hello");
List<Point> test = Arrays.asList(Arrays.copyOf(points, points.length));
System.out.println("Before: " + test);
Collections.shuffle(test);
System.out.println("Shuffled: " + test);
List<Point> rebuild = new ArrayList<>(test);
rebuild.add(0, new Point(0, 0));
rebuild(rebuild);
rebuild.remove(0);
System.out.println("Rebuilt: " + rebuild);
}
private void rebuild(List<Point> l) {
for (int i = 0; i < l.size() - 1; i++) {
Point a = l.get(i);
// Find the closest.
int closest = i;
double howClose = Double.MAX_VALUE;
for (int j = i + 1; j < l.size(); j++) {
double howFar = a.distance(l.get(j));
if (howFar < howClose) {
closest = j;
howClose = howFar;
}
}
if (closest != i + 1) {
// Swap it in.
Collections.swap(l, i + 1, closest);
}
}
}
prints:
Before: [(0,0), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8), (0,9), (1,9), (2,9), (3,9), (4,9), (5,9), (6,9), (7,9), (8,9), (9,9), (9,8), (9,7), (9,6), (9,5), (9,4), (9,3), (9,2), (9,1), (9,0), (8,0), (7,0), (6,0), (5,0), (4,0), (3,0), (2,0), (1,0)]
Shuffled: [(9,6), (0,9), (0,8), (3,9), (0,5), (9,4), (0,7), (1,0), (5,0), (9,3), (0,1), (3,0), (1,9), (8,9), (9,8), (2,0), (2,9), (9,5), (5,9), (9,7), (6,0), (0,3), (0,2), (9,1), (9,2), (4,0), (4,9), (7,9), (7,0), (8,0), (6,9), (0,6), (0,4), (9,0), (0,0), (9,9)]
Rebuilt: [(0,0), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (0,7), (0,8), (0,9), (1,9), (2,9), (3,9), (4,9), (5,9), (6,9), (7,9), (8,9), (9,9), (9,8), (9,7), (9,6), (9,5), (9,4), (9,3), (9,2), (9,1), (9,0), (8,0), (7,0), (6,0), (5,0), (4,0), (3,0), (2,0), (1,0)]
which looks like what you are looking for.
The efficiency of the algorithm is not good - somewhere around O(n log n) - I hope you don't need to do this millions of times.
If you want the points to appear in a predictable order (say leftmost one at the start) you could add a fake point at the start of the list before rebuilding it and remove it after. The algorithm will always leave the first point alone.
I started this shortly after the question, but it had been delayed due to the question being put on hold. It's the simple approach that in the meantime also has been mentioned in the comments and other answers, but I'll post it here anyhow:
Here is a MCVE showing the simplest and most straightforward approach. The approach simply consists of picking an arbitrary point, and then continuing by always picking the point that is closest to the previous one. Of course, this has limitations:
It may pick the wrong point, when there are sharp corners or cusps
It's not very efficient, because it repeatedly does a search for the closest point
One approach for accelerating it could be to sort the points based on the x-coordinate, and then exploit this partial ordering in order to skip most of the points when looking for the next neighbor. But as long as you don't want to apply this to ten-thousands of points in a time-critical context, this should not be an issue.
The possible ambiguities, in turn, may be a problem, but considering that, one has to say that the problem is underspecified anyhow. In some cases, not even a human could decide which point is the appropriate "next" point - at least, when the problem is not extended to detect the "interior/exterior" of shapes (this is somewhat similar to the problem of ambiguities in the marching cube algorithm: You just don't know what the intended path is).
Note that most of the code is not really important for your actual question, but ... you did not provide such a "stub" implementation. The relevant part is === marked ===
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SortShapePoints
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Shape shape = createExampleShape();
List<Point2D> points = computePoints(shape, 6);
Collections.shuffle(points);
List<Point2D> sortedPoints = sortPoints(points);
Path2D path = createPath(sortedPoints, true);
f.getContentPane().add(new ShapePanel(points, path));
f.setSize(800, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
//=== Relevant part starts here =========================================
private static List<Point2D> sortPoints(List<Point2D> points)
{
points = new ArrayList<Point2D>(points);
List<Point2D> sortedPoints = new ArrayList<Point2D>();
Point2D p = points.remove(0);
sortedPoints.add(p);
while (points.size() > 0)
{
int index = indexOfClosest(p, points);
p = points.remove(index);
sortedPoints.add(p);
}
return sortedPoints;
}
private static int indexOfClosest(Point2D p, List<Point2D> list)
{
double minDistanceSquared = Double.POSITIVE_INFINITY;
int minDistanceIndex = -1;
for (int i = 0; i < list.size(); i++)
{
Point2D other = list.get(i);
double distanceSquared = p.distanceSq(other);
if (distanceSquared < minDistanceSquared)
{
minDistanceSquared = distanceSquared;
minDistanceIndex = i;
}
}
return minDistanceIndex;
}
//=== Relevant part ends here ===========================================
private static Shape createExampleShape()
{
Area a = new Area();
a.add(new Area(new Ellipse2D.Double(200, 200, 200, 100)));
a.add(new Area(new Ellipse2D.Double(260, 160, 100, 500)));
a.add(new Area(new Ellipse2D.Double(220, 380, 180, 60)));
a.add(new Area(new Rectangle2D.Double(180, 520, 260, 40)));
return a;
}
private static List<Point2D> computePoints(Shape shape, double deviation)
{
List<Point2D> result = new ArrayList<Point2D>();
PathIterator pi = shape.getPathIterator(null, deviation);
double[] coords = new double[6];
Point2D newPoint = null;
Point2D previousMove = null;
Point2D previousPoint = null;
while (!pi.isDone())
{
int segment = pi.currentSegment(coords);
switch (segment)
{
case PathIterator.SEG_MOVETO:
previousPoint = new Point2D.Double(coords[0], coords[1]);
previousMove = new Point2D.Double(coords[0], coords[1]);
break;
case PathIterator.SEG_CLOSE:
createPoints(previousPoint, previousMove, result, deviation);
break;
case PathIterator.SEG_LINETO:
newPoint = new Point2D.Double(coords[0], coords[1]);
createPoints(previousPoint, newPoint, result, deviation);
previousPoint = new Point2D.Double(coords[0], coords[1]);
break;
case PathIterator.SEG_QUADTO:
case PathIterator.SEG_CUBICTO:
default:
// Should never occur
throw new AssertionError("Invalid segment in flattened path!");
}
pi.next();
}
return result;
}
private static void createPoints(Point2D p0, Point2D p1,
List<Point2D> result, double deviation)
{
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double d = Math.hypot(dx, dy);
int steps = (int) Math.ceil(d / deviation);
for (int i = 0; i < steps; i++)
{
double alpha = (double) i / steps;
double x = p0.getX() + alpha * dx;
double y = p0.getY() + alpha * dy;
result.add(new Point2D.Double(x, y));
}
}
public static Path2D createPath(Iterable<? extends Point2D> points,
boolean close)
{
Path2D path = new Path2D.Double();
Iterator<? extends Point2D> iterator = points.iterator();
boolean hasPoints = false;
if (iterator.hasNext())
{
Point2D point = iterator.next();
path.moveTo(point.getX(), point.getY());
hasPoints = true;
}
while (iterator.hasNext())
{
Point2D point = iterator.next();
path.lineTo(point.getX(), point.getY());
}
if (close && hasPoints)
{
path.closePath();
}
return path;
}
}
class ShapePanel extends JPanel
{
private final List<Point2D> points;
private final Shape shape;
public ShapePanel(List<Point2D> points, Shape shape)
{
this.points = points;
this.shape = shape;
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.RED);
g.draw(shape);
g.setColor(Color.BLACK);
for (Point2D p : points)
{
g.fill(new Ellipse2D.Double(p.getX() - 1, p.getY() - 1, 2, 2));
}
}
}
This is a pretty open ended question but if you want them stored in a certain way you need to define the ordering more than "So that they are next to each other in the array" You need to have a function where you can take two points and say, Point A is less than Point B or vice versa, or they are equal.
If you have that, then the algorithm you need to sort them is already implemented and you can use it by implementing a Comparator as SANN3 said.
As a side note, you might not want to store a shape as a set of points. I think you might want to store them as a line? You can use a cubic spline to get almost any shape you want then you could save on storage...
I had a task to sort the points to represent a line. I decided to keep the full weight of the path and update it upon standard Collection operations accordingly. The solution should work in your case too. Just take the elements of this LinkedList ps and connect its head and tail. Also, you can add more operations like PointXY get(int index) etc. with a bit more forwarding to the underlying LinkedList in this composition. Finally, you should guard the collection with excessive defensive copies where necessary. I tried to keep it simple for the sake of brevity.
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
public class ContinuousLineSet implements Collection<PointXY> {
LinkedList<PointXY> ps = new LinkedList<>(); // Exposed for simplicity
private int fullPath = 0; // Wighted sum of all edges in ps
#Override
public int size() {
return ps.size();
}
#Override
public boolean isEmpty() {
return ps.isEmpty();
}
#Override
public boolean contains(Object o) {
return ps.contains(o);
}
#Override
public Iterator<PointXY> iterator() {
return ps.iterator();
}
#Override
public Object[] toArray() {
return ps.toArray();
}
#Override
public <T> T[] toArray(T[] a) {
return ps.toArray(a);
}
private int dist(PointXY a, PointXY b) {
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
#Override
public boolean add(PointXY e) {
if (isEmpty())
return ps.add(e);
if (ps.getFirst().equals(e))
return false;
Iterator<PointXY> it = ps.iterator();
PointXY previous = it.next();
int asFirst = fullPath + dist(e, previous);
int minPath = asFirst;
int iMin = 0;
int i = 0;
while (it.hasNext()) {
i++;
PointXY next = it.next();
if (next.equals(e))
return false;
int asBetween = fullPath - dist(previous, next) + dist(previous, e) + dist(e, next);
if (asBetween < minPath) {
iMin = i;
minPath = asBetween;
}
previous = next;
}
int asLast = fullPath + dist(e, previous);
if (asLast < minPath) {
minPath = asLast;
iMin = size();
}
fullPath = minPath;
ps.add(iMin, e);
return true;
}
public void reverse() {
Collections.reverse(ps);
}
#Override
public boolean remove(Object o) {
PointXY last = null;
for (Iterator<PointXY> it = iterator(); it.hasNext();) {
PointXY p = it.next();
if (o.equals(p)) {
int part1 = last != null ? dist(last, p) : 0;
int part2 = it.hasNext() ? dist(p, it.next()) : 0;
fullPath -= part1 + part2;
break;
}
last = p;
}
return ps.remove(o);
}
#Override
public boolean containsAll(Collection<?> c) {
return ps.containsAll(c);
}
#Override
public boolean addAll(Collection<? extends PointXY> c) {
boolean wasAdded = false;
for (PointXY p : c) {
wasAdded |= add(p);
}
return wasAdded;
}
#Override
public boolean removeAll(Collection<?> c) {
boolean wasRemoved = false;
for (Object o : c) {
if (o instanceof PointXY) {
PointXY p = (PointXY) o;
wasRemoved |= remove(p);
}
}
return wasRemoved;
}
#Override
public boolean retainAll(Collection<?> c) {
ContinuousLineSet cls = new ContinuousLineSet();
for (Object o : c) {
if (o instanceof PointXY && ps.contains(o)) {
PointXY p = (PointXY) o;
cls.add(p);
}
}
int oldSize = ps.size();
ps = cls.ps;
fullPath = cls.fullPath;
return size() != oldSize;
}
#Override
public void clear() {
ps.clear();
fullPath = 0;
}
}
class PointXY {
public static PointXY of(int x, int y) {
return new PointXY(x, y);
}
public final int x, y;
private int hash;
private boolean wasHashInit = false;
private PointXY(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof PointXY))
return false;
PointXY p = (PointXY) obj;
return x == p.x && y == p.y;
}
#Override
public int hashCode() {
if (!wasHashInit) {
hash = 17;
hash = 31 * hash + y;
hash = 31 * hash + x;
wasHashInit = true;
}
return hash;
}
#Override
public String toString() {
return String.format("(%d, %d)", x, y);
}
}
public class Point implements Comparable
{
...
...
#Override
public int compareTo(Pointarg0)
{
....
}
...
}
...

Categories