Robots Board problem iOS - Multithreading

71 Views Asked by At

Im trying to create a robots challenge board game, but im really struggling with the round and thread logic.

Some suppositions:

1- Its a 7x7 board game

2- At the start of each round, randomly place a prize token somewhere on the game board and start each robot at opposite corners of the board.

3 - As each move is made the robot will leave a trail of each space it has moved through during the round — these trail spaces may not be passed through by the other robot during the course of the round.

4 - Each robot may move either left, right, up, or down to an unoccupied and unvisited space. Diagonal moves are not allowed.

5 - If a robot cannot move from its current position, it should hold its position until the end of the round.

6 - The first robot to reach the prize wins one point, the board then resets, and the next round starts. This simulation should run continuously, keeping track of the total score for each robot during the game session.

7 - Each robot should run concurrently in separate threads but it maintaining turn order.

and this is my current code:

enum BoardCellType {
    case empty
    case robot1
    case robot2
    case prize
}

struct Position: Equatable, Hashable {
    var x: Int
    var y: Int
    
    static func == (lhs: Position, rhs: Position) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}

class Robot: Equatable {
    let uuid: UUID
    var position: Position {
        didSet {
            visitedPositions.append(position)
        }
    }
    var score: Int
    var hasToWait: Bool = false
    var totalMoves: Int
    var visitedPositions: [Position]
    
    
    init(position: Position, score: Int = 0, totalMoves: Int = 0, visitedPositions: [Position] = []) {
        self.uuid = UUID()
        self.position = position
        self.score = score
        self.totalMoves = totalMoves
        self.visitedPositions = visitedPositions
    }
    
    func getPossibleMoves() -> [Position] {
        let moves = [Position(x: position.x + 1, y: position.y),
                     Position(x: position.x - 1, y: position.y),
                     Position(x: position.x, y: position.y + 1),
                     Position(x: position.x, y: position.y - 1)]
        return moves
    }
    
    static func == (lhs: Robot, rhs: Robot) -> Bool {
        return lhs.uuid == rhs.uuid
    }
}

protocol GameControllerDelegate: AnyObject {
    func gameDidUpdate()
    func showEndGameAlert(message: String)
}

class GameController {
    var board: [[BoardCellType]]
    let boardSize: Int
    var roundNumber: Int
    var prizePosition: Position
    var robot1: Robot
    var robot2: Robot
    var robot1Path: [Position]
    var robot2Path: [Position]
    var round: Int
    var moves: Int
    var currentPlayer: Robot?
    
    weak var delegate: GameControllerDelegate?
    
    private let robot1Queue = DispatchQueue(label: "com.robot1.queue")
    private let robot2Queue = DispatchQueue(label: "com.robot2.queue")
    private let gameQueue = DispatchQueue(label: "com.game.queue")
    
    var gameTimer: Timer?
    
    init(boardSize: Int) {
        self.boardSize = boardSize
        self.roundNumber = 0
        self.board = Array(repeating: Array(repeating: .empty, count: boardSize), count: boardSize)
        self.robot1 = Robot(position: Position(x: 0, y: 0), score: 0)
        self.robot2 = Robot(position: Position(x: boardSize - 1, y: boardSize - 1), score: 0)
        self.robot1Path = []
        self.robot2Path = []
        self.round = 0
        self.moves = 0
        self.prizePosition = Position(x: Int.random(in: 0..<boardSize), y: Int.random(in: 0..<boardSize))
    }

    func startGame() {
        roundNumber += 1
        
        resetRobots()
        
        currentPlayer = robot1
        resetBoard()
        play()
    }
    
    private func resetBoard() {
        for i in 0..<boardSize {
            for j in 0..<boardSize {
                board[i][j] = .empty
            }
        }
        board[robot1.position.x][robot1.position.y] = .robot1
        board[robot2.position.x][robot2.position.y] = .robot2
        board[prizePosition.x][prizePosition.y] = .prize
        
        robot1Path = [robot1.position]
        robot2Path = [robot2.position]
    }
    
    private func resetRobots() {
        var positions = Set<Position>()
        while positions.count < 3 {
            let randomPosition = Position(x: Int.random(in: 0..<boardSize), y: Int.random(in: 0..<boardSize))
            positions.insert(randomPosition)
        }
        
        let positionsArray = Array(positions)
        robot1 = Robot(position: positionsArray[0], visitedPositions: [positionsArray[0]])
        robot2 = Robot(position: positionsArray[1], visitedPositions: [positionsArray[1]])
        prizePosition = positionsArray[2]
    }
    
    func isMoveValid(position: Position, robot: Robot) -> Bool {
        // Check bounds
        if position.x < 0 || position.y < 0 || position.x >= boardSize || position.y >= boardSize {
            return false
        }
        
        // Check if position is already visited by the robot itself
        if robot.visitedPositions.contains(position) {
            return false
        }
        
        // Check if position is already visited by the other robot
        let otherRobot = (robot == robot1) ? robot2 : robot1
        if otherRobot.visitedPositions.contains(position) {
            return false
        }
        
        return true
    }

    
    func checkGameEnded() -> Bool {
        if !getPossibleMoves(robot: robot1).isEmpty || !getPossibleMoves(robot: robot2).isEmpty {
            return false
        }
        return true
    }
    
    func getPossibleMoves(robot: Robot) -> [Position] {
        return robot.getPossibleMoves().filter { isMoveValid(position: $0, robot: robot) }
    }
    
    func moveRobot(_ robot: Robot) {
        // If the robot has to wait, don't attempt to move it
        guard !robot.hasToWait else {
            checkGameStatus()
            print("robot has to Wait \(currentPlayer)")
            return
        }
        // get current robot =
        
        // create next robot move
        let direction = Int.random(in: 0...3)
        var newX = robot.position.x
        var newY = robot.position.y
        
        switch direction {
        case 0: // Move left
            newX -= 1
        case 1: // Move right
            newX += 1
        case 2: // Move up
            newY -= 1
        default: // Move down
            newY += 1
        }
        let newPosition = Position(x: newX, y: newY)
        let type: BoardCellType = robot == robot1 ? .robot1 : .robot2
        
        if isMoveValid(position: newPosition, robot: robot) {
                robot.position = newPosition
                // save on the robot path the newPosition
                if robot == self.robot1 {
                    self.robot1Path.append(newPosition)
                } else if robot == self.robot2 {
                    self.robot2Path.append(newPosition)
                }
                //Checks if the robot got the prize
                if self.board[newPosition.x][newPosition.y] == .prize {
                    print("robot got the prize \(type)")
                    robot.score += 1
                    // Update the board after the robot gets the prize
                    updateBoard(position: newPosition, type: .empty)
                    endGame()
                } else {
                    print("simple move \(type)")
                    // update the board position
                    updateBoard(position: newPosition, type: type)
                    robot.totalMoves += 1
                    
                    // Check if the robot can't move anymore
                    if self.getPossibleMoves(robot: robot).isEmpty {
                        robot.hasToWait = true
                    }
                }
                self.delegate?.gameDidUpdate()
                self.checkGameStatus() // Check if the game has ended
        } else {
            print("invalid move \(type)")
            robot.hasToWait = true
            self.delegate?.gameDidUpdate()
            self.checkGameStatus() // Check if the game has ended
        }
        print("robot1 \(robot1.visitedPositions)")
        print("robot2 \(robot2.visitedPositions)")
        // Check if the game has ended due to both robots having no valid moves
        if checkIfGameEnded() {
            endGame()
        } else {
            currentPlayer = (currentPlayer == robot1) ? robot2 : robot1
            playRound()
        }
    }
    
    private func updateBoard(position: Position, type: BoardCellType) {
        self.board[position.x][position.y] = type
    }
    
    private func checkIfGameEnded() -> Bool {
        return getPossibleMoves(robot: robot1).isEmpty && getPossibleMoves(robot: robot2).isEmpty
    }
    
    func checkGameStatus() {
        if robot1.hasToWait && robot2.hasToWait || checkGameEnded() {
                self.endGame()
        } else {
                self.currentPlayer = (self.currentPlayer == self.robot1) ? self.robot2 : self.robot1
                self.play()
        }
    }
    
    private func moveRobotOnThread(_ robot: Robot, queue: DispatchQueue, completion: @escaping () -> Void) {
        queue.asyncAfter(deadline: .now() + 2.0) {
            self.moveRobot(robot)
            DispatchQueue.main.async {
                completion()
            }
        }
    }
    
    func play() {
        playRound()
    }
    
    func playRound() {
        guard let currentPlayer = currentPlayer else {
            return
        }
        
        let queue = (currentPlayer == robot1) ? robot1Queue : robot2Queue
        moveRobotOnThread(currentPlayer, queue: queue) { [weak self] in
            self?.delegate?.gameDidUpdate()
            self?.checkGameStatus()
        }
    }
    
    func endGame() {
        // Reset the board
        
        // Print the result of the current round
        print("Round \(roundNumber):")
        print("Robot 1 score: \(robot1.score), total moves: \(robot1.totalMoves)")
        print("Robot 2 score: \(robot2.score), total moves: \(robot2.totalMoves)")
        
        // Determine the end condition and show the alert
        var message = ""
        if robot1.hasToWait && robot2.hasToWait {
            message = "End of moves for both robots."
        } else if robot1.hasToWait {
            message = "Robot 1 can't move anymore."
        } else if robot2.hasToWait {
            message = "Robot 2 can't move anymore."
        } else if robot1.score > robot2.score {
            message = "Robot 1 wins!"
        } else if robot2.score > robot1.score {
            message = "Robot 2 wins!"
        } else {
            message = "It's a draw!"
        }
        
        delegate?.gameDidUpdate()
        delegate?.showEndGameAlert(message: message)
        
        resetBoard()
    }
}

The problem is, I can't make the code run each robot on a separated thread and work by turns. Already tried with NSTimer, but I think its not the purpose of the challenge>

0

There are 0 best solutions below