/* トランプの一人遊び「ギャップ」 授業中に示したポーカーのプログラムでは、数値とカードの対応を伝統的なものに しようとしたが間違ってしまった。そこで、今度は間違えないようにして、この プログラムを書いてみた。そのため、このプログラムでは 13 を掛けたり 13 で 割ったりするところが一カ所もない。(現在のコンピュータは十分に速いので、 それらを避ける必要性はほとんどないのだが。) ジョーカーを含まない 52 枚のカードをよく切り、横 13 枚、縦 4 枚に並べる。 A を取り除き、そこを「ギャップ」と呼ぶ。 移動の規則は次の通り。 1.ギャップの左隣にカードがあり、それが K でない場合、そのカードと同じ スートでランクが一つ上のものをそこへ移動できる。 2.ギャップが左端にできた場合、そこへ 2 を移動できる。どのスートの 2 を 移動するかは、プレーヤーが自由に選べる。 目的は、各段に、同じスートのカードを 2 から K まで順に並べることである。 (そのとき、ギャップは K の右隣になければならない。) プログラム内部では、配列 card[ ] が並んでいるカードを表している。カードとの 対応は card[0] card[4] ... card[44] card[48] card[1] card[5] ... card[45] card[49] card[2] card[6] ... card[46] card[50] card[3] card[7] ... card[47] card[51] である。横でなく縦に並んでいるのは、13 を掛けたり 13 で割ったりするのを 避けるためである。 カードは 0 から 51 までの数値で表す。0 から順に、スペードの A, ハートの A, クラブの A, ダイアモンドの A, スペードの 2, ハートの 2, クラブの 2, ダイアモンドの 2, ..., スペードの K, ハートの K, クラブの K, ダイヤモンド の K である。すなわち、数値を i とするとき、i & 3 がスート(0, 1, 2, 3 が それぞれスペード、ハート、クラブ、ダイアモンド)を、i >> 2 がランク(0, 1, 2, ..., 9, 10, 11, 12 がそれぞれ A, 2, 3, ..., 10, J, Q, K)を表す。(これ が伝統的な対応のさせかたである。) ギャップは、内部では A のカードで表現されている。ギャップへのカードの移動 は、移動するカードと A とを交換することで実現されている。 各局面において、得点を計算して画面に出す。それは、2 が左端にあれば 12 点、 それ以外のカードは、3 の左隣に同じスートの 2 があれば 11 点、4 の左隣に同じ スートの 3 があれば 10 点、……、K の左隣に同じスートの Q があれば 1 点と して、それらを加算したものである。この得点は、ルールに従ってカードを動かす と、左端にある 2 をある段から別の段に移動した場合を除き、必ず増加する。この 事実は、学部時代に同級生だった、松本久義氏(現・東大数理)のご指摘による。 312 点が満点である。 なお、このプログラムは、目的を達成しても、ゆきづまっても、何も出力しない。 プレーヤーが勝手に終了すること。 関数 show() ではエスケープシーケンスを使っている。対応していない端末の場合 は削除されたい。 */ #include #include #include int card[52]; void init(void); void swap(int i, int j); void show(void); int val(void); main() { int i, j, n, rank; init(); show(); i = j = 0; /* こうしておくと、開始直後に undo されても何も起こらない */ for (n = 1; n != 0; ) { printf("%3d 点です. ", val()); printf("どのギャップにカードを移しますか? "); printf("(-1 で一度だけ undo, 0 で終了)>"); scanf("%d", &n); if (n == 0) { /* このときは終了へ */ ; } else if (n == -1) { /* undo のとき */ swap(i, j); /* (undo 直後に undo しよう */ show(); /* とすると re-do になる) */ } else if (n < -1 || n > 4) { printf("入力が範囲外です. やり直してください.\n"); } else { for (i = 0; card[i] != n-1; i++) { /* ギャップ n を探す */ ; } if (i >> 2 == 0) { /* ギャップが左の端のとき */ for (n = 0; n == 0; ) { printf("どの 2 を移しますか? "); printf("! なら 1, & なら 2, %% なら 3, @ なら 4. >"); scanf("%d", &n); if (n < 1 || n > 4) { n = 0; /* これでやり直しになる */ } } for (j = 0; card[j] != 4 + (n-1); j++) { /* その 2 を探す */ ; } swap(i, j); show(); } else { /* ギャップが左の端以外のとき */ rank = card[i-4] >> 2; /* 左隣のカードを調べる */ if (rank == 0) { /* 左隣が A(ギャップ)のとき */ printf("ギャップの右には移せません.\n"); } else if (rank == 12) { /* 左隣が K のとき */ printf("K の右には移せません.\n"); } else { for (j = 0; card[j] != card[i-4] + 4; j++) { ; /* もってくるべきカードを探す */ } swap(i, j); show(); } } } } } /* 初期化 */ void init (void) { int i; unsigned int seed; seed = (unsigned int)time(NULL); /* 現在時刻を取得して */ srand(seed); /* それを種に乱数を初期化 */ for (i = 0; i < 52; i++) { /* まずは順にカードをならべる */ card[i] = i; } for (i = 51; i >= 1; i--) { /* それをシャッフル */ swap(i, rand() % (i+1)); } } /* card[i] と card[j] とを交換 */ void swap(int i, int j) { int tmp; tmp = card[i]; card[i] = card[j]; card[j] = tmp; } /* カード全体を画面に出力 */ void show(void) { int i, j, rank, suit; printf("\033[2J"); /* 画面クリアのエスケープシーケンス */ printf("\033[1;1H"); /* カーソル左上隅へ */ for (i = 0; i < 4; i++) { printf("\n"); /* 縦 24 行の画面でデバッグする時はこの行は削る? */ for (j = 0; j < 13; j++) { rank = card[i + (j << 2)] >> 2; if (rank == 0) { /* A にあたるところはギャップ */ printf(" "); } else if (rank == 9) { printf("T "); /* これは ten の T */ } else if (rank == 10) { printf("J "); } else if (rank == 11) { printf("Q "); } else if (rank == 12) { printf("K "); } else { /* 2 から 9 までのとき */ printf("%d ", rank + 1); } } printf("\n"); for (j = 0; j < 13; j++) { suit = card[i + (j << 2)] & 3; if (card[i + (j << 2)] >> 2 == 0) { /* ギャップのとき */ printf("%d ", suit + 1); } else { /* ギャップでないとき */ if (suit == 0) { printf("\033[32m!\033[m "); /* スペードのつもり */ } else if (suit == 1) { printf("\033[31m&\033[m "); /* ハートのつもり */ } else if (suit == 2) { printf("\033[36m%%\033[m "); /* クラブのつもり */ } else { printf("\033[35m@\033[m "); /* ダイヤモンドのつもり */ } } } printf("\n"); } printf("\n"); /* 縦 24 行の画面でデバッグする時はこの行は削る? */ } /* 得点の計算。計算方法は冒頭のコメントを参照。*/ int val(void) { int i, j, n; n = 0; for (i = 0; i < 4; i++) { if (card[i + (0 << 2)] >> 2 == 1) { /* 左端が 2 のとき */ n = n + (13 - 1); } for (j = 1; j < 13; j++) { /* 左端以外 */ if (card[i + ((j-1) << 2)] + 4 == card[i + (j << 2)]) { if (card[i + (j << 2)] >= 8) { /* ギャップと 2 の並びは除く */ n = n + (13 - (card[i + (j << 2)] >> 2)); } } } } return n; }