I am trying to build a tree implementation in Swift to represent a chess game.
游戏由一系列棋步组成,但给定棋盘位置的替代棋步有效.我想穿越我的图形用户界面中的树,这就是为什么我添加了前往树中特定 node 的方法.
However I am struggling with getting the memory model right.对于我的任务,我想保留对给定 node 的下一个 node 及其父 node 的引用.据我了解,为了不引入保留周期,这些应该是weak.然而,这样做时,我的实现就崩溃了(因为我猜我没有持有对给定 node 的引用?).
有人能告诉我如何更改现有的实现以使其正确工作吗?当我删除weak关键字时,我的测试成功了,但我认为这是不对的,因为再次因为可能的保留周期.
我删除了一些实现,以使其更具可读性:
import Foundation
/// GameNode represents a node of a chess game tree
public final class GameNode {
// MARK: - Properties
/// The position of the node
public let position: Position
/// Uniquely identifies a node
public let nodeId: UUID
/// Is the node at the top of the tree
public let isTopNode: Bool
/// The chess move that gets from the parent node to this one
public let move: Move?
/// An optional move annotation like !!, !?, ??
public let annotation: String?
/// A comment for the move
public let comment: String?
/// The parent node
public internal(set) weak var parent: GameNode?
/// Pointer to the main variation
public internal(set) weak var next: GameNode?
/// Other possible variations from this node
public internal(set) var variations: [GameNode]
// MARK: - Init
/// Creates a root node
public init(position: Position = .initial, comment: String? = nil) {
self.position = position
self.nodeId = UUID()
self.isTopNode = true
self.move = nil
self.annotation = nil
self.parent = nil
self.comment = comment
self.next = nil
self.variations = []
}
/// Creates a node which is the result of making a move in another node
public init(position: Position, move: Move, parent: GameNode, annotation: String? = nil, comment: String? = nil) {
self.position = position
self.nodeId = UUID()
self.isTopNode = false
self.move = move
self.annotation = annotation
self.parent = parent
self.comment = comment
self.next = nil
self.variations = []
}
/// Reconstructs the move sequence from the start of the game to this point
public func reconstructMovesFromBeginning() -> [Move] {
if parent?.isTopNode == true {
return [move].compactMap({ $0 })
}
var moves = parent?.reconstructMovesFromBeginning() ?? []
if let move {
moves.append(move)
}
return moves
}
}
public final class Game {
public private(set) var current: GameNode
public init(root: GameNode = GameNode()) {
self.current = root
}
var root: GameNode? {
var tmp: GameNode? = current
while let currentTmp = tmp, !currentTmp.isTopNode {
tmp = currentTmp.parent
}
return tmp
}
public var isAtEnd: Bool {
current.next == nil
}
public func goBackward() {
guard let parent = current.parent else { return }
self.current = parent
}
public func go(to node: GameNode) {
self.current = node
}
public func play(move: Move, comment: String? = nil, annotation: String? = nil) throws {
let newPosition = try current.position.play(move: move)
let newNode = GameNode(position: newPosition, move: move, parent: current, annotation: annotation, comment: comment)
if !isAtEnd {
current.next?.variations.append(newNode)
} else {
current.next = newNode
}
go(to: newNode)
}
public var uciPath: [String] {
current.reconstructMovesFromBeginning().map(\.uci)
}
}
测试:
func testGameToUCIPath() throws {
let game = try Game(fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
try game.play(move: .init(from: Squares.e2, to: Squares.e4))
try game.play(move: .init(from: Squares.e7, to: Squares.e5))
try game.play(move: .init(from: Squares.g1, to: Squares.f3))
try game.play(move: .init(from: Squares.b8, to: Squares.c6))
try game.play(move: .init(from: Squares.f1, to: Squares.b5))
XCTAssertEqual(game.uciPath, ["e2e4", "e7e5", "g1f3", "b8c6", "f1b5"])
game.goBackward()
XCTAssertEqual(game.uciPath, ["e2e4", "e7e5", "g1f3", "b8c6"])
try game.play(move: .init(from: Squares.f1, to: Squares.c4))
XCTAssertEqual(game.uciPath, ["e2e4", "e7e5", "g1f3", "b8c6", "f1c4"])
}