Created
October 27, 2025 19:44
-
-
Save aniltv06/d32abaed9d9aa021a000e5c5bc846b32 to your computer and use it in GitHub Desktop.
Tic Tac Toe - Swift with Actors & Closures
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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