// 数当てゲーム - 構造化バージョン #packopt name "数当てゲーム" #packopt hide 1 // 定数(= const)を宣言 #const IDW_Main 0 // メイン画面のウィンドウID #const MAX_ANSWER 100 // 答えの最大 #const ANS_LIMIT 6 // 回答できる限界 *main gosub *initialize // 一番最初のサブルーチン gosub *SetVariable // 変数を用意 gosub *SettingWindow // スクリーンを構築 gosub *CreateQuestion // 問題を作る gsel IDW_Main, 1 stop //######## サブルーチン群 ######## *initialize randomize // 一回だけ使えばいい gsel 0, -1 return // 変数を用意 *SetVariable dim question // 問題の答え dim answer // プレイヤーの回答 dim nAnsCount // 回答した回数 dim ID_Edit // input のオブジェクトID return // スクリーンを構築する *SettingWindow screen IDW_Main, 480, 320, 2 // メイン画面 title "数当てゲーム ver.1.0" // タイトルバーには、ゲーム名を表示させる objsize 80, 25 // ボタンの大きさを設定 pos 20, 50 : mes "1 〜 "+ MAX_ANSWER +"までの私を当ててみよ!" pos 30, 80 : input answer, 120, 24 // 回答 pos 160, 80 : button gosub "答える", *OnAnswer // 答え合わせ pos 50, 140 // カレントポジションを移動 return // 画面をリセット *ResetWindow redraw 2 color 255, 255, 255 : boxf : color sysfont 0 // 最初のフォント pos 20, 50 : mes "1 〜 "+ MAX_ANSWER +"までの私を当ててみよ!" // もう一度書く(消されたから) pos 50, 140 redraw 1 return // 問題を作る *CreateQuestion // 0〜MAX_ANSWER-1 なので、+1 して 1〜MAX_ANSWER にする question = rnd(MAX_ANSWER) + 1 return // 判定処理 ( button で飛んでくる ) *OnAnswer // ※判定する順番は大事です。 nAnsCount ++ // 答えた回数を数える if ( answer == question ) { // 正解 gosub *Clear // 正解したときのラベルに飛ぶ } else : if ( (1 <= answer && answer <= MAX_ANSWER) == 0 ) { mes "1 〜 "+ MAX_ANSWER +" で選んでね。" } else : if ( answer * 2 < question ) { // 問題は、回答の二倍以上 mes "小さすぎない?" } else : if ( answer / 2 > question ) { // 問題は、回答の半分以下 mes "大きすぎない?" } else : if ( answer < question ) { // 問題は、回答より大きい mes "もっと大きいよ" } else : if ( answer > question ) { // 問題は、回答より小さい mes "もっと小さいよ" } // ここに来るということは、正解じゃない if ( nAnsCount >= ANS_LIMIT ) { // 限界まで答えていたら gosub *TimeUp // 時間切れのラベル } return // 正解した *Clear color 0, 0, 255 font msgothic, 50, 1 mes "正解!お見事!!" dialog "正解です!\nあなたは "+ nAnsCount +"回で正解しました", 0, "おめでとう" // 画面をリセット gosub *ResetWindow // 問題変更 nAnsCount = 0 gosub *CreateQuestion return // 時間切れ *TimeUp color 255, 0, 0 font msgothic, 60, 1 mes "タイムアップ!!" dialog "時間切れです。答えは "+ question +"でした。", 0, "残念" // 画面をリセット gosub *ResetWindow // 問題変更 nAnsCount = 0 gosub *CreateQuestion return
こんにちは、上大です。
↑にあるのはこの前作成した数当てゲームを、構造化したものです。
さて、今回は、数当てゲームのバージョンアップです。
あのままじゃどう考えてもシンプルすぎます!
というわけで、タイトル画面を実装しようと思います。
タイトル画面というのは、ゲームが起動されたときに表示される画面のことです。
いきなりゲームが開始するのではちょっと、おもしろみが欠けますね?
主に、タイトル画面には、以下の3つがあれば良いでしょう。
ちなみに、今回の実装ではべた塗りの背景を使います。
僕は、画像を用意するのが得意ではないので。(>*<;)
とりあえず、「項目選択」から始めましょう。
難しいのはこれの実装だけです。
選択させるには、主に3つのやり方があります。
それぞれの長所と短所を書き出してみます。
ボタン選択 | 領域クリック | カーソルで選択 | |
---|---|---|---|
外見 | オブジェクト | なんでもあり | (キー入力) |
長所 (メリット) |
プログラムが簡単。 変数が不要。 |
見た目が自由。 ループが不要。 |
選択する雰囲気が出る。 |
短所 (デメリット) |
見た目が悪い (シンプルすぎる) |
プログラムが難しい。 変数をたくさん使う。 |
プログラムが長くなる。 キー入力を監視する必要がある。 項目の配置を、カーソルの動き方がわかるよう考慮する必要がある。 |
とりあえずサンプルをそれぞれ用意しました。
[ボタン・領域クリック・カーソル]
さて、今回は「カーソル選択」の方法を使います。
というわけで、stick命令の知識が必要です
// stick を使う #const IDW_Main 0 // 今回は絶対いらない! *main gosub *SettingWindow goto *mlp *mlp redraw 2 color 255, 255, 255 : boxf : color // 変数 keys にキー押し下げ状況が格納される stick keys, -1 // -1 は、すべて非トリガータイプになります。 pos 20, 20 mes "keys = "+ keys redraw 1 await 17 goto *mlp *SettingWindow screen IDW_Main, 240, 180 title "stick test" return
こんなに簡単なスクリプトなら、構造化する必要がありません。
*mlp ラベルの部分だけで良かったのに……
それは置いておいて、↑のスクリプト実行中に、キーをいくつか押してみてください。
気付いたかもしれませんが、複数同時に押すと、数が加算されます。
stick命令を実行すると、p1 (↑では keys ) の変数に状況を代入します。
押し下げ状態を感知できるキーの数は少なめですが、一度にいくつもチェックできるので便利です。
しかし、さっきのように加算されてしまうと、
if ( keys == 1024 ) : // [Tab]キーが押されたなら……
のように相等演算子 == でやると、失敗します。
さて、どうしましょう。
stickのワンキー・ヘルプを見てください。
キーごとに、値が2倍に増えていっているのに気がつきましたか? (知らなきゃ普通気付きません)
これを利用します。
2倍に増えていっているということは、2のN乗であることが分かります。
値が2のN乗かを調べて、違ったら、値より小さい数の中で、一番大きい2のN乗になる数を探します。
その探した数のキーは押されています。
元の値からこのキーの値を引いて、また元に戻ります。
これを元の値が0になるまで続けていくと、押されたキーの組み合わせが分かります。
ちょっと難しいのですが、↓のサンプルでやってみています。
// stick から組み合わせを取得 #const IDW_Main 0 // 今回も絶対いらない! *main gosub *SettingWindow goto *mlp *mlp redraw 2 color 255, 255, 255 : boxf : color stick keys, -1 // 変数 keys にキー押し下げ状況が格納される pos 20, 20 mes "keys = "+ keys repeat if ( keys == 0 ) { break } // keys が2のN乗かどうかを調べる bBinPow = 0 // 2のN乗であるかどうかを表す数 npow2_n = 1 // 2のN乗の数 repeat 10, 1 if ( keys == npow2_n ) { bBinPow = 1 : break } // なる npow2_n *= 2 loop if ( bBinPow != 0 ) { mes "Down : "+ keys break } // keys より小さい中で最大の、2のN乗の数を求める npow2_n = 1 repeat 10, 1 // 2倍して keys を超えるなら、ここでやめる if ( (npow2_n * 2) > keys ) { break } npow2_n *= 2 // 2倍する loop // keys からその値を引く mes "Down : "+ npow2_n // 押されていたことを表す keys -= npow2_n // 始めに戻る loop redraw 1 await 17 goto *mlp *SettingWindow screen IDW_Main, 240, 180 title "stick test" return
さて出来ました! 完璧です!
完璧ですが、…………面倒です!!!!
こんな面倒な命令があるわけがありません。
当然、もっと簡単な方法があるはずです。
簡単な方法は、あるにはあります。これを使うと、一発で押されているか分かります。
しかし、説明していると非常にめんどうです。しかも非常にわかりにくいので、説明は省かせてもらいます。
狂う可能性99%の計画では、中級編のどこかで説明しますが、今はstick用として使ってください。
ビット演算子and(&)です。
使い方だけ説明します。
stickのp1の変数に代入された値と、押されてるか調べたいキーの値を、& して、if文の条件にします。
押されていたら、条件が成立します。
// stick の値を & で判定 #const IDW_Main 0 // 今回も絶対いらない! *main gosub *SettingWindow goto *mlp *mlp redraw 2 color 255, 255, 255 : boxf : color stick keys, -1 // 変数 keys にキー押し下げ状況が格納される pos 20, 20 mes "keys = "+ keys npow2_n = 1 repeat 11 if ( keys & npow2_n ) { // 押されていたら mes npow2_n } npow2_n *= 2 loop redraw 1 await 17 goto *mlp *SettingWindow screen IDW_Main, 240, 180 title "stick test" return
僕の考えた方法でやるより、びっくりするほど早いですね。
これがまさに、機械の力です。
補足みたいですが、stickの p2 に「非トリガータイプキー指定」というものがあります。
トリガータイプなら、押された瞬間しか反応しません。
非トリガーにすると、押しっぱなしに対応できます。
さて、stick命令の説明は完了です。(足りなければ掲示板へどうぞ)
stick さえ使えれば、ほかに難しいことはありません。
やることはさっきと同じです。
font で、でっかい文字にしたらOK!
以上。
本当は、画像や絵を背景にした方が良いのですが、
面倒なので boxf 命令を使います。
ここから、本格的にタイトル画面を実装します。
とはいっても難しいのは「項目選択」ぐらいで、
これを一人で組むのは難しいのじゃないかなーと…………。
という訳で、今回はいきなり完全版をお届けします。(おい)
// 数当てゲーム - タイトル画面つき #packopt name "数当てゲーム" #packopt hide 1 // 定数(= const)を宣言 #const IDW_Main 0 // メイン画面のウィンドウID #const MAX_ANSWER 100 // 答えの最大 #const ANS_LIMIT 6 // 回答できる限界 #const WIN_SIZEX 480 // ウィンドウのサイズX (横幅) #const WIN_SIZEY 320 // ウィンドウのサイズY (高さ) *main gosub *initialize // 一番最初のサブルーチン gosub *SetVariable // 変数を用意 gosub *SettingWindow // スクリーンを構築 gosub *ChangeScreenTitle // タイトル画面にする gosub *CreateQuestion // 問題を作る gsel IDW_Main, 1 goto *mainlp // main loop に突入 *mainlp redraw 2 // カーソル移動を読む stick kDown if ( kDown & 2 ) { // ↑ボタン gosub *CursorUp gosub *ChangeScreenTitle // 書き直す (再描画する) } else : if ( kDown & 8 ) { // ↓ボタン gosub *CursorDown gosub *ChangeScreenTitle // 書き直す (再描画する) } else : if ( kDown & 32 ) { // [Enter] 決定 // subroutine jump ! switch iCursor case 0 gosub *ChangeScreenToGame // ゲーム画面にする repeat // ゲーム中は停止する if ( bGameEnd != 0 ) { // 偽じゃなくなったら bGameEnd = 0 // 戻しておく break // 終了する } wait 10 loop gosub *ChangeBackColor // ついでに背景色を変更しておく gosub *ChangeScreenTitle // タイトル画面にする swbreak case 1 gosub *ChangeBackColor // 変更 gosub *ChangeScreenTitle // 再描画して有効にする swbreak case 2 : goto *exit : swbreak swend } redraw 1 await 51 goto *mainlp *CursorUp iCursor -- if ( iCursor < 0 ) { iCursor = 2 } return *CursorDown iCursor ++ iCursor \= 3 return //######## サブルーチン群 ######## *exit end *initialize randomize // 一回だけ使えばいい gsel 0, -1 return // 変数を用意 *SetVariable dim question // 問題の答え dim answer // プレイヤーの回答 dim nAnsCount // 回答した回数 dim bGameEnd // ゲームが終了したとき真になる dim ID_Edit // input のオブジェクトID dim iCursor // カーソル dim kDown // キー押し下げ状況 gosub *ChangeBackColor // タイトル画面の背景色 return // スクリーンを構築する *SettingWindow screen IDW_Main, WIN_SIZEX, WIN_SIZEY, 2 // メイン画面 title "数当てゲーム ver.1.1" // バージョンアップ! return // 画面を完全にリセットする *ResetScreen color 255, 255, 255 : boxf : color clrobj // オブジェクトをすべて削除 pos 0, 0 // カレントポジションも忘れずに! return // タイトル画面にする *ChangeScreenTitle redraw 2 gosub *ResetScreen // リセット // 背景色で塗りつぶし color titleclr_r, titleclr_g, titleclr_b : boxf : color // でっかいタイトルを書く font msgothic, 80 // 巨大 pos 10, 20 : mes "数当てゲーム" // 選択項目を設置 sysfont 0 // 通常のフォント if ( iCursor == 0 ) { pos 30, 120 : mes "→" : color 255 } else { color } pos 50, 120 : mes "ゲームを始める" if ( iCursor == 1 ) { pos 30, 140 : mes "→" : color 255 } else { color } pos 50, 140 : mes "背景色を変更する" if ( iCursor == 2 ) { pos 30, 160 : mes "→" : color 255 } else { color } pos 50, 160 : mes "終了する" redraw return // ゲーム用の画面にする *ChangeScreenToGame redraw 2 gosub *ResetScreen // リセット color titleclr_r, titleclr_g, titleclr_b : boxf : color sysfont 0 // 最初のフォント objsize 80, 25 // ボタンの大きさを設定 pos 20, 50 : mes "1 〜 "+ MAX_ANSWER +"までの私を当ててみよ!" pos 30, 80 : input answer, 120, 24 // 回答 pos 160, 80 : button gosub "答える", *OnAnswer // 答え合わせ pos 50, 140 // カレントポジションを移動 redraw return // タイトル画面の背景色を変更する *ChangeBackColor titleclr_r = (rnd(2) + 1) * 127 + 1 // 128 or 255 titleclr_g = (rnd(2) + 1) * 127 + 1 // 明るめの色になるはず titleclr_b = (rnd(2) + 1) * 127 + 1 return // 問題を作る *CreateQuestion // 0〜MAX_ANSWER-1 なので、+1 して 1〜MAX_ANSWER にする question = rnd(MAX_ANSWER) + 1 return // 判定処理 ( button で飛んでくる ) *OnAnswer // * 判定する順番は大事です。 nAnsCount ++ // 答えた回数を数える if ( answer == question ) { // 正解 gosub *Clear // 正解したときのラベルに飛ぶ } else : if ( (1 <= answer && answer <= MAX_ANSWER) == 0 ) { mes "1 〜 "+ MAX_ANSWER +" で選んでね。" } else : if ( answer * 2 < question ) { // 問題は、回答の二倍以上 mes "小さすぎない?" } else : if ( answer / 2 > question ) { // 問題は、回答の半分以下 mes "大きすぎない?" } else : if ( answer < question ) { // 問題は、回答より大きい mes "もっと大きいよ" } else : if ( answer > question ) { // 問題は、回答より小さい mes "もっと小さいよ" } // ここに来るということは、正解じゃない if ( nAnsCount >= ANS_LIMIT ) { // 限界まで答えていたら gosub *TimeUp // 時間切れのラベル } return // 正解した *Clear color 0, 0, 255 font msgothic, 50, 1 mes "正解!お見事!!" dialog "正解です!\nあなたは "+ nAnsCount +"回で正解しました", 0, "おめでとう" // 画面をリセット gosub *ChangeScreenToGame // 問題変更 nAnsCount = 0 gosub *CreateQuestion bGameEnd = 1 // ゲームが終了したことを表す return // 時間切れ *TimeUp color 255, 0, 0 font msgothic, 60, 1 mes "タイムアップ!!" dialog "時間切れです。答えは "+ question +"でした。", 0, "残念" // 画面をリセット gosub *ChangeScreenToGame // 問題変更 nAnsCount = 0 gosub *CreateQuestion bGameEnd = 1 return
さて、解説は不要でしょう。
もうすでにめちゃめちゃ長いですし、これ以上長くなってほしくないので、解説は省略、自分で読んでください。
※わからなかったら、ここの掲示板で質問してもかまいません。
ふぅ、これで以上です。また次回。
by 上大