我正在为自己设计的一个四人棋盘游戏创建AI。
关于棋盘游戏的详细信息:
四名玩家轮流移动他们的彩色棋子,同时向四个基本方向之一移动。棋子可以移出棋盘。游戏开始时,每个玩家有5条命。每移出一枚棋子,玩家就会失去1条命。新棋子会在游戏过程中按一定规则生成。
我查看了如何实现极小化极大算法,并找到了这个。我阅读了相关内容,认为自己已经理解了一切,于是尝试将第1.5节中的Java代码翻译成Swift。
这是我的思考过程:
- 由于我的游戏有4个玩家,我会将其他所有玩家视为最小化玩家。
- 在Java代码中,有一行代码用于撤销移动。由于我的游戏状态在每一步棋中可能发生巨大变化,我会将所有游戏状态存储在一个数组中,当需要撤销时,我可以直接在数组上调用
dropLast
。 - 由于我的游戏中的移动由
Direction
枚举表示,我将返回一个(Int, Direction)
元组,而不是像Java代码中的整数数组。 game
是一个计算属性,只返回gameStates.last!
。- 每次我调用
game
上的moveUp/Down/Left/Right
方法时,game.currentPlayer
都会改变,所以我不需要编写额外的代码来决定下一个玩家是谁。 - 在最后一行,我需要返回
(bestScore, bestDirection)
,但我意识到有时bestDirection
没有被赋值。因此,我将bestDirection
设为可选类型。如果在返回语句时它未被赋值,我将返回一个任意方向。
这是我的尝试:
private func minimax(depth: Int, color: Color) -> (score: Int, direction: Direction) { var bestScore = color == myColor ? Int.min : Int.max var currentScore: Int var bestDirection: Direction? if game.players.filter({$0.lives > 0}).count < 2 || depth == 0 { // 这是一个对我的启发式评估函数的调用 bestScore = evaluateHeuristics() } else { // 如果玩家在棋盘上没有棋子,只向上移动,因为向任何方向移动都不会改变任何事情 for move in (game.board.indicesOf(color: color).count == 0 ? [Direction.up] : [Direction.up, .down, .left, .right]) { let gameCopy = game.createCopy() switch move { case .up: gameCopy.moveUp() case .down: gameCopy.moveDown() case .left: gameCopy.moveLeft() case .right: gameCopy.moveRight() } gameStates.append(gameCopy) // myColor 类似于原始Java代码中的 mySeed if color == myColor { currentScore = minimax(depth: depth - 1, color: game.currentPlayer.color).score if currentScore > bestScore { bestScore = currentScore bestDirection = move } } else { currentScore = minimax(depth: depth - 1, color: game.currentPlayer.color).score if currentScore < bestScore { bestScore = currentScore bestDirection = move } } _ = gameStates.dropLast() } } return (bestScore, bestDirection ?? .left)}
当我用depth
为4测试这个AI时,它似乎要么做一些愚蠢的移动,比如将棋子移出棋盘,要么只向一个方向移动棋子。
我还注意到,当递归调用返回时,gameStates
的长度大约为90。正常情况下它应该是1,对吗?因为AI尝试的所有移动在递归调用返回之前都应该被撤销,gameStates
只会包含初始状态。
我做错了什么?
回答:
dropLast()返回一个包含数组中除最后一个元素之外的所有元素的数组切片。它不会修改原始数组。请使用removeLast()
编辑
你真正想要的是一个堆栈数据结构。这里有一个。
public struct Stack<Element>{ fileprivate var elements: [Element] = [] public init() {} /// 将一个元素推到堆栈顶部 /// /// - parameter newElement: 要推送的元素 public mutating func push(_ newElement: Element) { elements.append(newElement) } /// 从堆栈顶部弹出一个元素 /// /// - returns: 顶部元素或如果堆栈为空则返回nil。 public mutating func pop() -> Element? { let ret = elements.last if ret != nil { elements.removeLast() } return ret } /// 堆栈的顶部元素。如果堆栈为空则为nil public var top: Element? { return elements.last } /// 堆栈中的项目数量 public var count: Int { return elements.count } /// 如果堆栈为空则为真 public var isEmpty: Bool { return elements.isEmpty }}