マインスイーパの概要 マインスイーパの準備 マインスイーパの完成 マインスイーパの改良 ゲームの作成 マインスイーパの概要 マインスイーパの準備 マインスイーパの完成 マインスイーパの改良
マインスイーパの概要 ゲームの目的 ゲームの勝敗・スコア ゲームのやり方 地雷が隠れているマス目を開けずに、(できるだけ早く)すべての地雷を見つけること ゲームの勝敗・スコア 地雷を掘り出したら負け 地雷以外を全部開けたら勝ち (所要時間は短いほうが良い) ゲームのやり方 マス目の座標を入力してマス目を開く 表示される数字は隣接する周囲のマス目に隠れている地雷の数を示す
マイン・スイーパの準備 m[5]: 5つの地雷の位置 map[5][5]: 地雷マップ用配列 status[5][5]: 隣接地雷数情報(ゲームボードの状況) 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}
マイン・スイーパの準備(mines0.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; 整数値の座標で表現 Mine 型としてユーザー定義
マイン・スイーパの準備(mines0.c) static にしておくと初期化される // ゲームボード用データ構造 int status[BSIZE][BSIZE]; // 状況マップ(ゲームボード) static int map[BSIZE][BSIZE]; // 地雷マップ Mine m[MINENUM]; // 地雷の位置座標の配列 // 地雷を追加する関数 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; } 乱数で0~(BSIZE-1)を発生 すでに配置されている地雷と重複しないようチェック 地雷マップに登録
マイン・スイーパの準備(mines0.c) // ゲームボードの初期化関数 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])); } 未探査状態は -1 地雷の数だけ呼び出す ポインタを渡す
マイン・スイーパの準備(mines0.c) // ゲームボードを画面に表示する関数 void Board_show(int status[BSIZE][BSIZE]) { int i, x, y; printf(" "); for (i = 0; i < BSIZE; i++) printf("%c", '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 座標の数字表示 未探査なら # を表示 探査済みなら 隣接地雷数を表示
マイン・スイーパの準備(mines0.c) int main(void) { srand(time(NULL) % 100); // 乱数の初期化 Board_init(); Board_show(map); Board_show(status); return 0; } ゲームボードの初期化 地雷マップの表示(確認用) ゲームボードの表示
マイン・スイーパ(mineswpr.c) 前ページの main の代わりに以下のプログラムを差し替え enum MSTAT { MISS, NEAR, HIT }; // 地雷チェック結果 enum BSTAT { ALREADY, OPEN, BOMB }; // ボードオープン結果 // 地雷に近いかどうかをチェックする関数 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; // 地雷とのx方向の距離 distY = p??y - cy; // 地雷とのy方向の距離 if (-1 <= distX && distX <= 1 && -1 <= distY && distY <= 1) return NEAR; return MISS; } 地雷の位置と一致 地雷に隣接 周囲に地雷なし
マイン・スイーパ(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; すでに開いた場所 地雷 地雷なし
PAD 乱数シードセット ゲームボードの初期化 Board_init() Board_show(status) ゲームボードの表示 座標入力 X座標チェック Y座標チェック メイン TRUE 入力座標を開く bs=Board_open(x,y) 無限ループ "地雷を踏んでしまいました!" break bs==BOMB? bs==ALREADY? (bs==OPEN?) 判定 continue "クリアしました!" openCount++ Board_show(status) 地雷座標の出力 残り==地雷数? break
マイン・スイーパ(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(status); 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';
マイン・スイーパ(mineswpr.c) // 入力されたY座標をチェックする charY = key[1]; if (charY < '1' || charY > '1' + BSIZE-1) continue; y = charY - '1'; // オープン・判定 bs = Board_open(x, y); if (bs == BOMB) { printf("\a\a\a\a地雷を踏んでしまいました!\n"); break; } else if (bs == ALREADY) continue; else { openCount++; if (BSIZE*BSIZE - openCount == MINENUM) { printf("\aクリアしました!\n"); Board_show(status); }
マイン・スイーパ(mineswpr.c) // 地雷座標の出力 printf("\n"); for (i=0; i<MINENUM; i++) printf(" %c%d", 'A'+m[i]?x, 1+m[i]?y); return 0; }
スキルアップタイム1 マイン・スイーパの準備(mines0.c)をコンパイルし、地雷の位置が毎回異なることを確認せよ srand(…)の行をコメントアウトすると、どうなるか、確認せよ ゲームボードのサイズや地雷の数を変えてみよ
スキルアップタイム2 マイン・スイーパ(mineswpr.c)を完成させ、動作を確認せよ 地雷にヒットしたときの連続ビープ音を爆発音(EXPLODE.WAV)に変更せよ クリアしたときのビープ音をTa-dah!(ジャジャーン)に変更せよ(C:\WINDOWS\Media\TADA.WAV) 開始からクリアまでの時間を計測し、表示せよ
WAVファイルの再生 /* WAVファイルの再生 */ #include <windows.h> スキルアップタイム2のヒント /* WAVファイルの再生 */ #include <windows.h> #pragma comment(lib,"winmm") // winmm.lib をリンクする int main(void) { if (!PlaySound("EXPLODE.WAV", 0, SND_FILENAME)) printf("The sound didn't play."); if (!PlaySound("TADA.WAV", 0, SND_FILENAME)) return 0; } P:\2010年度後期\プログラミング入門2 フォルダに EXPLODE.WAV がある。 C:\WINDOWS\Media フォルダに TADA.WAV がある。
時間計測:秒単位(time1.c) #include <stdio.h> #include <stdlib.h> スキルアップタイム2のヒント #include <stdio.h> #include <stdlib.h> #include <time.h> int main(void) { int i, N=100000; int t1, t2; t1 = time(NULL); for (i=0; i<N; i++) printf(" %d", rand()%6 + 1); printf("\n"); t2 = time(NULL); printf("%d sec\n", t2-t1); return 0; } 時間関数用ヘッダ 変数宣言 計測対象 計測開始 計測終了 実行時間(秒数)
本日のパズル 次のプログラムは何を出力するか? #include <stdio.h> main(t,_,a) char *a; {return!0<t?t<3?main(-79,-13,a+main(-87,1-_, main(-86, 0, a+1 )+a)):1,t<_?main(t+1, _, a ):3,main ( -94, -27+t, a )&&t == 2 ?_<13 ?main ( 2, _+1, "%s %d %d\n" ):9:16:t<0?t<-72?main(_, t,"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l,+,/n{n+\ ,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/\ +k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){n\ l]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#\ n'wk nw' iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c \ ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;\ #'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/") :t<-50?_==*a ?putchar(a[31]):main(-65,_,a+1):main((*a == '/')+t,_,a\ +1 ):0<t?main ( 2, 2 , "%s"):*a=='/'||main(0,main(-61,*a, "!ek;dc \ i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m .vpbks,fxntdCeghiry"),a+1);} phillipps.c