3D Collision Mesh (more efficient collision calculation) - java

Before you go marking this as a duplicate. I would like it know that yes, there are some questions with similarly worded titles... However, I've read through them and they are vastly different.
I have recently completed a complete system for detecting collision of anywhere from the least to the most complex 3d meshes. The problem being that it is massively inefficient and very costly to gameplay experience in my engine. As a side note, I have completely made up this code, no reference, just to see if I could handle collision like this on my own. Sorry for the mess it is. So without further ado, here is the important code.
package nope;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.lwjgl.util.vector.Vector3f;
import net.aionstudios.nightfall.entities.Entity;
import net.aionstudios.nightfall.renderEngine.model.TexturedModel;
public class ColliderEntity extends Entity {
private List<CollisionMesh> entityBounds = new ArrayList<CollisionMesh>();
private boolean alertCollisions = false;
public ColliderEntity(TexturedModel model, Vector3f position, float rotX, float rotY, float rotZ, float scale, BoundingBox entityBounds) {
super(model, position, rotX, rotY, rotZ, scale);
this.entityBounds.add(entityBounds);
}
public List<ColliderEntity> detectImpact(List<ColliderEntity> colliders){
List<ColliderEntity> colE = new ArrayList<ColliderEntity>();
colE.clear();
for (ColliderEntity ce : colliders) {
if(ce != this) {
Vector3f boundsOffsets = new Vector3f(difference(this.getPosition().x, ce.getPosition().x), difference(this.getPosition().y, ce.getPosition().y), difference(this.getPosition().z, ce.getPosition().z));
boolean xCollide = false;
boolean yCollide = false;
boolean zCollide = false;
for (CollisionMesh b1 : this.getEntityBounds()){
for(MeshPoint mp : b1.getPoints()){
List<Vector3f> points = mp.getConnectionsAndPoint();
for (CollisionMesh b2 : ce.getEntityBounds()) {
for(MeshPoint mp2 : b2.getPoints()){
List<Vector3f> points2 = mp2.getConnectionsAndPoint();
for (Vector3f pt : points2){
pt = new Vector3f(pt.x-boundsOffsets.x, pt.y-boundsOffsets.y, pt.z-boundsOffsets.z);
for (int i = 1; i < points.size(); i++){
if(!xCollide || !yCollide || !zCollide){
if(points.get(i-1).x > pt.x && pt.x > points.get(i).x) {
xCollide = true;
}
if(points.get(i-1).y > pt.y && pt.y > points.get(i).y) {
yCollide = true;
}
if(points.get(i-1).z > pt.z && pt.z > points.get(i).z) {
zCollide = true;
}
}
}
}
if(!!xCollide || !yCollide || !zCollide){
for (Vector3f pts : points){
pts = new Vector3f(pts.x-boundsOffsets.x, pts.y-boundsOffsets.y, pts.z-boundsOffsets.z);
for (int i = 1; i < points2.size(); i++){
if(!xCollide || !yCollide || !zCollide){
if(points2.get(i-1).x > pts.x && pts.x > points2.get(i).x) {
xCollide = true;
}
if(points2.get(i-1).y > pts.y && pts.y > points2.get(i).y) {
yCollide = true;
}
if(points2.get(i-1).z > pts.z && pts.z > points2.get(i).z) {
zCollide = true;
}
}
}
}
}
if(xCollide && yCollide && zCollide){
colE.add(ce);
if(alertCollisions) {
System.out.println("Collision on Entity "+this.toString()+" at: "+this.getPosition().x+" "+this.getPosition().y+" "+this.getPosition().z+" with Entity "+ce.toString()+" at: "+ce.getPosition().x+" "+ce.getPosition().y+" "+ce.getPosition().z);
}
}
}
}
}
}
}
}
return colE;
}
private float difference(float x, float x1){
float dx = x - x1;
return (float) Math.sqrt(dx * dx);
}
public boolean isAlertCollisions() {
return alertCollisions;
}
public void setAlertCollisions(boolean alertCollisions) {
this.alertCollisions = alertCollisions;
}
public List<CollisionMesh> getEntityBounds() {
return entityBounds;
}
public void addEntityBounds(BoundingBox b){
this.entityBounds.add(b);
}
public void removeEntityBounds(BoundingBox b){
this.entityBounds.remove(entityBounds);
}
}
this class is just an entity that also has a collision mesh... And the impact detection. In order to understand what's going on here you'll need some more insight.
package nope;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.lwjgl.util.vector.Vector3f;
public class CollisionMesh {
private List<MeshPoint> points = new ArrayList<MeshPoint>();
public CollisionMesh(MeshPoint[] points){
for(MeshPoint p : points){
this.points.add(p);
}
}
public List<MeshPoint> getPoints() {
return points;
}
public void addMeshPoint(MeshPoint point){
for (MeshPoint p : points){
if(point == p){
return;
}
}
points.add(point);
}
public void removeMeshPoint(MeshPoint point){
for(MeshPoint p : points){
if(p == point){
points.remove(point);
return;
}
}
cleanupMeshPoints();
}
public void cleanupMeshPoints(){
for(MeshPoint p : points){
for(Vector3f pi : p.getConnections()){
boolean connected = false;
for(MeshPoint p2 : points){
if(p2.getPoint() == pi){
connected = true;
}
}
if(!connected){
p.getConnections().remove(pi);
}
}
}
}
}
this is the collision mesh given to a collidable entity, it is made up of individual mesh points that also store there connections. Here is that class:
package nope;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.lwjgl.util.vector.Vector3f;
public class MeshPoint {
private Vector3f point;
private List<Vector3f> connections = new ArrayList<Vector3f>();
public MeshPoint(Vector3f point, Vector3f[] connections){
this.point = point;
for(Vector3f connection : connections){
this.connections.add(connection);
}
}
public Vector3f getPoint() {
return point;
}
public void setPoint(Vector3f point) {
this.point = point;
}
public List<Vector3f> getConnections() {
return connections;
}
public List<Vector3f> getConnectionsAndPoint() {
List<Vector3f> cp = connections;
cp.add(this.point);
return cp;
}
public void addConnection(Vector3f connection){
for (Vector3f c : connections){
if(c.x == connection.x && c.y == connection.y && c.z == connection.z){
return;
}
}
connections.add(connection);
}
public void removeConnection(Vector3f connection){
for (Vector3f c : connections){
if(c.x == connection.x && c.y == connection.y && c.z == connection.z){
connections.remove(connection);
return;
}
}
}
}
the mesh connections are, what I think, is really killing the game's framerate. Which when objects as simple as 2 boxes have collisions enabled drops from the frame cap of 120 to usually about 3. While I am able to identify several problems I can think of no way to make this code less complicated than it currently is. Any help is much appreciated.
I know a question like this wouldn't typically be well received, and many people who come here will be looking for a minimal and complete example... But there really wasn't anything to be done to make this smaller than it is.

Suggestions:
detectImpact no less than 6 nested cycles. No wonder the performance is going to suffer. Is it possible to reduce the number of nesting? If not, can you at least precondition your data considered in those cycles? (e.g. don't consider all the vertices but only those inside a bounding-box overlap; and hope that the bounding box intersection will not contain most of these vertices - if they are your collision detection didn't do a proper job at previous stages, before the objects became so "intertwined").
detectImpact the inner-most cycle is under the form of for (int i = 1; i < points.size(); i++). Does the size() change during the cycle? If not, what is the point of calling a (virtual) method of a generic interface? Suggestion: create a local var to store the size and use it. Better still, try to use the foreach / for-in form, it has a better performance than the "iterating by index" (yes, I noted that inner-most cycle starts at 1, just skip the first step inside the cycle). As this is the inner-most loop, every bit counts.
As the vertices/edges/faces of your mesh are seldom going to modify once constructed, consider using arrays instead of lists. Yes, it's nice to have the flexibility of self-adjusting containers, but... there ain't such a thing like a free lunch and the performance is the wallet you are paying for it.Perhaps you could refine a bit the lifecycle of your meshed objects to have two distinct stages: construction (when you add vertices/edges to the mesh - self-adjusting collection come handy here) and "frozen/post-build-stage" (when you use arrays rather than containers). You'll get both the flexibility and performance, and you are going to pay from the "code complexity" account.

Related

What is wrong with this custom "path finding" algo?

(I hope this is not a duplicate as the many questions I came into do not fit my need)
I'm developping a 2D grid based game with 2 players with grid. There are two players: blue and red, each one places a stone in cells. So I want to find a path passing throught all cells with the same color back to the starting point, BUT ONLY if there is at least ONE cell that contains opponent's stone.
From the screenshot above: The red stones here in the upper right do not form a valid path. And those in the center are not forming a path neither even though that should be one.
I'm able to find a path but it is somehow broken, it doesn't work as expected.
EDIT:
Pather class
public class Pather {
private static final int MIN_PATH_LENGTH = 3;
public enum Neighbor{
UP_RIGHT(0,1,-1),
RIGHT(1,1,0),
DOWN_RIGHT(2,1,1),
DOWN(3,0,1),
DOWN_LEFT(4,-1,1),
LEFT(5,-1,0),
UP_LEFT(6,-1,-1),
UP(7,0,-1);
public int index, x, y;
Neighbor(int index, int x, int y){
this.index = index;
this.x = x;
this.y = y;
}
}
private static Neighbor[] neighbors = Neighbor.values();
public static ArrayList<Path> findPaths(Stone[][] gameBoard){
ArrayList<Path> paths = new ArrayList<>();
ArrayList<Point> checkedPoints = new ArrayList<>();
for (int i = 0; i < gameBoard.length ; i++) {
for (int j = 0; j < gameBoard[0].length; j++) {
if(gameBoard[i][j] != null){
//set the origin of a potential new path
ArrayList<Point> potentialPath = new ArrayList<>();
Point origin = new Point (i,j);
if(!checkedPoints.contains(origin)) {
potentialPath.add(origin);
checkedPoints.add(origin);
potentialPath = findPath(gameBoard, i, j, potentialPath, gameBoard[i][j].getPaint(), checkedPoints, Neighbor.RIGHT.index); //Changed from Neighbor.DOWN.index
if (potentialPath != null) {
paths.add(new Path(potentialPath, gameBoard[i][j].getPaint()));
}
}
}
}
}
return paths;
}
private static ArrayList<Point> findPath(Stone[][] gameBoard, int x, int y, ArrayList<Point> path, Paint color, ArrayList<Point> checkedPoints, int cameFrom){
int startClockwiseScanAtDirection = cameFrom + 5;
for (int i = startClockwiseScanAtDirection; i < startClockwiseScanAtDirection + 7; i++) {
// avoid ArrayIndexOutOfBounds
if(x+neighbors[i%8].x < 0 || y+neighbors[i%8].y < 0 || x+neighbors[i%8].x >= gameBoard.length || y+neighbors[i%8].y >= gameBoard[0].length)
continue;
// check if there's a stone that matches the current stone, we're scanning around
if(gameBoard[x+neighbors[i%8].x][y+neighbors[i%8].y] != null && gameBoard[x+neighbors[i%8].x][y+neighbors[i%8].y].getPaint() == color){
// found one
Point nextStone = new Point(x+neighbors[i%8].x,y+neighbors[i%8].y);
// is the point we just found the origin of the path?
if(nextStone.equals(path.get(0)) && path.size() > MIN_PATH_LENGTH) { //This seems to prevent drawing a path when we have less stone to form a path with
path.add(nextStone);
checkedPoints.add(nextStone);
return path;
}
// otherwise if it's already part of the path ignore it
if (path.contains(nextStone)) {
continue;
}
// else add it to the path and keep going
path.add(nextStone);
checkedPoints.add(nextStone);
// recurse on the next stone in the path
ArrayList<Point> newPath = findPath(gameBoard,x+neighbors[i%8].x, y+neighbors[i%8].y, path, color, checkedPoints, i%8);
if (newPath == null){
// didn't find a way to continue, so backtrack
path.remove(path.size()-1);
} else {
// we have a completed path to return
return newPath;
}
}
}
return null;
}
}
Path class
public class Path {
public Paint getColor() {
return color;
}
public void setColor(Paint color) {
this.color = color;
}
public ArrayList<Point> getCoordinateList() {
return coordinateList;
}
public void setCoordinateList(ArrayList<Point> coordinateList) {
this.coordinateList = coordinateList;
}
private ArrayList<Point> coordinateList;
private Paint color;
public Path(ArrayList<Point> coordinatePath, Paint color){
this.coordinateList = coordinatePath;
this.color = color;
}
#Override
public String toString() {
return coordinateList.toString();
}
}
Here some case test:
Called in the MainActivity's onCreate():
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gameGrid = findViewById(R.id.gameGrid);
bluePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bluePaint.setStyle(Paint.Style.FILL_AND_STROKE);
bluePaint.setColor(Color.BLUE);
redPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
redPaint.setStyle(Paint.Style.FILL);
redPaint.setColor(Color.RED);
bgrBluePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgrBluePaint.setStyle(Paint.Style.STROKE);
bgrBluePaint.setStrokeWidth(bgrStrokeWdth);
bgrBluePaint.setColor(Color.BLUE);
bgrRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgrRedPaint.setStyle(Paint.Style.STROKE);
bgrRedPaint.setStrokeWidth(bgrStrokeWdth);
bgrRedPaint.setColor(Color.RED);
bluePlayer = new Stone(1,bluePaint, bgrBluePaint);
redPlayer = new Stone(2, redPaint, bgrRedPaint);
gameBoard = new Stone[100][100];
gameBoard[47][47]= redPlayer;
gameBoard[46][47]= bluePlayer;
gameBoard[44][48]= redPlayer; //REDs form a path when you place this stone in the last positioon
gameBoard[44][49]= redPlayer;
gameBoard[45][47]= redPlayer;
gameBoard[45][48]= bluePlayer;
gameBoard[45][49]= bluePlayer;
gameBoard[45][50]= redPlayer;
gameBoard[46][50]= bluePlayer;
gameBoard[46][49]= redPlayer;
gameBoard[46][48]= redPlayer;
gameBoard[47][50]= bluePlayer;
gameBoard[47][48]= bluePlayer;
gameBoard[47][49]= redPlayer;
gameBoard[48][50]= redPlayer;
gameBoard[48][49]= redPlayer;
gameBoard[48][48]= redPlayer;
gameBoard[49][50]= bluePlayer;
gameBoard[48][51]= redPlayer;
gameBoard[44][50] = bluePlayer;
ArrayList<Path> paths = Pather.findPaths(gameBoard);
gameGrid.setPaths(paths);
gameGrid.setGameBoard(gameBoard);
}
Placing stones at the following positions clears the path:
//Adding the following deletes the path
gameBoard[43][50] = redPlayer; //Adding this one in last position clears the path
gameBoard[45][51] = redPlayer;
I need to figure out how to make a condition that check for an opponent nearby first then validate the path.
EDIT 2:
Stone.java
public class Stone{
private int _player;
private Paint _paint, _bgrPaint;
public Stone(int player, Paint paint, Paint bgrPaint){
_player = player;
_paint = paint;
_bgrPaint = bgrPaint;
}
public int getPlayer() {
return _player;
}
public Paint getPaint() {
return _paint;
}
public Paint get_bgrPaint() {
return _bgrPaint;
}
}
Point.java
public class Point {
int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
#Override
public boolean equals(Object point) {
return this.x == ((Point) point).x && this.y == ((Point) point).y;
}
#Override
public String toString() {
return "("+x+","+y+")";
}
}
Screenshoot of what a valid path should look
A more-or-less standard way to approach this kind of problem is a "sweep line" algorithm. For simplicity, say we're looking for blue paths wrapping red points.
(You can process red paths wrapping blue points at the same time or in a second pass, but you can work that out later.)
You can search for "sweep line algorithm" to see how they work in related applications. The Wikipedia page isn't bad.
For this problem, the sweep line is a set of y-intervals. It's initialized using the leftmost (least x) blue point(s). It gets one interval for each vertically adjacent set of blue points. Each interval represents a vertical slice through a potential blue polygon.
The rest of the algorithm is to design the rules needed to update the scan line when it is moved one position to the right, incrementing x. This will be a matter of updating each interval. When a step finds a disconnected set of vertically adjacent points, a new interval is added. In some cases, intervals will "die out" because the potential polygon boundary dead-ends (think of a C shape). In other cases, they will "merge" because, at the corresponding x-coordinate, there is a set of 1 or more vertically adjacent connecting points. In still other cases, the polygon will complete successfully with a final set of 1 or more vertically adjacent points.
The details will be fiddly, but not hard to work out by case analysis.
To trace successful polygons, intervals can include two chains of preceding points: the upper and lower polygon boundaries.
The last consideration is whether a successfully closed polygon encloses at least one red point. But this is easy. If for any x-coordinate, the interval representing a polygon bracketed a red point, then the answer is yes. This can be recorded as an initially false boolean maintained in the interval, which is set true every time such a red point is seen. When a polygon is successfully closed, check the flag to see whether it should be used or not.
All the above can be made efficient for very large grids by using suitable data structures: interval trees for example. But if the grid is comparatively small, it should be fine to use simple lists. At any rate, consider prototyping it with a list for the sweep line first first and optimize with more complicated data structures later if needed.
As I wrote in my comments, without mvce it is very hard to offer detailed help.
From what I see in the code I figure you are trying to map all cyclic single-color paths on the board.
I made some documented changes in the code, hoping (without being able to properly check it) that it may help you improve your code.
Note that as Stone class was not posted, I changed the representation of the board to int[][]
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Phather {
private static final int RED = 2, BLUE = 1;
private static final int MIN_PATH_LENGTH = 3;
public enum Neighbor{
UP_RIGHT ( 1,-1),
RIGHT ( 1, 0),
DOWN_RIGHT( 1, 1),
DOWN ( 0, 1),
DOWN_LEFT (-1, 1),
LEFT (-1, 0),
UP_LEFT (-1,-1),
UP ( 0,-1);
int x, y;
Neighbor(int x, int y){
this.x = x;
this.y = y;
}
}
public static Set<Path> findPaths(int[][] gameBoard){
//use set to prevent duplicate paths
Set<Path> paths = new HashSet<>();
for (int x = 0; x < gameBoard.length ; x++) {
for (int y = 0; y < gameBoard[0].length; y++) {
//note that array indexes are [y][x] while point arguments are x,y
if(gameBoard[y][x] != 0){
//use set to prevent duplicate elements. initialize it to allow for
//overlapping paths (paths that contain some shared points)
Set<Point> checkedPoints = new HashSet<>();
//set the origin of a potential new path
ArrayList<Point> potentialPath = new ArrayList<>();
Point origin = new Point (x,y);
if(checkedPoints.add(origin)) { //add returns false if duplicate
potentialPath.add(origin);
potentialPath = findPath(gameBoard, x, y, potentialPath, checkedPoints);
if (potentialPath != null) {
paths.add(new Path(potentialPath, gameBoard[y][x]));
}
}
}
}
}
return paths;
}
private static ArrayList<Point> findPath(int[][] gameBoard, int x, int y,
ArrayList<Point> path, Set<Point> checkedPoints){
int color = gameBoard[y][x]; //no need for color as argument. get from stone
for(Neighbor neighbor : Neighbor.values()) {
int neighborX = x + neighbor.x, neighborY = y + neighbor.y;
// avoid ArrayIndexOutOfBounds
//todo: refactor to method isValidAddress(x,y,maxX, maxY)
if((neighborX < 0) || ( neighborY < 0) || (neighborY >= gameBoard.length)
|| (neighborX >= gameBoard[0].length)) {
continue;
}
// check if there's a stone that matches the current stone, we're scanning around
if((gameBoard[neighborY][neighborX] != 0) && (gameBoard[neighborY][neighborX] == color)){
// found one
Point nextStone = new Point(neighborX,neighborY);
// is the point we just found the origin of the path ?
if(nextStone.equals(path.get(0)) && (path.size() > MIN_PATH_LENGTH)) {
path.add(nextStone); //do you want it in path twice ?
//checkedPoints.add(nextStone); //if added to path before, it is already in checkedPoints
return path;
}
// otherwise if it's already part of the path ignore it
if (path.contains(nextStone)) {
continue;
}
// else add it to the path and keep going
path.add(nextStone);
checkedPoints.add(nextStone);
// recurse on the next stone in the path
ArrayList<Point> newPath = findPath(gameBoard, neighborX, neighborY, path, checkedPoints);
if (newPath == null){
// didn't find a way to continue, so backtrack
path.remove(path.size()-1);
} else {
// we have a completed path to return
return newPath;
}
}
}
return null;
}
}
class Path {
private ArrayList<Point> coordinateList;
private int color;
Path(ArrayList<Point> coordinatePath, int color){
coordinateList = coordinatePath;
this.color = color;
}
int getColor() { return color; }
#Override
public String toString() {
return coordinateList.toString();
}
List<Point> getPoints() { return coordinateList; }
int size() { return coordinateList.size(); }
#Override
public boolean equals(Object p){
if (p == this) { return true; }
if (p == null) { return false;}
if (!(p instanceof Path)) {return false; }
Path path = (Path)p;
return getPoints().containsAll(path.getPoints())
&& path.getPoints().containsAll(getPoints());
}
}

Changing classic code to functional

I have the following portion of ugly (but working) code for checking if fields on a chessboard are vacant:
if (abs(xDst - xSrc) == abs(yDst - ySrc)) {
boolean backslashMove = xSrc < xDst && ySrc > yDst || xSrc > xDst && ySrc < yDst;
if (backslashMove) {
int y = max(ySrc, yDst) - 1;
for (int x = min(xSrc, xDst) + 1; x < max(xSrc, xDst); x++) {
if (board.getActiveChessmanAt(x, y).isAlive()) {
return false;
}
y--;
}
} else { //slash move
Obviously, it examines fields between coordinates (xScr, ySrc) and (xDst, yDst) in Bishop-like line of move.
I'm trying to transform this with using IntStream:
if (backslashMove) {
final int y = max(ySrc, yDst) - 1;
if (IntStream.range(min(xSrc, xDst) + 1, max(xSrc, xDst))
.anyMatch(x -> board.getActiveChessmanAt(x, y).isAlive()))
return false;
How can I perform y-- in this case? It has to be final if it's about to be used within 'anyMatch' command
If you really need to rewrite it using streams, then you can use the fact that both x and y are incremented simultaneously. So you can build a range of increments instead of the range of x-values:
final int xSrc = min(xSrc, xDst) + 1;
final int xDst = max(xSrc, xDst);
final int ySrc = max(ySrc, yDst) - 1;
if (IntStream.range(0, xDst - xSrc)
.anyMatch(distance -> board.getActiveChessmanAt(xSrc + distance, ySrc + distance).isAlive())) {
return false;
}
In general, it's not possible to use a non-final local variable from the "parent" method directly. Java doesn't support real closures. You would need a wrapper object for this (AtomicInteger is an often suggested candidate), or you could make the non-final variable a class field (note the potential thread safety problems). To me personally, these both "tricks" are bad.
What you need is not functional programming in terms of streams/folds.
Instead of, you should refactor your actual code to make it clearer/shorter/better.
You could for example :
extract the parts of logic scattered in the actual method in specific methods with meaningful names
use structured objects rather than too fine unitary variable
remove undesirable nesting : use early exit and not required conditional statements may help
It could give :
// -> extract method + structured objects
if (!isPointsMatch(pointSrc, pointDst)) {
return false; // -> early exit
}
// -> extract method + structured objects
if (isBackslashMove(pointSrc, pointDst)) {
if (board.hasAnyActiveChessmanAlive(pointSrc, pointDst)) {
return false;
}
}
// else slash move -> The else is useless
// ...
Your original code snipped is procedural. Your functional approach does not work well. So how about an object oriented approach?
class Position{
private final int x, y;
public Position(int x, int y){
this.x=x;
this.y=y;
}
public getX(){
return x;
}
public getY(){
return y;
}
}
interface Move {
Position moveFrom(Position start);
}
interface Figure {
Collection<Move> getPossibleMoves(Position start, Board board);
}
class Bishop implements Figure {
private final Collection<Move> moves = new HashSet<>();
public Bishop(){
moves.add(start->new Position(start.getX()-2,start.getY()-1));
moves.add(start->new Position(start.getX()-2,start.getY()+1));
moves.add(start->new Position(start.getX()+2,start.getY()-1));
moves.add(start->new Position(start.getX()+2,start.getY()+1));
moves.add(start->new Position(start.getX()-1,start.getY()-2));
moves.add(start->new Position(start.getX()-1,start.getY()+2));
moves.add(start->new Position(start.getX()+1,start.getY()-2));
moves.add(start->new Position(start.getX()+1,start.getY()+2));
}
#Override
public Collection<Move> getPossibleMoves(Position start, Board board){
return moves.stream()
.filter({ Position end = m.moveFrom(start);
return board.isOnBorad(end.getX(),end.getY())
&& board.getActiveChessmanAt(end.getX(), end.getY()).isAlive()})
.collect(Collectors.toSet());
}
}
Another implementation of Figure might return a separate Move instance for each step until it reaches a limit:
class Tower implements Figure {
enum Direction {
NORTH(1,0),EAST(0,1),SOUTH(-1,0),WEST(0,-1);
private final Position change;
private Direction(int x, int y){
change = new Position(x, y);
}
public Position getNextFrom(Position start){
return new Position(start.getX()+change.getX(),start.getX()+change.getY());
}
#Override
public Collection<Move> getPossibleMoves(Position start, Board board){
Collection<Move> moves = new HashSet<>();
for(Direction direction : Direction.values()){
Position current = direction.getNextFrom(start);
while( board.isOnBorad(current.getX(),current.getY())
&& board.getActiveChessmanAt(current.getX(), current.getY()).isAlive()){
moves.add(p-> new Position(current.getX(),current.getY());
}
}
return moves;
}
}

get point in list furthest away from points in other list

I have a list of players, and a list of spawnpoints. Every player has a character object, and every character has a position. A character has a number of lives, and when killed, he respawns at the spawnpoint furthest away from the other players as long as he has lives left. For this I created the following code:
for (Player spawnPlayer : players) {
if (spawnPlayer.getCharacter().getCanSpawn()) {
System.out.println("works");
List<Integer> distanceArrayList = new ArrayList();
for (Point point : map.getSpawnPoints()) {
int distance = 0;
for (Player player : players) {
if (player != spawnPlayer && player.getCharacter().getLives() > 0 && !player.getCharacter().getCanSpawn()) {
distance += Math.sqrt(Math.pow(point.x - player.getCharacter().getPosition().x, 2)
+ Math.pow(point.y - player.getCharacter().getPosition().y, 2));
}
}
distanceArrayList.add(distance);
}
Point spawnPoint = map.getSpawnPoints().get(distanceArrayList.indexOf(Collections.max(distanceArrayList)));
spawnPlayer.getCharacter().spawn(spawnPoint);
}
}
The spawnpoints are hardcoded, at 0,0, 200,0, 0,500 and 200,500. However players don't always go to the furthest spawnpoint (there are only two players and one doesn't move during the tests) and sometimes don't change position at all, even though this method was called.
EDIT
So the code we use at this moment is as follows:
public void SpawnPlayers()
{
for (Player spawnPlayer : players)
{
if (spawnPlayer.getCharacter().getCanSpawn())
{
int maxDistance = 0;
Point spawnPoint = null;
for (Point point : map.getSpawnPoints())
{
int sumDistancesFromOthers = 0;
for (Player player : players)
{
if (player != spawnPlayer && player.getCharacter().getLives() > 0 && !player.getCharacter().getCanSpawn())
{
sumDistancesFromOthers += Math.sqrt(Math.pow(point.x - player.getCharacter().getPosition().x, 2)
+ Math.pow(point.y - player.getCharacter().getPosition().y, 2));
}
}
if (maxDistance < sumDistancesFromOthers || spawnPoint == null)
{
maxDistance = sumDistancesFromOthers;
spawnPoint = point;
}
}
spawnPlayer.getCharacter().spawn(spawnPoint);
}
}
}
However, the players still sometimes spawn at wrong positions, sometimes don't spawn at a new location at all, and at the start of the match, all players spawn at the same location. The method SpawnPlayers() gets called every time the game updates, and the boolean canSpawn gets correctly updated when a player dies.
The spawn method:
public void spawn(Point spawnPoint)
{
setPosition(spawnPoint);
canSpawn = false;
for (Weapon weapon : weapons)
{
weapon.restartShotsRemaining();
}
new Timer().schedule(new TimerTask() {
#Override
public void run()
{
canBeHit = true;
}
}, 1500);
}
As mentioned in the comments, it's a bit difficult to flesh out what the actual question is. Questions that are looking for debugging help are usually considered as off-topic.
From the information that was provided so far, it's hard to clearly derive the "state space" of the objects that are involved in this computation. For example, the relationship between getCanSpawn() and getLives()>0. It is not clear when the canSpawn flag will be set to true or false, and when the lives count is decreased. The code in the question also does not seem to consider that positions that are already occupied by other players should not be used as a spawn position.
As a general recommendation is therefore to break down the algorithm into smaller parts, that are easier to test and debug. For example, looking at the original code:
public void SpawnPlayers()
{
for (Player spawnPlayer : players)
{
if (spawnPlayer.getCharacter().getCanSpawn())
{
...
}
}
}
The innermost part lends itself to be extracted into a method like
private void spawnPlayer(Player playerToSpawn)
{
System.out.println("Spawning "+playerToSpawn);
...
}
which makes it also far easier to understand (and see on the console) when a certain player is about to be spawned, and what happens with this player afterwards (as indicated by further System.out statements).
Now, there are two things that are relevant for computing the spawn position of a new player:
The positions that are still available for spawning
The positions that the other players have (and which consequently are no longer available for spawning)
These can be computed as two sets...
Set<Point> availableSpawnPoints = ...;
Set<Point> positionsOfOtherPlayers = ...;
The contents of these sets will depend on the getCanSpawn() and getLives() values, and may have to be adjusted according to your needs and the interplay of these methods.
However, after these sets have been computed, the whole algorithm that you asked for (according to the question title) boils down to a single method - namely a method that receives two sets of points, and computes the point from the first set that is "furthest away" from points in the second set.
There are different possible interpretations of what "furthest away" means. You computed some sum of distances, which looked a bit odd for me. Imagine you have two "fixed" points (the locations of existing players), and a set of "candidate" points (where the player may be spawned), as in this image:
Now, imagine that...
the distances of A to the others are 3.8 and 0.3, resulting in a sum of 4.1
the distances of B to the others are 2.0 and 2.0, resulting in a sum of 4.0
Then, with your approach, point A would be chosen as the spawn position. (The same applies in this example when you simply compute the maximum distance of the "candidate" point to any fixed point). But intuitively (and according to the description), you would probably like to compute the point that has the largest minimal distance to any other point. Or more naturally: The point that is as far away as possible from any other point.
So the computation of the spawn point could probably be done with some method like
private Point computePointWithLargestMinimumDistance(
Iterable<? extends Point> points, Set<? extends Point> others)
{
...
}
where you can pass in the availableSpawnPoints and the positionsOfOtherPlayers.
(BTW: The method signature is in its most generic form. You could also use more specific parameter types, like HashSet<Point>, but this is simply not required here - so why not do it generically...)
This is implemented here, sketching the classes that you mentioned, as far as reasonably possible:
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class PlayerSpawning
{
public static void main(String[] args)
{
PlayerSpawning p = new PlayerSpawning();
p.spawnPlayers();
}
private List<Player> players;
private PlayerMap map;
PlayerSpawning()
{
map = new PlayerMap();
players = new ArrayList<Player>();
Player player0 = new Player("player0");
player0.getCharacter().setPosition(new Point(0,0));
player0.getCharacter().setCanSpawn(false);
players.add(player0);
Player player1 = new Player("player1");
player1.getCharacter().setCanSpawn(true);
players.add(player1);
}
public void spawnPlayers()
{
for (Player player : players)
{
if (player.getCharacter().getCanSpawn())
{
spawnPlayer(player);
}
}
}
private void spawnPlayer(Player playerToSpawn)
{
System.out.println("Spawning "+playerToSpawn);
Set<Point> availableSpawnPoints =
new LinkedHashSet<Point>(map.getSpawnPoints());
Set<Point> positionsOfOtherPlayers =
new LinkedHashSet<Point>();
for (Player player : players)
{
if (player.getCharacter().getLives() <= 0)
{
continue;
}
if (player.getCharacter().getCanSpawn())
{
continue;
}
Point position = player.getCharacter().getPosition();
System.out.println(
"Have to consider that "+player+" is at "+position+
" - this position is no longer available for spawing!");
positionsOfOtherPlayers.add(position);
availableSpawnPoints.remove(position);
}
Point spawnPoint = computePointWithLargestMinimumDistance(
availableSpawnPoints, positionsOfOtherPlayers);
System.out.println("Spawning "+playerToSpawn+" at "+spawnPoint);
playerToSpawn.getCharacter().spawn(spawnPoint);
}
private Point computePointWithLargestMinimumDistance(
Iterable<? extends Point> points, Set<? extends Point> others)
{
System.out.println("Compute point from "+points);
System.out.println("that is furthest from "+others);
double largestMinDistance = Double.NEGATIVE_INFINITY;
Point result = null;
for (Point point : points)
{
double minDistance =
computeMinimumDistance(point, others);
if (minDistance > largestMinDistance)
{
largestMinDistance = minDistance;
result = point;
}
}
System.out.println(
"The point that has the largest minimum " +
"distance "+largestMinDistance+" to any other point is "+result);
return result;
}
private double computeMinimumDistance(
Point point, Iterable<? extends Point> others)
{
double minDistanceSquared = Double.POSITIVE_INFINITY;
for (Point other : others)
{
minDistanceSquared =
Math.min(minDistanceSquared, point.distanceSq(other));
}
return Math.sqrt(minDistanceSquared);
}
}
class Player
{
private String name;
private Character character = new Character();
public Player(String name)
{
this.name = name;
}
public Character getCharacter()
{
return character;
}
#Override
public String toString()
{
return name;
}
}
class Character
{
private Point position = new Point();
private boolean canSpawn = false;
public boolean getCanSpawn()
{
return canSpawn;
}
public void setCanSpawn(boolean canSpawn)
{
this.canSpawn = canSpawn;
}
public int getLives()
{
return 1;
}
public Point getPosition()
{
return position;
}
public void setPosition(Point p)
{
position.setLocation(p);
}
public void spawn(Point spawnPoint)
{
setPosition(spawnPoint);
canSpawn = false;
}
}
class PlayerMap
{
public List<Point> getSpawnPoints()
{
return Arrays.asList(
new Point(0,0),
new Point(200,0),
new Point(0, 500),
new Point(200,500));
}
}
The output of this MCVE is, as desired:
Spawning player1
Have to consider that player0 is at java.awt.Point[x=0,y=0] - this position is no longer available for spawing!
Compute point from [java.awt.Point[x=200,y=0], java.awt.Point[x=0,y=500], java.awt.Point[x=200,y=500]]
that is furthest from [java.awt.Point[x=0,y=0]]
The point that has the largest minimum distance 538.5164807134504 to any other point is java.awt.Point[x=200,y=500]
Spawning player1 at java.awt.Point[x=200,y=500]
Proposed code change:
for (Player spawnPlayer : players) {
if (spawnPlayer.getCharacter().getCanSpawn()) {
System.out.println("works");
int maxDistance = 0;
Point currentSpawnPoint = null;
for (Point point : map.getSpawnPoints()) {
int distance = 0;
for (Player player : players) {
if (player != spawnPlayer && player.getCharacter().getLives() > 0 && !player.getCharacter().getCanSpawn()) {
distance += Math.sqrt(Math.pow(point.x - player.getCharacter().getPosition().x, 2)
+ Math.pow(point.y - player.getCharacter().getPosition().y, 2));
}
}
if(distance>maxDistance){
maxDistance = distance;
currentSpawnPoint = Point;
}
}
spawnPlayer.getCharacter().spawn(spawnPoint);
}
}
Reasoning: Remembering the distances is not necessary, and reliance on list-indexes is not the way to clean code (They might change).
I suggest you use local variables to remember the current maximum and corresponding position. You will gain performance by avoiding searching a list. This will change the code as follows:
for (Player spawnPlayer : players) {
if (spawnPlayer.getCharacter().getCanSpawn()) {
System.out.println("works");
int maxDistance = 0;
Point spawnPoint = null;
for (Point point : map.getSpawnPoints()) {
int sumDistancesFromOthers = 0;
for (Player player : players) {
if (player != spawnPlayer && player.getCharacter().getLives() > 0 && !player.getCharacter().getCanSpawn()) {
sumDistancesFromOthers += Math.sqrt(Math.pow(point.x - player.getCharacter().getPosition().x, 2)
+ Math.pow(point.y - player.getCharacter().getPosition().y, 2));
}
}
if (maxDistance < sumDistancesFromOthers || spawnPoint == null) {
maxDistance = sumDistancesFromOthers;
spawnPoint = point;
}
}
spawnPlayer.getCharacter().spawn(spawnPoint);
}
}
I added the test spawnPoint == null to make sure spawnPoint won't be null when you exit the loop.
Hope this will help...
Jeff
------------ UPDATE ------------
I corrected the snippet above to take into consideration the sum of distances from other players as the definition of the distance to be maximized.
I don't think you have a problem in your code snippet if the following assumptions are true:
Character.getCanSpawn() implies Character.getLives() > 0
Character.spawn(spawnPoint) ensures Character.getCanSpawn() == false (post condition)
You can initially still get closer-than-optimal spawns: assuming you randomly choose a spawn position for the first character, the second is only optimally placed w.r.t. the first. However there might now be a more optimal position for the first character.
I was a little bit too lazy to 'debug' your code, that's why I created the snippet of code below.
Anyway,
I suggest you divide the code into smaller pieces. For example, I would move the code to calculate the distance between two points to the Point class.
Furthermore, your code below,
if (maxDistance < sumDistancesFromOthers || spawnPoint == null) {
maxDistance = sumDistancesFromOthers;
spawnPoint = point;
}
is a little weird: if either maxDistance < sumDistancesFromOthers or spawnPoint == null, then the current spawnpoint is selected. I assume you mean: if either maxDistance < sumDistancesFromOthers and spawnPoint == null...
The code below assumes that at least one player is alive. Then, when spawning all dead players, each spawnpoint is compared to each position of players who are alive. I have set the position to null if a player is dead and needs respawning.
This code also assumes that multiple players can be at the same spawnpoint. But that happens only if all spawnpoints are occupied.
Player
public class Player {
private Position position;
public Player(Position initialPosition) {
this.position = initialPosition;
}
/**
* Returns a copy of the player's position.
* #return The player's position.
*/
public Position getPosition() {
return new Position(this.position);
}
/**
*/
public SpawnPoint spawn(List<SpawnPoint> spawnPoints, List<Player> players) {
double highestDistance = 0.0d;
SpawnPoint bestSpawnPoint = null;
for (SpawnPoint sp : spawnPoints) {
double distance = 0.0d;
for (Player p : players) {
if (p.isAlive()) {
distance += sp.getPosition().getDistance(p.getPosition());
}
}
if (distance > highestDistance) {
highestDistance = distance;
bestSpawnPoint = sp;
}
}
if (bestSpawnPoint == null) {
// Do something if there is no best spawnpoint, that is,
// when all spawnpoints are occupied and thus the furthest
// spawnpoint is at a distance of 0.0.
}
return bestSpawnPoint;
}
public boolean isAlive() {
return (this.position != null);
}
}
Position
public class Position {
private int x;
private int y;
public Position(Position position) {
if (position != null) {
this.x = position.x;
this.y = position.y;
}
}
public Position(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
/**
* Calculates the distance between this position and the given position.
* #param anotherPosition The position to compare the current position with.
* #return The distance as a double.
*/
public double getDistance(Position anotherPosition) {
double xDistance = Math.abs(Math.pow(this.x - anotherPosition.x, 2));
double yDistance = Math.abs(Math.pow(this.y - anotherPosition.y, 2));
return Math.sqrt(xDistance + yDistance);
}
}
SpawnPoint
public class SpawnPoint {
private Position position;
public SpawnPoint(Position position) {
this.position = position;
}
public SpawnPoint(int x, int y) {
this(new Position(x, y));
}
public Position getPosition() {
return new Position(this.position);
}
}
And the main:
public static void main(String[] args) {
// Create some spawnpoints...
List<SpawnPoint> spawnPoints = new ArrayList<SpawnPoint>() {{
add(new SpawnPoint(0, 0));
add(new SpawnPoint(2, 0));
add(new SpawnPoint(0, 5));
add(new SpawnPoint(2, 5));
}};
// Create some players
Player playerA = new Player(new Position(0, 0));
Player playerB = new Player(new Position(4, 1));
Player playerC = new Player((Position) null);
// A null position means that the player is dead.
// Add the players to the list of players...
List<Player> players = new ArrayList<Player>() {{
add(playerA);
add(playerB);
add(playerC);
}};
// Spawn playerC (which is currently dead and need to be respawned)
// and return the best spawn point as defined by the OP
SpawnPoint sp = playerC.spawn(spawnPoints, players);
// Print the position
System.out.println(sp.getPosition());
}
I kept it simple, and it works.
Then you can check whether a player is alive and so on by yourself.
Note: As Marco13 already stated, it would more logical to determine the largest minimum between a spawnpoint and any player. Or you can make an algorithm which tries to take both into account.

Shoot more than one bullet

I'm having problems with my character shooting. When SPACE button is pressed character shoot one bullet. But when I press SPACE for example twice I should got two bullets shoot from my character. Unfortunately I got only one bullet at the time and when my bullet collides with something (for example wall) only then other second bullet is shooted! I understand than ArrayList update only one bullet at the time and when that bullet is deleted then update another. How I need to get the double (or even more) Bullets effect?
Here is my Player class
public class CoolGuy extends GameObject{
private ArrayList<PlayerBullet> bullets;
/*Tones of code here */
public void shoot(float delta){
if(PlayerBullet.shoot){
if(index != 0){
index++;
}
bullets.add(new PlayerBullet(full.x, full.y + sprite.getHeight()/2 - 30));
}
}
public void update(float delta) {
if(bullets.size() > 0){
if(bullets.get(index) != null){
bullets.get(index).update(delta);
}
for(GameObject t : list){
if(t instanceof Brick){
if(bullets.size() > 0 && bullets.get(index) != null && bullets.get(index).hits(t.getHitBox()) == 1){
bullets.remove(index);
}
}
}
}
}
}
And here's my Bullet class
public class PlayerBullet extends ItemObject{
private Sprite sprite;
private Rectangle full;
public static boolean shoot;
public PlayerBullet(int x, int y){
......
......
}
public int hits(Rectangle r) {
if(full.overlaps(r)){
return 1;
}
return -1;
}
public void update(float delta) {
full.x += (delta * 500);
setPosition(full.x,full.y);
}
}
And here in my main class
player1.update(Gdx.graphics.getDeltaTime());
if(Gdx.input.isKeyJustPressed(Input.Keys.SPACE)){
player1.shoot(Gdx.graphics.getDeltaTime());
}
I can't tell what your index parameter is supposed to be (the stuff you do with it doesn't make any sense to me), but it sure seems to be the source of the problem.
First of all, in the shoot method, you only increment it if it's non-zero, so if it is zero, it will forever be zero.
Then in render you are only updating a single bullet instead of all the bullets in the array.
The clearest solution to me (although again I don't know what you were trying to track with the index parameter) would be to completely remove the index parameter. Then replace ArrayList with libgdx's Array class, which allows fast modification and removal during a loop, and change your render method like this.
public void update(float delta) {
for (int i=bullets.size-1; i>=0; i--) { //count backward for safe removals
PlayerBullet bullet = bullets.get(i);
for(GameObject t : list){
if(t instanceof Brick){
if(bullet.hits(t.getHitBox()) == 1){
bullets.removeIndex(i);
}
}
}
}
}

Track best move from Minimax

I know this kind of question has been asked before, but i was unable to solve my doubts.
I have a simple Othello Engine (it plays very well actually), that uses the class below to get the best move:
import java.util.*;
import java.util.concurrent.*;
public class MinimaxOthello implements Runnable
{
private CountDownLatch doneSignal;
private int maxDepth;
private int calls;
private OthelloMove bestFound;
private OthelloBoard board;
private static float INFINITY = Float.MAX_VALUE/1000;
private boolean solve = false;
private Comparator<OthelloMove> comparator = Collections.reverseOrder(new MoveComparator());
public MinimaxOthello (OthelloBoard board, int maxDepth, CountDownLatch doneSignal, boolean solve)
{
this.board = board;
this.bestFound = new OthelloMove();
bestFound.setPlayer(board.getCurrentPlayer());
this.maxDepth = maxDepth;
this.doneSignal = doneSignal;
this.solve = solve;
}
public OthelloMove getBestFound()
{
return this.bestFound;
}
public void run()
{
float val = minimax(board, bestFound, -INFINITY, INFINITY, 0);
System.out.println("calls: " + calls);
System.out.println("eval: " + val);
System.out.println();
doneSignal.countDown();
}
private float minimax(OthelloBoard board, OthelloMove best, float alpha, float beta, int depth)
{
calls++;
OthelloMove garbage = new OthelloMove();
int currentPlayer = board.getCurrentPlayer();
if (board.checkEnd())
{
int bd = board.countDiscs(OthelloBoard.BLACK);
int wd = board.countDiscs(OthelloBoard.WHITE);
if ((bd > wd) && currentPlayer == OthelloBoard.BLACK)
{
return INFINITY/10;
}
else if ((bd < wd) && currentPlayer == OthelloBoard.BLACK)
{
return -INFINITY/10;
}
else if ((bd > wd) && currentPlayer == OthelloBoard.WHITE)
{
return -INFINITY/10;
}
else if ((bd < wd) && currentPlayer == OthelloBoard.WHITE)
{
return INFINITY/10;
}
else
{
return 0.0f;
}
}
if (!solve)
{
if (depth == maxDepth)
return OthelloHeuristics.eval(currentPlayer, board);
}
ArrayList<OthelloMove> moves = board.getAllMoves(currentPlayer);
if (moves.size() > 1)
{
OthelloHeuristics.scoreMoves(moves);
Collections.sort(moves, comparator);
}
for (OthelloMove mv : moves)
{
board.makeMove(mv);
float score = - minimax(board, garbage, -beta, -alpha, depth + 1);
board.undoMove(mv);
if(score > alpha)
{
alpha = score;
best.setFlipSquares(mv.getFlipSquares());
best.setIdx(mv.getIdx());
best.setPlayer(mv.getPlayer());
}
if (alpha >= beta)
break;
}
return alpha;
}
}
I have a bestFound instance variable and my doubt is, why a have to call
OthelloMove garbage = new OthelloMove();
and pass it along? The code works, but it seems very weird to me!
Is there a 'better' way to get the best move or the Principal Variation?
I really not a recursion expert, and this is very very hard to debug and visualize.
Thanks!
**PS: You can clone it at https://github.com/fernandotenorio/
It looks like you can get rid of the best parameter to minimax, thereby eliminating the need for garbage, and then replace best with this.bestFound. Only set bestFound's attributes if depth = 0.
You can get the principal variation by making this.bestFound an initially empty list. Before the moves loop, create a new move. In the if (score > alpha) part, set its attributes the same as you do now. Push the move to the list right after the loop. The principal variation will then be the reverse of the list.
If it's important, here are some changes you can make to improve the multi-threadability of your class:
Instead of storing the bestFound list as an instance variable, make it a local variable in run and add it as a parameter to minimax
Make Board.makeMove not modify the board, but instead return a new instance of the board with the move applied. You can implement that by cloning the board and applying your move code to the clone instead of mutating this. Then, pass the cloned board to the next invocation of minimax.
The second argument of minimax is used to return the best move.
The business with garbage is used to keep the best move for each turn separate. With the code you've provided, this is not important. But if you wanted to produce a sequence of moves from the current board to the end of the game, you would need to have them be separate move objects.
Using a separate best-move object for each turn allows you to do a number of tricks with threading. First, you might want to limit the thinking time of the Othello AI. Tracking the best move separately at each level means that you always have the best move so far available. It also means that you could cache the best move for a board and look that up in future minimax searches.
Second, you might want to search for the best move in parallel, and this is trivial to implement when each minimax call is independent.

Categories