【Swift】オセロ(リバーシ)

# 概要
友人に [本気で始めるiPhoneアプリ作り](https://www.amazon.co.jp/%E6%9C%AC%E6%B0%97%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8BiPhone%E3%82%A2%E3%83%97%E3%83%AA%E4%BD%9C%E3%82%8A-Xcode-10-x%E5%AF%BE%E5%BF%9C-Informatics-IDEA/dp/4815600031) という本を頂いたので、Swiftでオセロゲームを作りました。このページで紹介するものはオフライン対戦です。サーバーとの通信を実装したものは他のページで紹介します。

## 環境
OS
・ macOs
開発環境
・ Xcode10
・ Swift4

#### 完成画像
<img width="300" alt="スクリーンショット 2019-04-05 16.56.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/381963/a8076af2-9f7c-3164-d043-10542a342205.png">

## ファイル一覧
1. ViewController.swift
2. board.swift　　　オセロボード
3. player.swift　　　対戦するCPU
4. board.png
![board.png](https://qiita-image-store.s3.amazonaws.com/0/381963/25a89f6c-d95c-9dc9-fd39-0b068cd0bc5a.png)

5. black.png
![black.png](https://qiita-image-store.s3.amazonaws.com/0/381963/47773e6d-5359-e78c-c343-d483998dba2a.png)

6. white.png
![white.png](https://qiita-image-store.s3.amazonaws.com/0/381963/03a77679-f938-7e12-c6fc-7421a8f8e920.png)

この 4 ~ 6 の画像を使ってオセロ盤を作ります。

難しいところはないと思うのでコメントを読んで理解してください。わからないところがあればコメントお願いします。記事を修正します。

## board.swift

```swift:board.swift
import Foundation

class Board{
    
    var SIZE: Int = 0
    let DIRECTIONS_XY = [[-1, -1], [+0, -1], [+1, -1],
                         [-1, +0],           [+1, +0],
                         [-1, +1], [+0, +1], [+1, +1]]
    let BLACK = -1
    let WHITE = 1
    let BLANK = 0
    var square:[[Int]] = []

    // オセロを開始する際に呼ばれ、オセロ盤を初期化する
    func start(size: Int){
        self.SIZE = size
        let center = size / 2
        for _ in 0..<self.SIZE{
            var array:[Int] = []
            for _ in 0..<self.SIZE{
                array += [BLANK]
            }
            square += [array]
        }
        square[center-1][center-1] = self.WHITE
        square[center-1][center] = self.BLACK
        square[center][center-1] = self.BLACK
        square[center][center] = self.WHITE
    }

    // 盤上にある石の個数を返す
    func returnStone() -> (Int,Int) {
        var black = 0
        var white = 0
        var blank = 0
        for y in 0..<SIZE{
            for x in 0..<SIZE{
                switch square[y][x]{
                case BLACK:
                    black += 1
                case WHITE:
                    white += 1
                default:
                    blank += 1
                }
            }
        }
        return (black, white)
    }
    
    // 対戦終了時もう一度対戦する際にボード板をリセットする
    func reset(){
        var _square:[[Int]] = []
        let size = SIZE
        let center = size / 2
        for _ in 0..<SIZE{
            var array:[Int] = []
            for _ in 0..<SIZE{
                array += [BLANK]
            }
            _square += [array]
        }
        _square[center-1][center-1] = self.WHITE
        _square[center-1][center] = self.BLACK
        _square[center][center-1] = self.BLACK
        _square[center][center] = self.WHITE
        square = _square
    }
    
    // ボード盤を返す
    func return_board() -> [[Int]]{
        return square
    }
    
    // 呼ばれた段階で Game Over　であるかどうかを判定する
    func gameOver() -> Bool {
        var black = 0
        var white = 0
        var blank = 0
        for y in 0..<SIZE{
            for x in 0..<SIZE{
                switch square[y][x]{
                case BLACK:
                    black += 1
                case WHITE:
                    white += 1
                default:
                    blank += 1
                }
            }
        }
        if( blank == 0 || black == 0 || white == 0 ){
            return true
        }
        if( self.available(stone: BLACK).count == 0 && self.available(stone: WHITE).count == 0){
            return true
        }
        return false
    }
    
    func is_available( x: Int, y:Int, stone: Int) -> Bool {
        if ( square[x][y] != BLANK ){
            return false
        }
        for i in 0..<8 {
            let dx = DIRECTIONS_XY[i][0]
            let dy = DIRECTIONS_XY[i][1]
            if( self.count_reversible(x: x, y: y, dx: dx, dy: dy, stone: stone) > 0 ){
                return true
            }
        }
        return false
    }

    // 引数で与えられた石の次に打てる場所を返す
    func available(stone: Int) -> [[Int]]{
        var return_array:[[Int]] = []
        for x in 0..<SIZE{
            for y in 0..<SIZE{
                if( self.is_available( x: x, y: y, stone: stone) ){
                    return_array += [[x,y]]
                }
            }
        }
        return return_array
    }
    
    // ボードに石を置く
    func put( x: Int, y:Int, stone: Int){
        square[x][y] = stone
        for i in 0..<8 {
            let dx = DIRECTIONS_XY[i][0]
            let dy = DIRECTIONS_XY[i][1]
            let n = self.count_reversible( x: x, y: y, dx: dx, dy: dy, stone: stone)
            for j in 1..<(n+1){
                square[x + j * dx][y + j * dy] = stone
            }
        }
    }
    
    func count_reversible( x: Int, y: Int, dx: Int, dy: Int, stone: Int) -> Int {
        var _x = x
        var _y = y
        for i in 0..<SIZE{
            _x = _x + dx
            _y = _y + dy
            // 0 <= x < 4 : can't write <- Annoying!!!!
            if !( 0 <= _x && _x < SIZE && 0 <= _y && _y < SIZE ){
                return 0
            }
            if (square[_x][_y] == BLANK){
                return 0
            }
            if (square[_x][_y] == stone){
                return i
            }
        }
        return 0
    }
}
```

## player.swift
今回は簡単なランダム君だけです。

```swift:player.swift
import Foundation

class Player {
    
    func play(board: Board, stone: Int) -> (Int,Int) {
        return Random(available: board.available(stone: stone))
    }
    
    // 打てるところにランダムで打ちます。
    // swift には random.choice()　みたいなものありますか？
    func Random(available: [[Int]]) -> (Int,Int) {
        let int = Int.random(in: 0..<available.count)
        return (available[int][0], available[int][1])
    }
}
```

## ViewController.swift

```swift:ViewController.swift
class ViewController: UIViewController {

    // BOARDSIZE　で　ボードのサイズを変更できます。
    let BOARDSIZE = 8
    var board = Board()
    var player = Player()

    var player_name = "Random"
    var Stone_count = 0

    // -1: 黒
    //  1: 白
    let User_color = -1
    let Cpu_color = 1

    // ボードはボタンを行列配置して表現される
    var buttonArray: [UIButton] = []

    // board.png, white.png, black.png
    let baseBoard = UIImage(named: "board")
    let white = UIImage(named: "white")
    let black = UIImage(named: "black")

    var resetButton = UIButton()
    var passButton = UIButton()
    var viewStoneCount = UILabel()
    
    // オセロ盤を表現するボタン
    class buttonClass: UIButton{
        let x: Int
        let y: Int
        init( x:Int, y:Int, frame: CGRect ) {
            self.x = x
            self.y = y
            super.init(frame:frame)
        }
        required init?(coder aDecoder: NSCoder) {
            fatalError("error")
        }
    }
    
    // ボタンなどを生成
    func createUI(){
        board.start(size: BOARDSIZE)
        var y = 83
        let boxSize = 84 / (BOARDSIZE/4)
        viewStoneCount.frame = CGRect(x: 18, y: 430, width: 330, height: 60)
        viewStoneCount.textAlignment = NSTextAlignment.center
        viewStoneCount.font = UIFont.systemFont(ofSize: 25)
        self.view.addSubview(viewStoneCount)
        for i in 0..<BOARDSIZE{
            var x = 19
            for j in 0..<BOARDSIZE{
                let button: UIButton = buttonClass(
                    x: i,
                    y: j,
                    frame:CGRect(x: x,y: y, width: boxSize,height: boxSize))
                button.addTarget(self, action: #selector(ViewController.pushed), for: .touchUpInside)
                self.view.addSubview(button)
                button.isEnabled = false
                buttonArray.append(button)
                x = x + boxSize + 1
            }
            y = y + boxSize + 1
        }
        resetButton.frame = CGRect(x: 125, y: 575, width: 125, height: 45)
        resetButton.addTarget(self, action: #selector(ViewController.pushResetButton), for: .touchUpInside)
        resetButton.isEnabled = false
        resetButton.isHidden = true
        resetButton.setTitle("RESET", for: .normal)
        resetButton.titleLabel?.font = UIFont.systemFont(ofSize: 25)
        resetButton.setTitleColor(.white, for: .normal)
        resetButton.backgroundColor = UIColor(red: 0.3, green: 0.7, blue: 0.6, alpha: 1)
        resetButton.layer.cornerRadius = 25
        resetButton.layer.shadowOpacity = 0.5
        resetButton.layer.shadowOffset = CGSize(width: 2, height: 2)
        self.view.addSubview(resetButton)
        
        passButton.frame = CGRect(x: 150, y: 500, width: 80, height: 30)
        passButton.addTarget(self, action: #selector(ViewController.pushPassButton), for: .touchUpInside)
        passButton.isEnabled = false
        passButton.isHidden = true
        passButton.setTitle("Pass", for: .normal)
        passButton.titleLabel?.font = UIFont.systemFont(ofSize: 25)
        passButton.setTitleColor(.white, for: .normal)
        passButton.backgroundColor = UIColor(red: 0.3, green: 0.7, blue: 0.6, alpha: 1)
        passButton.layer.cornerRadius = 25
        passButton.layer.shadowOpacity = 0.5
        passButton.layer.shadowOffset = CGSize(width: 2, height: 2)
        self.view.addSubview(passButton)
        drawBoard()
    }
    
    // passButton を押された時の処理
    @objc func pushPassButton() {
        CpuTurn()
        passButton.isEnabled = false
        passButton.isHidden = true
    }

    // resetButton を押された時の処理
    @objc func pushResetButton() {
        board.reset()
        drawBoard()
        resetButton.isEnabled = false
        resetButton.isHidden = true
        passButton.isEnabled = false
        passButton.isHidden = true
    }
    
    // ボード盤をタッチされた時の処理
    @objc func pushed(mybtn: buttonClass){
        mybtn.isEnabled = false
        board.put(x: mybtn.x, y: mybtn.y, stone: User_color)
        drawBoard()
        if( board.gameOver() == true ){
            resetButton.isEnabled = true
            resetButton.isHidden = false
        }
        self.CpuTurn()
    }
    
    // CPU
    func CpuTurn() {
        if( board.available(stone: Cpu_color).count != 0 ){
            let xy = player.play(board: board, stone: Cpu_color)
            board.put(x: xy.0, y: xy.1, stone: Cpu_color)
            drawBoard()
            if( board.gameOver() == true ){
                resetButton.isHidden = false
                resetButton.isEnabled = true
            }
        }
        if( board.gameOver() == true ){
            resetButton.isHidden = false
            resetButton.isEnabled = true
        }
        if( board.available(stone: User_color).count == 0){
            passButton.isHidden = false
            passButton.isEnabled = true
        }
    }
    
    // 画面にオセロ盤を表示させる
    func drawBoard(){
        let stonecount = board.returnStone()
        viewStoneCount.text = "● Uer: " + String(stonecount.0) + "     ○ CPU: " + String(stonecount.1)
        var count = 0
        var _board = board.return_board()
        for y in 0..<BOARDSIZE{
            for x in 0..<BOARDSIZE{
                if( _board[y][x] == User_color ){
                    buttonArray[count].setImage(black, for: .normal)
                } else if( _board[y][x] == Cpu_color ){
                    buttonArray[count].setImage(white, for: .normal)
                } else {
                    buttonArray[count].setImage(baseBoard, for: .normal)
                }
                buttonArray[count].isEnabled = false
                count += 1
            }
        }
        let availableList = board.available(stone: User_color)
        for i in 0..<(availableList.count){
            let x = availableList[i][0]
            let y = availableList[i][1]
            buttonArray[x*BOARDSIZE+y].isEnabled = true
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.createUI()
    }
}
```

# まとめ
kotlinと似ていてとても書きやすかったです。今回はランダム君としか対戦できませんが、サーバーと通信することで強いCPUと対戦することができます。改善点や疑問点などがありましたらコメントお願いします。kotlin版もあとで書きます。