ポインタと文字列…とはほぼ名ばかりの、ゲーム制作での実践的なお題でしたが、いかがでしたでしょうか。ここから先のテストはこういう感じの内容が増えていきます。前回も言いましたが、正解は一つじゃないので、私の解答は参考としてください。PCのモニタが壊れて修理に出したので、少し説明が減るとは思いますが、それはご容赦ください。


  • 解24. ポインタへの置き換え
これは簡単でしたね。初期化に使われている艦名がリテラル(定数)なので、const を付ける部分がちょっとだけひっかけでした。VS2022 使っていれば、それすらも教えてくれるので、やっぱり簡単ですわ😓

struct TShip { const char* pName; // 名前 EShip Type; // タイプ Point Position; // 座標 bool IsVertical; // 縦方向か? };


  • 解25. 攻撃目標入力
この問題は C言語のコンソール上で、ゲームの入力部分の基礎となるような入力方法を学ぶのが目的です。scanf_s は基本的にはゲームでは使用しないですね。_getch はキー情報を一文字取得する関数ですが、リアルタイムキー入力ではないのでその点だけ注意が必要でした。では、コードを提示してみます。

int atkX, atkY; while (true) { // 攻撃目標を入力 printf("%sAttack ? : ", FWHITE); // 英字の入力 do { int chr = _getch(); atkX = chr > 'Z' ? chr - 'a' : chr - 'A'; } while (atkX < 0 || FIELD_WIDTH <= atkX); printf("%c-", atkX + 'A'); // 数字の入力 do { atkY = _getch() - '0'; } while (atkY < 0 || FIELD_HEIGHT <= atkY); printf("%d", atkY); printf("\n"); }

英字の入力ですが、大文字と小文字の違いも含めて入力となりますので、少し工夫が必要です。私は英大文字は英小文字より小さいのが前提として、入力文字が 'Z' より大きければ小文字の 'a' を、'Z' 未満なら 'A' を引くことで、アルファベット A からの連番に一旦変換してから、0 以上、かつ、FIELD_WIDTH 未満になるまで入力をループするようにしてみました。三項演算子部分は評価が分かれるところでしょうか。

数値の入力は '0' を引いて一旦オフセット値に変換していますが、単に表示だけであれば

char num; do { num = _getch(); } while (num < '0' || FIELD_HEIGHT <= num - '0'); printf("%c", num);

という書き方のほうがちょっとだけ実行が速いと思います。ただ、その後の処理を考えると、やはりオフセット値に変換したほうが便利だと思うので、このようにしています。
※ 40Gbpsと超高速。まあ、この速度を出すためには全ての関連機器も合わせないといけませんが、ケーブルの値段はだいぶ落ち着いてきたように思います。一気に揃えると高いのでケーブルから揃えてみませんか?

  • 解26. 関数使用方法の調査1
tolower 関数はネットで「C言語 tolower」で検索するとたくさん解説ページが見つかります。Microsoft の解説ページがヒットしないのは、それだけリファレンスの説明がわかりにくいのだと思います😓

検索して tolower について分かる事は以下の通り。
  1. 使用するには #include <ctype.h> が必要
  2. 引数は char の1文字
  3. 戻り地は char の1文字
  4. 与えた引数の文字が英大文字なら小文字に変換する
  5. 英大文字以外には何もしない
このことから、_getch() で得られた値をそのまま tolower に受け渡せば、英字に関しては小文字しかない状態を保証できるので、後は与えられた値から 'a' を引くだけでオフセット値に変換できるようになります。

atkX = tolower(_getch()) - 'a';

簡単になりましたよね。このように既に便利な関数が C言語には多数用意されているので、それを有効に使いましょう。そして、その便利な関数で「こんなのあったよな」とおぼえておくだけで、実際には覚えていなくてもこのように検索さえできればいつでも使えるのが今の世の中です。
※ ホント便利になりました…


  • 解27. 関数使用方法の調査2
これはプログラマ的な勘が養われているかどうかのテストです。tolower という小文字に変換する関数があるのなら、大文字変換もあるはずだと思えるかどうかです。そして、自力で検索してその関数を見つけられるかどうか。答えは toupper() ですね。

atkX = toupper(_getch()) - 'A';

tolower と toupper のどちらが良いかはお好みです。私はだいたい小文字に変換しています。


  • 解28. 当たり判定
配置した艦船の位置は、二次元配列 gSea に収められています。問26 で既に縦横のオフセット値は取得出来ていますので、その位置の配列の値を読み取るだけです。ただ、問題は「無効判定」です。一度攻撃したことを覚えておく必要があります。

そこで思い出していただきたいのが enum の定義です。

enum EShip { NONE, CARRIER, BATTLE, CRUISER1, CRUISER2, DSTSHIP, DESTROY = -1 };
何もないという NON から続いて艦船番号がありますが、最後に DESTROY という今まで使っていなかった定数があります。これは撃破マークで、一度当たった場所は DESTROY に置き換えることで、二度目からは無効と表示できるというわけです。

// 当たり判定 printf(" ... "); switch (gSea[atkX][atkY]) { case NONE: printf("ハズレ!"); break; case DESTROY: printf("無効"); break; default: printf("当たり!"); gSea[atkX][atkY] = DESTROY; break; }
今回は全て「当たり!」と表示するだけとしていますが、gSea[atkX][atkY] の値を参照すれば、何の艦船に命中したか判定できます…よね?

今回の解答サンプルは下記からダウンロードできます。

2023/05/13

BUFFALO 10GbE/2.5GbE対応 金属筐体 AC電源 6ポート ブラック スイッチングハブ LXW-10G2/2G4
バッファロー
2019-12-11
※ とはいえハブはまだまだ高価なんですよね…