【Kotlin+Spigot】2.4 サイコロを作る

## 0. 初めに
`build.gradle` や `plugin.yml` の設定は終わらせた前提で進めます。
[**プロジェクトを作成する**](https://qiita.com/sya-ri/items/1ecccdba38fd3260f5ff) を参考にメインクラスを作るまで終わらせておいてください。

また、コマンドを作成して進めていきます。
詳しい説明は **[2.2 コマンドを新しく作る](https://qiita.com/sya-ri/items/f69f1489b60650a7dd41)** でしていますので省略します。

## 1. plugin.yml にコマンドを登録する

```yml
commands:
  dice:
    usage: "/<command> [Max] [Count]"
    description: "サイコロを振ります"
```
このようなコマンドを登録しておきます。
サイコロの目とサイコロの数を引数としています。

## 2. 準備する

メッセージに色をつけたいので、`getColored` を用意しておきましょう。

```Util.kt
object Util {
    fun getColored(text: String): String {
        return ChatColor.translateAlternateColorCodes('&', text)
    }
}
```

## 3. コマンドの処理を書く
`CommandExecutor` を継承した `DiceCommand` というクラスを作成します。

```DiceCommand.kt
class DiceCommand: CommandExecutor {
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {

    }
}
```

さあ、引数の処理について考えてみましょう。
まず、引数が２つありますが、それぞれ１以上の整数です。
`args` から取得できる値は文字列なので、整数に変換しなくてはいけません。
また、必ずしも整数に変換できるわけではありませんね。

エラーとなるのは次のような場合です。
・引数が入力されなかった場合
・引数を整数に変換できなかった場合
この二つを一度に処理できたら楽ですよね。
そのためにはこういう書き方をしてみましょう。

```kotlin
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        val max = args.getOrNull(0)?.toIntOrNull()
        
    }
```

前回、`args` の取得には **`[]`** を使いました。これを使うと、戻り値が `null` になることは絶対ありません。
しかし、**存在しない引数を取得しようとすると、例外というエラーが発生** します。
そのために、`isNotEmpty`(`size`) を使って条件分岐したのです。
逆に、**`getOrNull`** を使うと、**存在しない引数を取得した場合には戻り値が `null`** になります。

また、`toIntOrNull` も同様です。`toInt` というのもありますが、**整数に変換できない場合は例外が発生** します。
`toIntOrNull` を使うと **整数に変換できない場合は `null`** になります。

この2つから `max` が `null` だった時、
・引数が入力されなかった場合
・引数を整数に変換できなかった場合
のどちらかに当たるわけです。とても簡単に書くことができました。
では、`null` だった場合のエラーを書きましょう。

```kotlin
        val max = args.getOrNull(0)?.toIntOrNull()
        if(max == null){
            sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
            return true
        }

```

エラーを出したら、それ以降を処理する必要はありません。そのため、`return true` をしておきましょう。
また、`max` は１以上でなければなりません。それも続けて書いてしまいましょう。

```kotlin
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        val max = args.getOrNull(0)?.toIntOrNull()
        if(max == null){
            sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
            return true
        }
        if(max < 1){
            sender.sendMessage(getColored("&cサイコロの目は１以上を入力してください"))
            return true
        }

    }
```

では、同様に `count` も取得してみましょう。

```kotlin
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        val max = args.getOrNull(0)?.toIntOrNull()
        if(max == null){
            sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
            return true
        }
        if(max < 1){
            sender.sendMessage(getColored("&cサイコロの目は１以上を入力してください"))
            return true
        }
        val count = args.getOrNull(1)?.toIntOrNull()
        if(count == null){
            sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
            return true
        }
        if(count < 1){
            sender.sendMessage(getColored("&cサイコロの数は１以上を入力してください"))
            return true
        }
        
    }
```

ほとんど変わりませんね。

では次に、`max` と `count` を引数とした `rollDice` 関数を作ってみましょう。
戻り値は振ったサイコロの目全部です。これには `List<Int>` というものが使えるでしょう。

まずは、こんな感じ。

```kotlin
    private fun rollDice(max: Int, count: Int): List<Int> {
        
    }
```

次に、結果を保存する変数を宣言しましょう。

```kotlin
    private fun rollDice(max: Int, count: Int): List<Int> {
        val result = mutableListOf<Int>()
        
        return result
    }
```

この間を書いていけばいいわけです。
「**`count` 個** のサイコロを振る」というのを言い換えると、「**`count` 回** サイコロを振る」となります。
これをプログラムで書くとこうなります。

```kotlin
    private fun rollDice(max: Int, count: Int): List<Int> {
        val result = mutableListOf<Int>()
        for(i in 0 until count){
            
        }
        return result
    }
```

内部的にはこのような処理になっています。

```kotlin
        var i = 0
        while (i < count){
            
            i ++
        }
```

`i` を 0 で初期化し、`i` が `max` と等しくなるまで 加算され続けます。
要するに、**`count` 回繰り返される** のです。しかし、これより上の書き方が楽なのでそちらを使いましょう。

残りは、ランダムに値を得るだけです。

```kotlin
(1 .. max).random()
```

と書くと 1 以上 `max` 以下 の中でランダムな整数が取得できます。
`Int .. Int` や `Int until Int` は `IntRange` となり、整数の範囲を表現することができます。
これを `rollDice` 内に実装しましょう。

```kotlin
    private fun rollDice(max: Int, count: Int): List<Int> {
        val result = mutableListOf<Int>()
        for(i in 0 until count){
            result.add((1 .. max).random())
        }
        return result
    }
```

これで `rollDice` は完成になりますが、`List` について少し解説します。
**`List`** と **`MutableList`** というものがあります。
`rollDice` の場合は、戻り値は `List` で `result` は `MutableList` になっています。
どちらも値の一覧を保存できますが、`MutableList` では **値の追加や削除** ができます。
逆に、`List` ではそれが出来ません。使い分けは簡単です。
**中身を操作したければ `MutableList`** を使えばいいのです。**それ以外は `List`** を使います。
`rollDice` の戻り値を自由に変更できる必要はありません。その為、`List` で問題ありません。
しかし、`result` が `List` だと値の追加が出来ません。だから、`MutableList` を使うのです。

では、`onCommand` に実装しましょう。

```kotlin
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        val max = args.getOrNull(0)?.toIntOrNull()
        if(max == null){
            sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
            return true
        }
        if(max < 1){
            sender.sendMessage(getColored("&cサイコロの目は１以上を入力してください"))
            return true
        }
        val count = args.getOrNull(1)?.toIntOrNull()
        if(count == null){
            sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
            return true
        }
        if(count < 1){
            sender.sendMessage(getColored("&cサイコロの数は１以上を入力してください"))
            return true
        }
        val diceList = rollDice(max, count)
        
    }
```

メッセージについて考えていきましょう。

`[Dice] {名前} がサイコロを振りました {合計}({目の一覧})`
こんなメッセージにしてみましょう。

```kotlin
        val diceList = rollDice(max, count)
        val result = "&6${diceList.sum()}&7(${diceList.joinToString()}"
```

**`diceList.sum()`** で合計を取得することができます。
例えば `[4, 2]` のようになっていたら `6`　が戻り値になります。
**`diceList.joinToString()`** で目の一覧を取得することができます。
例えば `[4, 2]` のようになっていたら `4, 2` が戻り値になります。区切り文字はデフォルトで `", "` になっています。

次にメッセージの送信です。
実行したプレイヤーだけにメッセージを送っても味気ないので、周囲10ブロックにいるプレイヤーに送るとしましょう。
しかし、コンソールからだと周囲10ブロックのプレイヤーは取得できないので、サーバー全員に送りましょう。

```kotlin
        val result = "&6${diceList.sum()}&7(${diceList.joinToString()})"
        if(sender is Player){
            // 範囲内のプレイヤーにメッセージを送る
        } else {
            // 全員にメッセージを送る
        }
```

まずは、範囲内のプレイヤーにメッセージを送る方を考えてみましょう。

```kotlin
        if(sender is Player){
            val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")

        } else {
```

基本的には変わっていません。
では、範囲内のプレイヤーにメッセージを送る方法です。

```kotlin
        if(sender is Player){
            val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")
            sender.getNearbyEntities(10.0, 10.0, 10.0).forEach { entity ->
                if(entity is Player){
                    entity.sendMessage(message)
                }
            }
        } else {
```

**`sender.getNearbyEntities(10.0, 10.0, 10.0)`** で `X, Y, Z` それぞれ10ブロック以内のエンティティを取得しています。
**`.forEach { entity -> }`** で取得したエンティティ全ての処理を行っています。
**`if(entity is Player)`** で `entity` が `Player` だった時のみ処理を行っています。

ただし、これだと `sender` 自身にはメッセージが送られません。

```kotlin
        if(sender is Player){
            val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")
            sender.getNearbyEntities(10.0, 10.0, 10.0).forEach { entity ->
                if(entity is Player){
                    entity.sendMessage(message)
                }
            }
            sender.sendMessage(getColored("&b[Dice] &fサイコロを振りました $result"))
        } else {
```

それ用のメッセージを書いて終わり。

次に、全員にメッセージを送る方を考えてみましょう。

```kotlin
        } else {
            val message = getColored("&b[Dice] &6サーバー &fがサイコロを振りました $result")
            Bukkit.broadcastMessage(message)
        }
```

最後に `return true` を書いたら完成です。

```kotlin
        } else {
            val message = getColored("&b[Dice] &6サーバー &fがサイコロを振りました $result")
            Bukkit.broadcastMessage(message)
        }
        return true
```

こんな感じになっていると思います。

```kotlin
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
        val max = args.getOrNull(0)?.toIntOrNull()
        if(max == null){
            sender.sendMessage(getColored("&cサイコロの目を整数で入力してください"))
            return true
        }
        if(max < 1){
            sender.sendMessage(getColored("&cサイコロの目は１以上を入力してください"))
            return true
        }
        val count = args.getOrNull(1)?.toIntOrNull()
        if(count == null){
            sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
            return true
        }
        if(count < 1){
            sender.sendMessage(getColored("&cサイコロの数は１以上を入力してください"))
            return true
        }
        val diceList = rollDice(max, count)
        val result = "&6${diceList.sum()}&7(${diceList.joinToString()})"
        if(sender is Player){
            val message = getColored("&b[Dice] &6${sender.displayName} &fがサイコロを振りました $result")
            sender.getNearbyEntities(10.0, 10.0, 10.0).forEach { entity ->
                if(entity is Player){
                    entity.sendMessage(message)
                }
            }
            sender.sendMessage(getColored("&b[Dice] &fサイコロを振りました $result"))
        } else {
            val message = getColored("&b[Dice] &6サーバー &fがサイコロを振りました $result")
            Bukkit.broadcastMessage(message)
        }
        return true
    }
```

## 4. コマンドを登録する

```Main.kt
class Main: JavaPlugin() {
    override fun onEnable() {
        registerCommand("dice", DiceCommand())
    }

    private fun registerCommand(label: String, executor: CommandExecutor){
        val command = getCommand(label)
        if(command != null){
            command.setExecutor(executor)
            logger.info("/$label を登録しました")
        } else {
            logger.severe("/$label を登録できませんでした")
        }
    }
}
```

では、サーバーでテストしましょう。

まずはプレイヤーが実行してみましょう。

・自分から見たメッセージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628547/d527280a-36e5-d6f0-9289-ca0012a023ac.png)

・周りの人から見たメッセージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628547/182852b3-9e9e-6428-fde3-32b3a888ccb0.png)

次にコンソールから実行してみましょう。

・コンソールから見たメッセージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628547/027af1f9-c615-f13a-23a5-872f579db609.png)

・プレイヤーから見たメッセージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628547/e787cdf2-a664-27a2-d306-7a670b65cc40.png)

このように表示されたら成功です。

表示範囲を引数のオプションとして追加したり、メッセージに振ったサイコロの情報を載せてみたりと、
たかがサイコロと思われるかもしれませんが、工夫できる点はたくさんあります。
今回作ったものを自分なりに改良してみてはいかがでしょうか。

お疲れ様でした。

## リンク
### [次の記事に進む](https://qiita.com/sya-ri/items/cc8be71c42dba8f86066)
### [目次に戻る](https://qiita.com/sya-ri/items/bc7eb01ed687b9ff9566#%E7%9B%AE%E6%AC%A1)