Kotlinで流行りのズンドコキヨシを日本語順通りに作りたかった

今流行りのズンドコキヨシチェックをKotlinで便乗してみたくなったのでせっかくなのでRangeとinfix使って日本語の語順通りにしようと頑張りました
### 元ネタ
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Javaの講義、試験が「自作関数を作り記述しなさい」って問題だったから<br>「ズン」「ドコ」のいずれかをランダムで出力し続けて「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら「キ・ヨ・シ！」って出力した後終了って関数作ったら満点で単位貰ってた</p>&mdash; てくも (@kumiromilk) <a href="https://twitter.com/kumiromilk/status/707437861881180160">2016年3月9日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

### ソース

```kotlin
import java.util.*

class ZunDoko(val ite: () -> Iterator<String>) : Iterable<String> {
    override fun iterator(): Iterator<String> {
        return ite()
    }

    // ランダムでcount個の要素を出力しながら次の要素に行くIterable
    fun randomPrint(count: Int): ZunDoko {
        val iterator = {
            object : Iterator<String> {
                var cache: MutableList<String>? = null
                var count = 0

                override fun hasNext() = count > this.count

                override fun next(): String {
                    this.count++
                    if (cache == null) {
                        cache = this@ZunDoko.toMutableList()
                        while (count < cache!!.size) {
                            cache!!.removeAt(random.nextInt(cache!!.size))
                        }
                    }
                    val element = cache!![0].apply { cache!!.removeAt(0) }
                    println(element)
                    if (cache!!.isEmpty()) cache = null
                    return element
                }

            }
        }
        return ZunDoko(iterator)
    }

    // count回ループするIterable
    // countが-1なら無限ループ
    fun loop(count: Int): ZunDoko {
        val iterator = {
            object : Iterator<String> {
                var isColoopEnnded = false
                var cache: MutableList<String>? = null
                var count = 0

                override fun hasNext() = count == forever || count > this.count || isColoopEnnded.not()

                override fun next(): String {
                    isColoopEnnded = false
                    if (cache == null) {
                        cache = this@ZunDoko.toMutableList()
                    }
                    require(cache!!.size > 0)
                    val element = cache!![0].apply { cache!!.removeAt(0) }
                    if (cache!!.isEmpty()) {
                        cache = null
                        isColoopEnnded = true
                        this.count++
                    }
                    return element
                }

            }
        }
        return ZunDoko(iterator)
    }

    // String配列が存在したときにイベント実行するListenerを返す
    fun ifAppear(words: Array<String>): Listener {
        return object : Listener {
            var event: () -> Boolean = { false }

            override fun call() {
                val iterator = iterator()
                var matchCount = 0
                //イテレーターがある限り確認する
                while (iterator.hasNext()) {
                    val element = iterator.next()
                    if (element == words[matchCount]) {
                        matchCount++
                    } else {
                        matchCount = 0
                    }
                    if (matchCount >= words.size) {
                        // eventの返り値がtrueなら処理終了
                        if (event()) {
                            break
                        }
                    }
                }
            }

            override fun addEvent(event: () -> Boolean) {
                val e = this.event
                this.event = {
                    e() || event()
                }
            }
        }
    }

    companion object {
        val random = Random()
    }
}

interface Listener {
    fun addEvent(event: () -> Boolean)

    fun call()
}

operator fun String.rangeTo(that: String) = ZunDoko({ arrayListOf(this, that).iterator() })

infix fun ZunDoko.loop(count: Int) = loop(count)

infix fun ZunDoko.randomPrint(count: Int) = randomPrint(count)

infix fun ZunDoko.ifAppear(words: Array<String>): Listener = ifAppear(words)

infix fun Listener.print(word: String) = this.after {
    println(word)
    false
}

infix fun Listener.after(event: () -> Boolean) = this.apply {
    addEvent(event)
}

// Listenerを呼び出す
fun Listener.writeAndGetUnit() {
    call()
}

// 演算子の左右には値がないといけないので値として宣言しておく
val loopEnd: () -> Boolean = { true }
val one = 1
val forever = -1

fun main(args: Array<String>) {
    ("ズン".."ドコ" randomPrint one loop forever ifAppear arrayOf("ズン", "ズン", "ズン", "ズン", "ドコ") print "キ・ヨ・シ！" after loopEnd).writeAndGetUnit()
}
```
長い(確信)英語力(皆無)

### 実行結果
![zundoko.PNG](https://qiita-image-store.s3.amazonaws.com/0/51176/f41c50e2-87d6-5c9b-2e00-fa158a9d9084.png)


### 肝心の呼び出し
```kotlin
("ズン".."ドコ" randomPrint one loop forever ifAppear arrayOf("ズン", "ズン", "ズン", "ズン", "ドコ") print "キ・ヨ・シ！" after loopEnd).writeAndGetUnit()
```

「ズン」「ドコ」
``` "ズン".."ドコ" ```

いずれかをランダムで出力
``` randomPrint one ```

し続けて
``` loop forever ```

「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら
``` ifAppear arrayOf("ズン", "ズン", "ズン", "ズン", "ドコ") ```

「キ・ヨ・シ！」って出力
``` print "キ・ヨ・シ！" ```

した後終了
``` after loopEnd ```

関数作ったら~~単位貰ってた
``` .writeAndGetUnit() ```

### 残念なとこ
1. 細部まで日本語の語順はやっぱり無理だった

2. 配列のとこでカッコを使ってしまった
``` arrayOf("ズン", "ズン", "ズン", "ズン", "ドコ") ```
文章としてはひとまとまりなとこなのでここではカッコ使いたくなかった

3. 問題(の配列などの個数)が変われば動かなさそうなとこ

### 解説
~~Rangeとinfix処女ズンドコキヨシさんに奪われたので~~Rangeとinfixは初めて自作したので詳しい解説はできないです

#### "ズン".."ドコ"
Rangeです、よくfor(i in 1..10)とかするやつです

```kotlin
operator fun String.rangeTo(that: String) = ZunDoko({ arrayListOf(this, that).iterator() })
```
ここで定義してます、rangeToは決まり文句みたいな感じっぽいです

#### randomPrint one
前述のとこでZunDokoクラスが返ってくるのでこれに拡張関数を定義し演算子のように使えるようにinfixキーワードつけてます

```kotlin
infix fun ZunDoko.randomPrint(count: Int) = randomPrint(count)
```
演算子のように真ん中に関数名がくるので右辺にくるInt型はそれっぽく見えるように他で定義しときます(逃げ)
randomPrint(count)はZunDokoクラスで定義してるものです、拡張関数なのでthisで参照できthisを省略してます

#### loop forever
むげんるーぷしたいのでふらぐを-1にした

#### ifAppear arrayOf("ズン", "ズン", "ズン", "ズン", "ドコ")

```kotlin
infix fun ZunDoko.ifAppear(words: Array<String>): Listener = ifAppear(words)
```

とりあえず左辺は決まってる、右辺は配列にしたい…じゃあ右辺どうするか？arrayOfしかないじゃない！(涙)
返り値のListenerですがこの後続の関数で処理を追加したい&それを追加したうえで最後に処理を実行する必要があるので作ることにしました(いわゆる遅延実行ってやつかな)

#### print "キ・ヨ・シ！" after loopEnd
前述のとこでListenerが返ってくるのでそれに合わせて定義

```kotlin
infix fun Listener.print(word: String) = this.after {
    println(word)
    false
}

infix fun Listener.after(event: () -> Boolean) = this.apply {
    addEvent(event)
}
```

after関数の引数がラムダである必要があるので

```kotlin
val loopEnd: () -> Boolean = { true }
```
と定義します(逃げ)

#### .writeAndGetUnit()
文章の最後で右辺がないのでinfixつけない拡張関数にします

```kotlin
fun Listener.writeAndGetUnit() {
    call()
}
```
Listenerを遅延実行するようにしておいたのでここで呼び出します(終わり)

### さいごに
もっといい実装方法あると思う()