我最近看了这个video,它使用BFS搜索算法来解决特修斯和米诺陶谜题.如果你不熟悉它,这是一个谜题,特修斯和牛头人被放在一个瓷砖迷宫中,特修斯必须在不被牛头人抓住的情况下逃脱.特修斯每移动一块瓷砖,牛头人就会移动两次.然而,牛头人的行为是这样的,它只能直接向特修斯移动,在try 垂直移动之前,它总是会try 水平移动.因此,可以利用这一点来逃离迷宫.如果你有兴趣try 玩这个游戏,你可以在here中找到一个很好的游戏实现.

我现在正try 用Kotlin实现我自己的求解器,但使用的是A*算法而不是BFS.然而,我在这样做时遇到了问题,我不确定为什么我的代码不能工作.在传统的A*算法中,有一个 node 对象来存储代理的位置,而我有一个状态对象来存储提修斯和牛头人的位置.然后,我使用一系列助手函数来确定提修斯和牛头人下一个可能的位置,这样提修斯就不会撞到墙壁、走出迷宫或进入牛头人(函数getNextState).从那里,我使用了A*算法的一个非常传统的实现,它只考虑提修斯的位置(函数astar),并使用一个助手函数根据提修斯从球门到球门的位置来计算启发式.以下是我的代码:

data class Position(val x: Int, val y: Int)
data class State(val theseusPos: Position, val minotaurPos: Position)

// Manhattan distance heuristic
fun heuristic(point: Position, goal: Position): Int {
    return Math.abs(point.x - goal.x) + Math.abs(point.y - goal.y)
}

// Checks if a given position is valid: if it is within the maze, and it is an empty space
fun isPositionValid(pos: Position, maze: Array<IntArray>): Boolean {
    return pos.y in 0 until maze.size && pos.x in 0 until maze[0].size && maze[pos.y][pos.x] == 0;
}

// Returns the next possible neighbors given the current node and a maze
fun getNextTheseusPositions(current: Position, maze: Array<IntArray>): List<Position> {
    val moves = listOf(-1 to 0, 1 to 0, 0 to -1, 0 to 1)
    val states = mutableListOf<Position>();

    for ((dx, dy) in moves) {
        val x = current.x + dx
        val y = current.y + dy
        val neighbor = Position(x, y)

        if (isPositionValid(neighbor, maze)) { 
            states.add(neighbor)
        }
    }

    return states
}

// Return change in x or y based on given horizontal/vertical coordinates
fun moveMinotaur(minotaurCoord: Int, theseusCoord: Int): Int {
    return when {
        minotaurCoord < theseusCoord -> 1
        minotaurCoord > theseusCoord -> -1
        else -> 0
    }
}

fun getNextMinotaurPosition(minotaurPos: Position, theseusPos: Position, maze: Array<IntArray>): Position {
    var currMinotaurX = minotaurPos.x;
    var currMinotaurY = minotaurPos.y;

    for(i in 0 until 2) {
        val dx = moveMinotaur(currMinotaurX, theseusPos.x)
        if(dx != 0 && isPositionValid(Position(currMinotaurX + dx, currMinotaurY), maze)) {
            currMinotaurX += dx
            continue
        }

        val dy = moveMinotaur(currMinotaurY, theseusPos.y)
        if(dy != 0 && isPositionValid(Position(currMinotaurX, currMinotaurY + dy), maze)) {
            currMinotaurY += dy
        }
    }

    return Position(currMinotaurX, currMinotaurY)
}

// Returns next possible states
fun getNextStates(state: State, maze: Array<IntArray>): List<State> {
    val states = mutableListOf<State>();

    // Adding possible states based on whether or not the minotaur gets to Theseus
    for(theseusPos in getNextTheseusPositions(state.theseusPos, maze)) {
        val minotaurPos = getNextMinotaurPosition(state.minotaurPos, theseusPos, maze)

        if(minotaurPos == theseusPos) {
            continue
        }

        states.add(State(theseusPos, minotaurPos))
    }

    return states
}

fun aStar(maze: Array<IntArray>, start: State, goal: Position): List<State>? {
    // Stores nodes based on priority (cost), based on how close they are to the goal
    // and how many steps they are from the starting point
    val openSet = PriorityQueue(compareBy<Pair<Int, State>> { it.first })
    openSet.add(0 to start)
    // Map that tracks previous state for each state so that the route can be reconstructed
    val cameFrom = mutableMapOf<State, State>()
    // gScore cost for each theseus position
    val gScore = mutableMapOf(start.theseusPos to 0)
    
    // Main loop, runs until priority queue is empty (all nodes have been processed)
    while (openSet.isNotEmpty()) {
        // Gets pair at front of queue, where current is the current Position
        val (_, current) = openSet.poll()

        // If we find the goal, reconstruct the path and return
        if (current.theseusPos == goal) {
            val path = mutableListOf<State>()
            var cur: State? = current
            // Reconstruct the path using the parent node from each node, reversed to get the original path
            while (cur != null) {
                path.add(cur)
                cur = cameFrom[cur]
            }
            path.reverse()
            return path
        }

        for (state in getNextStates(current, maze)) {
        // The gScore of moving from the current node to its neighbour is just the score
        // of the current node + 1, since moving from one tile to another has a uniform cost
        // of 1
            val tentativeG = gScore[current.theseusPos]!! + 1

            if (!gScore.containsKey(state.theseusPos) || tentativeG < gScore[state.theseusPos]!!) {
                gScore[state.theseusPos] = tentativeG
                val fScore = tentativeG + heuristic(state.theseusPos, goal)
                openSet.add(fScore to state)
                cameFrom[state] = current
            }
        }
    }

    // If all nodes have been explored and a path has not been found, return null
    return null
}

目前,我正在调用如下函数,该函数输出"NULL",表示没有找到路径:

fun main() {
    val maze = arrayOf(
        intArrayOf(0, 1, 0, 0, 0, 0, 0, 0),
        intArrayOf(0, 1, 0, 1, 0, 1, 1, 1),
        intArrayOf(0, 0, 0, 1, 0, 0, 0, 0),
        intArrayOf(1, 1, 0, 1, 0, 1, 1, 0),
        intArrayOf(0, 0, 0, 1, 0, 0, 0, 0),
        intArrayOf(1, 0, 1, 1, 0, 1, 0, 1),
        intArrayOf(0, 0, 1, 0, 0, 1, 0, 0)
    )

    val start = State(Position(1, 2), Position(5, 2))
    val goal = Position(7, 0)

    val path = aStar(maze, start, goal)
    println(path)
}

下面是迷宫的样子(特修斯的起跑位置用篮球标记,牛头怪用手标记):Image link

迷宫绝对是可能的,因为它是基于原始视频here中使用的迷宫

我已经广泛地测试了每个助手函数,它们都能正常工作(除了ASSTAR之外的每个函数).在添加助手函数之前,我还实现了A*算法,如果不考虑牛头怪,它就会起作用.我试着 for each 循环打印优先级队列前面的位置,看起来特修斯似乎被困在迷宫的角落里,无法回溯,尽管我可能解释错了.我最近才学会这个算法,所以我觉得我仍然不能完全理解它是如何工作的.

如果您有任何建议或需要澄清,请让我知道.

推荐答案

这是我看到的bug.

  1. 特修斯在你的解算器里等不下go 了.对他来说,原地踏步是有效的.也就是说,你需要移动0 to 0.
  2. 牛头怪可以在你解算器的目标处追上特修斯.但在比赛中,如果特修斯到达球门,他就赢了,即使牛头人可以追上.所以在getNextStates中,不要失go 牛头人在球门处追上特修斯的状态.
  3. 你错过了高墙.游戏中有牛头怪和特修斯无法穿过的墙.你需要一个代表,让你注意到你不仅需要合法的开始和结束位置,你还需要没有穿过墙壁.
  4. 你的gScore分必须是州的分数,而不是忒修斯在哪里的分数.你的游戏中的获胜路径是lddrdduuluuuuurrrrr.(l =左,r =右,u =上,d =下,w =等待)忒修斯需要被允许在诱捕牛头怪后原路返回.这不是一个重复的位置,因为它的重要性,牛头怪已被困.

Kotlin相关问答推荐

Kotlin异步不并行运行任务

使用另一个对象的列表创建对象

Rabin-Karp字符串匹配的实现(Rolling hash)

如何在 Kotlin 中不受约束?

你怎么知道什么时候需要 yield()?

如何在 Kotlin for Android 上使用setTextColor(hexaValue),

使用 Kotlin 协程时 Room dao 类出错

在 Kotlin 中,当枚举类实现接口时,如何解决继承的声明冲突?

如何禁用智能投射突出显示 Kotlin?

Kotlin-Java 互操作不能与可变参数一起使用

DatabaseManager_Impl 不是抽象的,不会覆盖 RoomDatabase 中的抽象方法 clearAllTables()

如何使用 Coil 从 URL 获取位图?

将 @Component.Builder 与构造函数参数一起使用

在用Kotlin编写的Android库公共API中处理R8+JvmStatic Annotation+Lambda

Kotlin:使用Gradle进行增量编译

如何在特定条件下清除jetpack数据存储数据

以Kotlin为单位的货币数据类型

Kotlin扩展函数与成员函数?

Kotlin 的数据类 == C# 的 struct ?

为什么在 Kotlin 中return可以返回一个return?