Edited at
移行Day 3

AdventCalendar Day03 [超Ruby入門]


はじめに

本記事は、超Ruby入門3日目の記事です。

コメント頂ける方は、ガイドラインを読んで頂けると幸いです。


目的

C言語の基本的な構文を理解する。


目標

○×ゲームを通して、プログラムを小規模から実装する方法を学ぶ。


○×ゲーム

C言語の基本的な構文のみを使って丸ばつゲームを実装する。

丸ばつゲームとは、プレイヤーが交互にマス目に対して○と×をつける。

勝敗の条件は一方のプレイヤーが横縦斜め、いずれかを揃えたら勝つとなる。


本題

仕様が与えられた時、どのようにプログラムを組めばいいのだろか。

もしかすると、仕様が与えられた瞬間にプログラムを書き始めているかも知れないが得策ではない。

構文を理解させるための簡単な練習問題ならそれでもいいかもしれないが、まとまったプログラムを書こうと思ったら上手くはいかないだろう。

では、どうすればいいかというと問題を小さな単位に分割して実装するとよい。

本題である○×ゲームを実装する時に、何から手をつけたらいいのだろうか。

様々な考え方があるだろうが、○×ゲームを分解すると以下のようになる。


  1. マス目を表示させる

  2. マス目を入力として与えれる

  3. 入力のチェック

  4. ○×をマス目に対応

  5. 勝利がいるか判断

  6. 縦勝利条件

  7. 横勝利条件

  8. 斜め勝利条件

どれくらいの大きさに分けて実装を行うかは、自分の腕と相談して決めるとよい。


1を実装

まず、1を実装すると以下のようなコードになる。


game01.c

#include <stdio.h>


#define INIT 0
#define PLAYER1 1
#define PLAYER2 2

void show(int masu[3][3]){
int i,j;
for( i = 0; i < 3;i ++){
for(j = 0; j < 3;j++){
printf("%d ", masu[i][j]);
}
printf("\n");
}
}

int main(int argc, char *argv[]){
int masu[3][3]; /* マス目 */
int i, j;

for( i = 0; i < 3;i ++){
for(j = 0; j < 3;j++){
masu[i][j] = INIT;
}
}
show(masu);

return 0;
}



実行.

gcc game01.c

./a.out


define構文

define文は、定数の定義や関数マクロなどに使われる構文だ。


2と3を実装

全体コード


game02.c


~~~~

enum TURN { P1_TURN , P2_TURN };

void show(int masu[3][3]){

~~~~

int check_input(int input_num){
int flag = 0; /* input_numが1 ~ 9の整数値か判断*/
int i;
for(i = 1; i <= 9;i++){
if(i == input_num){
flag = 1;
}
}
return flag;
}

int main(int argc, char *argv[]){
int input_num; /* 入力 */
enum TURN turn = P1_TURN; /* どちらのターンか */

~~~~

while(1){
scanf("%d",&input_num);
if(check_input(input_num)){
printf("flag = true : input_num = %d\n",input_num);
}
else{
printf("1 ~ 9を入力して下さい");
break;
}
printf("turn = %d\n", turn);
turn = (turn != P1_TURN) ? P1_TURN : P2_TURN; // ターンチェンジ
}
return 0;
}



enum

enumは列挙型の定義をするときに使う。

列挙型とは、整数定数に名前をつけて取り扱うことができる型だ。


三項演算子

turn = (turn != P1_TURN) ? P1_TURN : P2_TURN; // ターンチェンジ

条件? 処理1:処理2;で表現される三項演算子は下記のプログラムと等価だ。

if ( 条件 )

処理1;
else
処理2;


4を実装

全体コード


~~~~

void show(int masu[3][3]){
int i,j;
for( i = 0; i < 3;i ++){
for(j = 0; j < 3;j++){
if(masu[i][j] == INIT){
printf(" - ");
}
else if(masu[i][j] == PLAYER1){
printf(" ○ ");
}
else{
printf(" × ");
}
}
printf("\n");
}
}

int main(int argc, char *argv[]){

~~~~

while(1){

printf("1 ~ 9を入力して下さい\n");

scanf("%d",&input_num);

// 入力判定
if(!check_input(input_num)){
printf("入力が間違っています\n");
break;
}

line = (input_num - 1) / 3; // 行
row = (input_num - 1) % 3; // 列
if(turn == PLAYER1){
masu[line][row] = PLAYER1;
}
else if(turn == PLAYER2){
masu[line][row] = PLAYER2;
}
show(masu);
turn = (turn != P1_TURN) ? P1_TURN : P2_TURN; // ターンチェンジ
}
return 0;
}


5.6.7.8を実装

全体コード


~~~~

int judge_yoko(int masu[3][3], int turn){
int i;
for(i = 0; i < 3; i++){
if(masu[i][0] == turn && masu[i][1] == turn && masu[i][2] == turn){
return 1;
}
}
return 0;
}

int judge_tate(int masu[3][3], int turn){
int i;
for(i = 0; i < 3; i++){
if(masu[0][i] == turn && masu[1][i] == turn && masu[2][i] == turn){
return 1;
}
}
return 0;
}

int judge_naname(int masu[3][3], int turn){
int i;
int fr_cnt = 0;
int fl_cnt = 0;
for(i = 0; i < 3;i++){
if(masu[i][i] == turn){
fl_cnt += 1;
}
if(masu[i][2 - i] == turn){
fr_cnt += 1;
}
}
if(fr_cnt == 3 || fl_cnt == 3){
return 1;
}
return 0;
}

int judge_winner(int masu[3][3], int turn){
int winner_flag = 0;
if ( judge_yoko(masu , turn) || judge_tate(masu, turn) || judge_naname(masu, turn)){
winner_flag = 1;
}
return winner_flag;
}

int main(int argc, char *argv[]){

~~~~
while(1){
if(turn == P1_TURN){
printf("先行の番です\n");
}
else{
printf("後攻の番です\n");
}
printf("1 ~ 9を入力して下さい\n");

scanf("%d",&input_num);

// 入力判定
if(!check_input(input_num)){
printf("入力が間違っています\n");
break;
}

line = (input_num - 1) / 3; // 行
row = (input_num - 1) % 3; // 列

if(turn == PLAYER1){
masu[line][row] = PLAYER1;
if (judge_winner(masu, turn)){
printf("先行の勝ち\n");
printf("最終結果\n\n\n");
show(masu);
break;
}
}
else if (turn == PLAYER2){
masu[line][row] = PLAYER2;
if (judge_winner(masu, turn)){
printf("後攻の勝ち\n");
printf("最終結果\n\n\n");
show(masu);
break;
}
}
show(masu);
turn = (turn != P1_TURN) ? P1_TURN : P2_TURN; // ターンチェンジ
}
return 0;
}

大きく分けて4stepで実装を行なった。

検討がつかない問題が発生しても、小さな単位で考えることで実装を行うとよいことがわかっただろう。


課題

○×ゲームの大枠は完成し、正常に動くが欠陥が多い。(少ししかテストしてないので間違っていれば指摘して下さい)

例えば


  • 入力が間違っていた場合、プログラムが停止してしまう

  • 3x3にしか対応していない。

  • 1 ~ 9のマス目がどこを指すのかプログラムを見ないとわからない

以上に挙げられるような問題や自分で欠陥を探し○×ゲームを改良せよ。(汚いコード見る練習にもなるかも)


深めたい人

プログラミング言語C 第2版 ANSI規格準拠