I'm having problems with my implementation of the MiniMax algoritm for my chess game. Most parts of it seems to work, but it either never makes the good moves or something is wrong with the evaluation (score based of both players active pieces) of them.
For example if I set up check (fool's mate for example) the ai does something random instead of killing the king. I really can't pin out what I'm doing wrong.
The class that evaluates the board, StandardBoardEvaluator, seems to work after some testing, so the problem is most likely somewhere within the MiniMax implementation. The game is made up from a class Board, which has and 2D array with 8x8 objects of my own class Square, which in itself has a reference to an Piece (that can be null, or any of the typical chess pieces).
In the algoritm i constantly makes new Board instances as going down the searchthree, which is why i made these "deep clone" constructors in Board and Square, so that does not seem to be the problem. Like this:
public Board(Board originalBoard) {
this.turnIsWhite = originalBoard.getTurnIsWhite();
winner = null;
squares = new Square[8][8];
for (int rank=0; rank<squares.length; rank++) {
for(int file=0; file<squares[rank].length; file++) {
squares[rank][file] = new Square(originalBoard.getSquare(posStringFromFileRank(rank, file)));
}
}
}
AND
public Square(Square originalSquare) {
this.pos = new String(originalSquare.getPos());
this.piece = originalSquare.getPiece();
}
I have an typical command class, MovePiece, for moving pieces. This uses another class, MoveCheck, to check if the move command is legal. MovePiece returns a boolean representing if the move is legal. Both these classes have been heavily tested and are working, so I don't think the problem is within these classes.
Here is the algoritm:
public class MiniMax implements MoveStrategy{
BoardEveluator bV;
MoveGenerator mGen;
int depth;
public MiniMax(int depth){
bV = new StandardBoardEvaluator();
mGen = new MoveGenerator();
this.depth = depth;
}
#Override
public MovePiece execute(Board board) {
MovePiece bestMove = null;
int lowestValue = Integer.MAX_VALUE;
int highestValue = Integer.MIN_VALUE;
int currentValue = 0;
String color = (board.getTurnIsWhite() ? "white" : "black");
System.out.println(color + " is evaluation best move with MiniMax depth " + depth);
List<MovePiece> allPossibleMoves = mGen.getLegalMoves(board, board.getTurnIsWhite());
for (MovePiece mp : allPossibleMoves){
Board tempBoard = new Board(board);
mp.setBoard(tempBoard);
if (mp.execute()){
currentValue = tempBoard.getTurnIsWhite() ? min(tempBoard, depth -1) : max(tempBoard, depth -1);
if (board.getTurnIsWhite() && currentValue >= highestValue){
highestValue = currentValue;
bestMove = mp;
}
else if (!board.getTurnIsWhite() && currentValue <= lowestValue){
lowestValue = currentValue;
bestMove = mp;
}
mp.unexecute();
}
}
return bestMove;
}
int min (Board board, int depth){
if (depth == 0 || board.getWinner() != null){
return bV.eveluate(board);
}
int lowestValue = Integer.MAX_VALUE;
List<MovePiece> legalMoves = mGen.getLegalMoves(board, board.getTurnIsWhite());
for (MovePiece mp : legalMoves){
Board tempBoard = new Board(board);
mp.setBoard(tempBoard);
if (mp.execute()){
int currentValue = max(tempBoard, depth - 1);
if (currentValue <= lowestValue){
lowestValue = currentValue;
}
mp.unexecute();
}
}
return lowestValue;
}
int max (Board board, int depth){
if (depth == 0 || board.getWinner() != null){
return bV.eveluate(board);
}
int highestValue = Integer.MIN_VALUE;
List<MovePiece> legalMoves = mGen.getLegalMoves(board, board.getTurnIsWhite());
for (MovePiece mp : legalMoves){
Board tempBoard = new Board(board);
mp.setBoard(tempBoard);
if (mp.execute()){
int currentValue = min(tempBoard, depth - 1);
if (currentValue >= highestValue){
highestValue = currentValue;
}
mp.unexecute();
}
}
return highestValue;
}
And the evalutor class
public class StandardBoardEvaluator implements BoardEveluator {
private int scorePlayer(Board board, boolean isWhite){
return pieceValue(board, isWhite) + mobolity(isWhite, board);
}
private int mobolity(boolean isWhite, Board board){
return (int) (board.getActiveSquares(isWhite).size() * 1.5);
}
private static int pieceValue(Board board, boolean isWhite){
int piceValueScore = 0;
for (Square square : board.getActiveSquares(isWhite)){
piceValueScore += square.getPiece().getPieceValue();
}
return piceValueScore;
}
#Override
public int eveluate(Board board) {
return scorePlayer(board, true) - scorePlayer(board, false);
}
}
Here is the MovePiece class:
private Square from;
private Square to;
private Board board;
private MoveCheck mCheck;
private RulesCheck rCheck;
private boolean done = false;
private Piece killed;
public MovePiece(Board board, String from, String to) {
this.board = board;
this.from = board.getSquare(from);
this.to = board.getSquare(to);
mCheck = new MoveCheck();
}
public MovePiece(Board board, Square from, Square to) {
this.board = board;
this.from = from;
this.to = to;
mCheck = new MoveCheck();
rCheck = new RulesCheck(board);
}
public void setBoard(Board board) {
this.board = board;
}
public Board getBoard() {
return board;
}
public Square getFrom() {
return from;
}
public Square getTo() {
return to;
}
public void setFrom(Square from) {
this.from = from;
}
public void setTo(Square to) {
this.to = to;
}
public void setFrom(String from) {
this.from = board.getSquare(from);
}
public void setTo(String to) {
this.to = board.getSquare(to);
}
#Override
public boolean execute() {
rCheck = new RulesCheck(board);
if (done) {
board.movePiece(from, to);
return true;
}
else if (mCheck.isLegal(board, from, to)){
if (to.getPiece() != null) {
killed = to.getPiece();
rCheck.winCheck(killed);
}
board.setGameOutput("Moved " + from.pieceToString() + " at " + from.getPos() + " - to " + to.getPos() + "(" + to.pieceToString() + ")");
board.movePiece(from, to);
rCheck.checkPromotion(to);
done = true;
return true;
}
return false;
}
#Override
public void unexecute() {
if (to.getPiece().getClass() == Pawn.class)
((Pawn) to.getPiece()).decreaseMoves();
board.movePiece(to, from);
if (killed != null) {
to.setPiece(killed);
}
}
The MoveCheck class merely looks if the move is legal for the piece (path is clear, target is an enemy or empty and so on), don't think it's relevant for my problem since the code is tested and works.
The piece value is declared as an int in the subclasses (all the types of pieces) of the abstract class Piece. 100 points for a pawn, 300 for bishop and knight, 500 for rook, 900 for queen and 10 000 for the king.
If anyone could help me figure out the problem i would be eternally grateful! Please let me know if you need to se some other code i haven't showed.
You haven't shared the MovePiece implementation neither the main game loop, but I indentified two possible problems inside MiniMax.execute method:
currentValue = tempBoard.getTurnIsWhite() ? min(tempBoard, depth -1) : max(tempBoard, depth -1)
According to the above code, you are assuming that the MinMax player will always be black, as it evaluates min for white and max for black. For a generic algorithm this is a wrong assumption, don't know if it works for you though.
Second thing is after calling mp.execute() and assigning bestMove = mp you call mp.unexecute(), so effectively call bestMove.unexecute() since the variables point to the same object.
Please consider the suggestions above and if it does not fix the problem, share the abovementioned implementation pieces.
Related
(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());
}
}
I am programming a very basic bot for planet wars in java and I cant seem to find the errors in my code. I am receiving a few different error messages but the main issue for me is the error: class, interface, or enum expected. Ive checked my brackets about a thousand times. Any help would be appreciated. Here's my bot code:
import java.util.List;
import java.util.Random;
import shared.Planet;
import shared.PlanetWars;
public class MyNewBot {
public static void doTurn(PlanetWars pw) {
// (1) If we currently have a fleet in flight, then do nothing until
// it arrives.
if (pw.myFleets().size() >= 10) {
return;
}
// (2) Pick one of my planets based on the number of ships
Planet source = null;
int largestForce = 0;
for (Planet p : pw.myPlanets()){
int force = pw.numShips();
if( force > largestForce){
largestForce = force;
source = p;
}
}
// (3) Pick a target planet at random.
Planet dest = null;
int highestGrowthRate = 0;
int shortestDistance = 9999;
for (Planet p = pw.notMyPlanets()){
int growthRate = pw.growthRate();
if( growthRate > highestGrowthRate){
highestGrowthRate = growthRate;
dest = p;
}else if (growthRate == highestGrowthRate){
int distance = pw.distance(source,p);
if (distance < shortestDistance){
shortestDistance = distance;
dest = p;
}
}
}
// (4) Send half the ships from source to destination.
if (source != null && dest != null) {
int numShips = source.numShips() / 2;
pw.issueOrder(source, dest, numShips);
}
}
// Ignore the main method unless you know what you're doing.
// Refer to the doTurn function to code your bot.
public static void main(String[] args) {
String line = "";
String message = "";
int c;
try {
while ((c = System.in.read()) >= 0) {
switch (c) {
case '\n':
if (line.equals("go")) {
PlanetWars pw = new PlanetWars(message);
doTurn(pw);
pw.finishTurn();
message = "";
} else {
message += line + "\n";
}
line = "";
break;
default:
line += (char) c;
break;
}
}
} catch (Exception e) {
// Owned.
}
}
}
and the supporting class files:
package shared;
public class Planet implements Cloneable {
private int planetID;
private int owner;
private int numShips;
private int growthRate;
private double x, y;
public Planet(int planetID, int owner, int numShips, int growthRate,
double x, double y) {
this.planetID = planetID;
this.owner = owner;
this.numShips = numShips;
this.growthRate = growthRate;
this.x = x;
this.y = y;
}
public int planetID() {
return planetID;
}
public int owner() {
return owner;
}
public int numShips() {
return numShips;
}
public int growthRate() {
return growthRate;
}
public double x() {
return x;
}
public double y() {
return y;
}
public void owner(int newOwner) {
this.owner = newOwner;
}
public void numShips(int newNumShips) {
this.numShips = newNumShips;
}
public void addShips(int amount) {
numShips += amount;
}
public void removeShips(int amount) {
numShips -= amount;
}
private Planet(Planet _p) {
planetID = _p.planetID;
owner = _p.owner;
numShips = _p.numShips;
growthRate = _p.growthRate;
x = _p.x;
y = _p.y;
}
public Object clone() {
return new Planet(this);
}
}
package shared;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class PlanetWars {
// Constructs a PlanetWars object instance, given a string containing a
// description of a game state.
public PlanetWars(String gameStateString) {
planets = new ArrayList<Planet>();
fleets = new ArrayList<Fleet>();
parseGameState(gameStateString);
}
// Returns the number of planets. Planets are numbered starting with 0.
public int numPlanets() {
return planets.size();
}
// Returns the planet with the given planet_id. There are NumPlanets()
// planets. They are numbered starting at 0.
public Planet getPlanet(int planetID) {
return planets.get(planetID);
}
// Returns the number of fleets.
public int numFleets() {
return fleets.size();
}
// Returns the fleet with the given fleet_id. Fleets are numbered starting
// with 0. There are NumFleets() fleets. fleet_id's are not consistent from
// one turn to the next.
public Fleet getFleet(int fleetID) {
return fleets.get(fleetID);
}
// Returns a list of all the planets.
public List<Planet> planets() {
return planets;
}
// Return a list of all the planets owned by the current player. By
// convention, the current player is always player number 1.
public List<Planet> myPlanets() {
List<Planet> r = new ArrayList<Planet>();
for (Planet p : planets) {
if (p.owner() == 1) {
r.add(p);
}
}
return r;
}
// Return a list of all neutral planets.
public List<Planet> neutralPlanets() {
List<Planet> r = new ArrayList<Planet>();
for (Planet p : planets) {
if (p.owner() == 0) {
r.add(p);
}
}
return r;
}
// Return a list of all the planets owned by rival players. This excludes
// planets owned by the current player, as well as neutral planets.
public List<Planet> enemyPlanets() {
List<Planet> r = new ArrayList<Planet>();
for (Planet p : planets) {
if (p.owner() >= 2) {
r.add(p);
}
}
return r;
}
// Return a list of all the planets that are not owned by the current
// player. This includes all enemy planets and neutral planets.
public List<Planet> notMyPlanets() {
List<Planet> r = new ArrayList<Planet>();
for (Planet p : planets) {
if (p.owner() != 1) {
r.add(p);
}
}
return r;
}
// Return a list of all the fleets.
public List<Fleet> fleets() {
List<Fleet> r = new ArrayList<Fleet>();
for (Fleet f : fleets) {
r.add(f);
}
return r;
}
// Return a list of all the fleets owned by the current player.
public List<Fleet> myFleets() {
List<Fleet> r = new ArrayList<Fleet>();
for (Fleet f : fleets) {
if (f.owner() == 1) {
r.add(f);
}
}
return r;
}
// Return a list of all the fleets owned by enemy players.
public List<Fleet> enemyFleets() {
List<Fleet> r = new ArrayList<Fleet>();
for (Fleet f : fleets) {
if (f.owner() != 1) {
r.add(f);
}
}
return r;
}
// Returns the distance between two planets, rounded up to the next highest
// integer. This is the number of discrete time steps it takes to get
// between the two planets.
public int distance(int sourcePlanet, int destinationPlanet) {
Planet source = planets.get(sourcePlanet);
Planet destination = planets.get(destinationPlanet);
double dx = source.x() - destination.x();
double dy = source.y() - destination.y();
return (int) Math.ceil(Math.sqrt(dx * dx + dy * dy));
}
// Returns the distance between two planets, rounded up to the next highest
// integer. This is the number of discrete time steps it takes to get
// between the two planets.
public int distance(Planet source, Planet destination) {
double dx = source.x() - destination.x();
double dy = source.y() - destination.y();
return (int) Math.ceil(Math.sqrt(dx * dx + dy * dy));
}
// Sends an order to the game engine. An order is composed of a source
// planet number, a destination planet number, and a number of ships. A
// few things to keep in mind:
// * you can issue many orders per turn if you like.
// * the planets are numbered starting at zero, not one.
// * you must own the source planet. If you break this rule, the game
// engine kicks your bot out of the game instantly.
// * you can't move more ships than are currently on the source planet.
// * the ships will take a few turns to reach their destination. Travel
// is not instant. See the Distance() function for more info.
public void issueOrder(int sourcePlanet, int destinationPlanet, int
numShips) {
System.out.println("" + sourcePlanet + " " + destinationPlanet + " "
+ numShips);
System.out.flush();
}
// Sends an order to the game engine. An order is composed of a source
// planet number, a destination planet number, and a number of ships. A
// few things to keep in mind:
// * you can issue many orders per turn if you like.
// * the planets are numbered starting at zero, not one.
// * you must own the source planet. If you break this rule, the game
// engine kicks your bot out of the game instantly.
// * you can't move more ships than are currently on the source planet.
// * the ships will take a few turns to reach their destination. Travel
// is not instant. See the Distance() function for more info.
public void issueOrder(Planet source, Planet dest, int numShips) {
System.out.println("" + source.planetID() + " " + dest.planetID() + " "
+ numShips);
System.out.flush();
}
// Sends the game engine a message to let it know that we're done sending
// orders. This signifies the end of our turn.
public void finishTurn() {
System.out.println("go");
System.out.flush();
}
// Returns true if the named player owns at least one planet or fleet.
// Otherwise, the player is deemed to be dead and false is returned.
public boolean isAlive(int playerID) {
for (Planet p : planets) {
if (p.owner() == playerID) {
return true;
}
}
for (Fleet f : fleets) {
if (f.owner() == playerID) {
return true;
}
}
return false;
}
// If the game is not yet over (ie: at least two players have planets or
// fleets remaining), returns -1. If the game is over (ie: only one player
// is left) then that player's number is returned. If there are no
// remaining players, then the game is a draw and 0 is returned.
public int winner() {
Set<Integer> remainingPlayers = new TreeSet<Integer>();
for (Planet p : planets) {
remainingPlayers.add(p.owner());
}
for (Fleet f : fleets) {
remainingPlayers.add(f.owner());
}
switch (remainingPlayers.size()) {
case 0:
return 0;
case 1:
return ((Integer) remainingPlayers.toArray()[0]).intValue();
default:
return -1;
}
}
// Returns the number of ships that the current player has, either located
// on planets or in flight.
public int numShips(int playerID) {
int numShips = 0;
for (Planet p : planets) {
if (p.owner() == playerID) {
numShips += p.numShips();
}
}
for (Fleet f : fleets) {
if (f.owner() == playerID) {
numShips += f.numShips();
}
}
return numShips;
}
// Returns the production of the given player.
public int production(int playerID) {
int prod = 0;
for (Planet p : planets) {
if (p.owner() == playerID) {
prod += p.growthRate();
}
}
return prod;
}
// Parses a game state from a string. On success, returns 1. On failure,
// returns 0.
private int parseGameState(String s) {
planets.clear();
fleets.clear();
int planetID = 0;
String[] lines = s.split("\n");
for (int i = 0; i < lines.length; ++i) {
String line = lines[i];
int commentBegin = line.indexOf('#');
if (commentBegin >= 0) {
line = line.substring(0, commentBegin);
}
if (line.trim().length() == 0) {
continue;
}
String[] tokens = line.split(" ");
if (tokens.length == 0) {
continue;
}
if (tokens[0].equals("P")) {
if (tokens.length != 6) {
return 0;
}
double x = Double.parseDouble(tokens[1]);
double y = Double.parseDouble(tokens[2]);
int owner = Integer.parseInt(tokens[3]);
int numShips = Integer.parseInt(tokens[4]);
int growthRate = Integer.parseInt(tokens[5]);
Planet p = new Planet(planetID++, owner, numShips, growthRate,
x, y);
planets.add(p);
} else if (tokens[0].equals("F")) {
if (tokens.length != 7) {
return 0;
}
int owner = Integer.parseInt(tokens[1]);
int numShips = Integer.parseInt(tokens[2]);
int source = Integer.parseInt(tokens[3]);
int destination = Integer.parseInt(tokens[4]);
int totalTripLength = Integer.parseInt(tokens[5]);
int turnsRemaining = Integer.parseInt(tokens[6]);
Fleet f = new Fleet(owner, numShips, source, destination,
totalTripLength, turnsRemaining);
fleets.add(f);
} else {
return 0;
}
}
return 1;
}
// Store all the planets and fleets. OMG we wouldn't wanna lose all the
// planets and fleets, would we!?
private ArrayList<Planet> planets;
private ArrayList<Fleet> fleets;
}
package shared;
public class Fleet implements Comparable<Fleet>, Cloneable {
private int owner;
private int numShips;
private int sourcePlanet;
private int destinationPlanet;
private int totalTripLength;
private int turnsRemaining;
public Fleet(int owner, int numShips, int sourcePlanet,
int destinationPlanet, int totalTripLength, int turnsRemaining) {
this.owner = owner;
this.numShips = numShips;
this.sourcePlanet = sourcePlanet;
this.destinationPlanet = destinationPlanet;
this.totalTripLength = totalTripLength;
this.turnsRemaining = turnsRemaining;
}
public Fleet(int owner, int numShips) {
this.owner = owner;
this.numShips = numShips;
this.sourcePlanet = -1;
this.destinationPlanet = -1;
this.totalTripLength = -1;
this.turnsRemaining = -1;
}
public int owner() {
return owner;
}
public int numShips() {
return numShips;
}
public int sourcePlanet() {
return sourcePlanet;
}
public int destinationPlanet() {
return destinationPlanet;
}
public int totalTripLength() {
return totalTripLength;
}
public int turnsRemaining() {
return turnsRemaining;
}
public void removeShips(int amount) {
numShips -= amount;
}
// Subtracts one turn remaining. Call this function to make the fleet get
// one turn closer to its destination.
public void TimeStep() {
if (turnsRemaining > 0) {
--turnsRemaining;
} else {
turnsRemaining = 0;
}
}
#Override
public int compareTo(Fleet f) {
return this.numShips - f.numShips;
}
private Fleet(Fleet _f) {
owner = _f.owner;
numShips = _f.numShips;
sourcePlanet = _f.sourcePlanet;
destinationPlanet = _f.destinationPlanet;
totalTripLength = _f.totalTripLength;
turnsRemaining = _f.turnsRemaining;
}
public Object clone() {
return new Fleet(this);
}
}
for (Planet p = pw.notMyPlanets()){ should be for (Planet p : pw.notMyPlanets()){.
You've not posted the Fleet class, so as it is the code won't compile for me. However, the above is the only other error I could see.
I am trying to implement a Minimax (with alpha beta pruning. My Problem now is that if I evaluate a position and backtrack to the next move in the iteration (one level up) the "currentBoard" is not the initial board but the one from the evaluated leaf, even though makeMove and removeFigure both return a new board.
So how can I "save" the old board for correct backtracking?
P.s: I want to use copying instead of undoing a move because the board is a simple hashmap so i guess its easier this way.
Here is the code I have so far:
public int alphaBeta(Board currentBoard, int depth, int alpha, int beta, boolean maximisingPlayer) {
int score;
if (depth == 0) {
return Evaluator.evaluateLeaf(whichColorAmI, currentBoard);
}
else if (maximisingPlayer) {
ArrayList<Move> possibleMoves= new ArrayList<Move>();
possibleMoves=getPossibleMoves(whichColorAmI, currentBoard);
for (Move iterMoveForMe : possibleMoves) {
if(currentBoard.figureAt(iterMoveForMe.to)!=null){
currentBoard = currentBoard.removeFigure(iterMoveForMe.to);
}
currentBoard= currentBoard.moveFigure(iterMoveForMe.from, iterMoveForMe.to);
score = alphaBeta(currentBoard, depth-1, alpha, beta, false);
if(score>=alpha){
alpha=score;
if(depth==initialDepth){
moveToMake=iterMoveForMe;
}
}
if (alpha>=beta) {
break;
}
}
return alpha;
}
else {[Minimizer...]
}
I guess I found a way to do this. At least it seems to work. They key is to make a copy right after the for loop and use this copy later on instead of the currentBoard so the currentBoard for the loop gets never modified.
public int alphaBeta(Board currentBoard, int depth, int alpha, int beta, boolean maximisingPlayer) {
Display dis = new ConsoleDisplay();
int score;
if (depth == 0) {
int evaluatedScore = Evaluator.evaluateLeaf(whichColorAmI, currentBoard);
return evaluatedScore;
}
else if (maximisingPlayer) {
ArrayList<Move> possibleMoves= new ArrayList<Move>();
possibleMoves=getPossibleMoves(whichColorAmI, currentBoard);
for (Move iterMoveForMe : possibleMoves) {
Board copy = new Board(currentBoard.height, currentBoard.width,currentBoard.figures());
if(copy.figureAt(iterMoveForMe.to)!=null){
copy = currentBoard.removeFigure(iterMoveForMe.to);
}
copy= copy.moveFigure(iterMoveForMe.from, iterMoveForMe.to);
score = alphaBeta(copy, depth-1, alpha, beta, false);
if(score>=alpha){
alpha=score;
if(depth==maxDepth){
moveToMake=iterMoveForMe;
}
}
if (alpha>=beta) {
break;
}
}
return alpha;
}
else {
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 am making a robot maze where the robot reaches a target automatically without crashing into walls. I want the robot to do the maze once, learn the correct route and then the second time be able to get there straight away without going to any deadends. I thought I could do this by making three arraylists.
One for all the squares the robot visits.
Two for all the squares that lead to a deadend.
Three for all the directions the robot goes.
If the squares that lead to a dead end are found in the first arraylist then i can delete the same indexes in the third arraylist. That way, the second time, i can just iterate the third Arraylist.
My full code is below:
import java.util.ArrayList;
import java.util.*;
import java.util.Iterator;
import java.util.stream.IntStream;
public class Explorer {
private int pollRun = 0; // Incremented after each pass.
private RobotData robotData; // Data store for junctions.
private ArrayList<Integer> nonWallDirections;
private ArrayList<Integer> passageDirections;
private ArrayList<Integer> beenbeforeDirections;
private Random random = new Random();
int [] directions = {IRobot.AHEAD, IRobot.LEFT, IRobot.RIGHT, IRobot.BEHIND};
private ArrayList<Square> correctSquares;
private ArrayList<Square> wrongSquares;
private ArrayList<Integer> correctDirections;
public void controlRobot (IRobot robot) {
// On the first move of the first run of a new maze.
if ((robot.getRuns() == 0) && (pollRun ==0))
robotData = new RobotData();
pollRun++; /* Increment poll run so that the data is not reset
each time the robot moves. */
int exits = nonwallExits(robot);
int direction;
if ((robot.getRuns() != 0))
direction = grandfinale(robot);
nonWallDirections = new ArrayList<Integer>();
passageDirections = new ArrayList<Integer>();
beenbeforeDirections = new ArrayList<Integer>();
correctSquares = new ArrayList<Square>();
correctDirections = new ArrayList<Integer>();
// Adding each direction to the appropriate state ArrayList.
for(int item : directions) {
if(robot.look(item) != IRobot.WALL) {
nonWallDirections.add(item);
}
}
for(int item : directions) {
if(robot.look(item) == IRobot.PASSAGE) {
passageDirections.add(item);
}
}
for(int item : directions) {
if(robot.look(item) == IRobot.BEENBEFORE) {
beenbeforeDirections.add(item);
}
}
// Calling the appropriate method depending on the number of exits.
if (exits < 2) {
direction = deadEnd(robot);
} else if (exits == 2) {
direction = corridor(robot);
} else {
direction = junction(robot);
robotData.addJunction(robot);
robotData.printJunction(robot);
}
robot.face(direction);
addcorrectSquares(robot);
correctDirections.add(direction);
}
/* The specification advised to have to seperate controls: Explorer and Backtrack
and a variable explorerMode to switch between them.
Instead, whenever needed I shall call this backtrack method.
If at a junction, the robot will head back the junction as to when it first approached it.
When at a deadend or corridor, it will follow the beenbefore squares until it
reaches an unexplored path. */
public int backtrack (IRobot robot) {
if (nonwallExits(robot) > 2) {
addwrongSquares(robot);
return robotData.reverseHeading(robot);
} else {
do {
addwrongSquares(robot);
return nonWallDirections.get(0);
} while (nonwallExits(robot) == 1);
}
}
// Deadend method makes the robot follow the only nonwall exit.
public int deadEnd (IRobot robot) {
return backtrack(robot);
}
/* Corridor method will make the robot follow the one and only passage.
The exception is at the start. Sometimes, the robot will start with
two passages available to it in which case it will choose one randomly.
If there is no passage, it will follow the beenbefore squares
until it reaches an unexplored path.*/
public int corridor (IRobot robot) {
if (passageExits(robot) == 1) {
return passageDirections.get(0);
} else if (passageExits(robot) == 2) {
int randomPassage = random.nextInt(passageDirections.size());
return passageDirections.get(randomPassage);
} else {
return backtrack(robot);
}
}
/* Junction method states if there is more than one passage, it will randomly select one.
This applies to crossroads as well as essentially they are the same.
If there is no passage, it will follow the beenbefore squares until it reaches an unexplored
path. */
public int junction(IRobot robot) {
if (passageExits(robot) == 1) {
return passageDirections.get(0);
} else if (passageExits(robot) > 1) {
int randomPassage = random.nextInt(passageDirections.size());
return passageDirections.get(randomPassage);
} else {
return backtrack(robot);
}
}
// Calculates number of exits.
private int nonwallExits (IRobot robot) {
int nonwallExits = 0;
for(int item : directions) {
if(robot.look(item) != IRobot.WALL) {
nonwallExits++;
}
}
return nonwallExits;
}
// Calculates number of passages.
private int passageExits (IRobot robot) {
int passageExits = 0;
for(int item : directions) {
if(robot.look(item) == IRobot.PASSAGE) {
passageExits++;
}
}
return passageExits;
}
// Calculates number of beenbefores.
private int beenbeforeExits (IRobot robot) {
int beenbeforeExits = 0;
for(int item : directions) {
if(robot.look(item) == IRobot.PASSAGE) {
beenbeforeExits++;
}
}
return beenbeforeExits;
}
// Resets Junction Counter in RobotData class.
public int reset() {
return robotData.resetJunctionCounter();
}
public void addcorrectSquares(IRobot robot) {
Square newSquare = new Square(robot.getLocation().x, robot.getLocation().y);
correctSquares.add(newSquare);
}
public void addwrongSquares(IRobot robot) {
Square badSquare = new Square(robot.getLocation().x, robot.getLocation().y);
wrongSquares.add(badSquare);
}
public int grandfinale (IRobot robot) {
IntStream.range(0, correctSquares.size())
.map(index -> correctSquares.size() - index - 1)
.filter(index -> (((wrongSquares.x).contains(correctSquares.x)) && ((wrongSquares.y).contains(correctSquares.y))).get(index))
.forEach(index -> correctDirections.remove(index));
Iterator<Integer> routeIterator = correctDirections.iterator();
while (routeIterator.hasNext()) {
break;
}
return (routeIterator.next());
}
}
class RobotData {
/* It was advised in the specification to include the variable:
private static int maxJunctions = 10000;
However, as I am not using arrays, but ArrayLists, I do not
need this. */
private static int junctionCounter = 0;
private ArrayList<Junction> junctionList = new ArrayList<Junction>();
// Resets the Junction counter.
public int resetJunctionCounter() {
return junctionCounter = 0;
}
// Adds the current junction to the list of arrays.
public void addJunction(IRobot robot) {
Junction newJunction = new Junction(robot.getLocation().x, robot.getLocation().y, robot.getHeading());
junctionList.add(newJunction);
junctionCounter++;
}
// Gets the junction counter for Junction info method in Junction class.
public int getJunctionCounter (IRobot robot) {
return junctionCounter;
}
// Prints Junction info.
public void printJunction(IRobot robot) {
String course = "";
switch (robot.getHeading()) {
case IRobot.NORTH:
course = "NORTH";
break;
case IRobot.EAST:
course = "EAST";
break;
case IRobot.SOUTH:
course = "SOUTH";
break;
case IRobot.WEST:
course = "WEST";
break;
}
System.out.println("Junction " + junctionCounter + " (x=" + robot.getLocation().x + ", y=" + robot.getLocation().y +") heading " + course);
}
/* Iterates through the junction arrayList to find the
heading of the robot when it first approached the junction.
It does this by finding the first junction in the ArrayList
that has the same x and y coordinates as the robot.*/
public int searchJunction(IRobot robot) {
Junction currentJunction = null;
Iterator<Junction> junctionIterator = junctionList.iterator();
while (junctionIterator.hasNext()) {
currentJunction = junctionIterator.next();
if ((((currentJunction.x)==(robot.getLocation().x))) && ((currentJunction.y)==(robot.getLocation().y)))
break;
}
return currentJunction.arrived;
}
// Returns the reverse of the heading the robot had when first approaching the junction.
public int reverseHeading(IRobot robot) {
int firstHeading = searchJunction(robot);
int reverseHeading = 1; // Random integer to Iniitalise variable.
switch (firstHeading) {
case IRobot.NORTH:
if (robot.getHeading() == IRobot.NORTH)
reverseHeading = IRobot.BEHIND;
else if (robot.getHeading() == IRobot.EAST)
reverseHeading = IRobot.RIGHT;
else if (robot.getHeading() == IRobot.SOUTH)
reverseHeading = IRobot.AHEAD;
else
reverseHeading = IRobot.LEFT;
break;
case IRobot.EAST:
if (robot.getHeading() == IRobot.NORTH)
reverseHeading = IRobot.LEFT;
else if (robot.getHeading() == IRobot.EAST)
reverseHeading = IRobot.BEHIND;
else if (robot.getHeading() == IRobot.SOUTH)
reverseHeading = IRobot.RIGHT;
else
reverseHeading = IRobot.AHEAD;
break;
case IRobot.SOUTH:
if (robot.getHeading() == IRobot.NORTH)
reverseHeading = IRobot.AHEAD;
else if (robot.getHeading() == IRobot.EAST)
reverseHeading = IRobot.LEFT;
else if (robot.getHeading() == IRobot.SOUTH)
reverseHeading = IRobot.BEHIND;
else
reverseHeading = IRobot.RIGHT;
break;
case IRobot.WEST:
if (robot.getHeading() == IRobot.NORTH)
reverseHeading = IRobot.RIGHT;
else if (robot.getHeading() == IRobot.EAST)
reverseHeading = IRobot.AHEAD;
else if (robot.getHeading() == IRobot.SOUTH)
reverseHeading = IRobot.LEFT;
else
reverseHeading = IRobot.BEHIND;
break;
}
return reverseHeading;
}
}
class Junction {
int x;
int y;
int arrived;
public Junction(int xcoord, int ycoord, int course) {
x = xcoord;
y = ycoord;
arrived = course;
}
}
class Square {
int x;
int y;
public Square(int cordx, int cordy){
x = cordx;
y = cordy;
}
}
IntStream.range(0, al1.length)
.filter(index -> al2.contains(al1.get(index)))
.forEach(index -> al3.remove(index));
Slightly more complex than this if removing elements from al3 shifts them left but in that case just reverse the stream before the .filter- then it will delete from the end. The easiest way to do that is:
.map(index -> al1.length - index - 1)
Without Streams the equivalent would be
for (int i = 0; i < al1.length; i++) {
if (al2.contains(al1.get(i))) {
al3.remove(i);
}
}
Similarly, if you need to delete from the right then the for loop would need to count down rather than up.
Without further details on arraylist structure it's hard to give any more hints.