129 lines
3.3 KiB
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
|
|
}
|
|
}
|