Don_Mag

tic tac toe

Feb 27th, 2022 (edited)
1,399
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 9.02 KB | None | 0 0
  1. // assumption for Move struct
  2. struct Move {
  3.     var player: Player?
  4.     var position: Int?
  5.     var boardIndex: Int = 0
  6.     var indicator: String {
  7.         if let p = player {
  8.             return p == .human ? "x" : "o"
  9.         }
  10.         return ""
  11.     }
  12. }
  13.  
  14. // assumption for AlertItem struct
  15. struct AlertItem {
  16.     var title: String = "Result"
  17.     var message: String = "message"
  18.     var buttonTitle: String = "OK"
  19. }
  20.  
  21. enum Player {
  22.     case human
  23.     case computer
  24. }
  25.  
  26. class CodeTicTacToeViewController: UIViewController {
  27.    
  28.     var gameBoardView: UIView = {
  29.         let v = UIView()
  30.         v.backgroundColor = .systemBlue
  31.         return v
  32.     }()
  33.     var buttons: [UIButton] = []
  34.     var viewModel = TicTacToeViewModel()
  35.    
  36.     override func viewDidLoad() {
  37.         super.viewDidLoad()
  38.         configureUI()
  39.        
  40.         let outerStack: UIStackView = {
  41.             let v = UIStackView()
  42.             v.axis = .vertical
  43.             v.spacing = 8
  44.             v.distribution = .fillEqually
  45.             return v
  46.         }()
  47.        
  48.         for _ in 1...3 {
  49.             let rowStack: UIStackView = {
  50.                 let v = UIStackView()
  51.                 v.axis = .horizontal
  52.                 v.spacing = 8
  53.                 v.distribution = .fillEqually
  54.                 return v
  55.             }()
  56.             for _ in 1...3 {
  57.                 let b: UIButton = {
  58.                     let v = UIButton()
  59.                     v.backgroundColor = .systemYellow
  60.                     return v
  61.                 }()
  62.                 rowStack.addArrangedSubview(b)
  63.                 buttons.append(b)
  64.             }
  65.             outerStack.addArrangedSubview(rowStack)
  66.         }
  67.        
  68.         outerStack.translatesAutoresizingMaskIntoConstraints = false
  69.         gameBoardView.translatesAutoresizingMaskIntoConstraints = false
  70.  
  71.         gameBoardView.addSubview(outerStack)
  72.         view.addSubview(gameBoardView)
  73.        
  74.         let g = view.safeAreaLayoutGuide
  75.         NSLayoutConstraint.activate([
  76.            
  77.             outerStack.topAnchor.constraint(equalTo: gameBoardView.topAnchor, constant: 8.0),
  78.             outerStack.leadingAnchor.constraint(equalTo: gameBoardView.leadingAnchor, constant: 8.0),
  79.             outerStack.trailingAnchor.constraint(equalTo: gameBoardView.trailingAnchor, constant: -8.0),
  80.             outerStack.bottomAnchor.constraint(equalTo: gameBoardView.bottomAnchor, constant: -8.0),
  81.  
  82.             gameBoardView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
  83.             gameBoardView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
  84.             gameBoardView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
  85.             gameBoardView.heightAnchor.constraint(equalTo: gameBoardView.widthAnchor),
  86.  
  87.         ])
  88.        
  89.         // set tag and action for the buttons
  90.         var t: Int = 1
  91.         buttons.forEach { b in
  92.             b.tag = t
  93.             b.addTarget(self, action: #selector(buttonClicked(_:)), for: .touchUpInside)
  94.             t += 1
  95.         }
  96.  
  97.     }
  98.    
  99.     func configureUI() {
  100.         viewModel.delegate = self
  101.     }
  102.    
  103.     @IBAction func buttonClicked(_ sender: UIButton) {
  104.         viewModel.processPlayerMove(for: sender.tag)
  105.     }
  106.  
  107.     /// This **function is to reset the view means remove close and circle image once player wants to restart the game. But this code is not working**
  108.  
  109.     /// should be working now
  110.     func updateButtonView() {
  111.         DispatchQueue.main.async {
  112.             for (index, moves) in self.viewModel.moves.enumerated() {
  113.                 let button = self.buttons[index]
  114.                 if let moves = moves {
  115.                     // let's make sure we get a valid image
  116.                     if let image = UIImage(named: moves.indicator) {
  117.                         button.setImage(image, for: [])
  118.                         button.setTitle("", for: [])
  119.                     } else {
  120.                         button.setImage(nil, for: [])
  121.                         button.setTitle(moves.indicator, for: [])
  122.                     }
  123.                 } else {
  124.                     button.setImage(nil, for: [])
  125.                     button.setTitle("", for: [])
  126.                 }
  127.             }
  128.         }
  129.     }
  130. }
  131.  
  132. /// Delegates are called from viewmodel
  133.  
  134. extension CodeTicTacToeViewController: TicTacToeViewModelProtocol {
  135.    
  136.     func updatePlayerInfo(index: Int) {
  137.         let button = buttons[index - 1]
  138.         let systemImageName =  viewModel.moves[index - 1]?.indicator ?? ""
  139.         if !systemImageName.isEmpty {
  140.             // let's make sure we get a valid image
  141.             if let image = UIImage(named: systemImageName) {
  142.                 button.setImage(image, for: [])
  143.                 button.setTitle("", for: [])
  144.             } else {
  145.                 button.setImage(nil, for: [])
  146.                 button.setTitle(systemImageName, for: [])
  147.             }
  148.         } else {
  149.             button.setImage(nil, for: [])
  150.             button.setTitle("", for: [])
  151.         }
  152.     }
  153.    
  154.     func displayAlert(alertData: AlertItem) {
  155.         let alert = UIAlertController(title: alertData.title, message: alertData.message, preferredStyle: UIAlertController.Style.alert)
  156.        
  157.         alert.addAction(UIAlertAction(title: alertData.buttonTitle, style: UIAlertAction.Style.default, handler: { [weak self] _ in
  158.             self?.viewModel.resetGame()
  159.             self?.updateButtonView()
  160.         }))
  161.         self.present(alert, animated: true, completion: nil)
  162.     }
  163.    
  164.     func gameBoardDisabled(isGameBoardDisable: Bool) {
  165.         gameBoardView.isUserInteractionEnabled = !isGameBoardDisable
  166.     }
  167. }
  168.  
  169.  
  170. protocol TicTacToeViewModelProtocol: AnyObject {
  171.     func updatePlayerInfo(index: Int)
  172.     func displayAlert(alertData: AlertItem)
  173.     func gameBoardDisabled(isGameBoardDisable: Bool)
  174. }
  175.  
  176. class TicTacToeViewModel {
  177.     var currentMovePosition: Int?
  178.    
  179.     var moves: [Move?]  = Array(repeating: nil, count: 9) {
  180.         didSet {
  181.             if let delegate = delegate, let position = self.currentMovePosition {
  182.                 delegate.updatePlayerInfo(index: position)
  183.             }
  184.         }
  185.     }
  186.     var isGameBoardDisable = false {
  187.         didSet {
  188.             if let delegate = delegate {
  189.                 delegate.gameBoardDisabled(isGameBoardDisable: isGameBoardDisable)
  190.             }
  191.         }
  192.        
  193.     }
  194.     var alertItem: AlertItem? {
  195.         didSet {
  196.             if let delegate = delegate, let alertItem = alertItem {
  197.                 delegate.displayAlert(alertData: alertItem)
  198.             }
  199.         }
  200.     }
  201.    
  202.     let winPatterns: Set<Set<Int>> = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7], [2, 5, 8], [3, 6, 9], [1, 5, 9], [3, 5, 7]]
  203.    
  204.     weak var delegate: TicTacToeViewModelProtocol!
  205.    
  206.     func processPlayerMove(for position: Int) {
  207.         self.currentMovePosition = position
  208.         if isSquareOccupied(in: moves, forIndex: position) { return }
  209.         moves[position - 1] = Move(player:  .human, boardIndex: position)
  210.  
  211.         // logic for win, draw or lose
  212.         if checkWinCondition(for: .human, in: moves) {
  213.             var a = AlertItem()
  214.             a.message = "Human Wins"
  215.             alertItem = a
  216.             return
  217.         }
  218.        
  219.         if checkForDraw(in: moves) {
  220.             var a = AlertItem()
  221.             a.message = "Player Draw"
  222.             alertItem = a
  223.             return
  224.         }
  225.         isGameBoardDisable = true
  226.        
  227.         processComputerMove()
  228.     }
  229.    
  230.     func processComputerMove() {
  231.         let computerPosition = determinComputerMovePosition(in: moves)
  232.         self.currentMovePosition = computerPosition
  233.        
  234.         moves[computerPosition - 1] = Move(player:  .computer, boardIndex: computerPosition)
  235.         isGameBoardDisable = false
  236.        
  237.         if checkWinCondition(for: .computer, in: moves) {
  238.             var a = AlertItem()
  239.             a.message = "Computer Wins"
  240.             alertItem = a
  241.             return
  242.         }
  243.         if checkForDraw(in: moves) {
  244.             var a = AlertItem()
  245.             a.message = "Computer Draw"
  246.             alertItem = a
  247.             return
  248.         }
  249.     }
  250.    
  251.     func isSquareOccupied(in moves:[Move?], forIndex index: Int) -> Bool {
  252.         return moves.contains(where: { $0?.boardIndex == index })
  253.     }
  254.    
  255.     func determinComputerMovePosition(in moves: [Move?]) -> Int {
  256.        
  257.         let computerMoves: [Move] = moves.compactMap { $0 }.filter { $0.player == .computer }
  258.         let computerPositions: Set<Int> = Set(computerMoves.map { $0.boardIndex })
  259.        
  260.         for pattern in winPatterns {
  261.             let winPositions = pattern.subtracting(computerPositions)
  262.             if winPositions.count == 1 {
  263.                 let isAvailable = !isSquareOccupied(in: moves, forIndex: winPositions.first!)
  264.                 if isAvailable { return winPositions.first! }
  265.             }
  266.         }
  267.         // if the AI ​​can't finish the game it will block
  268.         let humanMoves = moves.compactMap { $0 }.filter { $0.player == .human }
  269.         let humanPositions = Set(humanMoves.map { $0.boardIndex })
  270.        
  271.         for pattern in winPatterns {
  272.             let winPositions = pattern.subtracting(humanPositions)
  273.            
  274.             if winPositions.count == 1 {
  275.                 let isAvailable = !isSquareOccupied(in: moves, forIndex: winPositions.first!)
  276.                 if isAvailable { return winPositions.first! }
  277.             }
  278.         }
  279.         // always take the middle block
  280.         let middleSquare = 5
  281.         if !isSquareOccupied(in: moves, forIndex: middleSquare) {
  282.             return middleSquare
  283.         }
  284.  
  285.         // this could theoretically take a LONG time to process!
  286.         // if the AI ​​can't get position middle block it will get a random position
  287.         //var movePosition = Int.random(in: 1..<10)
  288.         //while isSquareOccupied(in: moves, forIndex: movePosition) {
  289.         //  movePosition = Int.random(in: 1..<10)
  290.         //}
  291.         //return movePosition
  292.        
  293.         // create a shuffled array of 1 to 9
  294.         //  loop through until we find an available position
  295.         var positions: [Int] = Array(1...9).shuffled()
  296.         var movePosition: Int = positions.removeFirst()
  297.         while isSquareOccupied(in: moves, forIndex: movePosition) {
  298.             movePosition = positions.removeFirst()
  299.         }
  300.         return movePosition
  301.     }
  302.    
  303.     func checkWinCondition(for player: Player, in moves:[Move?]) -> Bool {
  304.        
  305.         let playerMoves = moves.compactMap({ $0 }).filter { $0.player == player }
  306.         let playerPositions = Set(playerMoves.map { $0.boardIndex })
  307.        
  308.         for pattern in winPatterns where pattern.isSubset(of: playerPositions) {return true}
  309.         return false
  310.     }
  311.    
  312.     func checkForDraw(in moves: [Move?]) -> Bool {
  313.         return moves.compactMap { $0 }.count == 9
  314.        
  315.     }
  316.    
  317.     func resetGame() {
  318.         moves = Array(repeating: nil, count: 9)
  319.     }
  320. }
  321.  
Add Comment
Please, Sign In to add comment