update 2004.4.25

ばんがいへん1 コンバータの作り方
〜最終回 テキストコンバータを作る〜


概要
今回が「ばんがいへん1」の最終回になります。
使用言語はVisual C++6.0をを使用してテキストコンバータを作る手法の説明です。
あくまで私自身がVC++6.0を使っているだけで、コンパイラに依存する命令は使いませんので、
少し変更するだけでBorland C++ CompilerやLSI-C86などにも対応できると思います。

なお「C言語の関数の説明・コンパイルの方法」などについては全て説明を省きます。
その辺りは書店で「初めてのC」などの各C言語の参考書をご覧ください。
(そうでないと、単なるC言語講座だけで膨大な量になってしまいますので)。

では、まずはプログラミングの前にコンバータで扱うためのフォントテーブルファイルの作成を行いましょう。

作り方1 テキストエディタで入力

第1回で作成したフォントテーブル
現段階のフォントテーブル *=未知 / □=スペース
   +0+1+2+3+4+5+6+7+8+9+A+B+C+D+E+F
00 *1********ABCDEF
10 GHIJKLMNOPQRSTUV
20 WXYZ「」**□*******
30 あいうえおかきくけこさしすせそた
40 ちつてとなにぬねのはひふへほまみ
50 むめもやゆよらりるれろわをんっー
60 が*****じ***だ**でどば
70 び******ぺ********

がありますが、これの周りの番地(+0+1+2...や 00 10 20など)を全て取り除いて、
文字の固まりにしましょう。
*1********ABCDEFGHIJKLMNOPQRSTUVWXYZ「」**□*******あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんっーが*****じ***だ**でどばび******ぺ********
実は、これをそのまま保存したのがフォントテーブルファイル CONV_SMP.TBL です。
この方法は私と同じくコンバータを作られているねこかぶさんがよく使われています。

作り方2 バイナリエディタエディタで入力
この方法は私がよく使うのですが、Stirlingなどのファイル作成機能をもった
バイナリエディタで直接ダンプ入力します。
ただしオフセット単位でデータを扱うので、全角(2バイト)文字のテーブルを作る場合は、
フォント番号 × 2 の場所に文字を入力という入力方法になります。
(例)table[3A]="さ" → 3Ax2= オフセット 00000074 に「さ」を入力
フォントテーブル風に表すと以下のようになります。
   0123456789ABCDEF     0123456789ABCDEF
00 *1****** 80 ちつてとなにぬね
10 **ABCDEF 90 のはひふへほまみ
20 GHIJKLMN A0 むめもやゆよらり
30 OPQRSTUV B0 るれろわをんっー
40 WXYZ「」** C0 が*****じ*
50 □******* D0 **だ**でどば
60 あいうえおかきく E0 び******ぺ
70 けこさしすせそた F0 ********

見た感じは激しく面倒なのですが、ひらがなの文字コードが 0050 で、漢字が 06E7 だったりと、
文字の種類ごとに間隔が広い場合には直接オフセット単位で指定することができる、
この方法が有効になります。
(この例では「50x2=A0にひらがな、06E7x2=0DCEに漢字を入力」でOK)
逆に、テキストエディタではそのような場合には漢字を打つ場所を特定するのが難しいです。
なので、
テキストエディタでテーブルを作ると、連続した入力が簡単
バイナリエディタでテーブルを作ると、飛び飛びの入力が簡単
と、使い分けると良いでしょう。

それでは続いて「きっかけサンプル」同封のソースコード CONV_SMP.CPP を例にして、
テキストコンバータの作り方を説明します。

Visual C++6.0の設定
メニューから「ファイル(F)→新規作成→Win32 Console Application」を選択し、
プロジェクト名に適当な名前(ここでは CONV_SMP )を入力し、位置は任意のフォルダを指定してOKをクリックします。
アプリケーションの種類に「単純アプリケーション」を選んで「終了(E)」で完了です。

ワークスペースのSource Files内に conv_smp.cpp というファイルが出来ていますので、
これに「きっかけサンプル」の CONV_SMP.CPP を上書きするか、内容をコピーします。
F7キーを押せばコンパイル&ビルドが開始され「エラー 0、警告 0」と表示されたらOKです。
ここでSHIFT+F5を押せば CONV_SMP.EXE が実行されますが、変換対象の MESSAGE.DAT や、
フォントテーブルファイルである CONV_SMP.TBL が同一フォルダに無ければエラーになってしまうので、
VC++6.0で作成した CONV_SMP ワークスペースと同じフォルダに、
「きっかけサンプル」の MESSAGE.DAT と CONV_SMP.TBL をコピーしてください。
これで実行して、正しく変換メッセージが出力されたら成功です。
(ワークスペースのサブフォルダdebugにEXEプログラムが出力されていますが、
 MSDEVでSHIFT+F5で実行したときのデフォルト実行場所はワークスペースになります)。

それでは、ソースコードを見てゆきましょう。

1〜5行目 : プリプロセッサ(ヘッダ読み込み)

// テキストコンバータ「きっかけサンプル」
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
1つ目の"stdafx.h"はウィザードでワークスペースを作成したときに作成される標準ヘッダですが、
stdafx.hが不要なBorland C++ Compilerなど他の言語でコンパイルする場合はコメントアウトしてください。
後に参照しているヘッダファイルは、画面出力やファイルアクセスなどの出力周りに必要な stdio.h 、
終了処理などに必要な stdlib.h 、文字列操作用の string.h です。
まあ、きっかけサンプルは文字列操作なんてしてないので、実はstring.hは不要なんですが(ぉ)、
大抵のコンバータでは文字列操作も必要になるのでそのままにしています。

8〜13行目 : 変数宣言

	// 変数宣言
	FILE *fp_ft;			// フォントテーブル用ファイルポインタ
	FILE *fp_ms;			// MESSAGE.DAT用ファイルポインタ
	int GetFile=0;			// MESSAGE.DATを読んだデータを格納
	int i=0;			// 汎用変数
	unsigned char table[128][3];	// フォントテーブル格納用2次元配列
コメントも書いてますし、これ以上に説明しようがないのですが、変数宣言です。
たった5つしか宣言していませんし、役割も単純ですね。

17〜26行目 : ファイルオープンとチェック

	// フォントテーブル(CONV_SMP.TBL) テキストモードオープン&チェック
	if((fp_ft=fopen("CONV_SMP.TBL","rt"))==NULL){
		printf("フォントテーブル CONV_SMP.TBL が見つかりません。\n");
		exit(1);
	}
	// 変換対象(MESSAGE.DAT) バイナリモードオープン&チェック
	if((fp_ms=fopen("MESSAGE.DAT","rb"))==NULL){
		printf("フォントテーブル CONV_SMP.TBL が見つかりません。\n");
		exit(1);
	}
サンプルであればもっと関数を切り分けたほうがわかりやすいと思いますが、
まず fp_ft=fopen("CONV_SMP.TBL","rt") で、フォントテーブルファイル CONV_SMP.TBL を、
テキスト読み込み"rt"モードで開いています。
オープンに成功すると fp_ft にはファイルのポインタが返り、失敗するとNULLが返ります。
これを条件分岐関数 if(〜〜==NULL) で判断し、失敗した場合はアラートを表示して、
終了させています。
なお、このカッコの位置や個数を間違えると fopen("CONV_SMP.TBL","rt")==NULL
のようなありえない解釈がされ、エラーやバグの原因になりますので注意してください。

2番目も同じような流れで fp_ms=fopen("MESSAGE.DAT","rb") で、
変換対象ファイル MESSAGE.DAT をバイナリ読み込みモード "rb" で開いています。
バイナリファイルを "rt" で開いたりすると、テキストモード特有の処理が発生し、
正しくデータを扱えない場合がありますので注意してください。
後の判定などの処理はテーブル読み込みと同じです。

28〜32行目 : フォントテーブルの内容を配列に格納
ここがこの講座で最も重要な処理になります。
前回ではN88-BASIC風に文字型配列に格納して説明しましたが、C言語に文字型配列はありません。
文字を扱うためのchar型変数があり、これを配列として使うことはできます。
(例)メモリ上での配置 (番地は本来のものとは異なります)
char str[4]="ABC" → 0000 str[0] "A"
0001 str[1] "B"
0002 str[2] "C"
0003 NULL(00)
上記の例で定義した配列 str は、BASICでいう文字型変数と同じような使い方が可能で、
printf("%s",str);とすれば、そのまま "ABC" が画面に描画されますし、
printf("%c",str[1]);とすれば "ABC" の "B" だけを表示することもできます。
この仕組みを利用して、前回説明した読み込んだ元データを指標として利用することができます。
( printf("%c",table[フォント番号]); のような感じですね)

しかしchar配列では各文字を1バイトで扱うため、全てが全角(2バイト)文字の
サンプルのフォントテーブルを正しく扱うことができません。
(例)全角の場合のメモリ上の配置
char str[7]="あいう" → 0000 str[0] A0(「あ」の1バイト目)
0001 str[1] 82(「あ」の2バイト目)
0002 str[2] A2(「い」の1バイト目)
0003 str[3] 82(「い」の2バイト目)
0004 str[3] A4(「う」の1バイト目)
0005 str[4] 82(「う」の2バイト目)
0006 NULL(00)
例えばここで printf("%c",str[0]); を指定しても "あ" の文字は表示されず、
1バイト目である A0 の数値に割り当てられた文字を無理やり表示しようとして、文字化けします。
この方法で正しく表示するには printf("%c%c",str[0],str[1]); とすれば良いのですが、
初めにバイナリエディタでテーブルを作成したときと同じように、
フォント番号×2の場所を指示する必要があったりと、少しわかりづらいのが難点です。
(プログラムを工夫すればある程度は緩和できますが…そうすると説明が大変(汗

そこで「きっかけサンプル」では二次元配列を用いています。
C言語では変数宣言時に str[10][10] のように、カッコを1つ増やすことで二次元として定義され、
一般的にはX軸・Y軸の処理を伴う場合などに使われます。
(例)char map[10][5] → X軸10マス、Y軸5マス分の二次元配列50バイトを確保

「きっかけサンプル」では初めの変数宣言で unsigned char table[128][3]; と定義し、
これは「table[0]〜table[127]に全角128文字を格納」という扱い方をします。
table[x][0]〜table[x][1]が2バイト文字、table[x][2]が文字の終端を表すNULL……つまり、
全角1文字=半角2文字として「文字列として」扱うということです。
そのため画面出力の際には半角文字であれば printf("%c",str[0]); のように
キャラクタ単位で扱っていたものを printf("%s",table[0]); のように文字列単位で扱います。

以下がソースコードでのフォントテーブル読み込み処理です。
	// フォントテーブルの内容(全128文字)を全て配列に入れる
	for(i=0;i<128;i++){
		fread(&table[i],2,1,fp_ft);
		table[i][2]='\0'; // 終端を表すNULLを挿入
	}
まず for()〜のループでiを0〜127までカウントさせて全フォントテーブルを処理します。
fread(&table[i],2,1,fp_ft);は、フォントテーブルから全角1文字(2バイト)のデータを読み込み、
二次元配列 table[ i ][0&1] の i 番目にその内容をコピーしています。
そのままでは文字列の終端を表すNULL文字が無いので table[i][2]='\0'; で終端を指定してやります。
この方法では、メモリ上の配置は以下のようになります。
(例)二次元配列に全角文字を格納した場合
char str[3][3]に
"あいう"を格納→
0000 str[0][0] A0(「あ」の1バイト目)
0001 str[0][1] 82(「あ」の2バイト目)
0002 str[0][2] NULL(00)
0003 str[1][0] A2(「い」の1バイト目)
0004 str[1][1] 82(「い」の2バイト目)
0005 str[1][2] NULL(00)
0006 str[2][0] A0(「う」の1バイト目)
0007 str[2][1] 82(「う」の2バイト目)
0008 str[2][2] NULL(00)
間にNULLが入るため、初めよりもメモリ使用量は増えてはいるものの、
これで table[フォント番号] と指定することで任意の文字を得ることが
できるようになりました。

おなじみフォントテーブル
現段階のフォントテーブル *=未知 / □=スペース
   +0+1+2+3+4+5+6+7+8+9+A+B+C+D+E+F
00 *1********ABCDEF
10 GHIJKLMNOPQRSTUV
20 WXYZ「」**□*******
30 あいうえおかきくけこさしすせそた
40 ちつてとなにぬねのはひふへほまみ
50 むめもやゆよらりるれろわをんっー
60 が*****じ***だ**でどば
70 び******ぺ********

「きっかけサンプル」のフォントテーブルを読み込んだ状態で、
printf("%s",table[30]); を実行すれば "あ" が表示されます。

34〜43行目 : テキスト変換処理
	// ループ
	while(1){
		GetFile=getc(fp_ms);		// MESSAGE.DATから1バイト読み込み
		if(GetFile==EOF) break;		// 終端であればループ脱出
		if(GetFile==0xFF){			// 改行を処理する。
			printf("\n");		// 改行
		}else{
			printf("%s",table[GetFile]);// 改行でなければテーブルを参照して変換出力
		}
	}
またまたコメントたっぷりで説明のしようが無い気もしますが、
まず while(1) はいわゆる「無限ループ」で、自身ではループを終了することはありません。
GetFile=getc(fp_ms); は、getc()関数でMESSAGE.DATから1バイト読み込み、
内容をGetFile変数に格納しています。
その次の if(GetFile==EOF) break; は唯一のループ脱出処理で、
GetFileに格納された値がEOF(ファイル終端)のときにループを抜けるようになっています。

続いてif()とelseの組み合わせの出力で、if(GetFile==0xFF)は、
MESSAGE.DATの0xFFが「改行」を表しているため、printf("\n"); で改行を処理しています。
もしそうでなければ(else)、変換出力処理である printf("%s",table[GetFile]); が実行されます。
読み込んだデータ(GetFile)を指標として、フォントテーブル(table[ ])の任意の文字を指定し、
画面に出力(printf)しています。

47〜48行目 : キー入力待ち
	printf("\n\n-- 何かキーを押すと終了します --\n");
	getchar();
テキストコンバータとは全く関係ありませんが、出力終了後に
キー入力待ちを行う関数 getchar() でストップさせています。
本当は全く不要なのですが
「実行してもすぐ消えて何も起こりません。なぜですか?」
というGUI世代に激しくありがちな質問を回避するためにこういう仕様にしてます(--;;;

〜さいごに〜
というわけで「ばんがいへん1」はこれにて終了です。
説明そのものは激しく長いように見えますが、やってることは全然たいしたこと無いです。
(むしろこの程度で「コンバータ」と名乗るのも気が引けますねぇ…)

画像変換プログラムやサウンドコンバータなどよりも比較的手軽で、
海外ではこういったテキスト変換は盛んに行われているようですが、
日本国内ではどうも18禁ゲームのシナリオ吸出しがメインで、
レトロゲームのテキスト変換はイマイチ普及していない気がします。

まだ国内で未開拓のジャンルに、是非挑戦してみてはいかが?


TOPに戻る