Kotlin 初心者がじゃんけんのプログラムを書いてみた

Kotlin という言語に興味を持ち、少しコードを書いてみたくなったので書いてみました。
つい先日 [Java Advent Calendar 2014](http://qiita.com/advent-calendar/2014/java) の記事で[プログラミング初心者がじゃんけんプログラムを書いてみた](http://qiita.com/dk_masu/items/d7bb7a81cbc6e16fbb49)というのがありましたが、初めて触る言語の題材としては申し分なさそうだったので、あの記事の仕様を借りて Kotlin で再実装してみたいと思いました。


```lang:kotlin
import java.util.Random
import java.util.Scanner

enum class Call(val value: Int, val msg: String){
  WIN: Call(1, "勝ち")
  LOSE: Call(-1, "負け")
  EVEN: Call(0, "引き分け")
}

enum class Strategy(val value: Int, val msg: String) {
  GU: Strategy(0, "グー")
  CHOKI: Strategy(1, "チョキ")
  PAR: Strategy(2, "パー")

  fun compare(s: Strategy): Call = when {
    this.value == s.value -> Call.EVEN
    this.value == s.value + 1,
    this.value == 2 && s.value == 0 -> Call.WIN
    else -> Call.LOSE
  }
}

class Computer {
  private val random = Random()

  fun selectStrategy(): Strategy {
    val r: Int = Math.abs(random.nextInt())
    return Strategy.values().first { s -> s.value == (r mod 3) }
  }
}

data class Result() {
  private var numberOfWins = 0
  private var numberOfLoses = 0

  fun addCall(call: Call) {
    when(call) {
      Call.WIN -> numberOfWins++
      Call.LOSE -> numberOfLoses++
    }
  }

  override fun toString() =
      "あなたの勝敗数\n" +
      "勝ち:\t${this.numberOfWins} 回\n" +
      "負け:\t${this.numberOfLoses} 回"
}

class Game() {
  private val scanner = Scanner(System.`in`)
  private val com = Computer()
  private var result = Result()

  fun execute() {
    println("さあ、私とじゃんけんしましょう！！勝負ですよー！！")

    do {
      var call: Call

      while(true) {
        val input = userInput()
        println("あなたが出したのは「$input」です。")

        val userStrategy = Strategy.values().first { s -> s.msg equals input }

        val comStrategy = com.selectStrategy()
        println("私が出したのは「${comStrategy.msg}」です！！")

        call = userStrategy.compare(comStrategy)

        if(call != Call.EVEN) {
          println("あなたの${call.msg}です！${if (call == Call.WIN) "やったね" else "ざんねん"}！")
          break
        } else {
          println("あーいこーで？")
        }
      }
      println("***************************")

      result.addCall(call)
    } while(retry())
  }

  fun showResult() {
    println(result)
  }

  private fun userInput(): String {
    var input: String

    while(true) {
      print("「グー」「チョキ」「パー」のどれかを打ち込んで下さい ⇒ ")

      input = scanner.nextLine()

      if(Strategy.values().any { s -> s.msg equals input }) { break }
      println("「グー」「チョキ」「パー」以外は打ち込まないで下さい")
    }

    return input
  }


  private fun retry(): Boolean {
    print("もう一度続ける場合は「continue」を入力して下さい。[continue] ⇒ ");
    val input = scanner.nextLine()
    val result = input.isEmpty() || input equals "continue"
    if(result) { println("よし！もう一回勝負しましょう！") }
    return result
  }
}

fun main(args : Array<String>) {
  val game = Game()
  game.execute()
  game.showResult()
}
```

## 感想
Kotlin 難しかったです。コードそのものはもう少しスマートにする余地があるのかなーと思いますが、ちょっと Kotlin 分からないのでこれが限界でした…。

- 変数の宣言が面倒くさい(どの宣言方法がベターかわからない)
  - 変数宣言をするときに `var`, `val` の使い分けは簡単に分かる
  - `var foo = "foo"` と `var foo: String = "foo"` の場合、前者は簡単な型推論によって型が導かれるけど、後者はプログラマ自身が明示的に書けるし `String` の様にリテラルで型が分かるようなものはいいかもしれないけど、関数などで値を返す場合は後者のように明示的に書いたほうがいいのかもしれない。
  - ただ、その場合プログラマの好みに書き方が委ねられるので、私個人の感想としては微妙だと思ったのでいっそ後者で統一したいと思った。
- `find` みたいな一般的な名前の関数が非推奨になってる( Ver 0.9.x )
  - `Array#find` は現在非推奨な関数らしい
  - 代わりに `Array#first` を使うようです。
  - `Array#first` は引数を取らない場合は純粋な一番目のオブジェクトを返し、引数ありの場合は引数の条件を一番目に満たすオブジェクトを返します(けど、私は `first` と `find` の意味は違うと思うのでちゃんと関数を分けたいなって思いました)。
- `null` に遭遇しないようにプログラムするのが少し手間
  - 今回 `Array#first` を使いましたが、 `Array#firstOrNull` を使うか悩みました。
  - `find` の文脈では対象のオブジェクトがないことはよくあります。
     - つまり、返り値として `null`(ないし、それに該当するオブジェクト) が許容されることはよくあります。
  - ですが、 `first` としての文脈で対象のオブジェクトがないというのは少し気持ち悪いなって思いました。
  - それから `null` を許容したいときに Kotlin では、変数宣言あたりも変更しないといけないので面倒だなと思いました。
  - 例えば `Ruby` では `find` で `nil` が返るのを許容しているので、 `if foo` のような `nil` チェックのイディオムがあります。
  - Kotlin の場合、 `null` を許容するように書くと変数の宣言からコードを変えないといけないことと、その変数に `null` が入ってきても後続のチェックにより `null` だったら処理をスキップするみたいな風に書くのなら、その変数に `null` が入らないように存在チェックみたいなものを先にした方がいいのかなと思いました。
     - 今回で言うところの `Array#any` がその存在チェックですね。
     - ただ、これは少しばかりコストがかかるのと同じコードが 2 回出てくることになるのでもう少し綺麗にまとめたい。
- コーディングスタイルがよく分からない
  - `null` を極力避けるべきなのかとか(今回の葛藤ですね)
  - 変数宣言の仕方とか。
  - Question Colon Operator がないのもちょっと書くときに悩みますね。
     - いわゆる String Interpolation で `${result ? "foo" : "bar"}` と書けるなら違和感少ないのですが、 `${if(result) "foo" else "bar"}` だと微妙だなと思ったりとかしました。

## 参考
[プログラミング初心者がじゃんけんのプログラムを書いてみた](http://qiita.com/dk_masu/items/d7bb7a81cbc6e16fbb49)