読者です 読者をやめる 読者になる 読者になる

Swift によるオブジェクト指向ジャンケン・プログラム

なぜ、あなたはJavaでオブジェクト指向開発ができないのか―Javaの壁を克服する実践トレーニング

なぜ、あなたはJavaでオブジェクト指向開発ができないのか―Javaの壁を克服する実践トレーニング

この書籍の Java 版をこれまで Ruby参照)と Python参照)に移植しましたが、今度は Swift 3.0 に移植しました。まあ何だかバカバカしいようなプログラムですが、初心者の勉強のためです。いやー、かなり色いろハマりました。

とりあえずコード。Linux 向けなので Mac の人は import を変更して下さい。

import Glibc
import Foundation    //再追記参照

let (STONE, SCISSORS, PAPER) = (0, 1, 2)

class Player {
    let name: String
    var wincount: Int
    
    init(playername: String) {
        name = playername
        wincount = 0
    }
    
    func showHand() -> Int {
        let hand: Int
        let rnd = random() % 3
        if rnd < 1 {
            hand = STONE
        } else if rnd < 2 {
            hand = SCISSORS
        } else {
            hand = PAPER
        }
        return hand
    }
    
    func notifyResult() {
        wincount += 1
    }
}

class Judge {
    func janken(_ player1: Player,_ player2: Player) {
        srand(UInt32(time(nil)))
        print("【ジャンケン開始】")

        for c in 1...3 {playJanken(c, player1, player2)}
        
        print("\n【ジャンケン終了】\n\n")
        let final_winner = judgeFinalWinner(player1, player2)
        print("\(player1.wincount)\(player2.wincount)で", terminator: "")
        if final_winner != nil {
            print(final_winner!.name + "の勝ちです!")
        } else {
            print("引き分けです")
        }
    }
    
    func playJanken(_ c: Int,_ player1: Player,_ player2: Player) {
        print("\n【\(c)回戦目】")
        let winner = judgeJanken(player1, player2)
        if winner != nil {
            print(winner!.name + "が勝ちました!")
            winner!.notifyResult()
        } else {
            print("引き分けです")
        }
    }
    
    func judgeJanken(_ player1: Player,_ player2: Player) -> Player? {
        var winner: Player?
        let p1hand: Int = player1.showHand()
        let p2hand: Int = player2.showHand()
        
        printHand(p1hand)
        print("  vs.  ", terminator: "")
        printHand(p2hand)
        print()
        
        if p1hand == STONE && p2hand == SCISSORS ||
          p1hand == SCISSORS && p2hand == PAPER ||
          p1hand == PAPER && p2hand == STONE {
            winner = player1
        } else if p1hand == STONE && p2hand == PAPER ||
          p1hand == SCISSORS && p2hand == STONE ||
          p1hand == PAPER && p2hand == SCISSORS {
            winner = player2
        }
        return winner
    }
    
    func judgeFinalWinner(_ player1: Player,_ player2: Player) -> Player? {
        var winner: Player?
        let p1: Int = player1.wincount
        let p2: Int = player2.wincount
        if p1 > p2 {
            winner = player1
        } else if p1 < p2 {
            winner = player2
        }
        return winner
    }
    
    func printHand(_ hand: Int) {
        switch hand {
        case STONE:    print("グー",  terminator: "")
        case SCISSORS: print("チョキ", terminator: "")
        case PAPER:    print("パー",  terminator: "")
        default:       break
        }
    }
}

let player1 = Player(playername: "村田さん")
let player2 = Player(playername: "山田さん")
let judge = Judge()
judge.janken(player1, player2)

実行例。

【ジャンケン開始】

【1回戦目】
パー  vs.  グー
村田さんが勝ちました!

【2回戦目】
グー  vs.  パー
山田さんが勝ちました!

【3回戦目】
パー  vs.  グー
村田さんが勝ちました!

【ジャンケン終了】


2 対 1で村田さんの勝ちです!



いちばんハマったのは、関数の呼び出しです。デフォルトでは引数には必ずラベルをつけて呼び出さなくてはならないので、省略する場合には _(アンダースコア)を関数定義の引数の前につけねばなりません。それから、変数に nil は代入できないので、そうしたければオプショナル型(変数定義の際に型の最後に ? をつける)というものを使います。オプショナル型を呼び出すときは、変数のあとに ! をつけて unwrap(ラップを解く)しなければなりません。あと、ささいなことですが、print() で改行したくないときは、引数の最後に terminator: "" をつけます。

このくらいですかね。ああ、初心者には疲れた。


ごらんのとおり、かなりスッキリとは書けます。自分のような初心者でも使いやすい感じです。


※注記
上で random() 関数を使っていますが、これは僕の OS が Linux で、Linux 向けの Swift では arc4dandom() がサポートされていない(!)からです。random() はインタプリタでは普通に使えますが、コンパイルすると実行毎にすべて同じ乱数になってしまいます。このへんはほとんど BUG です。回避の方法もあるようですが、面倒です(ここなど)。

後記:
解決しました。srand() で random() の seed を与えることができるので、srand(UInt32(time(nil))) を追加しました。これでコンパイルしても実行毎に異なった乱数を発生させることができました。なお、time() は import Fondation が必要です。


※再追記
Swift 3.1-dev だと 2行目の import Foundation は必要ないようです(あるとエラーになります)。これでコンパイルしても time() は使えます。