rogue: メッセージを3行表示

rogue のメッセージは単行で表示される。メッセージは何か行動を起こす度に出力されるのでスペースキーを押して、次のメッセージへと切り替えていく。このスペースキーでの切り替えは、ゆっくり読める所は良いのだけど、実際は頻繁に押さなくてはならなくてすごく煩わしい。

では、なぜ、頻繁に押さなければならないかというと、画面が1行と狭くてメッセージが収まりきらないからなので、広くすれば解決できると思うので、やってみる。

画面の構成と文字の出力

rogueは画面をcursesで描画していて、1つの全画面(window)にステータス、マップ、メッセージを描いている。対して、メッセージの改良は、複数行で表示したいし、いずれ過去ログを見れるようにもしたい。となると、独立したwindowにした方が制御しやすそうなのと、更に好みのバッファサイズにするとなると、curses の pad がぴったりくるので、メッセージをpadで描画するようにする。

  msg_win = newpad(MSG_PAD_NLINES, MSG_PAD_COLS);
  wmove(msg_win, MSG_PAD_BOT, 0);
  scrollok(msg_win, TRUE);
  idlok(msg_win, TRUE);

ちょこちょこ書いたけど、必須なのは newpad() のみ。

次に、padでのメッセージの出力。これは、vw_printw()など window* を指定できる関数で行い、画面に反映するために prefresh() を呼べば出力できる。

  vw_printw(msg_win, fmt, args);
  prefresh(msg_win, MSG_PAD_VIEW_TOP,0,VIEW_MSG_TOP,VIEW_MSG_LEFT,VIEW_MSG_BOT,VIEW_MSG_RIGHT);

このコードに切り替える事で、3行表示できるようになって改行毎に自動スクロールされるようになった。

画面更新されない

しかし、i (inventory)とか o (option) キーで別画面を表示した後、ゲーム画面に戻すとメッセージが消えてしまうという事が起こった。

色々いじってみた結果、windowであれば refresh() 時に再描画されるけど、padは再描画されない事で消えてしまっていたので、refresh() 後に prefresh() を追加することで表示できるようになった。

command.c:command(void)

    if (!((running || count) && jump))
+ {
      refresh(); /* Draw screen */
+     prefresh(msg_win, MSG_PAD_VIEW_TOP, 0, VIEW_MSG_TOP, VIEW_MSG_LEFT, VIEW_MSG_BOT, VIEW_MSG_RIGHT);
+ }

refresh -> prefresh の順でないと表示されなくて、これに気付くのに時間がかかった。

最後、スクショ。

Level: 1  Gold: 53     Hp: 10(12)  Str: 16(16)  Arm: 4















            ------
            |....+######
            |....|     #    ---+--------------------
            |....|     #    |............@.........|
            |....|     #####+.............B........|
            ------          |......................|
                            ------------------------
you found 53 gold pieces
You barely miss the bat
The bat has injured you

rogue: メッセージを最下部に移動

ゲームしてると、メッセージが最上部にある事に違和感を覚える。トルネコや一般のゲームのステレオタイプかなと思いつつ、だとしても最下部に移動したい。同様に最下部のステータスは上にほしい。つまり入れ替えたい。

rogueのメッセージやステータスはio.cにまとまってる。メッセージ処理は渡された文字列を表示し、長すぎる場合は折り返すようになっていた。ステータスは1行固定でフォーマットして出すという感じ。

さて、場所の入れ替えは、下記のようにして行った。

int
endmsg(void)
{
..
  if (mpos) {
..
-   mvaddstr(0, mpos, "--More--");
+   mvaddstr(VIEW_Y_MSG, mpos, "--More--");
..
  }
..
  if (islower((int)msgbuf[0]) && !lower_msg && msgbuf[1] != ')')
..
-   mvaddstr(0, 0, msgbuf);..
+   mvaddstr(VIEW_Y_MSG, 0, msgbuf);..
..
}

void
status(void)
{
    if (stat_msg)
..
    else
    {
-   move(STATLINE, 0);
+   move(VIEW_Y_STAT, 0);

最初のが折り返し、2番目がメッセージ。3番目がステータス。見つけてしまえばすごく単純。VIEW_Y_XX を好みな位置にすればいい。

あと、画面のリフレッシュもrefresh2()も同様にメンテ。ソースは省略するけどステータスの色付け処理は要らないので削除する。

-       if (color_mode2 == TRUE)
-           attrset(COLOR_PAIR(9) | A_BOLD);

変更後の画面はこんな感じ。

Level: 1  Gold: 0      Hp: 12(12)  Str: 16(16)  Arm: 4
                                   -----------------
                                   +............*..|
                                   |.......!.......|
                                   |...............+
                                   |..!....@.......|
                                   -+---------------
















Hello Rodney.  Welcome to the Dungeons of Doom.

意図通りになったので満足。

後記:

インデントのズレが結構多い。オリジナルを残す文化もわかるけど不統一だと読みづらいのでやはりなんとかしたい。といっても、clang-formatのようなフォーマッタで全部かけると返って見づらくなる事もあるし、好きなプリセットも見つかってない。

rogue: 名前をRodneyに固定する

Rogueはゲームのオプションとして、昔の遅い端末の対策だったり、カラーなどの追加設定がある。

それらの設定を好みに調整して環境変数に書いておけば固定できる。と思ったら、コマンドラインオプションでしか設定できない項目がある。

コマンドラインオプションを見てみると、何やら複雑な事をしている。 例えば、何もしないと名前がログイン名になる。これはワークステーションにログインしてゲームする時代のコードで確かにそういう時もあった。

あと、このコードが難解。

    if (argc >= 2) {
        if (argv[1][0] == '-'
        &&  isdigit(argv[1][1]) && isdigit(argv[1][2])) {
            i = (argv[1][1] - '0') * 16 + argv[1][2] - '0';

一言でいうと、様々なPC移植版を再現するオプションを短く書いたコード。設定の意味は下のページの最後の方みると載ってる。

Rogue_Archive.Official/rogue54/rogue5.4.5x-src at master · suzukiiichiro/Rogue_Archive.Official

それぞれのPCに移植していく際にオプションが異なっているので、おじさんが rogue はまったよねーと話した場合に実はそれぞれ違うものを指してそうだ。ただ、自分自身、色合いとかスライムだったか等、全然覚えてないので、そういった会話を楽しむ事はできなそう。

と、ソースコードは読んでて面白いのだけど、しばらくソースいじりながらゲームでも遊びたい。 なので今日のところは、不要なとこ削って、設定もgetoptで書いてすっきりさせる事にした。

const struct option longopts[] = {
    { "bright",       no_argument,       NULL, OPT_BRIGHT },
    { "color",        no_argument,       NULL, OPT_COLOR },
    { "file",         required_argument, NULL, OPT_SAVE_FILE },
...
};

while ((opt=getopt_long(optc, optv, "", longopts, &longindex)) != -1) {
  switch (opt) {
    case OPT_BRIGHT:
      bright_mode = TRUE;
      break;
    ..
  }  
}

引数がoptcc, optv になっているのは、今は毎回コマンドライン入れずに設定固定させたいから。

$ ./rogue
Hello Rodney, just a moment while I dig the dungeon...

そして、このように固定できた。 満足したので、ゲームしないで終わる。

そういえば、学生時代、試験の前日に部屋を大掃除してたな。

HUAWEI Ultrathin Keyboard

Bluetoothキーボードを購入。

Windows10は、標準では日本語配列英語配列キーボードを共存できないと知らなかったので、少々はまった。

日本語配列英語配列の共存

USB接続された日本語配列キーボード(ノートPCのキーボードも同じ)とBluetooth接続された英語配列キーボードの設定。

まず、これを参考に英語キーボードの設定をする。

qiita.com

その後、これを参考にPC接続された日本語キーボード側を設定する。

qiita.com

両方とも "Device Parameters" にキーを設定する事に注意する。

これでPC再起動して完了。

英語配列の日本語変換

タスクバーの日本語入力アイコン右クリックからして、Microsoft IMEオプション → キーとタッチのカスタマイズ → Ctrl + Space : IME-オン/オフを選択

※古い場合は変換のショートカットキー設定あたりで同様の設定ができたはず。

キーボードの感想

  • 今時なシンプル+キレイな梱包
  • キータッチは高級感ある。Macbookに似てる。個人的にはこっちの方が更に良い。やや重め。
  • 平らで低いので奥のキーは少し慣れが必要。
  • 十字キーが大きいのがよい
  • Home, End, Page Up, Page Down があるのが嬉しい。しかし久々過ぎて慣れない。
  • 重い。安定感は抜群。
  • Ctrlキーが小さい。左の指の腹で押す派としては、十分出っ張ってるから押せるのだけど、小さいので少し角度を選ぶ必要あって慣れが必要。
  • Fnの切り替えが 1クリックでいつのまにか Fnキーが入れ替わっててイラつく。

最初、キー配列、変換、慣れで躓いたけど、1日使ったら慣れ、元の安いキーボードはもう触りたくないと思うほどかなり満足。

rogue で遊んでみる

rogue をソースをメンテしながら遊んでみたいと思いたち、やってみることにした。

ソースコードの入手

rogue といってもいっぱいあるのだけども、最新版というよりも昔少しだけ遊んだものに近いものがいいと思い、探した結果、下記のソースコードを使用させて頂く事にした。

https://github.com/suzukiiichiro/Rogue_Archive.Official/tree/master/rogue54/rogue5.4.5x-src

特徴としては、カラー、スライムがあって日本語が無い事。日本語無しとしたのはソースコードを読む量が減るだろうと思ったため。

コンパイル

コンパイル環境は WSL、gcc とする。お手軽そうなので。

まず、何もいじらずに makeしてみた。これは残念ながらコンパイルエラーで終了した。

エラーはcursesの構造体メンバーが見つからない事で、原因は最新のncursesでは参照させなくした事のようだとわかり、下記のようにしたところコンパイルを通す事ができた。

  • ./configure を実行しない
  • Makefile に -DNCURSES_INTERNALS を追加

スクショ

動いた記念にスクショを貼る。


                                                              --------------
                                                              |............|
                                                              |..*.H@......|
                                                              |............|
                                                              |............|
                                                              ---+----------
















Level: 1  Gold: 0      Hp: 12(12)  Str: 16(16)  Arm: 4   Exp: 1/0

ターミナルをコピペでゲームの画面が貼れるのっていいね。ブログが書きやすそう。

遊び方

$ ./rogue -14 Rodney
  • 名前を Rodney にする
  • レベルが数字 -> 称号 になる
  • Sがスライムになり、スライムは分裂する
  • passogo : 通路の曲がり角で止まらない
  • pc_md : PCモードで、色々変わるらしい
  • idscr_md : 識別の巻物が1種類になる

スコア:

./rogue -s

セーブしたところから再開

./rogue rogue.save

以上。

多次元の配列へのポインタ配列の型

C言語はポインタ変数の型を考えるのが大変。 先日、多次元配列へのポインタ配列を返す関数の宣言が思い浮かばなかったので、復習する事にした。

前提として、 扱うデータは、以下の多次元配列が複数個とする。

char a[2][3] = {{10,11,12},{13,14,15}};
char b[2][3] = {{20,21,22},{23,24,25}};
char c[2][3] = {{30,31,32},{33,34,35}};
char d[2][3] = {{40,41,42},{43,44,45}};

まず、配列のポインタ。これはいいよね。

char (*p)[2][3] = &a;
printf("%d\n", (*p)[1][2]); // => 15

次は、配列のポインタの配列。 早くもつまづきそうになるが、ポインタ宣言は右優先で考えていけばいいので

char (*q[4])[2][3] = { &a, &b, &c, &d };
printf("%d\n", (*q[1])[1][2]); // => 25

配列のポインタの配列のポインタ。 日本語としても意味が分かりづらいが、1つ前の例との差分を考えていけばコードが書ける。

char (*(*r)[4])[2][3] = &q;
printf("%d\n", (*(*r)[2])[1][2]); // => 35

このように、順にコードを書いてけば、 配列のポインタの配列のポインタを返却する関数も宣言できるようになる。

まずは、考えるのが面倒で void* に頼ってしまうダメな例を先に。

void *f(void){
  static char (*p[4])[2][3] = { &a, &b, &c, &d };
  return (void*)&p;
}

これを、ポインタ変数の宣言ができればこうなる。

char (*(*f(void))[4])[2][3]{
  static char (*p[4])[2][3] = { &a, &b, &c, &d };
  return &p;
}

printf("%d\n", (*(*(*f)())[3])[1][2]); // => 45

ふぅ、満足。