Daydreaming in Brookline, MA

キー入力と任意位置へのテキスト表示

Rogue + Wizライクプロジェクトの2回目はキー入力と画面表示です。

1 ノンブロッキングキー入力

まずはリアルタイムキースキャンから。これがいきなり曲者で、簡単には実現できないようです。

Windowsではmsvcrtというライブラリを使うみたいです。msvcrt.getch()は使い勝手がよさそう。しかし私の手元にはMacしかないので、別のやり方を探してみます。検索すること数十分、いちおう標準ライブラリの(?)termiosを使うやり方がいいのかな。StackoverflowのこのQ&A に例が出ています。引用します。

import termios, fcntl, sys, os
fd = sys.stdin.fileno()

oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)

oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)

try:
    while 1:
        try:
            c = sys.stdin.read(1)
            if c:
                print("Got character", repr(c))
        except IOError: pass
finally:
    termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

Windowsよりもずいぶん準備や後処理が面倒だし、tryブロックがネストしていて微妙な感じですが、試したところ十分使えそうです。これをお借りしましょう。

2 任意位置への文字表示

次は画面上の指定位置への文字表示です。昔懐かしのBASIC言語でいうところのLOCATEに相当することがしたいです。検索でだいぶ手間取りましたが、思いのほか簡単に実現できることがわかりました。ANSIエスケープシーケンスで指定するのですね。

print(f"\033[{y};{x}H@", end='')

これでx, yの位置にアットマーク('@')を表示します。バックスラッシュ('\')から'H'までが位置指定のエスケープシーケンスです。ここでは1文字しか表示していませんが、2文字以上の文字列を表示させることももちろんできます。

簡単な性能テストをしてみます。

import random
import time
while True:
    c = random.choice([b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9'])
    start = time.time()
    for y in range(25):
        print(f"\033[{y};0H", end='')
        line = bytearray(c*60)
        print(line.decode())
    delta = time.time() - start
    print(f"{delta:.3f}")

これはランダムな数字を60x25文字の表示エリアに一様に表示することを繰り返します。実際のゲームと同様にbytearrayで持つ1行ごとのデータを、文字列にデコードしながら1回のプリント文で表示しています。

実行したところ、十分に高速でした。描画しているところはほとんど見えず、実行時間は1画面あたり100分の一秒未満でした。行けそうです。

3 次回

次はローグライクなランダムマップの自動生成に挑戦します。ありがたいことにRoguelike Tutorialなページが ありました ので、参考にさせていただきます。なるほど、部屋と部屋を結ぶ方法は意外と単純だったのですね。線を引くのにジェネレーターを使うのはよいアイデアと思いました。