第66章 碁盤を拡張する


今回は、碁盤のマス目を拡張します。といっても画面サイズに限りが あるので、その範囲内です。



左の図は前回のゲームのマス目を5*5に拡張したものです。

これで、少しはゲームらしくなりました。



碁盤を拡張する場合、前回までのプログラムで3*3に影響を受けていたところ (依存していたところ)を書き直せばよいです。

では、プログラムを見てみましょう。

// game03.c #include <windows.h> #include <stdio.h> #define MASU 5 //MASUの数値を変えると盤面の大きさが変わります int banshow(HANDLE); int Locate(HANDLE, int, int); int check_location(int, int); //すでに駒がおかれていないかどうか調べる int judge(); int ban[MASU][MASU];

マス目のサイズをMASUとしました。この値を変えるだけでマス目を変えることが できます。

int ban[MASU][MASU];

さっそく配列banにMASUを使っています。このように盤の大きさに 依存しているところをMASUで表せばよいですね。

int main() { HANDLE hOut; char cMoji, szTxt[8]; BOOL bOrd = TRUE; //FALSE:後手 TRUE:先手 int i, row =0, col=0, jud; char location[8]; //位置指定 A2, B1,など char *sente = "先手", *gote = "後手"; char order[8], hantei[16]; hOut = GetStdHandle(STD_OUTPUT_HANDLE); banshow(hOut); for (i = 0; i < MASU * MASU; i++) { if (bOrd) strcpy(order, sente); else strcpy(order, gote); INP: Locate(hOut, 28, MASU + 2); printf(" "); Locate(hOut, 0, MASU + 2); printf("位置を指定してください(%s)", order); gets(location); if (location[0] == '0') return -1; if (isalpha(location[0]) == 0) { Locate(hOut, 0, MASU + 3); printf("指定が違います"); goto INP; } if (isupper(location[0]) == 0) cMoji = toupper(location[0]); else cMoji = location[0]; row = cMoji - 'A'; if (row < 0 || row > MASU - 1) { Locate(hOut, 0, MASU + 3); printf("行の指定が違います"); goto INP; } if (isdigit(location[1]) != 0 && isdigit(location[2]) != 0 && location[3] == '\0') { szTxt[0] = location[1]; szTxt[1] = location[2]; szTxt[2] = '\0'; col = atoi(szTxt) - 1; } else if (isdigit(location[1]) != 0 && location[2] == '\0') { szTxt[0] = location[1]; szTxt[1] = '\0'; col = atoi(szTxt) - 1; } else { Locate(hOut, 0, MASU + 3); printf("列の指定が違います"); goto INP; } if (col < 0 || col > MASU - 1) { Locate(hOut, 0, MASU + 3); printf("列の位置指定が違います"); goto INP; } if (check_location(row, col) != 0) { Locate(hOut, 0, MASU + 3); printf("そこには置けません!"); goto INP; } if (bOrd) ban[row][col] = 1; else ban[row][col] = 2; bOrd = !bOrd; banshow(hOut); Locate(hOut, 0, MASU + 3); printf(" "); jud = judge(); switch (jud) { case 0: strcpy(hantei, ""); break; case 1: strcpy(hantei, "先手の勝ち"); break; case 2: strcpy(hantei, "後手の勝ち"); break; default: strcpy(hantei, "内部エラー"); break; } Locate(hOut, 0, MASU + 4); printf(hantei); if (jud != 0) break; } if (jud == 0) { printf("引き分けです"); } printf("\n\n"); return 0; }

main関数です。

盤の大きさに依存しているのは、最大の石置き回数、文字の表示位置などですね。

ここでちょっと注意があります。いままでは、石の位置を指定するのは 最大(?)でも「c3」でした。しかし今度は「j12」などということもあり得ます。 ユーザーの入力した文字列から行と列を取得するのに一工夫いります。

ユーザーが入力した文字列をlocationとすると、行を表す文字はlocation[0]です。 (行は最大でもZ迄しかないとします。実際画面の関係でZも無理です。) location[0]が小文字なら大文字に変換して、cMojiとします。ユーザーが間違って 最初の文字をアルファベット以外にした場合は、直ちに注意を促すようにします。 文字を調べるにはis****関数というのがたくさんあるので、これらを使います。

さて、cMojiが決まったらこれを数値にします。Aなら0, Bなら1,.....です。 これは、cMoji - 'A'で求めることができます。(よく使うテクニックです)

次に列を求めます。これはlocation[1]以降を調べます。

location[1], location[2]がともに数字を表す文字('0'-'9')でなおかつlocation[3]が ヌル文字であれば列は2桁です。

location[1]が数値を表す文字でなおかつlocation[2]がヌル文字であれば列は1桁です。 列部分を文字列として取り出して数値に変換すればよいですね。

#include <ctype.h> int isalpha( int c );

cが'A'-'Z', 'a'-'z'の範囲であれば0以外を、そうでなければ0を返します。

#include <ctype.h> int iswupper( wint_t c );

cが'A'-'Z'の範囲であれば0以外を、そうでなければ0を返します。
これと同様にislower関数というのもあります。

#include <ctype.h> int isdigit( int c );

cが10進数字の時は0以外を、そうでないときは0を返します。

atoi関数については第34章第52章などを参照してください。

さて、これだけわかるとmain関数は特に難しくはないですね。 あとは、Locate関数で表示位置を指定する部分に注意すればよいだけです。

int banshow(HANDLE hStdOut) { int i, j; Locate(hStdOut, 0, 0); printf(" "); for (i = 0; i < MASU; i++) { printf("[%2d]", i + 1); } printf("\n"); for (i = 0; i < MASU; i++) { printf("[%c]\n", i + 'A'); } for (i = 0; i < MASU; i++) { for (j = 0; j < MASU; j++) { if (ban[i][j] == 1) { Locate(hStdOut, j * 4 + 4, i + 1); printf("○"); } if (ban[i][j] == 2) { Locate(hStdOut, j * 4 + 4, i + 1); printf("×"); } } } return 0; }

グローバル変数のban配列を見て、石を描画する関数です。 これもMASUの使われている部分をよく見てください。

int Locate(HANDLE hOut, int x, int y) { COORD dwPos; dwPos.X = (SHORT)x; dwPos.Y = (SHORT)y; if (SetConsoleCursorPosition(hOut, dwPos) == 0) return -1; else return 0; } int check_location(int row, int col) { if (ban[row][col] == 0) return 0; else return -1; }

この2つの関数に変更はありません。

int judge() //戻り値 1:先手の勝ち 2:後手の勝ち 0:勝敗はまだ { int i, j, rseki[MASU], rwa[MASU], cseki[MASU], cwa[MASU]; int cross1seki = 1, cross1wa = 0, cross2seki = 1, cross2wa = 0; // 配列の初期化 for (i = 0; i < MASU; i++) { rseki[i] = 1; rwa[i] = 0; cseki[i] = 1; cwa[i] = 0; } for (i = 0; i < MASU; i++) { for (j = 0; j < MASU; j++) { rseki[i] = rseki[i] * ban[i][j]; rwa[i] = rwa[i] + ban[i][j]; cseki[i] = cseki[i] * ban[j][i]; cwa[i] = cwa[i] + ban[j][i]; } cross1seki = cross1seki * ban[i][i]; cross1wa = cross1wa + ban[i][i]; cross2seki = cross2seki * ban[MASU - 1 - i][i]; cross2wa = cross2wa + ban[MASU - 1 - i][i]; } for (i = 0; i < MASU; i++) { if (rseki[i] != 0 && rwa[i] == MASU) return 1; if (rseki[i] != 0 && rwa[i] == MASU * 2) return 2; if (cseki[i] != 0 && cwa[i] == MASU) return 1; if (cseki[i] != 0 && cwa[i] == MASU * 2) return 2; } if (cross1seki != 0 && cross1wa == MASU) return 1; if (cross1seki != 0 && cross1wa == MASU * 2) return 2; if (cross2seki != 0 && cross2wa == MASU) return 1; if (cross2seki != 0 && cross2wa == MASU * 2) return 2; return 0; }

勝敗を判定する関数ですが、これも前回の「3」に依存している部分を MASUに置き換えればよいですね。

さてこのように、プログラムを作るときは最初はごく簡単なものを作り、 仕組みがわかったら、拡張して大きなものに作り替えていくという 手順を踏むと比較的楽に作業が進みます。


[Index][総合Index] [Previous Chapter] [Next Chapter]

Update May/28/2001 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。