此自定义“路径查找”算法有什么问题?

时间:2018-08-05 11:14:51

标签: java android recursion 2d path-finding

(我希望这不是重复的,因为我遇到的许多问题都不符合我的需要)

我正在开发一个2D网格游戏,其中有2个具有网格的玩家。有两个参与者:蓝色和红色,每个参与者在牢房中放置一块石头。因此,我想找到一条路径,该路径将所有具有相同颜色的单元格返回到起点,但只有在至少有一个包含对手石的单元格时才可以。

Gameboard with some stones

从上面的屏幕截图中:右上角的红色石头没有形成有效的路径。而且居中的人也没有形成一条路,即使那应该是一条路。

我能够找到一条路径,但由于某种原因该路径已损坏,无法正常工作。

编辑: Pather类

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;
    }
}

路径类

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();
    }
}

这里有一些案例测试:

在MainActivity的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);

}

在以下位置放置石头可以清除路径:

 //Adding the following deletes the path
    gameBoard[43][50] = redPlayer; //Adding this one in last position clears the path
    gameBoard[45][51] = redPlayer;

我需要弄清楚如何制定条件,首先检查附近的对手,然后验证路径。

编辑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+")";
}
}

有效路径的外观截图

Exemple

2 个答案:

答案 0 :(得分:1)

解决这种问题的一种或多或少的标准方法是“扫描线”算法。为简单起见,假设我们正在寻找包裹红色点的蓝色路径。 (您可以同时或在第二遍处理中处理包裹蓝点的红色路径,但可以稍后进行处理。)

您可以搜索“扫描线算法”以查看它们在相关应用程序中的工作方式。 Wikipedia page不错。

对于此问题,扫描线是一组y间隔。使用最左边的(最少x个)蓝点进行初始化。对于每个垂直相邻的蓝点,它会获得一个间隔。每个间隔代表一个潜在的蓝色多边形的垂直切片。

算法的其余部分是设计将扫描线向右移动一个位置(x递增)时更新扫描线所需的规则。这将是更新每个间隔的问题。当步骤找到一组断开连接的垂直相邻点时,将添加一个新间隔。在某些情况下,间隔将“消失”,因为潜在的多边形边界死角(认为是C形)。在其他情况下,它们将“合并”,因为在相应的x坐标处,存在一组1个或多个垂直相邻的连接点。在其他情况下,多边形将成功完成最终的一组1个或更多垂直相邻点。

细节会很简单,但通过案例分析不难得出结论。

要跟踪成功的多边形,间隔可以包含两条先前的点链:多边形上下边界。

最后一个要考虑的是,成功闭合的多边形是否至少包含一个红点。但这很容易。如果对于任何x坐标,表示多边形的间隔在一个红点旁括起来,则答案为是。可以将其记录为在时间间隔中保留的初始错误布尔值,每次看到这样的红点时都将其设置为true。成功关闭多边形后,检查标志以查看是否应使用它。

通过使用适当的数据结构,例如间隔树,对于大型网格,上述所有内容都可以变得高效。但是,如果网格相对较小,则使用简单列表应该会很好。无论如何,请考虑首先使用扫描线列表作为原型,然后根据需要使用更复杂的数据结构进行优化。

答案 1 :(得分:1)

正如我在评论中所写,没有mvce很难提供详细的帮助。
根据我在代码中看到的信息,您正在尝试映射板上的所有循环单色路径。
我对代码进行了一些记录在案的更改,希望(无法正确检查)它可以帮助您改进代码。
请注意,由于未发布Stone类,因此我将董事会的代表制更改为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());
    }
}