Skip to content

Instantly share code, notes, and snippets.

@aniltv06
Created October 27, 2025 19:44
Show Gist options
  • Save aniltv06/d32abaed9d9aa021a000e5c5bc846b32 to your computer and use it in GitHub Desktop.
Save aniltv06/d32abaed9d9aa021a000e5c5bc846b32 to your computer and use it in GitHub Desktop.
Tic Tac Toe - Swift with Actors & Closures
import Foundation
// MARK: - Player Enum
enum Player: String, Sendable {
case x = "X"
case o = "O"
var opponent: Player {
return self == .x ? .o : .x
}
}
// MARK: - Game State
struct GameState: Sendable {
let board: [[Player?]]
let currentPlayer: Player
let isGameOver: Bool
let winner: Player?
init(board: [[Player?]] = Array(repeating: Array(repeating: nil, count: 3), count: 3),
currentPlayer: Player = .x,
isGameOver: Bool = false,
winner: Player? = nil) {
self.board = board
self.currentPlayer = currentPlayer
self.isGameOver = isGameOver
self.winner = winner
}
}
// MARK: - Move Result
enum MoveResult: Sendable {
case success(GameState)
case invalidMove(String)
case gameAlreadyOver
}
// MARK: - Game Event
enum GameEvent: Sendable {
case moveMade(row: Int, col: Int, player: Player)
case gameWon(Player)
case gameDraw
case gameReset
case invalidMove(row: Int, col: Int, reason: String)
}
// MARK: - TicTacToe Actor
actor TicTacToeGame {
private var state: GameState
// Closure-based event handlers
private var eventHandlers: [(GameEvent) async -> Void] = []
private var stateChangeHandlers: [(GameState) async -> Void] = []
// Closure for custom win condition (extensibility)
private var winConditionChecker: ((GameState) -> Bool)?
init() {
self.state = GameState()
}
// MARK: - State Access
func getCurrentState() -> GameState {
return state
}
func getPlayer(at row: Int, col: Int) -> Player? {
guard isValidPosition(row: row, col: col) else { return nil }
return state.board[row][col]
}
// MARK: - Event Subscription (Closures)
/// Subscribe to game events with a closure
func onEvent(_ handler: @escaping (GameEvent) async -> Void) {
eventHandlers.append(handler)
}
/// Subscribe to state changes with a closure
func onStateChange(_ handler: @escaping (GameState) async -> Void) {
stateChangeHandlers.append(handler)
}
/// Set custom win condition checker
func setWinCondition(_ checker: @escaping (GameState) -> Bool) {
winConditionChecker = checker
}
// MARK: - Game Actions
func makeMove(row: Int, col: Int) async -> MoveResult {
guard !state.isGameOver else {
await notifyEvent(.invalidMove(row: row, col: col, reason: "Game is over"))
return .gameAlreadyOver
}
guard isValidPosition(row: row, col: col) else {
await notifyEvent(.invalidMove(row: row, col: col, reason: "Invalid position"))
return .invalidMove("Position out of bounds")
}
guard state.board[row][col] == nil else {
await notifyEvent(.invalidMove(row: row, col: col, reason: "Position occupied"))
return .invalidMove("Position already occupied")
}
// Make the move
var newBoard = state.board
newBoard[row][col] = state.currentPlayer
await notifyEvent(.moveMade(row: row, col: col, player: state.currentPlayer))
// Check game end conditions
let newState = GameState(board: newBoard, currentPlayer: state.currentPlayer)
if checkWinner(in: newState) {
state = GameState(
board: newBoard,
currentPlayer: state.currentPlayer,
isGameOver: true,
winner: state.currentPlayer
)
await notifyEvent(.gameWon(state.currentPlayer))
} else if isBoardFull(newBoard) {
state = GameState(
board: newBoard,
currentPlayer: state.currentPlayer,
isGameOver: true,
winner: nil
)
await notifyEvent(.gameDraw)
} else {
state = GameState(
board: newBoard,
currentPlayer: state.currentPlayer.opponent,
isGameOver: false,
winner: nil
)
}
await notifyStateChange(state)
return .success(state)
}
func reset() async {
state = GameState()
await notifyEvent(.gameReset)
await notifyStateChange(state)
}
// MARK: - Async Operations with Closures
/// Execute a series of moves with validation
func executeMoves(_ moves: [(Int, Int)],
onProgress: @escaping (Int, MoveResult) async -> Void) async {
for (index, move) in moves.enumerated() {
let result = await makeMove(row: move.0, col: move.1)
await onProgress(index, result)
if case .gameAlreadyOver = result {
break
}
}
}
/// Find available moves with filtering closure
func getAvailableMoves(filter: ((Int, Int) -> Bool)? = nil) -> [(Int, Int)] {
var moves: [(Int, Int)] = []
for row in 0..<3 {
for col in 0..<3 {
if state.board[row][col] == nil {
if let filter = filter {
if filter(row, col) {
moves.append((row, col))
}
} else {
moves.append((row, col))
}
}
}
}
return moves
}
/// Evaluate board position with custom evaluator
func evaluatePosition(evaluator: (GameState) -> Int) -> Int {
return evaluator(state)
}
// MARK: - AI Move with Minimax
func getBestMove(depth: Int = 9,
onEvaluation: ((Int, Int, Int) -> Void)? = nil) async -> (row: Int, col: Int)? {
guard !state.isGameOver else { return nil }
var bestScore = Int.min
var bestMove: (Int, Int)?
for row in 0..<3 {
for col in 0..<3 {
if state.board[row][col] == nil {
var testBoard = state.board
testBoard[row][col] = state.currentPlayer
let testState = GameState(
board: testBoard,
currentPlayer: state.currentPlayer.opponent
)
let score = minimax(
state: testState,
depth: depth - 1,
isMaximizing: false,
alpha: Int.min,
beta: Int.max
)
onEvaluation?(row, col, score)
if score > bestScore {
bestScore = score
bestMove = (row, col)
}
}
}
}
return bestMove
}
// MARK: - Private Helpers
private func isValidPosition(row: Int, col: Int) -> Bool {
return row >= 0 && row < 3 && col >= 0 && col < 3
}
private func checkWinner(in gameState: GameState) -> Bool {
// Use custom win condition if provided
if let customChecker = winConditionChecker {
return customChecker(gameState)
}
let board = gameState.board
// Check rows
for row in 0..<3 {
if let player = board[row][0],
board[row][1] == player,
board[row][2] == player {
return true
}
}
// Check columns
for col in 0..<3 {
if let player = board[0][col],
board[1][col] == player,
board[2][col] == player {
return true
}
}
// Check diagonals
if let player = board[1][1] {
if board[0][0] == player && board[2][2] == player {
return true
}
if board[0][2] == player && board[2][0] == player {
return true
}
}
return false
}
private func isBoardFull(_ board: [[Player?]]) -> Bool {
return board.allSatisfy { row in
row.allSatisfy { $0 != nil }
}
}
private func minimax(state: GameState,
depth: Int,
isMaximizing: Bool,
alpha: Int,
beta: Int) -> Int {
// Check terminal states
if checkWinner(in: state) {
return isMaximizing ? -10 + (9 - depth) : 10 - (9 - depth)
}
if isBoardFull(state.board) {
return 0
}
if depth == 0 {
return 0
}
var alpha = alpha
var beta = beta
if isMaximizing {
var maxScore = Int.min
for row in 0..<3 {
for col in 0..<3 {
if state.board[row][col] == nil {
var testBoard = state.board
testBoard[row][col] = self.state.currentPlayer
let testState = GameState(
board: testBoard,
currentPlayer: self.state.currentPlayer.opponent
)
let score = minimax(
state: testState,
depth: depth - 1,
isMaximizing: false,
alpha: alpha,
beta: beta
)
maxScore = max(score, maxScore)
alpha = max(alpha, score)
if beta <= alpha {
break
}
}
}
}
return maxScore
} else {
var minScore = Int.max
for row in 0..<3 {
for col in 0..<3 {
if state.board[row][col] == nil {
var testBoard = state.board
testBoard[row][col] = self.state.currentPlayer.opponent
let testState = GameState(
board: testBoard,
currentPlayer: self.state.currentPlayer
)
let score = minimax(
state: testState,
depth: depth + 1,
isMaximizing: true,
alpha: alpha,
beta: beta
)
minScore = min(score, minScore)
beta = min(beta, score)
if beta <= alpha {
break
}
}
}
}
return minScore
}
}
private func notifyEvent(_ event: GameEvent) async {
for handler in eventHandlers {
await handler(event)
}
}
private func notifyStateChange(_ state: GameState) async {
for handler in stateChangeHandlers {
await handler(state)
}
}
}
// MARK: - Game Coordinator Actor
actor GameCoordinator {
private let game: TicTacToeGame
private var gameHistory: [GameState] = []
init() {
self.game = TicTacToeGame()
}
func setupGame() async {
// Subscribe to events with closures
await game.onEvent { [weak self] event in
await self?.logEvent(event)
}
await game.onStateChange { [weak self] state in
await self?.recordState(state)
}
}
func playWithAI(playerMove: (Int, Int)) async {
// Player move
let result = await game.makeMove(row: playerMove.0, col: playerMove.1)
if case .success(let state) = result, !state.isGameOver {
// AI move with evaluation callback
if let aiMove = await game.getBestMove { row, col, score in
print("AI considering (\(row),\(col)) with score: \(score)")
} {
await game.makeMove(row: aiMove.0, col: aiMove.1)
}
}
}
func getHistory() -> [GameState] {
return gameHistory
}
private func logEvent(_ event: GameEvent) {
switch event {
case .moveMade(let row, let col, let player):
print("πŸ“ Player \(player.rawValue) moved to (\(row), \(col))")
case .gameWon(let player):
print("πŸŽ‰ Player \(player.rawValue) wins!")
case .gameDraw:
print("🀝 Game ended in a draw!")
case .gameReset:
print("πŸ”„ Game reset")
case .invalidMove(let row, let col, let reason):
print("❌ Invalid move at (\(row), \(col)): \(reason)")
}
}
private func recordState(_ state: GameState) {
gameHistory.append(state)
}
}
// MARK: - Usage Examples
// Example 1: Basic game with event handlers
func example1_BasicGameWithClosures() async {
print("\n=== Example 1: Basic Game with Event Handlers ===\n")
let game = TicTacToeGame()
// Subscribe to events
await game.onEvent { event in
switch event {
case .moveMade(let row, let col, let player):
print("Move: \(player.rawValue) at (\(row), \(col))")
case .gameWon(let player):
print("Winner: \(player.rawValue)! πŸŽ‰")
case .gameDraw:
print("Draw! 🀝")
default:
break
}
}
// Subscribe to state changes
await game.onStateChange { state in
await printBoard(state.board)
}
// Play game
_ = await game.makeMove(row: 0, col: 0)
_ = await game.makeMove(row: 1, col: 1)
_ = await game.makeMove(row: 0, col: 1)
_ = await game.makeMove(row: 2, col: 2)
_ = await game.makeMove(row: 0, col: 2)
}
// Example 2: Execute moves with progress tracking
func example2_BatchMovesWithProgress() async {
print("\n=== Example 2: Batch Moves with Progress ===\n")
let game = TicTacToeGame()
let moves = [(0, 0), (1, 1), (0, 1), (2, 2), (0, 2)]
await game.executeMoves(moves) { index, result in
switch result {
case .success(let state):
print("Move \(index + 1) successful")
if state.isGameOver {
if let winner = state.winner {
print("Game over! Winner: \(winner.rawValue)")
}
}
case .invalidMove(let reason):
print("Move \(index + 1) failed: \(reason)")
case .gameAlreadyOver:
print("Game already over, stopping execution")
}
}
}
// Example 3: AI with custom evaluation
func example3_AIWithEvaluation() async {
print("\n=== Example 3: AI with Custom Evaluation ===\n")
let game = TicTacToeGame()
// Custom position evaluator
let centerBonus: (GameState) -> Int = { state in
var score = 0
if state.board[1][1] != nil {
score += 5 // Bonus for center control
}
return score
}
// Player X moves
_ = await game.makeMove(row: 0, col: 0)
// AI finds best move
if let aiMove = await game.getBestMove { row, col, score in
print("Evaluating (\(row), \(col)): score = \(score)")
} {
print("AI chooses: (\(aiMove.row), \(aiMove.col))")
_ = await game.makeMove(row: aiMove.row, col: aiMove.col)
}
let state = await game.getCurrentState()
await printBoard(state.board)
}
// Example 4: Custom win condition
func example4_CustomWinCondition() async {
print("\n=== Example 4: Custom Win Condition (Corners Only) ===\n")
let game = TicTacToeGame()
// Custom win: all four corners
await game.setWinCondition { state in
let corners = [
state.board[0][0],
state.board[0][2],
state.board[2][0],
state.board[2][2]
]
guard let firstCorner = corners[0] else { return false }
return corners.allSatisfy { $0 == firstCorner }
}
await game.onEvent { event in
if case .gameWon(let player) = event {
print("Custom win! \(player.rawValue) captured all corners!")
}
}
// This would trigger custom win
_ = await game.makeMove(row: 0, col: 0) // X
_ = await game.makeMove(row: 1, col: 1) // O
_ = await game.makeMove(row: 0, col: 2) // X
_ = await game.makeMove(row: 1, col: 0) // O
_ = await game.makeMove(row: 2, col: 0) // X
_ = await game.makeMove(row: 1, col: 2) // O
_ = await game.makeMove(row: 2, col: 2) // X - Wins!
}
// Example 5: Game coordinator with AI
func example5_CoordinatorWithAI() async {
print("\n=== Example 5: Game Coordinator with AI ===\n")
let coordinator = GameCoordinator()
await coordinator.setupGame()
// Play several moves
await coordinator.playWithAI(playerMove: (0, 0))
await coordinator.playWithAI(playerMove: (0, 1))
let history = await coordinator.getHistory()
print("\nTotal states recorded: \(history.count)")
}
// Helper function
func printBoard(_ board: [[Player?]]) async {
print("\n")
for (index, row) in board.enumerated() {
let rowString = row.map { $0?.rawValue ?? " " }.joined(separator: " | ")
print(" \(rowString) ")
if index < 2 {
print("-----------")
}
}
print("\n")
}
// MARK: - Run Examples
Task {
await example1_BasicGameWithClosures()
await example2_BatchMovesWithProgress()
await example3_AIWithEvaluation()
await example4_CustomWinCondition()
await example5_CoordinatorWithAI()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment