Related
I'm having an issue and I'm not certain if it's recursion-based. I created a GUI maze that solves itself but the curser jumps over any recursion-traveled square instead of re-traveling the square. Even though it ultimately finds the goal, I want to show it's entire path but I can't stop the curser from jumping around.
I'm using Runnable to track the maze in real-time so I can see it bounce but without the recursion-travel keeping it bound, the recursive functions cease to work (it just bounces back and forth which, again, I don't quite understand.) I started java about three months ago in an accelerated program so I'm not sure if the issue is my understanding of recursion, or a simple addition to a method, or if I'll have to rewrite a large portion of code.
I included the whole code just in case but really it's an issue that's within the travel method or the visited method. Would the answer be to write an entirely new method that re-travels the changed "visited" string maze? I've been struggling with this for a bit and I just need some direction toward an answer.
import java.awt.*;
import javax.swing.*;
class extraCreditMaze extends JPanel implements Runnable { //uses Runnable to execute jPanel
private String [][] ratMaze = //string maze
{{"blocked","blocked","blocked","blocked","blocked","blocked","blocked","blocked"},
{"blocked","open","blocked","blocked","blocked","blocked","blocked","blocked"},
{"blocked","open","open","open","open","open","open","blocked"},
{"blocked","blocked","open","blocked","open","blocked","open","blocked"},
{"blocked","blocked","open","blocked","open","blocked","open","goal"},
{"blocked","open","open","open","blocked","open","open","blocked"},
{"blocked","blocked","blocked","open","open","open","blocked","blocked"},
{"blocked","blocked","blocked","blocked","blocked","blocked","blocked","blocked"}};
final private int SquareSize = 15;
final private int BoardSize = 17;
private boolean free = false;
int axisX = 1, axisY = 1;
public void paintComponent(Graphics color) //paint components for char
{
super.paintComponent(color);
for(int row = 0; row < ratMaze.length; row++)
{
for(int col = 0; col< ratMaze.length; col++)
{
if(row==axisX && col==axisY) //traveling curser = blue
{
color.setColor(Color.blue);
color.fillOval(col*15,row*15,15,15);
}
else if(ratMaze[row][col]=="blocked") //empty = black
{
color.setColor(Color.black);
color.fillRect(col*SquareSize,row*SquareSize,BoardSize,BoardSize);
}
else if(ratMaze[row][col]=="goal")
{
color.setColor(Color.red); //goal = red
color.fillOval(col*15,row*15,15,15);
}
else if(ratMaze[row][col]=="visited")
{
color.setColor(Color.green); //path traveled = green
color.fillOval(col*15,row*15,15,15);
}
else
{
color.setColor(Color.white); //empty space = white
color.fillRect(col*SquareSize,row*SquareSize,BoardSize,BoardSize);
}
}
}
}
public void run () //starts run at (1,1)
{
travel(1,1);
}
public boolean goal(int x, int y){ //method to check goal (true/false)
if(ratMaze[x][y]=="goal")
return true;
else
return false;
}
public void changedVisited(int x, int y) //method to change traveled
{
ratMaze[x][y] = "visited";
axisX = x;
axisY = y;
}
public boolean boundaries(int x, int y) //check boundaries
{
if(ratMaze[x][y]=="blocked")
return true;
else
return false;
}
public boolean visited(int x, int y) //check if visited
{
if(ratMaze[x][y]=="visited")
return true;
else
return false;
}
private void travel(int x, int y)
{
if(boundaries(x,y)) //makes sure it's within bounds
return;
if(visited(x,y)) //makes sure it hasn't already been visited
return;
if(goal(x,y)) //checks if it's the goal/changes boolean
{
free = true;
JOptionPane.showMessageDialog(this, "You did it, Dr. Cui!"); //fun message!
}
if(!free) //all recursion functions if free=false
{
changedVisited(x,y); //changes traveled block to "visited"
repaint(); //repaints visited
try {Thread.sleep(300); } catch (Exception e) { }//slows down the traveling curser
//I do not understand catch (Exception e)
travel(x-1,y); //recursive travel functions
travel(x+1,y);
travel(x,y-1);
travel(x,y+1);
}
}
}
public class runExtraCreditMaze {
public static void main (String [] args) { //JFrame panel and perimeters
JFrame output = new JFrame();
output.setSize(115, 150);
output.setTitle("The Rat Maze");
output.setLocationRelativeTo(null);
extraCreditMaze Maze = new extraCreditMaze();
output.setContentPane(Maze);
output.setVisible(true);
Thread runnable = new Thread(Maze); //Creates Runnable thread for Maze object
runnable.start(); //Starts Runnable thread of Maze object
}
}
Problem is, as you wrote with the "visited". You are missing an decision tree on what to do, when there is no valid move and you are not in the goal. You will need to allow your rat to back track itself. You will probably need to "free" the visited cells when returning from no valid move.
I will try to add some code samples when I get to IDE :)
update: this is very badly written, and it is a bit lagging. but it should work. It needs a bit of cleaning and verification... I reused your boolean variable, which is bad .. :) and switched the true/false. I will do a bit of cleaning up tomorrow just to leave a nicer answer, but I think you will manage to understand what is going on.
update2:I have cleaned it a bit. Important lessons here are as follows:
1) backtracking needs to be done when all 4 steps fails. When your rat have nowhere to go, you need to disqualify the cell from your shortest path (ratMaze[x][y]="open")
2) You need to change position of your rat, when you return from recursion call, but before you continue with next step into. You will also need to let your program know that you are returning from recursion (thus the isBacktracking)
private void repaintMaze(int x, int y) {
changedVisited(x, y); //changes traveled block to "visited"
repaint(); //repaints visited
isBacktracking = false;
try {
Thread.sleep(500);
} catch (Exception e) {
}
}
private boolean travel(int x, int y) {
if (goal(x, y)) //checks if it's the goal/changes boolean
{
JOptionPane.showMessageDialog(this, "You did it, Dr. Cui!");//fun message!
return true;
}
if (boundaries(x, y) || visited(x, y)) //makes sure it's within bounds
return false;
repaintMaze(x, y);
boolean result; //recursive travel functions
result = travel(x - 1, y);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
result = travel(x + 1, y);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
result = travel(x, y - 1);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
result = travel(x, y + 1);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
ratMaze[x][y] = "open";
isBacktracking = true;
return false;
}
you should also add exit on close to your JFrame :) It will stop the application once you click the X button on the window itself
output.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
I am writing a Chess program in Java. In my Board class, which deals with all movements of the Piece, I created a function called movePiece that moves a piece to a specific location and captures the enemy block if it exists on that location. The function for the movePiece looks like this:
public void movePiece(int x, int y, Piece pieceToMove) {
if(pieceToMove.canMove(board, this.x, this.y, x, y)){ //Check if the piece canMove to the location
if(board[x][y] == null) { //Check if the location is empty
removePiece(this.x, this.y);
board[x][y] = pieceToMove;
} else {
if(board[x][y].getColor() != pieceToMove.getColor()) { //Check if the location is occupied by an enemy
removePiece(this.x, this.y);
board[x][y] = pieceToMove;
} else {
System.out.println("Invalid Move");
}
}
} else {
System.out.println("Invalid Move");
}
}
It works fine, but I wanted to keep in track of the captured enemy Piece in an ArrayList. So, I created an ArrayList capturedList inside a Player class to keep in track of it, and tried to add
if(board[x][y].getColor() != pieceToMove.getColor()) { //Check if the location is occupied by an enemy
addCapturedPiece(board[x][y], capturedList); <--- THIS LINE
removePiece(this.x, this.y);
board[x][y] = pieceToMove;
}
But this did not work because capturedList is inside the Player class. Is there a way to make this work smoothly?
You could make addCapturedPiece a method in the Player class. Then, the player that is moving the current piece can call that method and add the piece to his/her specific capturedList. For instance:
/***** in Player class *****/
private LinkedList<Piece> capturedList = new LinkedList<Piece>();
// ...
private static void addCapturedPiece(Piece p)
{
capturedList.add(p);
}
/***** in your movePiece method *****/
player1.addCapturedPiece(board[x][y]);
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.
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.
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.