たまりば

パソコン・インターネット パソコン・インターネット三鷹市 三鷹市

ファミコンで9×9ドット文字表示(ほか)
2019年04月08日 02:02

ファミコンあたりのゲーム機は画面を8×8ドットのタイルを並べて構成している。よって文字表示は8×8ドット(空白含む)にするとプログラムが簡単だ。
このサイズだとひらがなカタカナ(濁点・半濁点なし)にちょうどいいサイズである。英数字にはやや大きいがさほど不自然でもない。

問題は漢字表示である。
8×8ドットに漢字を描くのはかなりの無理がある。16×16ドットにすれば何不自由なく描けるが、このサイズの文字は大きすぎて画面内に書ける文章量が減る。
そこでその中間あたりのサイズの文字表示をやってみようと思い立った。

まず考えたのが12×12ドット(空白含む)だ。1.5マスで比較的扱いが楽そうだ。
ゲームボーイでプログラムを組んでみて、まあうまくいった。
12×12ドット文字_ゲームボーイ

なお後に知ったのだがゲームボーイで12×12ドットのフォントはいくつか例がある。その1つの「合格ボーイ」シリーズの「常識の書」がこちらだ。
常識の書_横書き常識の書_縦書き

さて、12ドット文字プログラムだが、意外と面倒な面が多かった。
1文字を18バイトで持っていたのだが、18は切りが悪い。縦横に空白をとると11×11ドットであり、11×11=121ビットというのは16バイトに収まる。せっかくだから収めたくなるがなかなか上手くいかない。
半角文字も用意したくなるが、横6ドットとなるとせっかくの1.5マスの切りの良さが無くなってしまう。
12×12ドットはわりと面積が広く、書き込みに意外と時間がかかる。
(なお先述の「常識の書」だが、英数字は全角の半分ではなく横8ドットにして切りの良さを保ち、フォントデータは中央4ドットを重複して持つことで表示を楽にしている。勉強になる)

12ドットでも面倒なら別のサイズでも似たようなものではないかという思いにいたり、別のサイズを模索してみることにした。
すると9×9ドット(空白含まず)がかなり楽そうだと分かった。
一見中途半端だが、9×9ドットはどのように配置しても8×8ドットのマス目の4マスにかかるという重要な性質がある。場合分けが不要というのは楽だ。
8bitCPUのレジスタは8bitだが、キャリーフラグをうまく使えば9bitを保持しておくことができる。
文字を9×9ドットとして1ドットの空白を設ければ10ドット。偶数なので奇数より多少楽だ。

そんなわけでゲームボーイで組んだ。
9×9ドット文字_ゲームボーイ
見ての通り表示できる文字数が増えるのも利点だ。漢字の読みやすさは12ドットには劣るものの、十分だろう。

その後ファミコンに移植してみた。
ファミコンでもカートリッジにメモリを積めばゲームボーイと同様にキャラクタを書き換えることができる。(CHR-RAMと呼ばれる)
(以前はあまりカートリッジ側に複雑なものを載せるのはレトロゲームプログラミングとして反則な気がして敬遠していたのだが、CHR-RAMを使っているソフトは普通にある感じなのでいいかと思い直した)
9×9ドット文字_ファミコン
ゲームボーイでは1文字出力のコードしか書いていなかったのだが(上の画像は1文字出力関数を文字数分呼んでいるのだ)、ファミコン版でやっとテキストデータを呼んで文字列を表示するコードを書いた。
(もっとも現状文字コードが1バイトで文字を256種しか使えないのだが)

これの製作中に1つ思いついて実装したのが、フォントデータの一部をテキストデータに持たせる手法だ。
フォントデータは普通には9×9で81bit、10バイトと余り1bitになる。これを11バイトで持つのは無駄が多く気に食わない。
そこで、例えば文字コードの最上位bitを文字の右上のドットに対応するようにする。するとフォントデータとして持つのは残りの10バイトのみでよくなる。
文字コードの並びは不規則になってしまうが、文字列を表示するだけなら問題は無い。
問題が起こるのは、まず外部とのやり取りで既存の文字コードを使う場合。これはゲームではあまり考えなくてよいだろう。
それと、文字列をソートしたい場合。これはやや問題になるかもしれない。漢字はソートできなくてよいと考えて、仮名のみならテーブルを作るか、文字コードを上手く作れば仮名の順は取れるようにできるかもしれない。

あと文字の送り幅を変えるコードもやっつけで書いた。やっつけなので1文字あたり1バイトを余計に使う無駄なコードだが。文字データも10バイト使って無駄だ。

NESファイルは[こちら]
コードは汚いので(まだ)公開しない。そのうち整えてCC-BY-NCあたりで公開するつもり。ゲームボーイからファミコンへの移植で変わったとことかも文章にまとめたい。

(2019/07/17追記)
実機動作していただいた!

…そして初期化忘れに気付かされる。
エミュレータはふつう初期状態のメモリは全ゼロになっているので初期化しなくても動いてしまい、実機で動作させて初めて気づくことが多い。PICマイコンでもしょっちゅうやらかした。
これをエミュレータでテストするのはなかなか面倒である。今回はテスト用に乱数生成(Xorshift)のコードを書いてまず最初にRAMをランダム値で埋めることにした。
それでできたのがこちらである。見事に再現できた。
初期化忘れ再現
さらに調べているとスタックが3バイト単位の繰り返しで全部埋まっていることにも気づいた。スタックオーバーフローだ。
コードを確認すると、初期化からVBlank割り込みルーチンに流れ込むわ、VBlank割り込みルーチンは最後リターンせずにVBlank待ちに入るわのめちゃくちゃなコードだった。よく動いていたな。

そんなわけで割り込みルーチンを直しメモリのゼロクリア処理を追加して、
確認してみると目的のノイズは消えたのだが、起動時に一瞬別のノイズが見えるようになったのが治らない。
起動時一瞬のノイズ
どうやらランダム値をVRAMに書き込む際に出ているようで、表示は切ってあるはずなのに不思議である。
ランダム化を消せば消えると思われたが、理由が分からないのは不安なので調べることにした。
結果、案の定仕様を勘違いしていたことが分かった。PPUADDRレジスタがパレットのあるアドレスを「表示中に」指しているとそのパレットの色が出るという仕様があるのだが、これを非表示中に起こるものと思っていた。
https://wiki.nesdev.com/w/index.php/PPU_palettes#The_background_palette_hack
更に、この仕様に関連して、ファミコンは起動時の一瞬、PPUが操作可能になる前に(条件によって異なる)とある1色を表示するということも知った。そういえばそんなだった気がするな。PPUが操作可能になる前なのでどうしようもないらしい。

理由が分かったので安心してゼロクリア処理を消す。今まで確認に使っていてNintendulatorではノイズが見えなくなったが、再現度の高いらしいMesenでは本来のパレットへの書き込みの分まだわずかに残っている。VBlank中へ持っていけば消せるのかもしれないが面倒なのでひとまずこれでよしとする。
画像を載せようかと思ったが上のと全く同じなのでやめる。
上のNESファイルは更新した…はず。エミュレータで見ても表示は同一なので確認が難しい。

あと全画面任意画像の方にも同じバグがあったので合わせて修正した。  

  • PCのキーボードのアーウが反応しなくなったあどうすえばよいか
    2016年07月17日 04:34

    PCのキーボードの「R」が反応しなくなったことをその「R」の使えないキーボードで打ったツイートが少し前に話題になっていた。
    ここで反応しなくなったキーが「R」だというのが絶妙だ。
    Rを使わないとアールと文字名を打つこともできず、アーウという間の抜けた字面になるのがよい味を出している。
    これが
    Aエー、Cシー、Eイー、Fエフ(EHU)、Gジー、Hエイチ(EITI)、Jジェー(ZYE-)、Lエル、Qキュー、Vブイ、Wダブリュー、Xエックス、Yワイ
     ではその文字を使わなくても文字名が打ててしまう。
    Bイー、Pイー、Kエー
     ではE・Aに読めてしまい分かりづらい。
    Dヒー、Tヒー
     は「ディー」や「ティー」の発音の中に存在しない「h」が出てきてしまい文字名に結びつきづらい。
    Iア、Oー、Uy-
     この辺は情報が足りなすぎなんだか分からない。
    Mエウ、Nエウ、Sエウ
     これらはなかなかよい。「Nのいぎどなりのエウ」などよい間の抜けぐあいだ。ただ3つのどれか分からないのは欠点か。
    Zエット
     これもなかなか面白い響きだ。「Shiftの右隣のエット」「Xの左隣のエット」が打ててしまうのが多少残念。



    さてそれはそうと、R(など英字キー)が打てなくなった時の対処法を考えてみよう。
    なお当該ツイートの画像はMacっぽい気もするがMacはよく知らないので主にWindowsについて考える。

    まずはRが打てなくなったことを伝えるために1回だけでいいから「R」の文字を入力する方法を考えよう。

    色々あるが、面白いところからいくと、英単語をカナ表記から変換する方法がある。
    MS-IMEでは例えば「かー」を変換すると「CAR」が出せるので、Rを打たなくてもRの文字を出せる。
    他の文字も考えてみると例えば
    BらむLAMB
    DしゅみっとSCHMIDT
    IらてんLATIN
    KないふKNIFE
    MあんばーAMBER
    NこらむCOLUMN
    OかむCOME
    PれしーとRECEIPT
    SきっずKIDS
    TくりすますCHRISTMAS
    UかっとCUT
    ZぴっつぁPIZZA
    といった出し方があるだろう。
    なおZは「じー」でも出たりする。

    他に、どこかからコピペしてくる方法もある。
    Webブラウザで「ASCIIコード表」など適当な語句で検索すれば見つかるだろう。

    まともな方法では、IMEパッドの手書きや
    IMEパッドの手書き
    文字一覧、
    IMEパッドの文字一覧
    ソフトキーボードが使える。
    IMEパッドのソフトキーボード

    文字一覧と同様のもので、「文字コード表」もある。
    文字コード表
    名前を指定して実行から「charmap」…が打てないのでどうにかして探そう。

    変わったところで、Alt+テンキーでの文字コード入力。
    日本語WindowsではShift_JISの文字コードを入力する。Rなら0x52(=82)なので、Altを押しながらテンキーの8,2を順に押し、Altを離すとRが入力される。
    またレジストリのHKEY_CURRENT_USER\Control Panel\Input Methodに文字列値「EnableHexNumpad」を作成し値を「1」にするとAltを押しながら「+」を頭につけた16進のUnicodeで入力できるようになる。Shift_JIS外の文字も打てるようになるし、文字コードはふつう16進で書かれるものなのでそのまま打てて便利である。



    さてしかし「R」だけ打てても日本人は困る。「らりるれろ」を入力したい。
    そんなときにはソフトウェアキーボードだ。
    IMEパッドのソフトキーボードのかなモードを使って1文字づつ打つか、
    IMEパッドのソフトキーボード(かな)
    osk.exe「スクリーンキーボード」なら普通にR,Aと打って「ら」を入力することができる。
    スクリーンキーボード
    Windows+R…が使えないのでマウス操作で、名前を指定して実行から「osk」を実行。
    O,S,Kが使えない時はマウス操作でなんとかしよう。
    Windows8以降なら「タッチキーボード」も同様に使える。
    タッチキーボードのアイコン
    タッチキーボード
    (それにしてもWindowsにはソフトウェアキーボードが多いな…)



    さて何度も打ちたいときはこれではまどろっこしい。やはり物理キーボードを使いたい。
    それならばMS-IMEのローマ字設定を変えよう。
    使っていない例えばQやXあたりをラ行にあてればよいだろう。
    (なおMacのことえりではデフォルトでLがラ行を打つのに使えたように思う。)
    MS-IMEでローマ字の追加
    ここでちょっとしたおまけで、例えば「QYA」で「りゃ」が打てる状態で、QYAで打った「りゃ」を1文字消して「り」になったものをF10で変換すると「RI」になってくれるので、Rキーを使わず「R」の文字を出すこともできる。

    MS-IMEを使う方法は日本語入力時はよいが、IMEをオフの状態で入力しなければならない場合に困る。
    面倒だがレジストリをいじってキー配置を変更するのが最終手段である。
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layoutにバイナリ値「Scancode Map」を作成し以下のようなフォーマットで書く。
    dword 0;
    dword 0;
    dword length+1;
    {
        word scancode_out;
        word scancode_in;
    } * length
    dword 0;
    めったに使わない無変換やF1など適当なキーをRとして使うようにするとよいだろう。
    例えばF1をRにするなら、F1のスキャンコードが0x3B、Rは0x13であるから、
    00 00 00 00 00 00 00 00
    02 00 00 00 13 00 3B 00
    00 00 00 00
    こうなる。
    スキャンコードの表は検索すれば見つかるだろう。例えばここ
    なお注意点として、PS/2キーボードから送出されるスキャンコードとWindowsが内部的にScancode Mapに使っているスキャンコードは別物なので、「Scancode Mapに設定するための」スキャンコード表を探そう。
    また、1ユーザーのみの設定がHKEY_CURRENT_USER\Keyboard Layout\Scancode Mapで昔はできたのだが7以降できなくなっただか環境によってできないだかという。実際Windows8.1で試してできなかった。
    昔WindowsXPで1ユーザーのみの設定をした時の記事も参考になるかもしれない。
    http://wentwayup.tamaliver.jp/e50134.html

    以上、突然Rが反応しなくなった時にはぜひ試してみてほしい。  

  • 漢点字一覧表
    2015年04月26日 02:24

    バックスラッシュと円記号の話
    2014年12月20日 02:05

    0x5Cはバックスラッシュですが円記号で表示されます。
    まあ常識ですね。

    その先の話をこまごまと。

    ・韓国では₩
    0x5Cは日本で¥になるのと同様、韓国では₩になる。
    日本語版WindowsにもBatang, Dotum, Gulim, Gungsuh, Malgun Gothicといった韓国語フォントが入っているので試せる。
    なお中国ではどうやらバックスラッシュのままのようだ。

    ・Tahoma
    Tahomaは非Unicodeの頃からWindowsのUIに使われていたフォントなので、たぶん何らかの互換性のための配慮だと思うのだが、不思議な力で特定の状況でだけ0x5Cが¥表示になる。(ならない場面も多く詳細不明)
    これはコントロールパネルの「言語」ではなく、「地域」から設定できるUnicode対応でないプログラムの言語(システムロケール)によるようで、これが日本または韓国に設定されている場合、Tahomaの0x5CはTahomaにFontLinkされた最初のフォントで表示される(場合がある)。
    なので、MS UIGothicのビットマップが気に入らなければメイリオにするとか、Gulimにして₩を見て楽しむとかができる。

    ・Word
    MSOffice Wordには「バックスラッシュを円記号に変換する」設定がある。
    この機能は正確には、特定のフォントで0x5Cを打ち込んだとき、見た目だけを当該フォントの0xA5「¥」で表示するものである。
    表示だけの変更なので、0xA5のグリフで表示された文字をコピーすると0x5Cとしてコピーできる。
    この機能の働くフォントの条件はよく分からないが、MSPゴシック・Gulim・SimSunで効かないところを見ると「漢字圏以外」といったところか。

    ・MacのSafari
    「font-familyにMS系日本語フォントが先行して指定されている」か「HTMLドキュメントの文字コードがレガシーな日本語系文字コードである」場合に0x5Cを0xA5で表示するらしい。
    参考: Mac/iOS Safariでバックスラッシュを円記号として表示する方法 - teppeis blog

    ・余談
    なぜかWindowsのパスや正規表現のエスケープ文字としての0x5Cが0xA5で書かれているブログ記事がたまにあって不思議なのだが、何か特定のブログシステムが勝手に変換しているのではないかとにらんでいる。  

  • 重畳漢字ROM
    2013年10月25日 01:41

    ちょっとした機器で漢字を表示したい時、文字のデータをROMに持つことになる。
    ROMの容量を抑えたいなら、漢字のレパートリーをJIS第1水準のみに絞ったりするだろう。
    その際、使う文字コードが(Shift_)JISならば問題ないのだが、何かの都合でUnicodeを使わざるを得ない場合もある…と思う。
    (無いと話が終わってしまうのであることにしていただきたい)
    ここで発生する問題が、変換テーブルが巨大になるということである。
    UnicodeはJISコードとは無関係に文字が並べられているので、UnicodeでJIS第1水準の文字はバラバラに存在している。
    例えばこんなだ。
    UnicodeでのJIS第1水準漢字
    空白部分を漢字ROMに持つのは無駄なのでROMには詰めて入れるが、そうすると文字コードとの対応を別にテーブルでもっておかなければならない。
    これがどれほどのデータ量になるか。
    まず漢字ROM本体だが、例えば12×12ドットのフォントを使うとして、JIS第1水準漢字2965文字は2965*12*12/8 = 53370Byte ≒ 53KBとなる。
    一方変換テーブルだが、JIS第1水準漢字はUnicodeにU+4E00「一」からU+9F8D「龍」までのおよそ21000文字にわたって分布している。
    2965文字を指定するには12bit必要なので、素直に考えれば21000*12/8 = 31500Byte、すなわち32KBと、本体の半分ものデータ量になってしまう。

    と、ここまでが前置きで、今回これを解決するアルゴリズムを考案した。タイトルにもあるように、このアルゴリズムは「重畳漢字ROM」と名付けた。
    この方式では漢字ROMの容量は少々増えるが(今回のサンプルでは14%増)、変換テーブルの容量を劇的に縮小することができる(32KB→1.3KB)。
    仕組みは次のようになっている。
    ・ROMの番地は12bitある。
    ・その下位4bitは文字コードの下位4bitと同じである。
    ・上位8bitは文字コードの上位12bitからテーブルを引いて求める。
    つまり文字コードの上位12bitが異なる文字を、番地の上位8bitが同じ箇所に押し込めている事になる。
    先に述べたとおりUnicode中のJIS第1水準漢字は飛び飛びに存在しているのでこういうことができるのだ。
    図で示すと分かりやすいかもしれない。
    漢字ROM重畳例

    ただしこの方式には1つ重大な欠点がある。第1水準外の文字が入力された時でも区別できずに何らかの文字を表示してしまうのだ。
    きちんと表示させるためにはデータを事前に調整しなければならず、それならJISに変換すればいいじゃないかということになりかねない。

    最後にサンプルを用意した。
    テキストボックスに任意の第1水準漢字を入れボタンかEnterを押せば下にその漢字の画像が表示される。わざと第2水準漢字を入れて変な文字が出るのを試すのもよいだろう。
    変換テーブルはソースを参照。
    コードは単純で、漢字ROM相当の画像ファイルの表示位置をずらして当該文字を出している。
    なおこのROMの重畳のパターンは文字の多い順に並べてからナイーブな貪欲法で1つづつ重ねて作ったものである。やりようによってはもっと縮められると思う。