Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // assumption for Move struct
- struct Move {
- var player: Player?
- var position: Int?
- var boardIndex: Int = 0
- var indicator: String {
- if let p = player {
- return p == .human ? "x" : "o"
- }
- return ""
- }
- }
- // assumption for AlertItem struct
- struct AlertItem {
- var title: String = "Result"
- var message: String = "message"
- var buttonTitle: String = "OK"
- }
- enum Player {
- case human
- case computer
- }
- class CodeTicTacToeViewController: UIViewController {
- var gameBoardView: UIView = {
- let v = UIView()
- v.backgroundColor = .systemBlue
- return v
- }()
- var buttons: [UIButton] = []
- var viewModel = TicTacToeViewModel()
- override func viewDidLoad() {
- super.viewDidLoad()
- configureUI()
- let outerStack: UIStackView = {
- let v = UIStackView()
- v.axis = .vertical
- v.spacing = 8
- v.distribution = .fillEqually
- return v
- }()
- for _ in 1...3 {
- let rowStack: UIStackView = {
- let v = UIStackView()
- v.axis = .horizontal
- v.spacing = 8
- v.distribution = .fillEqually
- return v
- }()
- for _ in 1...3 {
- let b: UIButton = {
- let v = UIButton()
- v.backgroundColor = .systemYellow
- return v
- }()
- rowStack.addArrangedSubview(b)
- buttons.append(b)
- }
- outerStack.addArrangedSubview(rowStack)
- }
- outerStack.translatesAutoresizingMaskIntoConstraints = false
- gameBoardView.translatesAutoresizingMaskIntoConstraints = false
- gameBoardView.addSubview(outerStack)
- view.addSubview(gameBoardView)
- let g = view.safeAreaLayoutGuide
- NSLayoutConstraint.activate([
- outerStack.topAnchor.constraint(equalTo: gameBoardView.topAnchor, constant: 8.0),
- outerStack.leadingAnchor.constraint(equalTo: gameBoardView.leadingAnchor, constant: 8.0),
- outerStack.trailingAnchor.constraint(equalTo: gameBoardView.trailingAnchor, constant: -8.0),
- outerStack.bottomAnchor.constraint(equalTo: gameBoardView.bottomAnchor, constant: -8.0),
- gameBoardView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
- gameBoardView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
- gameBoardView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
- gameBoardView.heightAnchor.constraint(equalTo: gameBoardView.widthAnchor),
- ])
- // set tag and action for the buttons
- var t: Int = 1
- buttons.forEach { b in
- b.tag = t
- b.addTarget(self, action: #selector(buttonClicked(_:)), for: .touchUpInside)
- t += 1
- }
- }
- func configureUI() {
- viewModel.delegate = self
- }
- @IBAction func buttonClicked(_ sender: UIButton) {
- viewModel.processPlayerMove(for: sender.tag)
- }
- /// 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**
- /// should be working now
- func updateButtonView() {
- DispatchQueue.main.async {
- for (index, moves) in self.viewModel.moves.enumerated() {
- let button = self.buttons[index]
- if let moves = moves {
- // let's make sure we get a valid image
- if let image = UIImage(named: moves.indicator) {
- button.setImage(image, for: [])
- button.setTitle("", for: [])
- } else {
- button.setImage(nil, for: [])
- button.setTitle(moves.indicator, for: [])
- }
- } else {
- button.setImage(nil, for: [])
- button.setTitle("", for: [])
- }
- }
- }
- }
- }
- /// Delegates are called from viewmodel
- extension CodeTicTacToeViewController: TicTacToeViewModelProtocol {
- func updatePlayerInfo(index: Int) {
- let button = buttons[index - 1]
- let systemImageName = viewModel.moves[index - 1]?.indicator ?? ""
- if !systemImageName.isEmpty {
- // let's make sure we get a valid image
- if let image = UIImage(named: systemImageName) {
- button.setImage(image, for: [])
- button.setTitle("", for: [])
- } else {
- button.setImage(nil, for: [])
- button.setTitle(systemImageName, for: [])
- }
- } else {
- button.setImage(nil, for: [])
- button.setTitle("", for: [])
- }
- }
- func displayAlert(alertData: AlertItem) {
- let alert = UIAlertController(title: alertData.title, message: alertData.message, preferredStyle: UIAlertController.Style.alert)
- alert.addAction(UIAlertAction(title: alertData.buttonTitle, style: UIAlertAction.Style.default, handler: { [weak self] _ in
- self?.viewModel.resetGame()
- self?.updateButtonView()
- }))
- self.present(alert, animated: true, completion: nil)
- }
- func gameBoardDisabled(isGameBoardDisable: Bool) {
- gameBoardView.isUserInteractionEnabled = !isGameBoardDisable
- }
- }
- protocol TicTacToeViewModelProtocol: AnyObject {
- func updatePlayerInfo(index: Int)
- func displayAlert(alertData: AlertItem)
- func gameBoardDisabled(isGameBoardDisable: Bool)
- }
- class TicTacToeViewModel {
- var currentMovePosition: Int?
- var moves: [Move?] = Array(repeating: nil, count: 9) {
- didSet {
- if let delegate = delegate, let position = self.currentMovePosition {
- delegate.updatePlayerInfo(index: position)
- }
- }
- }
- var isGameBoardDisable = false {
- didSet {
- if let delegate = delegate {
- delegate.gameBoardDisabled(isGameBoardDisable: isGameBoardDisable)
- }
- }
- }
- var alertItem: AlertItem? {
- didSet {
- if let delegate = delegate, let alertItem = alertItem {
- delegate.displayAlert(alertData: alertItem)
- }
- }
- }
- 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]]
- weak var delegate: TicTacToeViewModelProtocol!
- func processPlayerMove(for position: Int) {
- self.currentMovePosition = position
- if isSquareOccupied(in: moves, forIndex: position) { return }
- moves[position - 1] = Move(player: .human, boardIndex: position)
- // logic for win, draw or lose
- if checkWinCondition(for: .human, in: moves) {
- var a = AlertItem()
- a.message = "Human Wins"
- alertItem = a
- return
- }
- if checkForDraw(in: moves) {
- var a = AlertItem()
- a.message = "Player Draw"
- alertItem = a
- return
- }
- isGameBoardDisable = true
- processComputerMove()
- }
- func processComputerMove() {
- let computerPosition = determinComputerMovePosition(in: moves)
- self.currentMovePosition = computerPosition
- moves[computerPosition - 1] = Move(player: .computer, boardIndex: computerPosition)
- isGameBoardDisable = false
- if checkWinCondition(for: .computer, in: moves) {
- var a = AlertItem()
- a.message = "Computer Wins"
- alertItem = a
- return
- }
- if checkForDraw(in: moves) {
- var a = AlertItem()
- a.message = "Computer Draw"
- alertItem = a
- return
- }
- }
- func isSquareOccupied(in moves:[Move?], forIndex index: Int) -> Bool {
- return moves.contains(where: { $0?.boardIndex == index })
- }
- func determinComputerMovePosition(in moves: [Move?]) -> Int {
- let computerMoves: [Move] = moves.compactMap { $0 }.filter { $0.player == .computer }
- let computerPositions: Set<Int> = Set(computerMoves.map { $0.boardIndex })
- for pattern in winPatterns {
- let winPositions = pattern.subtracting(computerPositions)
- if winPositions.count == 1 {
- let isAvailable = !isSquareOccupied(in: moves, forIndex: winPositions.first!)
- if isAvailable { return winPositions.first! }
- }
- }
- // if the AI can't finish the game it will block
- let humanMoves = moves.compactMap { $0 }.filter { $0.player == .human }
- let humanPositions = Set(humanMoves.map { $0.boardIndex })
- for pattern in winPatterns {
- let winPositions = pattern.subtracting(humanPositions)
- if winPositions.count == 1 {
- let isAvailable = !isSquareOccupied(in: moves, forIndex: winPositions.first!)
- if isAvailable { return winPositions.first! }
- }
- }
- // always take the middle block
- let middleSquare = 5
- if !isSquareOccupied(in: moves, forIndex: middleSquare) {
- return middleSquare
- }
- // this could theoretically take a LONG time to process!
- // if the AI can't get position middle block it will get a random position
- //var movePosition = Int.random(in: 1..<10)
- //while isSquareOccupied(in: moves, forIndex: movePosition) {
- // movePosition = Int.random(in: 1..<10)
- //}
- //return movePosition
- // create a shuffled array of 1 to 9
- // loop through until we find an available position
- var positions: [Int] = Array(1...9).shuffled()
- var movePosition: Int = positions.removeFirst()
- while isSquareOccupied(in: moves, forIndex: movePosition) {
- movePosition = positions.removeFirst()
- }
- return movePosition
- }
- func checkWinCondition(for player: Player, in moves:[Move?]) -> Bool {
- let playerMoves = moves.compactMap({ $0 }).filter { $0.player == player }
- let playerPositions = Set(playerMoves.map { $0.boardIndex })
- for pattern in winPatterns where pattern.isSubset(of: playerPositions) {return true}
- return false
- }
- func checkForDraw(in moves: [Move?]) -> Bool {
- return moves.compactMap { $0 }.count == 9
- }
- func resetGame() {
- moves = Array(repeating: nil, count: 9)
- }
- }
Add Comment
Please, Sign In to add comment