【プチコン4】乱数で文字色を変えるプログラム #2
前回までのあらすじ
プチコン4(SmileBASICv4.4.3)で0~16,777,216の乱数からRGBの色コードを読み取りテキストスクリーン上に描画する文字の色を変えるプログラムを書き、その色コードの値でRPGのキャラクターID番号を振り分けるという計画をぶち上げました。
記事で紹介したchitoseArkさんにも話題にしていただき、ありがとうございました。noteの縁ってこうやって広がるんですね……。Pi STARTERのフォーラムでも一緒に考えていただきありがとうございます。
16,777,216枚の札を重複させず配り終える「RGB色コードかるた(仮称)」。
全部でなくても、ざっと一万人分もあればゲームとしては充分すぎるくらいかもしれませんね……と弱音を吐いてしまいそう。それでも多いけど。
果たしてそんなことが可能なのでしょうか?
ピンチの連続、そんな時「重複しない乱数」がほしい
前回の投稿後、ツイッターで改めてアドバイスを頂きました。みなつさん、ありがとうございました。
♪テーン
「重複しない乱数」
( ゚д゚ )・・・
そんな喉から手が出るほど欲しかったようなプログラムを実行すると、以下のように表示されます。
た、たしかに10個の乱数0~9が重複せず並べられている……!
このプログラムの内容について僕なりに嚙み砕いて解説します。プチコン4の細かい言語要素についてはリファレンスサイト/公式リファレンスブックをご参照ください。
(念のため、プチコン4環境で可能なことがプチコン3号及びPi STARTER環境では不可能な場合があることにご留意ください。)
重複しない(ユニークな)ダイス値を返すプログラムを解説
まず、FOR~NEXT文にユーザー関数UNIQ_DICEを入れて短いメインループを回しているのがわかります。
FOR I = 1 TO 10
?I;":";UNIQ_DICE(10) '?はPRINT文の省略形'
NEXT
END
DEF UNIQ_DICE(DICE_MAX) 'DICE_MAX未満の、重複しない(ユニークな)ダイス値を返す'
UNIQ_DICE(10)と書く事で、ユーザー関数UNIQ_DICE(DICE_MAX)の引数DICE_MAXに10が代入されます。ここが乱数の最大値にもなります。
ダイス(乱数)を振る前に、出たダイス値を記録しておく配列MEMOの全要素数をLENで調べ、それがDICE_MAXの値を上回ったら「これ以上出ていない目がありません」としてプログラムを中断するようになっています。
ACLS
DIM MEMO[0] '出たダイス値を記録しておく配列'
(中略)
IF LEN(MEMO) >= DICE_MAX THEN ?"これ以上出ていない目がありません":STOP
配列MEMOはプログラムの冒頭に次元数0で指定されているため、イメージとしては……後で配列の末尾に変数の値を追加する際に、ボールを入れる箱を1つずつ増やしているような処理ということでしょうか? はっきり理解していません(空配列宣言、という声に出して読みたい専門用語がありますが、それと同じかもよくわかっていません)空配列宣言で合っていました。
続くREPEAT~UNTIL文でダイスを振る処理がループしています。
REPEAT
D = RND(DICE_MAX)
UNTIL FIND(MEMO,D) == -1 '配列MEMOにDの値が見つからなければループを抜ける'
PUSH MEMO,D '配列MEMOの末尾にDの値を追加'
FINDは指定の値(この場合乱数を代入した変数D)を持つ要素を配列から検索します。同じ値が見つからなければ-1を返し、これはUNTILの式の結果が真(TRUE)となるためループ処理を抜けて次のPUSH(配列MEMOの末尾にDの値を追加)の処理に移ります。
そしてDEFの返り値を設定して呼び出し元に復帰する命令RETURNで変数Dの値を返し、ユーザー関数UNIQ_DICEの処理は終了します。
RETURN D
戻ってきたところでPRINT文にはDの値が表示され、これが10回繰り返される、かつプログラムを変更して11回以上繰り返そうとすると必ず処理が中断する仕組みになっているわけですね。
勉強になりました。
それにしても読めば読むほど溜息がこぼれるような、機能美に惚れ惚れするプログラムですね。確かにリファレンスブックにも書いてあってプチコンで実際に動いてるわけですが、僕一人では考えもつかなかったプログラミング技術です。
プログラムの最後にあるユーザー関数RESET_DICEはメインループ上では機能していないようですが、念のためのプログラム初期化処理として、やはりよくできています……(RESIZEは配列の要素数を変更する処理で、配列MEMOの要素数を0にする=配列を空っぽにする)。
DEF RESET_DICE 'MEMOを空っぽにする'
RESIZE MEMO,0
END
GATTAI
上記のプログラムをFOR I = 1 TO 16777216、?I;":";UNIQ_DICE(16777216)に変更して実行したら、猛スピードで何十万という数を配っていくプチコンが見れると思います。
百万を超えたあたりから先がどうにも時間がかかりすぎるので10,000,000回目で配列の要素数が足りずエラー落ちすると思われる(もしくは配列の終端をFOR文の終わりまで変更し続ける?)ところまでチェックできませんでしたが、この分ならほぼ理想的な結果と言えそうです。
では、さっそくこのプログラムを前回のものに組み込みましょう。
(後日プチコンの画面も併せて貼る予定です)ご用意できず申し訳ありません。
OPTION STRICT '変数宣言'
ACLS
GCLS RGB(255,255,255) '背景色を白に変更'
'---------------------------------------------------------------------'
' 変数宣言'
'---------------------------------------------------------------------'
VAR D = 0, SUM = 0, R_VAL = 0, G_VAL = 0, B_VAL = 0, DICE_MAX = 0
DIM SCC[0] 'Soul.Color.CHORD'
'---------------------------------------------------------------------'
' プログラム初期化処理'
'---------------------------------------------------------------------'
RESET_DICE
'---------------------------------------------------------------------'
' メインループ'
'---------------------------------------------------------------------'
LOOP
SUM = SUM + 1
IF SUM == 256 THEN BREAK 'カンストしたらループ抜ける'
PRINT "■",R_VAL,G_VAL,B_VAL,UNIQ_DICE(16777216)
VSYNC 1
ENDLOOP
'---------------------------------------------------------------------'
' 終了処理'
'---------------------------------------------------------------------'
COLOR RGB(0,0,0) '文字色リセット'
BEEP 2
LOCATE 35,27 'テキスト座標位置'
PRINT "Es ist gut"
END 'プログラムを終了する'
'---------------------------------------------------------------------'
' ユーザー関数'
'---------------------------------------------------------------------'
' DICE_MAX未満の、重複しない(ユニークな)ダイス値を返す'
'---------------------------------------------------------------------'
DEF UNIQ_DICE(DICE_MAX)
IF LEN(SCC) >= DICE_MAX THEN PRINT"これ以上出ていない目がありません":STOP
REPEAT
D = RND(DICE_MAX)+1 '1~16777216'
UNTIL FIND(SCC,D) == -1 '配列SCCにDの値が見つからなければループを抜ける'
PUSH SCC,D '配列SCCの末尾にDの値を追加'
RGB D OUT R_VAL,G_VAL,B_VAL 'カラー値取得'
COLOR RGB(R_VAL,G_VAL,B_VAL) '文字色変更'
RETURN D
END
'---------------------------------------------------------------------'
' SCCを空っぽにする'
'---------------------------------------------------------------------'
DEF RESET_DICE
RESIZE SCC,0
END
前回との主な変更点について
前回はC_ROLLというユーザー関数を用い、変数CにRND関数で得た乱数を入れて文字色を変更してからメインループにとって返し、色の付いた■と各種変数を表示する形でしたが今回はユーザー関数UNIQ_DICEをPRINT文に直接並べたのでたった1行の差ですがプログラムがすっきりしました(この1行の差がスパゲッティコードとそれ以外を分けることを、皆さんにもわかっているはずだ)。
そしてUNIQ_DICEの処理はほとんどそのまま、末尾に前回使ったC_ROLLの変数Cからカラー値を取得し文字色を変更する処理を加えています。
そもそもC_ROLLのCとはColorのことだったのですが、「DICE_MAX未満の、重複しない(ユニークな)ダイス値を返す」というそのものズバリな形になったので乱数を代入する変数をDとしています。
最後に小さな変更ですが終了処理のBEEPも5(Gauge up)から2(Square)に変えて「プログラムの処理に終止符が打たれました!」って雰囲気にしています。まあここは別に好きな音に変えていただいてOKです。
IF SUM == 256 THEN BREAKにおけるSUMの値を16,777,216以内の数に変更することで好きなだけ「RGB色コードかるた(仮称)」を配ることができます! と言っても肉眼での重複チェックはVSYNCで処理を遅くしてスクリーンショットを撮っても時間がかかりすぎますが……。
「重複しない乱数」のプログラムが短くてシンプルなのでバグは少ないとは思うのですが、念には念を入れて人間が確認するにはExcelに入力してチェックするのが一番だと思います。
しかし想像ではもっと簡単にできると思っていた、画像から数字を文字起こしするフリーソフトがうまくいかなかったので連休の間に確認作業を済ませることができませんでした。……この期に及んでExcel手打ちが面倒臭いとか言ってられないですよね……OTL
とりあえず今はプログラムを実行するたびに乱数を垂れ流している状態なので、ある程度セーブ&ロードできたり個別に呼び出しや検索などができればより実用化に向けて一歩踏み出せるかな? といった感じです。
フォーラムでお考えいただいたアイデアについても「プログラムの正解は一つじゃない」ということでぜひ研究させていただきたいです。
今回の報告は以上です。
……やっぱりフレーバーテキストとして使うのが一番楽でいいのかな。
(終)
この記事が気に入ったらサポートをしてみませんか?