16.3 関数と構造体 ■ 乱数の発生 ■ 制御コード ■ 列挙型 ■ system関数 第16章 構造体 ■ ゲーム作成 16.3 関数と構造体 ■ 乱数の発生 ■ 制御コード ■ 列挙型 ■ system関数
今日のポイント 構造体、タグ名、メンバーとそれらの 使い方を思い出す! 構造体のデータを関数間でやりとり する場合の仮引数・実引数の使い方 使い方を思い出す! 構造体のデータを関数間でやりとり する場合の仮引数・実引数の使い方 関数値を構造体で戻す方法
中点を求めて出力する関数 center_point のプロトタイプ宣言 16.3 関数と構造体 構造体、タグ名、メンバーとは何だったか? x 座標 y 座標 point プログラム例 16.3.2 から タグ名 struct point { double x; double y; }; 「2つのdouble型から成る構造体に point という名前のブランドタグをつけよう」 という感じ メンバーの宣言 void center_point(struct point p1, struct point p2); 中点を求めて出力する関数 center_point のプロトタイプ宣言 p1, p2 は point というタグがついた構造体変数
16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し #include <stdio.h> 16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し #include <stdio.h> struct point { double x; double y; }; void center_point(struct point p1, struct point p2); int main(void) { struct point p1, p2; p1.x = 3.8; p1.y = 5.6; p2.x = 8.4; p2.y = 18.2; center_point(p1, p2); return 0; } 構造体の実引数を関数に引き渡す
16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し 16.3 関数と構造体 プログラム例 16.3.2 構造体データの引渡し 仮引数が構造体である関数の定義 void center_point(struct point p1, struct point p2) { double xm, ym; xm = (p1.x + p2.x) / 2.; ym = (p1.y + p2.y) / 2.; printf("(%.2f, %.2f) と (%.2f, %.2f) の" "中点の座標は (%.2f, %.2f)\n", p1.x, p1.y, p2.x, p2.y, xm, ym); } メンバーを用いて計算 出力はメンバーで
平均点計算用の関数 average_of_scores のプロトタイプ宣言 16.3 関数と構造体 関数から結果を受け取る場合 → ポインタで プログラム例16.3.3から タグ名 struct grade { char *name; int subject1; int subject2; int subject3; double average; }; void average_of_scores(struct grade *h_p); 氏 名 科目1の点数 科目2の点数 科目3の点数 平均点 grade メンバーの宣言 平均点計算用の関数 average_of_scores のプロトタイプ宣言 h_p は grade というタグがついた構造体の先頭アドレスを指すポインタ変数
16.3 関数と構造体 プログラム例 16.3.3 改 #include <stdio.h> struct grade { 16.3 関数と構造体 3科目の平均点を出すプログラム プログラム例 16.3.3 改 平均点計算用の関数のプロトタイプ宣言 h_p は grade というタグがついた 構造体の先頭を指すポインタ変数 #include <stdio.h> struct grade { char *name; int subject1; int subject2; int subject3; double average; }; void average_of_scores(struct grade *h_p); int main(void) { struct grade Smith = {"John Smith", 90, 80, 35, 0}; average_of_scores(&Smith); printf("name: %s\n", Smith.name); printf("科目1: %d 科目2: %d 科目3: %d\n", Smith.subject1, Smith.subject2, Smith.subject3); printf("平均: %f\n", Smith.average); return 0; } ここでは Smith という構造体変数の アドレス(=ポインタ)が実引数
16.3 関数と構造体 *h_p.average ⇔ h_p -> average プログラム例 16.3.3 改 16.3 関数と構造体 プログラム例 16.3.3 改 3科目の平均点を出すプログラム 平均点計算用の関数の定義 h_p は grade というタグがついた 構造体の先頭を指すポインタ変数 void average_of_scores(struct grade *h_p) { h_p -> average = (h_p -> subject1 + h_p -> subject2 + h_p -> subject3) / 3.; } ポインタの場合 選択演算子 "->" でメンバーを指定する *h_p.average ⇔ h_p -> average
16.3 関数と構造体 プログラム例 16.3.2 改 関数から結果を構造体で 受け取るもう1つの方法 16.3 関数と構造体 関数から結果を構造体で 受け取るもう1つの方法 プログラム例 16.3.2 改 #include <stdio.h> struct point {double x; double y;}; struct point center_point(struct point p1, struct point p2); int main(void) { struct point p1, p2, p3; p1.x = 3.8; p1.y = 5.6; p2.x = 8.4; p2.y = 18.2; p3 = center_point(p1, p2); printf("(%.2f, %.2f) と (%.2f, %.2f) の" "中点の座標は (%.2f, %.2f)\n", p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); return 0; } 戻り値が構造体であることを宣言 結果を受け取る構造体 p3 を用意 結果の構造体を関数から戻り値で受け取る
16.3 関数と構造体 プログラム例 16.3.2 改 関数から結果を構造体で 受け取るもう1つの方法 16.3 関数と構造体 関数から結果を構造体で 受け取るもう1つの方法 プログラム例 16.3.2 改 戻り値が構造体である関数の定義 struct point center_point(struct point p1, struct point p2) { struct point p; p.x = (p1.x + p2.x) / 2.; p.y = (p1.y + p2.y) / 2.; return p; } 結果を入れる構造体を用意 メンバーを用いて計算 結果を構造体で返す
■ 乱数の発生 int rand(void) 疑似乱数の発生 範囲:0~RAND_MAX(=32767) p.183 int rand(void) 疑似乱数の発生 範囲:0~RAND_MAX(=32767) <stdlib.h>内で宣言 void srand(unsigned n) 疑似乱数のシード(種)の指定・変更 time_t time(time_t *timer) 経過時間を秒単位で表した数値 引数は NULL (空ポインタ)でよい <time.h>内で宣言 現在時間を使って 乱数の種を決定
サイコロ・プログラム(dice.c) #include <stdio.h> #include <stdlib.h> #include <time.h> int main(void) { int i; /* 実行するたびに違う値が得られるように、 * 現在の時刻値を使って乱数ジェネレータを初期化 */ srand((unsigned)time(NULL)); /* サイコロを10回振る */ for (i = 0; i < 10; i++) printf("%d ", (rand() % 6) + 1); // 6の剰余系+1 printf("\n"); return 0; }
■ 制御コード 教科書 p.180, 表A.2: 制御コード 使用例 \a ベル・警告音の出力 \r 行頭に戻る(キャリッジ・リターン) \f そのままの位置で行送り(ライン・フィード) \n 改行(\r\f) \t 水平タブ 使用例 putchar('\a'); printf("現在 %d 回目\r\a", i);
サイコロ・プログラム2(dice2.c) #include <stdlib.h> #include <stdio.h> #include <time.h> int main(void) { int i, n, m; /* 現在の時刻値を使って乱数ジェネレータを初期化 */ srand((unsigned)time(NULL)); printf(" ---\n"); n = rand() % 6; m = n + 8 + rand() % 10; for (i = n; i < m; i++) printf("\r| %d |\a", i % 6 + 1); printf("\n ---\n"); return 0; } n = 0 ~ 5 m = n+8 ~ n+17 1回の alert に約200 ms かかる → このサイコロは1.6 ~3.4秒間転がる
■ 列挙型 名前付き定数をゼロからの整数に対応させる enum タグ {列挙子リスト}; 例: 実体→ 0, 1, ..., 6 実体→ 0, 1, ..., 6 enum DAYS { SUNDAY, MONDAY, ..., SATURDAY };
■ system関数 OS(コマンドプロンプト)のコマンドをプログラムの中から実行できる。 system("cls") で画面クリア #include <stdlib.h> が必要
地底探検ゲーム(undergnd.c) 講義資料のWebページからダウンロード #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> /* 地面を格納する構造体の宣言 */ struct ground { int block[10]; // 地下情報を格納する配列の宣言 char blockStr[21]; // 地下ブロックパターン }; 乱数発生に必要 乱数初期化に必要 g[0].block = {1, 0, 0, 1, 0, 0, 0, 1, 0, 1} g[0].blockstr ■__■___■_■ 2バイト文字なので倍のサイズが必要 + \0 → 21バイト
地底探検ゲーム(undergnd.c) /* 地面を作成する関数 */ void makeGround(struct ground *p) { int i; char *wall[] = { " ", "■" }; /* 左の壁 */ strcpy(p->blockStr, wall[1]); p->block[0] = 1; /* 中央のブロックパターン */ for (i = 1; i <= 8; i++) { p->block[i] = rand() % 4 ? 0 : 1; // 確率1/4でブロック発生 strcat(p->blockStr, wall[p->block[i]]); } /* 右の壁 */ strcat(p->blockStr, wall[1]); p->block[9] = 1; 3バイトの文字列2つの配列 "■" 4で割り切れたら偽→1, 割り切れなければ真→0 対応する位置にブロックを置く "■"
次回の印刷時に前回の☆印が残らないように2バイト分の空白で事前に消しておく 地底探検ゲーム(undergnd.c) 現在地記憶用変数 int main(void) { int i, score = 0, x = 4, key = 1; struct ground g[4]; // 地面の構造体配列(4層分) /* 最初の地面の作成 */ srand(time(NULL) % 100); // 乱数シード:現在時刻の100の剰余 for (i = 0; i < 4; i++) makeGround(g + i); /* メインループ */ while (key) { /* 画面表示 */ strncpy(g[2].blockStr + x * 2, "☆", 2); for (i = 0; i < 4; i++) printf("%s\n", g[i].blockStr); strncpy(g[2].blockStr + x * 2, " ", 2); ギブアップチェック用フラグ 構造体配列の ポインタで引渡し 現在地マーク の埋め込み 4層分の地面を描画 次回の印刷時に前回の☆印が残らないように2バイト分の空白で事前に消しておく
地底探検ゲーム(undergnd.c) /* キー入力 */ printf("距離 %d m: ", score); scanf("%d", &key); /* 移動 */ if (key == 4 && g[2].block[x - 1] == 0) x--; else if (key == 6 && g[2].block[x + 1] == 0) x++; else if (key == 2 && g[3].block[x] == 0) { for (i = 0; i < 3; i++) g[i] = g[i + 1]; makeGround(g + 3); score++; } /* 終了処理 */ printf("スコア: %d メートル!\n", score); return 0; 左に移動 右に移動 下に移動 1段下からコピー 最下層の新規作成
マイン・スイーパー(mineswpr.c) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define TRUE 1 #define BSIZE 5 // ゲームボードのサイズ #define MINENUM 5 // 地雷の数 // 地雷を表現する構造体 typedef struct { int x, y; } Mine; enum MSTAT { MISS, NEAR, HIT }; static char map[BSIZE][BSIZE]; 整数値の座標で表現 Mine 型として定義 地雷の探査結果用の列挙型MSTATの定義 地雷マップ用の配列 staticにしておくと初期化される
マイン・スイーパー(mineswpr.c) // 地雷を追加する関数 void Mine_set(Mine *p) { do { p->x = rand() % BSIZE; p->y = rand() % BSIZE; } while (map[p->y][p->x]); map[p->y][p->x] = TRUE; } // 地雷に近いかどうかをチェックする関数 enum MSTAT Mine_check(int cx, int cy, Mine *p) int distX, distY; if (p->x == cx && p->y == cy) return HIT; distX = p->x - cx; distY = p->y - cy; if (-1 <= distX && distX <= 1 && -1 <= distY && distY <= 1) return NEAR; return MISS; すでに配置されている地雷と重複しないようチェック 地雷マップに登録 地雷を踏んだらHITを出力 地雷の隣ならNEARを出力 それ以外ならMISSを出力
マイン・スイーパー(mineswpr.c) // ゲームボード用データ構造 int status[BSIZE][BSIZE]; Mine m[MINENUM]; enum BSTAT { ALREADY, OPEN, BOMB }; // ゲームボードの初期化関数 void Board_init(void) { int x, y, i; // 状態の初期化 for (y = 0; y < BSIZE; y++) for (x = 0; x < BSIZE; x++) status[y][x] = -1; // 地雷セット for (i = 0; i < MINENUM; i++) Mine_set(&(m[i])); } ボードの状況用配列 地雷用配列を確保 ボードの状況記述用の 列挙型BSTATの定義 未探査状態は -1 地雷の数だけ呼び出す ポインタを渡す
マイン・スイーパー(mineswpr.c) // 指定された座標を開く関数 enum BSTAT Board_open(int x, int y) { int i, nearCount = 0; enum MSTAT ms; if (status[y][x] >= 0) return ALREADY; for (i = 0; i < MINENUM; i++) { ms = Mine_check(x, y, &m[i]); if (ms == HIT) return BOMB; if (ms == NEAR) nearCount++; } status[y][x] = nearCount; return OPEN; 地雷の探査結果用 探査済みなら 0 以上 →ALREADY を出力 未探査ならチェック 結果がHITならBOMBを出力 結果がNEARならカウンタを1つ増して次の地雷をチェック NEARの数をボードに記載 全部の地雷についてチェックして NEARばかりならOPENを出力
マイン・スイーパー(mineswpr.c) // ゲームボードを画面に表示する関数 void Board_show(void) { int i, x, y; printf(" "); for (i = 0; i < BSIZE; i++) putchar('A' + i); printf("\n"); for (y = 0; y < BSIZE; y++) { printf("%d ", y + 1); for (x = 0; x < BSIZE; x++) { if (status[y][x] < 0) printf("#"); else printf("%1d",status[y][x]); } ボードの x 座標 A~E の表示 y 座標の数字表示 未探査なら # を表示 探査済みなら NEARの数を表示
マイン・スイーパー(mineswpr.c) int main(void) { int i, x, y, openCount = 0; char charX, charY, key[100]; enum BSTAT bs; // 乱数の初期化 srand(time(NULL) % 100); Board_init(); // メインループ while (TRUE) { Board_show(); printf("座標を入力してください(例 A1): "); fgets(key, 100, stdin); if (strlen(key) < 2) continue; // 入力されたX座標をチェックする charX = key[0]; if (charX < 'A' || charX > 'A' + BSIZE-1) continue; x = charX - 'A'; ゲームボードの状況報告用 ゲームボードの初期化 ゲームボードの表示 キーボードから1行入力 2文字未満ならやりなおし ボードの範囲外ならやりなおし
マイン・スイーパー(mineswpr.c) // 入力されたY座標をチェックする charY = key[1]; if (charY < '1' || charY > '1' + BSIZE-1) continue; y = charY - '1'; // 判 定 if ((bs = Board_open(x, y)) == BOMB) { printf("\a\a\a\a地雷を踏んでしまいました!\n"); break; } if (bs == ALREADY) continue; if (++openCount == BSIZE*BSIZE - MINENUM) { printf("\aクリアしました!\n"); Board_show(); ボードの範囲外ならやりなおし 地雷を踏んだら強制終了 探査済みならやりなおし 探査済み数=全座標数-地雷数 ならクリア 最終結果を表示
マイン・スイーパー(mineswpr.c) printf("\n"); for (i=0; i<MINENUM; i++) printf(" %c%d",'A'+m[i].x,1+m[i].y); return 0; } 正解(地雷の座標)の表示 status[][] map[][] A B C D E 1 0 1 0 0 0 2 0 0 0 0 1 3 1 0 0 0 0 4 0 0 0 0 0 5 1 0 0 1 0 A B C D E 1 -1-1-1-1-1 2 -1-1-1-1-1 3 -1-1-1-1-1 4 -1-1-1-1-1 5 -1-1-1-1-1 Mine m[] m[0]={4,1} m[1]={1,0} m[2]={3,4} m[3]={0,4} m[4]={0,2}
スキルアップタイム1 地底探検ゲーム(undergnd.c)をコンパイルし、動作を確認せよ srand(…)の行をコメントアウトすると、どうなるか、確認せよ ファイル入出力関数の利用により、「ハイスコアの表示・保存機能」を付け足せ(保存ファイル名はhighscr.txt)
スキルアップタイム1のヒント int highscore=0; FILE *fp; ... if ((fp = fopen("highscr.txt", "r")) != NULL) { fscanf(fp, "%d", &highscore); fclose(fp); } if (score > highscore) { printf("ハイスコアです!\n"); fp = fopen("highscr.txt", "w"); fprintf(fp, "%d\n", score); } else printf("ハイスコア: %d メートル\n", highscore);
スキルアップタイム2 マイン・スイーパー(mineswpr.c)をコンパイルし、動作を確認せよ srand(…)の行をコメントアウトすると、どうなるか、確認せよ ゲームボードを表示するたびに、実行ウィンドウの上部から書き直すように改良せよ ゲームボードのサイズや地雷の数を変えてみよ
入門Ⅱでいままで学んできたこと 関 数 配 列 ポインタ 構造体 関 数 ◎ ○ ○ ○ 配 列 ○ ◎ ◎ ○ ポインタ ○ ◎ ◎ △ 関 数 配 列 ポインタ 構造体 関 数 ◎ ○ ○ ○ 配 列 ○ ◎ ◎ ○ ポインタ ○ ◎ ◎ △ 構造体 ○ ○ △ ○ 文字列 ○ ファイル操作 ○ 列挙型 ○ コマンドライン × 再帰呼出し × 共用体 × 動的メモリ確保 ×