3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-12-13

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

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 は引数を取らない場合は純粋な一番目のオブジェクトを返し、引数ありの場合は引数の条件を一番目に満たすオブジェクトを返します(けど、私は firstfind の意味は違うと思うのでちゃんと関数を分けたいなって思いました)。
  • null に遭遇しないようにプログラムするのが少し手間
    • 今回 Array#first を使いましたが、 Array#firstOrNull を使うか悩みました。
    • find の文脈では対象のオブジェクトがないことはよくあります。
      • つまり、返り値として null(ないし、それに該当するオブジェクト) が許容されることはよくあります。
    • ですが、 first としての文脈で対象のオブジェクトがないというのは少し気持ち悪いなって思いました。
    • それから null を許容したいときに Kotlin では、変数宣言あたりも変更しないといけないので面倒だなと思いました。
    • 例えば Ruby では findnil が返るのを許容しているので、 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"} だと微妙だなと思ったりとかしました。

参考

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

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?