I don't think it's so grim, consider this example chess algorithm that uses "utmost safe" functions throughout up until I/O is needed (drawing to the screen).
Chess
// Simple chess game using upmost safe functions (mostly)
// TODO:
// - pawn promotions
// - castling
// - en passan
// - draw rules
// - checks / mate checks
// - game termination constion
// - good cost estimation
// - various optimizations (alpha beta pruning, etc)
import Foundation
#if canImport(Cocoa)
import Cocoa
#endif
struct Tuple8<T> {
var elements: (T, T, T, T, T, T, T, T)
subscript(_ i: Int) -> T {
get {
precondition(i >= 0 && i < 8)
switch i {
case 0: return elements.0
case 1: return elements.1
case 2: return elements.2
case 3: return elements.3
case 4: return elements.4
case 5: return elements.5
case 6: return elements.6
case 7: return elements.7
default: fatalError()
}
}
set {
precondition(i >= 0 && i < 8)
switch i {
case 0: elements.0 = newValue
case 1: elements.1 = newValue
case 2: elements.2 = newValue
case 3: elements.3 = newValue
case 4: elements.4 = newValue
case 5: elements.5 = newValue
case 6: elements.6 = newValue
case 7: elements.7 = newValue
default: fatalError()
}
}
}
}
typealias Row = Tuple8<Piece>
typealias Rows = Tuple8<Row>
struct Point {
var x, y: Int
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
var valid: Bool {
x >= 0 && x < 8 && y >= 0 && y < 8
}
static func + (lhs: Self, rhs: Self) -> Self {
Point(lhs.x + rhs.x, lhs.y + rhs.y)
}
}
struct Move {
let from: Point
let to: Point
}
enum PieceNominal: Int {
case none, pawn, knight, bishop, rook, queen, king
var cost: Int {
switch self {
case .none: return 0
case .pawn: return 100
case .knight: return 300
case .bishop: return 300
case .rook: return 500
case .queen: return 900
case .king: return 10000000
}
}
}
enum PieceColor: Int {
case black = -1, `none` = 0, white = 1
var opposite: PieceColor {
switch self {
case .black: return .white
case .white: return .black
case .none: return .none
}
}
}
struct Piece {
var nominal: PieceNominal
var color: PieceColor
init(_ nominal: PieceNominal, _ color: PieceColor) {
self.nominal = nominal
self.color = color
}
static let empty = Piece(.none, .none)
static let no = Piece(.none, .none)
static let wp = Piece(.pawn, .white)
static let wn = Piece(.knight, .white)
static let wb = Piece(.bishop, .white)
static let wr = Piece(.rook, .white)
static let wq = Piece(.queen, .white)
static let wk = Piece(.king, .white)
static let bp = Piece(.pawn, .black)
static let bn = Piece(.knight, .black)
static let bb = Piece(.bishop, .black)
static let br = Piece(.rook, .black)
static let bq = Piece(.queen, .black)
static let bk = Piece(.king, .black)
}
var silly = 0
var moveCount = 0
struct Board {
static let initial = Board(rows: Rows(elements: (
Row(elements: (.wr, .wn, .wb, .wq, .wk, .wb, .wn, .wr)),
Row(elements: (.wp, .wp, .wp, .wp, .wp, .wp, .wp, .wp)),
Row(elements: (.no, .no, .no, .no, .no, .no, .no, .no)),
Row(elements: (.no, .no, .no, .no, .no, .no, .no, .no)),
Row(elements: (.no, .no, .no, .no, .no, .no, .no, .no)),
Row(elements: (.no, .no, .no, .no, .no, .no, .no, .no)),
Row(elements: (.bp, .bp, .bp, .bp, .bp, .bp, .bp, .bp)),
Row(elements: (.br, .bn, .bb, .bq, .bk, .bb, .bn, .br))
)))
private var rows: Rows
private subscript(_ p: Point) -> Piece {
get { rows[p.y][p.x] }
set { rows[p.y][p.x] = newValue }
}
private func iterateLongMoves(_ color: PieceColor, _ p0: Point, _ dp: Point, oneStep: Bool = false, _ execute: (Point) -> Void) {
var p = p0
repeat {
p.x += dp.x
p.y += dp.y
if !p.valid { break }
let c = self[p].color
if c == color { break }
execute(p)
if c != .none { break }
} while !oneStep
}
private func iterateKingOrQueenMoves(color: PieceColor, p0: Point, isKing: Bool, _ execute: (Point) -> Void) {
for dy in -1 ... +1 {
for dx in -1 ... +1 {
if !(dx == 0 && dy == 0) {
iterateLongMoves(color, p0, Point(dx, dy), oneStep: isKing, execute)
}
}
}
}
private func iterateQueenMoves(_ color: PieceColor, _ p0: Point, _ execute: (Point) -> Void) {
iterateKingOrQueenMoves(color: color, p0: p0, isKing: false, execute)
}
private func iterateKingMoves(_ color: PieceColor, _ p0: Point, _ execute: (Point) -> Void) {
// TODO: castle
iterateKingOrQueenMoves(color: color, p0: p0, isKing: true, execute)
}
private func iterateBishopMoves(_ color: PieceColor, _ p0: Point, _ execute: (Point) -> Void) {
iterateLongMoves(color, p0, Point(-1, -1), execute)
iterateLongMoves(color, p0, Point(-1, +1), execute)
iterateLongMoves(color, p0, Point(+1, +1), execute)
iterateLongMoves(color, p0, Point(+1, -1), execute)
}
private func iterateKnightMoves(_ color: PieceColor, _ p0: Point, _ execute: (Point) -> Void) {
iterateLongMoves(color, p0, Point(-2, -1), oneStep: true, execute)
iterateLongMoves(color, p0, Point(-2, +1), oneStep: true, execute)
iterateLongMoves(color, p0, Point(-1, +2), oneStep: true, execute)
iterateLongMoves(color, p0, Point(+1, +2), oneStep: true, execute)
iterateLongMoves(color, p0, Point(+2, +1), oneStep: true, execute)
iterateLongMoves(color, p0, Point(+2, -1), oneStep: true, execute)
iterateLongMoves(color, p0, Point(+1, -2), oneStep: true, execute)
iterateLongMoves(color, p0, Point(-1, -2), oneStep: true, execute)
}
private func iterateRookMoves(_ color: PieceColor, _ p0: Point, _ execute: (Point) -> Void) {
iterateLongMoves(color, p0, Point(-1, -0), execute)
iterateLongMoves(color, p0, Point(+0, +1), execute)
iterateLongMoves(color, p0, Point(+1, +0), execute)
iterateLongMoves(color, p0, Point(-0, -1), execute)
}
private func iteratePawnMoves(_ color: PieceColor, _ p0: Point, _ execute: (Point) -> Void) {
// TODO: en passant, promotions
let isWhite = color == .white
let dy = isWhite ? +1 : -1
let dp = Point(0, dy)
let p1 = p0 + dp
if p1.valid && self[p1].color == .none {
execute(p1)
if p0.y == (isWhite ? 1 : 6) {
let p2 = p1 + dp
if p2.valid && self[p2].color == .none {
execute(p2)
}
}
}
let p3 = p0 + Point(-1, dy)
if p3.valid && self[p3].color == color.opposite {
execute(p3)
}
let p4 = p0 + Point(+1, dy)
if p4.valid && self[p4].color == color.opposite {
execute(p4)
}
}
private func iterateMoves(_ piece: Piece, _ p0: Point, _ execute: (Point) -> Void) {
let color = piece.color
switch piece.nominal {
case .none: break
case .pawn: iteratePawnMoves(color, p0, execute)
case .knight: iterateKnightMoves(color, p0, execute)
case .bishop: iterateBishopMoves(color, p0, execute)
case .rook: iterateRookMoves(color, p0, execute)
case .queen: iterateQueenMoves(color, p0, execute)
case .king: iterateKingMoves(color, p0, execute)
}
}
mutating func traverse(color: PieceColor, depth: Int = 1, maxDepth: Int) -> (Move, Int) {
var bestMove = Move(from: Point.init(0, 0), to: Point(0, 0))
var bestCost = -100000
var (y, dy, sentinel) = (color == .white) ? (7, -1, -1) : (0, +1, 8)
while y != sentinel {
for x in 0 ..< 8 {
let p0 = Point(x, y)
let piece0 = self[p0]
if piece0.color == color {
iterateMoves(piece0, p0) { p1 in
precondition(self[p1].color == .none || self[p1].color == color.opposite)
let piece1 = self[p1]
self[p0] = .empty
self[p1] = piece0
var cost = piece1.nominal.cost
if depth < maxDepth {
cost -= traverse(color: color.opposite, depth: depth + 1, maxDepth: maxDepth).1
} else {
// TODO: positional estimate
if moveCount < 10 && piece0.nominal == .pawn {
cost += 3
}
if x > 1 && x < 6 {
cost += 3
}
if moveCount < 20 && piece0.nominal == .rook {
cost -= 5
}
if moveCount < 10 && piece0.nominal == .queen {
cost -= 4
}
if piece0.nominal == .king {
cost -= 7
}
cost += (silly + x + y + depth + moveCount) & 0x03 // some silly randomization
silly += 1
}
if bestCost < cost {
bestMove = Move(from: p0, to: p1)
bestCost = cost
}
self[p1] = piece1
self[p0] = piece0
}
}
}
y += dy
}
return (bestMove, bestCost)
}
mutating func makeMove(_ move: Move) {
// TODO: castle, en passant
self[move.to] = self[move.from]
self[move.from] = .empty
}
}
// ---------------------------------------
// Unsafe area
extension Piece {
/*unsafe*/ var face: String {
switch (nominal, color) {
case (.pawn, .white): return "♙"
case (.pawn, .black): return "♟"
case (.knight, .white): return "♘"
case (.knight, .black): return "♞"
case (.bishop, .white): return "♗"
case (.bishop, .black): return "♝"
case (.rook, .white): return "♖"
case (.rook, .black): return "♜"
case (.queen, .white): return "♕"
case (.queen, .black): return "♛"
case (.king, .white): return "♔"
case (.king, .black): return "♚"
default: return "·"
}
}
}
extension Board {
/*unsafe*/ func draw() {
for y in 0 ..< 8 {
for x in 0 ..< 8 {
let piece = self[Point(x, (8 - y - 1))]
print(piece.face, terminator: " ")
}
print("") // line terminator
}
print()
}
}
/*unsafe*/ func beep() {
#if os(macOS)
NSSound.beep()
#endif
}
/*unsafe*/ func play() {
silly = Int(clock() & 0x7FFFFFFF) // some silly randomization
var board = Board.initial
board.draw()
var color = PieceColor.white
// TODO: game termination
while true {
print("Next move if \(color == .white ? "white" : "black")\n")
#if DEBUG
let maxDepth = 4
#else
let maxDepth = 5
#endif
let (move, cost) = board.traverse(color: color, maxDepth: maxDepth)
beep()
board.makeMove(move)
board.draw()
color = color.opposite
if color == .white {
moveCount += 1
}
}
}
play()
It's doing an unoptimised brute force and quite a few bits and pieces are missing game wise (promotions, mate checks, etc). Possibly there are a few bugs. Release mode is recommended and max depths up to 5 or 6.