今回は初期化です。これ、C言語講座分類にしようか悩みました。理由は C言語で説明するためです。とはいえ、基本的な考え方は普遍的で共通ですので、その辺りを見て頂けると幸いです。


  • まずは宿題の答え
前回の ALG 状態遷移で宿題が出ていました。こちらは実行すればすぐに分かるのですが、作り直しをしても前回の配置が残った状態で、さらに配置をしています。そのため、数回作り直しを指定すると最後は作り直せなくなってフリーズ(厳密には無限ループ)してしまいます。

一番の問題は…

// ゲームフィールド EShip gSea[FIELD_WIDTH][FIELD_HEIGHT] = { NONE };
初期化を起動直後の初期化子だけに任せている点でした。この初期化子を使うとラクなのですが、このように何度も初期化が必要か場合は、初期化子での変数値の初期化は不適切なのです。そのため、宣言時には初期化子でゼロクリアは取っ払って、配列の初期化を明示的に行います。

/// <summary> /// 敵船を配置する /// </summary> void deployShips(void) { int ofst[][2] = { { 0, 1 },{ 1, 0 } }; // 縦横オフセット // ゲームフィールドを初期化する memset(gSea, NONE, FIELD_WIDTH * FIELD_HEIGHT * sizeof(EShip)); // 以下略
あと、画面の書き換えで以前の画面が残るのも初期化忘れです。こちらもゲームフィールドの表示で画面を初期化するようにします。

/// <summary> /// ゲームフィールドを表示する /// </summary> void drawGameFiled(void) { char colors[][10] = { FWHITE, FYELLOW, FCYAN, FGREEN, FGREEN, FBLUE }; printf(SCRCLEAR); printf(LOCATE, 1, 1);
これで完成…と思うかもですが、実はまだあります。乱数の初期化を配置関数に入れているため、再配置を1秒以内に行うと同じ結果が繰り返されてしまうのです。これを防ぐためには srand((unsigned)time(NULL)); の記述は起動時一回だけの場所に移す必要があります。今回の場合は main の直後に一度だけ呼び出すのが適切かと思います。

/// <summary> /// メイン関数 /// </summary> /// <returns>終了コード</returns> int main(void) { srand((unsigned)time(NULL)); while (true) {
もちろん、deployShips 関数内の srand は削除しておきます。これで動作としてはバグは無くなりました。
パナソニック 日本製 LEDシーリングライト 調光・調色タイプ ~6畳 3699lm リモコン付 HH-CF0620AZ 【Amazon.co.jp限定】
2020-10-21 ※ 殆どの個室にこれの旧タイプを使用しています。リモコンがとても便利で、値段の割に重宝しています。

  • ウエイトを入れる
実はまだ気に入らない箇所があります。それは、再作成を選択した時の No の文字が見えないことです。実際には表示しているのですが、次の処理に移行するのが速すぎて、No の文字が見えないのです。そこで、文字入力の後は0.5秒くらいウエイトを入れます。

/// <summary> /// 配置を確認する /// </summary> void sureDeploy(void) { char key; printf("%sAre you sure ? : ", FWHITE); do { key = tolower(_getch()); } while (key != 'y' && key != 'n'); printf("%s\n", key == 'y' ? "Yes" : "No"); gGame = key == 'y' ? ATTACK : DEPLOY; Sleep(500); }
Sleep() ですが、#include <windows.h> とすれば簡単なのですが、ここは標準C言語の関数に拘ってみます。clock() という関数は CPU 処理時間を表しています。この clock() を使って、Sleep を自分で作ってしまいます。

/// <summary> /// 指定時間ウエイトを入れる /// </summary> /// <param name="msec">待ち時間(msec)</param> void Sleep(int msec) { clock_t clStart = clock(); while ((clock() - clStart) * 1000 / CLOCKS_PER_SEC < msec); }
これで、入力された Yes / No の文字が普通に読めるようになりました。
サンプルコードは以下からダウンロードできます。

CTest07Debug.zip
20230530
※ 本文記事よりももう少し改善してあります。


  • 初期化の種類
さて、初期化には3種類の違いがあります。

■ システムの初期化/コールドスタート

起動時に最初に一度だけ初期化をすれば、あとは弄る必要がない種類です。宿題の回答で言えば srand のような乱数の初期化がそれに当たります。処理をわかりやすくするため、関数として InitSystem などのようにまとめておくこともありますし、数が少なければエントリ関数(C言語ではmain関数)の直下にズラズラと記述したりします。

他にはハイスコアの初期化だったりも、このシステムの初期化に相当するでしょう。コールドスタートと呼ばれる電源を入れた直後の初期化に相当します。

■ 再始動の初期化/ホットスタート

再起動で中途半端になっている可能性がある変数やシステムを初期化します。宿題の回答で言えば、ゲームフィールドの初期化がそれにあたります。初期化をまとめておくことが多いです。

■ 状態変化による初期化

状態遷移という変化により、その各処理単位毎に行う初期化です。宿題の回答で言えば、画面クリアがそれに当たります。他にはゲームスタート時のスコアのゼロクリアであるとか、残機の設定であるとか、その状態に持っていくために必要な初期化です。だいたいは状態遷移の処理開始直前に初期化処理が記述されます。動作の前に初期化を行う記述が最もエンバグが少ないです。

以上、今回は主に宿題の回答解説でしたー

パナソニック LEDシーリングライト 調光・調色タイプ リモコン付 ~8畳 ミディアムブラウン仕上 HH-CE0819AH
2019-10-21 ※和室がこちら。シンプルなのが一番使いやすいです。