1
0

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 3 years have passed since last update.

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

Last updated at Posted at 2020-05-02

0. 初めに

build.gradleplugin.yml の設定は終わらせた前提で進めます。
プロジェクトを作成する を参考にメインクラスを作るまで終わらせておいてください。

また、コマンドを作成して進めていきます。
詳しい説明は 2.2 コマンドを新しく作る でしていますので省略します。

1. plugin.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
object DiceCommand: CommandExecutor {
    override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {

    }
}

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

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

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

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

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

    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サイコロの目は1以上を入力してください"))
            return true
        }

    }

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

    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サイコロの目は1以上を入力してください"))
            return true
        }
        val count = args.getOrNull(1)?.toIntOrNull()
        if(count == null){
            sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
            return true
        }
        if(count < 1){
            sender.sendMessage(getColored("&cサイコロの数は1以上を入力してください"))
            return true
        }
        
    }

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

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

まずは、こんな感じ。

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

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

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

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

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

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

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

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

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

(1 .. max).random()

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

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

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

    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サイコロの目は1以上を入力してください"))
            return true
        }
        val count = args.getOrNull(1)?.toIntOrNull()
        if(count == null){
            sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
            return true
        }
        if(count < 1){
            sender.sendMessage(getColored("&cサイコロの数は1以上を入力してください"))
            return true
        }
        val diceList = rollDice(max, count)
        
    }

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

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

        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ブロックのプレイヤーは取得できないので、サーバー全員に送りましょう。

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

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

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

        } else {

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

        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)entityPlayer だった時のみ処理を行っています。

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

        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 {

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

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

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

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

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

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

    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サイコロの目は1以上を入力してください"))
            return true
        }
        val count = args.getOrNull(1)?.toIntOrNull()
        if(count == null){
            sender.sendMessage(getColored("&cサイコロの数を整数で入力してください"))
            return true
        }
        if(count < 1){
            sender.sendMessage(getColored("&cサイコロの数は1以上を入力してください"))
            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

・周りの人から見たメッセージ
image.png

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

・コンソールから見たメッセージ
image.png

・プレイヤーから見たメッセージ
image.png

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

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

お疲れ様でした。

リンク

次の記事に進む

目次に戻る

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?