A問題
演習問題1(文字列の扱い復習)
C言語では、'0', '1', .... '9' の数字であっても、文字として扱う場合はアスキーコードで管理される (例えば scanf() で書式に "%c" や "%s" を使って読み込んだ時など)。int myatoi(char *)
を作成してみよう。
関数の仕様は以下の通り。myatoi
関数を追加して
仕様通り動作するようにすること。また、string.h をインクルードしてはならない。ライブラリ関数 atoiも使用しないこと。
#include <stdio.h> int myatoi(char *); int main() { char str2[][20]={ " 2024", " -21.50", "+16m", " 654,321", "+-40", "x86", "-x86", "2024/1/31", ""}; int ival, i; for ( i=0; str2[i][0]!='\0'; i++ ){ ival = myatoi( str2[i] ); printf("String:%s\n", str2[i]); printf("Value:%d\n", ival); } return 0; } /* この後に関数 myatoi を作成する */実行例:
% ./a.out String: 2024 Value:2024 String: -21.50 Value:-21 String:+16m Value:16 String: 654,321 Value:654 String:+-40 Value:0 String:x86 Value:0 String:-x86 Value:0 String:2024/1/31 Value:2024 %
B問題
演習問題2(問題1の応用)
日本では西暦年のほかに、昭和xx年や平成xx年という和暦が並行して使われている。 令和元年(1年)は西暦では2019年、平成元年は1989年、昭和元年は1926年、大正元年は1912年、明治元年は1868年 にあたる。myatoi
をそのまま
コピーして使用し、main関数のみを新たに作成すること(ヒント:ハンドアウトLec05-9, 17)。
入力が実際の年号の範囲に収まっているか(昭和なら64以下か等)はチェックしなくてよい。そのため、H32が西暦2020年と表示されるのは正しいことになる。
なお、入力の1文字目のアルファベットが R, H, S, T, M のいずれでもない場合、また2文字目以降の内容が数字として読み取れない場合はそれぞれその旨のメッセージを表示した上で、前者の場合は exit(1)
で、後者の場合はexit(2)
で強制終了せよ(最初に stdlib.h をインクルードしておくこと)。実行例:
% ./a.out 和暦(S45, R6等): R6 西暦 (AD): 2024 % ./a.out 和暦(S45, R6等): R10 西暦 (AD): 2028 % ./a.out 和暦(S45, R6等): H31 西暦 (AD): 2019 % ./a.out 和暦(S45, R6等): H32 西暦 (AD): 2020 % ./a.out 和暦(S45, R6等): H29 西暦 (AD): 2017 % ./a.out 和暦(S45, R6等): S53 西暦 (AD): 1978 % echo $? 0 % ./a.out 和暦(S45, R6等): K1 Kに対応する元号が存在しません % echo $? 1 % ./a.out 和暦(S45, R6等): TSHR 数字を読み取れません % echo $? 2 % ./a.out 和暦(S45, R6等): 100 数字を読み取れません % echo $? 2 %
exit()
関数はハンドアウトプログラミング入門パートのLec12-23 (p67) やプログラミングCパートのLec03-4 (p94) と演習第3回などで紹介・利用されている。exit()
関数が呼び出されると、その時点でプログラムは強制終了し、プログラムの呼び出し元であるシェルには終了コードとしてexit()
関数の引数が戻る。main()
関数でreturn
文に到達してプログラムが終了した際も、return
文のパラメータが終了コードとなる。
bashでは直前のプログラム・コマンドの終了コードはシェル変数$?
に格納されているので、echo $?
とすれば表示される。exit()
関数の戻り値や、main()
関数中のreturn
文のパラメータを適切に設定しておけば、終了コードを調べることで、プログラム実行の成否などを確認することができる。多くのコマンドでは、正常終了の場合の終了コードは0
、何らかのエラーがあった場合はそれ以外の整数値となっている。
演習問題3(リスト内の検索)
前回(第9回演習)の演習問題2を元に、ID番号が前方一致(文字列等を先頭から比較して一致しているかどうかを判定するもの)したものをリストから検索し表示したい。 そのために、前回のディレクトリから今回のディレクトリにprog02.c
をprog03.c
として、また
studlist.h
をstudlist10.h
としてコピーし、検索表示するための
関数void listprint_sel(char *)
を追加する。
またデータとしてStudent.txt
も前回のディレクトリからコピーしておくこと。
listprint_selは文字列のポインタを引数とし、リスト内の
IDをsprintf
関数を使って一旦文字列に変換した後strncmp
関数を
使って比較することにする(前方一致検索)。
sprintf
関数はprintf
関数が標準出力に、
fprintf
関数がファイルに対して行うのと同様の出力を
文字列に対して行ってくれる関数である。例えば、
int i = 999; char str[10]; sprintf(str, "%d", i);とすることでstrには"999"という文字列が格納される。
strncmp
関数はstrcmp
関数が文字列全体を
比較するのに対し、先頭から指定した文字数分のみ比較する関数である。例えば、
char str1[] = "ABCD"; char str2[] = "ABCE"; int result; result = strncmp(str1, str2, 3);により、resultにはstr1とstr2の3文字目までの比較結果が代入される (戻り値の内容は
strcmp
関数と同じなので、この場合は
等しいとして 0 が返り、resultに代入される)。
sprintf
関数, strncmp
関数の使い方についてはオンラインマニュアル
(man)も参照せよ。
% ./a.out Head - 1301001 AIZU Taro 19 1301022 BANDAI Hanako 22 1301033 TSURUGA Shirou 21 1301004 BANGE Jiro 18 1301015 WAKAMATSU Saburo 19 1301036 KORIYAMA Sakura 19 1301007 FUKUSHIMA Ichiro 20 1301081 TADAMI Testuko 19 1301098 HIGASHIYAMA Tadashi 18 1301099 IWAKI Youko 18 1301123 SOUMA Hikaru 19 1301150 KITAKATA Testuo 18 1301164 SUKAGAWA Yoshiko 18 1301200 ASHINOMAKI Masaru 19 14 nodes exist in the list. Input match data (ID) -> 130103 1301033 TSURUGA Shirou 21 1301036 KORIYAMA Sakura 19 Input match data (ID) -> 13011 1301123 SOUMA Hikaru 19 1301150 KITAKATA Testuo 18 1301164 SUKAGAWA Yoshiko 18 Input match data (ID) -> Ctrl+d %
Extra問題
演習問題4(座標や属性を表す構造体)
チェス盤上に、1 から n までの番号が割り振られた n 匹のアリがいるとする。 チェス盤には横に w 個、縦に h 個のマスがあり、左上隅を白として、白黒のマス目が交互に 並んでいる。以下、左上隅のマスを (x, y) = (1, 1) とし、右に向かって x が増加、 下に向かって y が増加すると考える。
初期状態では、どのアリも盤上のいずれかのマスの中にいて、右または下を向いており、
一マスの中に複数のアリは居なかったものとする。
(図の例だと、チェス盤のサイズが横 5、縦 4 であり、3 匹のアリが
(4,1), (1,4), (5,3) にいる。)
時間が1ステップ進むごとに、すべてのアリはその向いている方向に1マス移動する。
移動先がチェス盤の外に出たら、アリは姿を消すとする。
盤上で2匹のアリが同じマスに入った時、アリは以下のような行動をとる。
チェス盤のサイズと初期状態のアリの位置・方向の情報がファイル(ファイル名は コマンドライン引数で指定)から与えられた時、時間とともにアリがどう動くかをシミュレーション して、何ステップ目にどのアリが盤の外に出るか表示するプログラムを作成しなさい。 なお、入力ファイルには、1 行目に横のマス目の数 w、縦のマス目の数 h、アリの数 n が書かれており、2 行目から n+1 行目に各アリの横方向の位置 x、縦方向の位置 y、 アリの向き(右なら 'R'、下なら 'D')が与えられている。
なおプログラムでは、下記のリスト1のように、アリの情報を保持しておくための構造体として AntPos型を定義して使用する。 アリの数の上限は決まっていないので、ファイルに書かれたアリの数に合わせて動的にメモリを 確保して情報を格納すること。使用する変数はリストに示した物で十分なはずだが、 追加しても構わない。
(提出ファイル名: prog04.c)
リスト1:#include <stdio.h> #include <stdlib.h> typedef struct { int x; /* アリの位置(横) */ int y; /* アリの位置(縦) */ char dr; /* 'R':右向き 'D':下向き */ int in; /* 1:アリは盤上にいる 0:既に盤外に出ている */ }AntPos; int main(int argc, char *argv[]) { int w, h, n; /* 幅、高さ、初期状態でのアリの数 */ int t=0; /* 時間ステップのカウンタ */ int i, j, num; AntPos *ant; FILE *ifile; if (argc < 2) { printf( "error: input file name is required!\n"); exit(1); } /* ファイルオープン */ /* ファイルの1行目から盤の幅w、高さh、アリの数nを読み込む */ /* アリの情報を収めておく構造体配列を動的に確保 */ /* ファイルから各アリの位置と向きを読み込む。なお、文字型を読む時は " %c"のように%c手前にスペースを入れると、R/D手前のスペースを読み飛ばせる。 読み込めたら、確認のため読んだ情報を一度出力しておこう */ /* 1ステップごとのループ。盤上にいるアリの数が0になったら終了 */ /* 時間の更新、アリの座標の更新、アリが盤の外に出たかどうかのチェックと表示、 2匹のアリが同じマスにいるかどうかのチェックと(必要なら)向きの更新 */ ..... return 0; }実行例:
% ./a.out Antdata1.txt 2 1 D 1 2 R 2 2 R Step 2: Ant No.3 has gone out to (4,2). ←2ステップでアリNo.3が盤の外へ Step 3: Ant No.1 has gone out to (4,2). ←3ステップでアリNo.1が盤の外へ Step 3: Ant No.2 has gone out to (2,4). ←アリNo.2も同時に盤の外へ % ./a.out Antdata2.txt 3 1 D 2 2 R 1 3 R Step 4: Ant No.2 has gone out to (6,2). Step 4: Ant No.3 has gone out to (3,5). Step 5: Ant No.1 has gone out to (6,3). % ./a.out Antdata3.txt 4 1 D 1 4 R 5 3 D Step 2: Ant No.3 has gone out to (5,5). Step 4: Ant No.2 has gone out to (4,5). Step 5: Ant No.1 has gone out to (6,4). %ヒント:マスの色が白か黒かは、(1,1) が白で、x 座標、y 座標とも 1 増えるごとに 黒、白、黒、白、と変化することを利用すると、容易に計算で判定できます。
演習問題5(ソースコード・リーディング)
今まで、皆さんは演習問題では1つの課題につき、長くても100行を越える程度のプログラムを書いてきました。 こうした100行くらいのプログラムでは、大体作る関数も多くて3~4個であり、何か一つの機能のみを扱うというようなものでした。
実際のソフトウエア製作現場では、100行程度でプログラムが終わることはありません。システムを1つ組み上げるのには、1万行、3万行、10万行とかかるものがあります。みなさんが普段使っているUnixというOSも一つのプログラムですが、そのOSはドライバー等を含めない純粋なシステム部分で10万行、ドライバーを含めると100万行を超えると言われています。
今回の演習では、今までの100行程度のサイズからワンサイズ大きい、500行を超えるプログラムを見てみましょう。
プログラム/home/course/prog1/public_html/2024/ex/ex10/consoletetris.htm
は、コンソール上で動くテトリスのプログラムです。
テトリスは皆さんご存知かと思いますが、4つのブロックがくっついた7パターンのブロックの塊が、20x12のサイズのフィールドにランダムに1個ずつ落ちてきて、1行ぴったり埋め尽くすと行のブロックが消える『落ちモノパズル』の代表格ともいえるゲームです。
このプログラムをまずはコンパイルして実行してみてください。
注意事項
・ゲームスタート時のスプライトの表示では外部からファイルを読みますので、ファイル(CentOSでは sprite.txt 、Macでは sprite.txt)をダウンロードして、コンパイルした a.out のバイナリがあるディレクトリと同じところに入れてください。
・ゲーム中のブロックを表現するのに, CentOSとMacでフォントの違いの問題が発生するため(具体的には■が小さく表示される), Macを使用している教室では文字を変更する必要があります。 '圖' 等を使用すると良いと思います。
・また、実際に動かす機種によって、ブロックの落ちるスピードが変わります。これは、その動かすシステム自体の処理速度の影響です。ブロックの落ちるスピードが遅い場合は、
#define FPS 2000
の値を変更して調整するようにしてください。LV1で待ちが3秒から4秒くらいが適正だと思います。
なお、このプログラムには、ハンドアウトには無いものを利用して作っている int kbhit() と言う関数が含まれています。これについては、ブラックボックスとして扱ってもらってよいです。 kbhit() 関数は、キーボードが押されたか押されていないかの判定を行う関数で、押された場合は 1 が、押されていない場合は 0 が常に返るものです。
各関数の仕様は次の通り。
int CreateBlock(); /* 新しいブロックを生成して次のブロックに発生させる */
void ShowGameField(); /* field[][]の中身に応じて、画面を描画する */
void ControlBlock(); /* キー入力に応じてブロックに移動や回転等の処理を行わせる */
int CheckOverlap(int, int); /* 落下中のブロックが壁や固定済みブロックに接触していないか判別 */
void MoveBlock(int, int); /* 落下中ブロックを一旦消して、任意の座標に移動させる */
int TurnRightBlock(); /* ブロックの右回転を処理する */
int TurnLeftBlock(); /* ブロックの左回転を処理する */
void DropBlock(); /* ブロックを落下させる。下に移動できない場合ブロックをその位置に固定 */
void LockBlock(); /* 着地したブロックを固定済みブロックに加える関数 */
void CheckLines(); /* ブロックが横一列にそろえばそこを消去後、上のブロックをそこに下ろす */
int kbhit(); /* キーボードを1回ヒットしたかどうかの判定 */
int GetTimeOfLevel(int); /* レベル毎の時間設定 */
void GameStart(); /* ゲームスタート時のスプライト表示 */
課題
このプログラムを理解して、主要な部分各行にコメントを記入してください。 その際、特に次のことに注意してソースコードにコメントを書くようにしてください。演習問題6(新しいゲームの製作)
演習問題5のテトリスを骨組みとして、自分のアイディアを付与した新しいゲームを製作してください。
この時、インクルード宣言、定義、グローバル変数および関数のプロトタイプ宣言は、ヘッダファイルとして分離してください。
また prog05.h の上部に、どこに新しい要素を入れたのかを、コメントとして明確に記してください。
(提出ファイル名: prog06.c, prog06.h)
例