cg85-v2/Sources/Np85Snake/Board/Board.swift

129 lines
3.3 KiB
Swift

public struct Board {
public var occupiedSpaces: Set<Vertex> // todo: turn me into unoccupied
public var apples: Set<Vertex>
public var otherSnakes: [Snake]
public var mySnake: Snake?
public let width: Int
public let height: Int
public let size: Vertex
private var graph: Graph
init(_ width: Int, _ height: Int) {
self.width = width
self.height = height
size = Vertex(width, height)
occupiedSpaces = []
otherSnakes = []
apples = []
occupiedSpaces.reserveCapacity(width * height)
apples.reserveCapacity(width * height)
graph = Graph(numberOfNodes: width * height)
}
public static func start(io: inout BoardIO) throws -> Board {
io.commit(text: "utf8")
guard let inputLine = io.next(), let width = Int.init(inputLine, radix: 10), width > 0
else {
throw SnakeError.invalidWidth
}
guard let inputLine = io.next(), let height = Int.init(inputLine, radix: 10), height > 0
else {
throw SnakeError.invalidHeight
}
return Board(width, height)
}
mutating public func parse(io: inout BoardIO) throws -> Bool {
apples.removeAll()
occupiedSpaces.removeAll()
var lines: [any StringProtocol] = []
while lines.count < height {
guard let inputLine = io.next(),
inputLine.count == width || inputLine.isEmpty
else {
return false
}
if inputLine.isEmpty {
continue
}
lines.append(inputLine)
}
var input: [[BoardNode]] = []
input.reserveCapacity(height)
for (y, inputLine) in lines.enumerated() {
var line: [BoardNode] = []
line.reserveCapacity(width)
for (x, ch) in Array(inputLine.unicodeScalars).enumerated() {
guard let part = BoardPart(rawValue: String(ch)) else {
throw SnakeError.malformedBoardState
}
let node = BoardNode(part: part, vertex: Vertex(x, y))
if node.part == .apple {
apples.insert(node.vertex)
} else if !node.part.isSafe {
occupiedSpaces.insert(node.vertex)
}
if node.part == .illegalHead {
// fixme: there's an edge case where if a snake jumps over a snake body that it could still collide
for dir in Vertex.cardinals {
occupiedSpaces.insert(node.vertex + dir)
occupiedSpaces.insert(node.vertex + dir + dir)
}
}
line.append(node)
}
input.append(line)
}
_ = io.next()
otherSnakes.removeAll()
mySnake = nil
for vert in input.joined().filter({ $0.part.isHead }) {
let snake = try Snake(size: size, nodes: input, origin: vert)
if snake.isMine {
mySnake = snake
occupiedSpaces.remove(vert.vertex)
for node in snake.body {
if node.part.isJumpable { // .normalized() <-- has a higher death rate
occupiedSpaces.remove(node.vertex)
}
}
} else {
otherSnakes.append(snake)
}
}
return true
}
mutating public func calculate() {
graph.populate(self)
}
mutating public func commit(io: inout BoardIO) -> Bool {
guard let s = graph.dijkstra(self),
let last = s.last
else {
io.commit(command: SnakeCommand.fromDirection(Vertex.zero))
return false
}
io.commit(command: SnakeCommand.fromDirection(last.1))
return true
}
}