What we will do:
In this part we will implement methods checking win conditions. After implementig whole game logic finaly we will be able to play the game.
In this part we cover the following topics
Source code for this chapter.
EngineGameBattleship
class modificationAdd to
EngineGameBattleship
class three private properties to keep information about board dimension and sizes of ships used during the game
1 2 |
private var shipsSize: [Int] private var rows, cols: Int |
and modify accordingly initializer
1 2 3 4 5 6 7 8 9 |
init(rows: Int = 10, cols: Int = 10, shipsSize: [Int]) { self.boardPlayer = Board(rows: rows, cols: cols) self.boardOpponent = Board(rows: rows, cols: cols) self.shipsSize = shipsSize self.rows = rows self.cols = cols } |
To make playing game possible we need a method used to check if there is a winner and, if the answer is yes, who is this.
1 2 3 4 5 6 7 8 9 |
func checkWhoWins() -> Who? { if boardPlayer.ships.shipsAtCommand == 0 { return Who.opponent } else if boardOpponent.ships.shipsAtCommand == 0 { return Who.player } return nil } |
Next let's implement a very simple shooting method. In this case we try maxTries
to find an accpetable cell (that is a cell we may shoot on). If we fail, we systematicaly, row by row and column by column, search until we find acceptable cell.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
func getShotCoordinatesForOpponent(maxTries: Int) -> (row: Int, col: Int)? { var row, col: Int? // Use random approach for _ in 1...maxTries { row = EngineGameBattleshipUtils.getRandomInt(from: 1, to: rows) col = EngineGameBattleshipUtils.getRandomInt(from: 1, to: cols) if let r = row, let c = col { if mayShot(who: Who.opponent, row: r, col: c) { return (row: r, col: c) } } } // If previous failed, use systematic search approach for r in 0...rows+1 { for c in 0...cols+1 { if mayShot(who: Who.opponent, row: r, col: c) { return (row: r, col: c) } } } return nil } |
Because previously implemented method mayShot(row: Int, col: Int) -> Bool
is defined in Board
class, we need a boilerplate method to call it.
1 2 3 4 |
func mayShot(who: Who, row: Int, col: Int) -> Bool { let boardWho = getWhoBoard(who: getWhoTarget(who: who)) return boardWho.mayShot(row: row, col: col) } |
This simple method uses getWhoTarget(who: Who) -> Who
method to get current player's opponent so we can get current player opponent's board
1 2 3 4 5 6 |
private func getWhoTarget(who: Who) -> Who { if who == Who.player { return Who.opponent } return Who.player } |
Knowing that shot is possible, we can shoot
1 2 3 4 |
func shot(who: Who, row: Int, col: Int) { let boardWho = getWhoBoard(who: getWhoTarget(who: who)) boardWho.shot(row: row, col: col) } |
Last thing left to implemented to make our engine usable is a boilerplate mathod calling shiop auto-layout metod from Board
1 2 3 4 5 6 7 8 9 10 |
func shipsAutoSetup(shipsSize: [Int], maxTriesPerShip: Int, who: Who) -> Bool { let boardWho = getWhoBoard(who: who) let number = boardWho.shipsAutoSetup(shipsSize: shipsSize, maxTriesPerShip: maxTriesPerShip) if shipsSize.count == number { return true } return false } |
At this moment our engine is ready, and we can try to use it to play the game. All the changes from this section are about
main
file.
First let's add some variables.
- Array with sizes of the ships
1let shipsSize = [4, 3, 3, 2, 2, 2, 1, 1, 1, 1] - An instance of our game engine
1let game = EngineGameBattleship(shipsSize: shipsSize) - If ships auto-layout for an opponent (computer) succeeded
123let opponentReady = game.shipsAutoSetup(shipsSize: shipsSize,maxTriesPerShip: 20,who: .opponent) - If ships auto-layout for a player (human) succeeded
123let playerReady = game.shipsAutoSetup(shipsSize: shipsSize,maxTriesPerShip: 20,who: .player) - Indicating whos turn is now
1var whoseTurn = EngineGameBattleship.Who.player - Player's or opponent's shot coordinates
1var coordinates: (row: Int, col: Int)?
Now we can implement main game loop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
if opponentReady, playerReady { while(true){ print("\n\n\n TURN") print(whoseTurn) if whoseTurn == EngineGameBattleship.Who.player { coordinates = game.getShotCoordinatesForPlayer(engine: game, maxRows: 10, maxCols: 10) if coordinates == nil { print("Player can't shoot") break } else { print("Shot at row \(coordinates!.row) and column \(coordinates!.col)") } game.shot(who: whoseTurn, row: coordinates!.row, col: coordinates!.col) whoseTurn = EngineGameBattleship.Who.opponent } else { coordinates = game.getShotCoordinatesForOpponent(maxTries: 100) if coordinates == nil { print("Opponent can't shoot") break } else { print("Shot at row \(coordinates!.row) and column \(coordinates!.col)") } game.shot(who: whoseTurn, row: coordinates!.row, col: coordinates!.col) whoseTurn = EngineGameBattleship.Who.player } game.printBoards() if let who = game.checkWhoWins() { print("The winner is \(who)") break } } } else { print("Can't arrange all ships") } |
Almost ready but we have to add two methods to get shot coordinates from human player. Add the following methods just after import Foundation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func getIntFromCommandLine(message: String, rangeMin: Int, rangeMax: Int) -> Int { print(message) while(true) { if let input = readLine() { if let int = Int(input) { if int >= rangeMin && int <= rangeMax { return int } else { print("\(input) is not in range [\(rangeMin)-\(rangeMax)]. Please try againt") } } else{ print("\(input) is not a valid integer. Please try againt") } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func getShotCoordinatesForPlayer(engine: EngineGameBattleship, maxRows: Int, maxCols: Int) -> (row: Int, col: Int)? { while(true) { let row = getIntFromCommandLine(message: "Enter row", rangeMin: 1, rangeMax: maxRows) let col = getIntFromCommandLine(message: "Enter column", rangeMin: 1, rangeMax: maxCols) if engine.mayShot(who: .player, row: row, col: col) { return (row: row, col: col) } } } |
Now we can run our code. Ships for player (human player) and his opponent (computer player) should be automaticaly arranged on game boards. Both players should have 10 ships. To make debug possible, both boards are printed on the screen.