C言語に関らず、プログラミング言語では、複数のプログラム部品を組み合わせることによって一つのプログラムを作りあげることができる。
このように、組み合わせを前提に作成されたプログラムの部品を「モジュール」と呼ぶことにする。
C言語では、「関数」が、「モジュール」となる。
C言語のプログラムでは、(原則として) main関数が必要だが、main関数以外の関数は、モジュールとして扱われる。
本日の実習の目的は、モジュールの利用によって様々な利点が得られるので、それを体験しようというものである。
今回の実習では、前回 (2006/11/10) の演習の課題soft17_1を題材とする。
soft17_1は、既に複数の関数からなっているので、このままでも良いのだが、後のことを考えて、次のような形で、ファイルを分割する。
int cat_attacks(void) int jump_distance(void)
int mouse_runs_away(int cmd) int runaway_distance(int cmd)
void display_guide(void) void display_scene(void) void display_result(int game)
game.c: main
main()
元のファイルから、上記のtom.c jerry.c display.cを取り除いたもので、更に、これに次のincludeを追加する。
#include "display.c" #include "jerry.c" #include "tom.c"
game.c以外は、該当する関数を単にそのまま、抜き出して、新しい名前を付けて保存するだけである。game.cは元のsoft17_1.cから、逆に、該当する関数を取り去ったものであるが、上記にあるように、取り去った後に、#includeを指定することによって、結果的に、元のsoft17_1.cと同じように動作する。
表示周りは、全てdisplay.cに集まっているので、これを変更することにより、表示の様子を変更することができる。
エスケープシーケンス(1)機能とは、特別な文字列(2)を、画面上(3)に出力することにより、様々な効果をもたらす機能のことであり、状況によって使えたり、使えなかったりするのだが、Windows 上では利用可能なので、これを利用して、表示上の効果を実現する。
エスケープシーケンス機能は、単に、出力する文字列に特別な符号を追加するだけであるが、プログラムの読みやすさを実現するために、エスケープシーケンス機能を実現するためファイル( esc.h )を作成し、それをdisplay.cの中で利用する。
#define crt_flush() fflush(stdout) #define crt_out(s) fputs(s,stdout);crt_flush() #define crt_pos(x,y) printf("\x1b[%02d;%02dH",(y)+1,(x)+1) #define crt_clear() crt_out("\x1b[2J") #define CRT_BLACK 30 // 黒 #define CRT_RED 31 // 赤 #define CRT_BLUE 32 // 青 #define CRT_PURPLE 33 // 紫 #define CRT_GREEN 34 // 緑 #define CRT_YELLOW 35 // 黄 #define CRT_SKY_BLUE 36 // 水色 #define CRT_WHITE 37 // 白 #define CRT_DEFAULT 0 // デフォルト #define CRT_REVERSE 7 // 反転 #define crt_color(color) printf("\x1b[%dm",color)
表示の変更として、次の三つを行う。
display.cのdisplay_guideを変更して、説明を画面中央に表示するようにしなさい。
実際に、game.cをコンパイルし、これが実現できていることを確認すること。
display.cのdisplay_sceneを変更して、状況を画面下部の一定の位置にに表示するようにしなさい。
実際に、game.cをコンパイルし、これが実現できていることを確認すること。
display.cのdisplay_resultを変更して、結果に色が付くようにしなさい。
実際に、game.cをコンパイルし、これが実現できていることを確認すること。
display.cに、自分なりの変更を加えて、色々と試してみなさい。
例えば、次のようなことが考えらえる。
トムの振舞いは、tom.cの中で記述されているjump_distanceによって定められている。
この関数の振舞い、簡単に言えば、次のような流れになっている。
この方法なので、次のような現象がおきる。
これは、ちょっと、トムも芸がないし、また、ジェリーの逃げ方と無関係に勝敗が決るのもつまらない(4)。
そこで、tom.c (の中のjump_distance )を変更して、もう少しゲーム風にしてみよう。
では、トムの振舞いをどのように考えてみればよいだろうか?トムがアニメの主人公(?)としても、やっぱり、現実の制約を受けることは間違いない。特に、トムはジェリーに比較して体が大きいので慣性法則は、無視できないだろう。
そこで(5)、次のような方針を考える。
上記の設計方針の為に、プログラムに次の変更を加える。
/* (2) 状態変数の宣言 */ int tom, jerry; /* ネコ, ネズミの位置 */ int tom_v; /* ネコの速度 */ /* ←(新規追加) */
tom = CAT; tom_v = 0; /* ネコの速度の初期変化 */ /* ←(新規追加) */ jerry = MOUSE;
上記のトムの設計だと、最初、巣穴の前に待ち構えていたトムが急激にジェリーに近付いてくる様子が実現され、最初の要望は改善されたのだが、問題は、一旦すれちがってしまうと、もう、トムがジェリーに追い付くことができない。
方向転換が難しいという点が仇になったわけだ。
これでは、ゲームとして余りにも面白くないので、方向転換が急激にできるようにしよう(6)。
すなわち、次のような変更をtom.cの中にあるjump_distanceに加えることにする。
} else if ( d > 0 ) { /* すれちがった */ tom_v = tom_v + 1; /* 右方向に加速 */ } else {
} else if ( d > 0 ) { /* すれちがった */ if ( tom_v < 0 ) { /* すれちがったので急転換を許す */ tom_v = 2; } else { tom_v = tom_v + 1; /* 右方向に加速 */ } } else {
また、すれちがった後に、追い掛ける余裕を与えるために、ゲーム板を少し大きくしてみよう。この為に、game.cに手を加えよう。
#define LENGTH 54 /* 通路の長さ */ #define HOLE 50 /* 穴の位置 */
#define LENGTH 64 /* 通路の長さ */ #define HOLE 60 /* 穴の位置 */
これで、多少ゲームとしてバランスが取れるようになった。
ジェリーは人間が操作することになっているが、これを計算機に任せることを考えてみよう。
この為には、jerry.cのrunaway_distanceを書き換えれば良い。この関数は、引数として、人間から入力をおこなっているが、これは無視して、計算機の考えた結果を返せばよい。
ここでは、簡単に、トムの挙動を無視して、兎に角、穴に入ることを考えることしよう(7)。
int runaway_distance(int cmd) { switch ( HOLE - jerry ) { /* もし、移動可能な場所に HOLE があれば */ case -1: case 1: case 3: case 5: return HOLE - jerry; /* HOLE に飛び込む */ break; default: /* そうでなければ、ひたすら右へ.. */ return 5; } }
自分なりに作った、jerry.cを提出してください。