【追加機能編】Misskey Playを作ってみよう#2

どうも、揚げられたじゃがいもです
マックのポテトみたいに細長く切られました

前回はテンプレート編として、おみくじのプリセットをいじりました
今回はそのいじったプリセットに対して、新しい機能を追加してみようと思います


はじめに

今回のシリーズを読んでみて、分からないことがあったら前回の記事を読んでみることをおすすめします(それでも分からなかったりしたらコメントなりできいてね)

追加する機能を考える

まず、前回作ったものは

起動すると
午後○〇をお知らせします。
という文字列が表示され、その投稿ができるというものでした

どうしたらより面白くなるでしょう?
そこで今回は

  1. 「午後」の部分も「午前」か「午後」からランダムに選ばれるようにする

  2. 選ばれる確率を自由に操作できるようにする

ということをします

ランダムな選択肢の追加

1.「午後」の部分も「午前」か「午後」からランダムに選ばれるようにする
の部分を考えてみます

このPlayには元々、午後○○の部分について○○に入れるものを1時~12時の中から選ぶという機能がありました、これを午後の部分にも応用しようというものです

前回のPlayのコードを、一旦すべて見てみます

// 選択肢
let choices = [
	"1時"
	"2時"
	"3時"
	"4時"
	"5時"
	"6時"
	"7時"
	"8時"
	"9時"
	"10時"
	"11時"
	"12時"
]

// シードが「PlayID+ユーザーID+今日の日付」である乱数生成器を用意
let random = Math:gen_rng(`{THIS_ID}{USER_ID}{Date:year()}{Date:month()}{Date:day()}`)

// ランダムに選択肢を選ぶ
let chosen = choices[random(0, (choices.len - 1))]

// 結果のテキスト
let result = `**午後{chosen}** をお知らせします。`

// UIを表示
Ui:render([
	Ui:C:container({
		align: 'center'
		children: [
			Ui:C:mfm({ text: result })
			Ui:C:postFormButton({
				text: "投稿する"
				rounded: true
				primary: true
				form: {
					text: `{result}{Str:lf}{THIS_URL}`
				}
			})
		]
	})
])

これは

  1. choicesという選択肢のリストを作る

  2. ランダムな数値を生成する機能を持ってくる

  3. choicesからランダムな文字列を選び、chosenという変数に格納する

  4. 午後○○を~という文字列にchosenを埋め込み、resultという変数に格納する

  5. resultを結果として画面に表示する

という流れでした

この中で、1~3は選択肢の準備であり、4が結果への埋め込みです
これを真似すれば新しい選択肢を増やせそうです

選択肢のリストを作る

まず1の部分を見ると

// 選択肢
let choices = [
	"1時"
	"2時"
	"3時"
	"4時"
	"5時"
	"6時"
	"7時"
	"8時"
	"9時"
	"10時"
	"11時"
	"12時"
]

これを真似してその真下の行に新しい選択肢のリストを作ります

// 選択肢
let timeScope = [
	"午前"
	"午後"
]

変数名はtimeScope(時間の範囲)としておきました
好きなもの(分かりやすいもの)でいいですが、他の変数と名前が被らないようにしてください

2の部分については使いまわすので、その機能がrandomという名前であることだけ知っていてください

ランダムな選択肢を選ぶ

3の部分になります、ここは

// ランダムに選択肢を選ぶ
let chosen = choices[random(0, (choices.len - 1))]

と対応します

上では選択肢がchoicesで、結果を入れる変数がchosenだったので、とりあえず
・choicesをtimeScopeに書き換える
・chosenをchosenScopeに書き換える
ということをします(choicesは2回書かれていることに注意)

// ランダムに選択肢を選ぶ
let chosenScope = timeScope[random(0, (timeScope.len - 1))]

これを真下の行に書き加えます
ここから処理を細かく見ていきます
まず

timeScope[ … ]

の部分について、これは

リスト[数値]

とするとその番号の要素が得られるという機能です
(前回の記事で、リストは中身を「要素」として持ち、順番がある、つまり、番号が付けられているということを説明しました)

ここで、「番号」がどのように付けられているかを知る必要があります
多くの場合、プログラミングでは番号を
0から始まる整数
として扱います(1からではなく0から)
この番号のことを、インデックスと呼びます

例えば

let abc=[
    "a"
    "b"
    "c"
    "d"
    "e"
]
let first=abc[0]
let second=abc[1]

とすれば、firstには"a"が入り、secondには"b"が入ります

リストに関して、もう一つ

[
    要素1
    要素2
    …
]

という改行で区切る書き方以外に

[要素1,要素2,…]

[要素1 要素2 …]

のように、コンマや空白でも区切ることができます


Note
区切りが改行であるなら
[
    要素
]
とせずに
[
要素
]
のようにタブ(または半角の空白)を使わずに表現してもいいのではないか?と思うかもしれません
たしかにそれでも動きやすいのですが、これは「読みやすくするため」にあります、何か構文で囲むようなときには大体隙間を開けます
この考え方をインデント(字下げ)と呼びます


リストについて学んだので、timeScope[~]の中身は整数になるはずだということが分かります
つまり

random(0, (timeScope.len - 1))

の部分が整数になるということです
randomというのはその前の行で作った、ランダムな整数を生成する機能のことでした
このrandomという機能に対して、かっこ書きで続けて機能を利用するための情報を書き込んでいます
具体的には

random(範囲の始まり , 範囲の終わり)

となり、この範囲の中でランダムな値を返してくれるように機能に頼んでいることになります(注意:範囲の終わりの値は生成される数値の中に含まれる)

このような「機能」(今回はrandom)のことを関数といい、渡した情報(かっこ書きの中身)のことを引数(ひきすう)といいます
返って来る値のこと(今回はランダムな整数)はそのまま返り値と呼びます


Note
初学者は時々引数を「いんすう」と呼んでしまうことがあります
というより、僕もしばらくそれをしていました
テキストだと読み方をあまり教えてくれないので仕方ないですね…


timeScope.lenというのは

リスト.len

とするとその長さ(length、つまりリストが持っている要素の数)を返してくれるものです
ここで、要素数がtimeScope.lenであるということは、インデックスがtimeScope.lenである要素は存在しないことに注意してください、最初はよく間違えるのですが、0から始まるので最後の要素のインデックスはtimeScope.len - 1になります

結果を格納する

4の部分は

// 結果のテキスト
let result = `**午後{chosen}** をお知らせします。`

となっていました、これは前回の記事で見た`で囲む文字列です
{}の部分を埋め込んでいるので

// 結果のテキスト
let result = `**{chosenScope}{chosen}** をお知らせします。`

と書き換えれば結果を格納することができます(ここは追加ではなく書き換えです)
この先はresultを結果として表示したりするので、いじる必要はありません

これまでのコードを追加した結果

コードについてですが、真下に書くようにする、のような指定をしましたが必ずしもその順番である必要はありません
ただし、基本的に上の行から順番に実行されることは覚えておいてください
つまり、timeScopeと名付けた変数をまだ作っていないのに、timeScopeを使うことはできないというようなことです


Note
「基本的に」上の行から実行される
と言いました、例外は関数を使った場合などです
関数は用意されていたり、自分で作ったりします
その中身はコードの集合で、一旦今実行している場所を離れてその関数のコードの最初に移動し、関数の処理が終わったら元々実行していた場所に返り値を持って帰ってきます(返り値はないこともあります)
また、指定した条件の中で同じ処理を何回も繰り返す「ループ」を行うような構文も後々登場します


確率を変化させる

次に、2.選ばれる確率を自由に操作できるようにする
をやります

randomの機能

ここで、randomの機能についてもう少し細かく見ていきます
これは

random(範囲の始まり , 範囲の終わり)

というように使いました
このとき返り値となる乱数は、すべて同じ確率で出てきます(いわゆる、「同様に確からしい」)
つまり、1~4が出てくるように指定すれば、1,2,3,4それぞれが出てくる確率は全部同じ1/4となります

簡単な操作方法

特定の選択肢だけ多く出るようにしたい、となったときに簡単に実装する方法としては

// 選択肢
let timeScope = [
	"午前"
	"午後"
	"午後"
	"午後"
]

というように重複する選択肢を何個も置くことで操作できます(この場合、午前は1/4、午後は3/4で出てくる)

詳細に確率を指定する(条件分岐)

上記の方法では、当たりを1%の確率にしたい!となったときにハズレを99個書かないといけなくなります、そうするとすごい読みづらくなるので別の方法を考えます

そこで使うのが「条件分岐」です
これは条件が満たされたときにだけ処理を行うもので、以下のように書きます

if (条件) {
    処理
}

条件が満たされると、中の処理を行う構文です、これをif文と言います(ifは「イフ」と読む)
注意:ifの後には必ず空白を置いてください(しないとエラーになるので)

条件の書き方は色々ありますが、今回は大小の判定だけにします

例えば

a > 10

これは「aが10より大きいとき」を表します(数学のものとおおよそ同じと考えてください)
注意:>は半角です(プログラミングでは基本的に構文として全角文字を使いません)

a <= 5

と書けば、「aが5以下のとき」を表します
<と<=、>と>=はその値を含むかどうかの細かい違いですが、プログラミングではその小さな違いがバグの元になります(例えば、0より大きい値が欲しいのに、0を通してしまうと、割り算の処理に入れてバグったりするなど)

同じ値であることを条件にしたい場合は

a == 1

(例は「aが1のとき」)
ここで、「数学と同じなら、=じゃないか?」と思うかもしれませんが、=は変数に使うので条件では==を使います

if文には、以下のような構文を追加できます

if (条件1) {
    処理1
} else if (条件2) {
    処理2
}

これは、「条件1は満たされたときは処理1を行う」ことは同じですが、「条件1が満たされない場合に、条件2が満たされれば処理2を行う」という意味です
これ(else if以降)をelse if文(else if部)と言います(else ifは「エルス イフ」)
(else文も同様に、間に空白を書く必要があります)

これは

if (条件1) {
    処理1
} else if (条件2) {
    処理2
} else if (条件3) {
    処理3
}

というように何個も連ねることができます
この場合、処理3が行われるのは「条件1も条件2も満たさないけれど、条件3は満たすとき」になります、つまり、条件の適用は上から順に行って、条件が満たされたらその先は無視するということになります

最後に、if文にはもう一つ構文を付けられます

if (条件1) {
   処理1
} else if (条件2) {
    ...
} 
...(この間には書きたいだけelse if文を書ける)
} else if (条件n) {
	処理n
} else {
	処理x
}

ここで新しいのは最後のelseの部分です
これは、「どの条件も満たさなかったときに処理xを実行する」ことを意味し、else文(else部)と言います

これを使うと、詳細な確率操作ができるようになります
それは「乱数を生成して、この範囲の値が生成されたらこの選択肢を選ぶ」と言ったようにします
なので
午前を10%、午後を90%で選びたい
と思ったら

// ランダムに選択肢を選ぶ
let chosenScope = timeScope[random(0, (timeScope.len - 1))]

の部分を

// timeScopeからランダムに選ぶ
var chosenScope=""
let randomNum=random(1 , 10)
if (randomNum==1) {
	chosenScope=timeScope[0]
} else {
	chosenScope=timeScope[1]
}

のように書き換えます
注意:範囲を0~10のようにすると、午前の確率が1/11になります、範囲はよく考えましょう

ここで

var

とは、letと同じような意味ですが、「後から中身を書き換えることができる」変数を作ることができます

var chosenScope=""

の部分は変数の宣言といい(初期値として中身のない文字列を入れています、letのときも同じように宣言といいます)

chosenScope=timeScope[0]
chosenScope=timeScope[1]

の部分は変数の代入といいます

宣言は新しく変数を作成するための処理で、代入は変数の中身を変更するための処理です、宣言は1度だけ行います、代入は何回でも行えますが、先に宣言している必要があります

変数を使いたいとき、varから始まる構文で作成すると、その後は=で自由に書き換えられると覚えておきましょう


Note
varで宣言すれば後から書き換えられるのであれば、letを使う意味はないのではないか?と思うかもしれません
実はletの方は変数というより定数という概念の方が正しいです
これは「後から書き換えられない」ので、処理中にずっと同じ値の場合、書き換えられると困るのでletにして固定したりします、場合によっては定数を使うことで処理の高速化が行える場合があります
また、可読性を高めるために変数の中身は書き換えない方がいいという派閥もあります(変化すると、今どの値なのか分からなくなるなど)
ある程度前のバージョンのAiScriptでは、letで宣言した場合でも書き換えることができていました、それまでずっとletを使っていたので、アップデートが来たときに自作のPlayがほとんど動かなくなるという障害が起きていました


まとめ

機能追加後のコード全体を置いておきます

// 選択肢
let choices = [
	"1時"
	"2時"
	"3時"
	"4時"
	"5時"
	"6時"
	"7時"
	"8時"
	"9時"
	"10時"
	"11時"
	"12時"
]

// 選択肢
let timeScope = [
	"午前"
	"午後"
]

// シードが「PlayID+ユーザーID+今日の日付」である乱数生成器を用意
let random = Math:gen_rng(`{THIS_ID}{USER_ID}{Date:year()}{Date:month()}{Date:day()}`)

// ランダムに選択肢を選ぶ
let chosen = choices[random(0, (choices.len - 1))]

// timeScopeからランダムに選ぶ
var chosenScope=""
let randomNum=random(1 , 10)
if (randomNum==1) {
	chosenScope=timeScope[0]
} else {
	chosenScope=timeScope[1]
}

// 結果のテキスト
let result = `**{chosenScope}{chosen}** をお知らせします。`

// UIを表示
Ui:render([
	Ui:C:container({
		align: 'center'
		children: [
			Ui:C:mfm({ text: result })
			Ui:C:postFormButton({
				text: "投稿する"
				rounded: true
				primary: true
				form: {
					text: `{result}{Str:lf}{THIS_URL}`
				}
			})
		]
	})
])

これを基にして、挨拶を選択肢として付け加えたり、ランダムに全く違う言葉を言うようにしたりしてもいいかもしれません(今回作ったプログラムはテンプレートとして使っていいよ)

では、また次の記事で

この記事が気に入ったらサポートをしてみませんか?