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
は引数を取らない場合は純粋な一番目のオブジェクトを返し、引数ありの場合は引数の条件を一番目に満たすオブジェクトを返します(けど、私は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"}
だと微妙だなと思ったりとかしました。
- いわゆる String Interpolation で
-