質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

1回答

1270閲覧

書いたマルバツゲームの判定方法の間違いが分かりません。

haru0

総合スコア5

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2022/12/14 15:03

編集2022/12/15 22:14

前提

NxNマスのマルバツゲームを作っています。
プレイヤー同士で対戦します。

実感としては90%ぐらいで来たと思います。

実現したいこと

詰まっている点は2つです。

  1. 判定方法のところのコードが間違っているのですが、それがなぜなのか全く分かりません。
  2. 勝負がついた時に、どのようにしてループを抜け出すのか(一応自分が終わって欲しいと思うところにbreakとは書いているのですが、下のパターン1、2の様に判定方法が間違っているせいなのかループから抜け出してくれません。)

発生している問題・エラーメッセージ

パターン1 OX_ XXO OXO x座標を0〜2から入力してください: //この様になっても終わらない。続いてしまう。さらに続けると OXO XXO OXO 引き分けです //となってしまう。 ____________________ パターン2.0 OXO XO_ OX_ //この様になっても終わらない パターン2.1 OXO XOX OXO Oの勝ちです! //パターン2.0をこの状態まで続けるとなぜか終わる。

該当のソースコード

C言語

1#include <stdio.h> 2 3int main() { 4 5 int n; 6 int while_count = 0;//全体をカウントする。これの偶奇でプレイヤー1、2を判断する。 7 printf("マスの数を入力してください:"); 8 scanf("%d", &n); 9 10 //ここからボードを作ってく 11 int x = n, y = n; 12 char board[y][x]; 13 int i, j; 14 15 for (i = 0; i < n ; i++){ 16 printf("\n"); 17 for(j = 0; j < n ; j++){ 18 printf("_ "); 19 board[i][j] = '_'; 20 } 21 } 22 printf("\n"); 23 24 /* 25 whileの中でやること 26 1.◯を入力する(◯スタート。偶数回目であれば◯、奇数回目であればXを入力。1回目からスタート?それとも0回目?調べる) 27 2.◯の場所が(A)範囲内で(B)空白なのかどうか確認 28 3.◯を記入して、ボードを表示 29 4.勝敗を判断 30 5.ターンを進める 31 6.1に戻る 32 */ 33 while(1){ 34 //1.入力する+2.入力チェック 35 do{ 36 printf("x座標を0〜%dから入力してください:", n - 1); 37 scanf("%d", &x); 38 printf("y座標を0〜%dから入力してください:", n - 1); 39 scanf("%d", &y); 40 41 if(x < 0 || x > n - 1 || y < 0 || y > n - 1){ 42 printf("入力された値が不正です。\n"); 43 }else if(board[y][x] != '_'){ 44 printf("そのマスは埋まってます。\n"); 45 } 46 }while((x < 0 || x > n - 1 || y < 0 || y > n - 1) || (board[y][x] != '_')); 47 48 //3.◯xを記入して、 49 if(while_count % 2 == 0){ 50 board[y][x] = 'O'; 51 }else{ 52 board[y][x] = 'X'; 53 } 54 55 //ボードを表示 56 for (i = 0; i < n ; i++){ 57 printf("\n"); 58 for(j = 0; j < n ; j++){ 59 printf("%c", board[i][j]); 60 } 61 } 62 printf("\n"); 63 64 //4勝敗の判断。縦横斜めが同じかどうかをループで判断引き分け(全マス埋まるも考える)。 65 char kigou; //if文を使って偶数回目であればkigou='O',奇数回目であればkigou='X'を代入 66 if(while_count % 2 == 0){ 67 kigou = 'O'; 68 }else{ 69 kigou = 'X'; 70 } 71 72 int count1 = 0; 73 for(y = 0; y < n; y++){//横方向の行をn行分チェック。countで数えてその回数とnが同じであれば勝ちになるはず。 74 count1 = 0; 75 for(x = 0; x < n; x++){ 76 if(board[y][x] == kigou){ 77 printf("count1 %d\n", count1); 78 count1++; 79 } 80 } 81 if(count1 == n){ 82 break; 83 } 84 } 85 if(count1 == n){ 86 printf("%cの勝ちです!\n", kigou); 87 break; 88 } 89 90 int count2 = 0; 91 for(x = 0; x < n; x++){//縦方向の行をn列分チェック。countで数えてその回数とnが同じであれば勝ちになるはず。 92 count2 = 0; 93 for(y = 0; y < n; y++){ 94 if(board[y][x] == kigou){ 95 printf("count2 %d\n", count2); 96 count2++; 97 } 98 } 99 if(count2 == n){ 100 break; 101 } 102 } 103 if(count2 == n){ 104 printf("%cの勝ちです!\n", kigou); 105 break; 106 } 107 108 109 int count3 = 0; 110 int a; 111 for(a = 0; a < n; a++){//左上から右下に向かってチェックする。countで数えてその回数とnが同じであれば勝ちになるはず。 112 if(board[a][a] == kigou){ 113 printf("count3 %d\n", count3); 114 count3++; 115 } 116 } 117 if(count3 == n){ 118 printf("%cの勝ちです!\n", kigou); 119 break; 120 } 121 122 int count4 = 0; 123 for (a = 0; a < n; a++){//右上から左下に向かってチェックする。countで数えてその回数とnが同じであれば勝ちになるはず。 124 if(board[n - 1 - a][a] == kigou){ 125 printf("count4 %d\n", count4); 126 count4++; 127 } 128 } 129 if(count4 == n){ 130 printf("%cの勝ちです!\n", kigou); 131 break; 132 } 133 134 if(while_count == n*n - 1){// 135 printf("引き分けです"); 136 break; 137 } 138 //5.ターンを進める 139 printf("\n%d周目終わり\n", while_count); 140 while_count++; 141 printf("\n%d周目始まる\n", while_count); 142 } 143 printf("\n"); 144 return 0; 145} 146 147

試したこと

判定方法に問題があると考えてので、76行目から125行目を変えたりして試しました。ただ自分が判定方法の何が問題なのか理解できていなかったため、何も分からなかったのが本音です。

判定方法の考え方としては、例えば76行目から86行目が横方向への判定方法なのですが、y=0(0行目)の時、二つ目のforでxを0からn-1(3x3のゲームであれば、nは2)まで動かし、if文の中でkigouが何度boardと同じなのかを数え、countの数とnが同じになれば”勝ち”となる様に考えました。

しかし実際には勝っていても終わらなかったり、引き分けと判定してしまうのです。

補足情報(FW/ツールのバージョンなど)

Macbook Air M1 2020 Ver 12.6
Xcode を使っています。

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2022/12/14 15:33 編集

>一応自分が終わって欲しいと思うところにbreakとは書いているのですが コンピュータに『思い』を伝えても意味はありません。プログラムの書いてある通りにしか動きません。 書いた break が考えている動作を実現するためになっているのかは、書いた側が分かっていなければなりません。 >3時間ぐらい色々試しましたが 何を試したのでしょう。 調査したのでしたら、何処まで分かったのかを教えて下さい。
haru0

2022/12/14 15:48

判定方法に問題があると考えてので、76行目から125行目を変えたりして試しました。ただ自分が判定方法の何が問題なのか理解できていなかったため、何も分からなかったのが本音です。 判定方法の考え方としては、例えば76行目から86行目が横方向への判定方法なのですが、y=0(0行目)の時、二つ目のforでxを0からn-1(3x3のゲームであれば、nは2)まで動かし、if文の中でkigouが何度boardと同じなのかを数え、countの数とnが同じになれば”勝ち”となる様に考えました。 しかし実際には勝っていても終わらなかったり、引き分けと判定してしまうのです。
jimbe

2022/12/14 15:58 編集

勝ちとしての判断そのものがされているかどうかが怪しいのであれば、説明されている通りに本当に各行が実行されているのか、一行毎に行番号の表示を入れて実行してみては如何でしょうか。
thkana

2022/12/14 16:16

いきなりC言語で考えようとしてませんか。それは結構難しいことなんだから、まず日本語で判定条件を「ちゃんと」記述してみて、それをCに翻訳するという手順でやってみたらどうかと思うのですが。
haru0

2022/12/14 19:00

ご指摘を頂いた通り、コードを修正した所コードが完成しました。 ループの中のcountをprintfすることで色々な間違いに気づくことが出来ました。 ただ一つだけ、コードが完成しても意味がわからなかった点があったので重ねて質問したいです。 78、93行目のcount1, count2をループの外ではなく一つ目のループの中に初期化しておいたら、何故かコードが正常に動きました。それまでは、例えば◯の数が3個さえあれば、たとえそれが連続でなくとも勝敗が決まってしまい、正常に動かなかったのですが、count1, count2を一つ目のループの中に置いた途端、コードが正常に動く様ななりました。これの意味が分かりません。教えてもらえると嬉しいです。 アホな質問かもしれませんが15時間ほどこのコードと睨めっこしていたのでもう本当に頭が働きません。 お願いします。
jimbe

2022/12/14 19:29 編集

>15時間ほどこのコードと睨めっこしていた 仕方がない状況とは察しますが、長時間グダクダな状態で続けるよりも、適度に休憩・リフレッシュしてからやった方が結果的に効率が良いことはほぼ常識です。 >count1, count2を一つ目のループの中に置いた途端、コードが正常に動く そうすることで動くと理解してからやったのではないということでしょうか。 だとすれば、プログラムの作り方を間違っています。家を建ててからどうして建ってるのか分からないなんていう施工者がいるでしょうか。 count1 や count2 は 外側のループが示す各行(もしくは各列)内のカウントをするのですから、1つの行(もしくは列)のカウントが終わって次の行(もしくは列)のカウントを始める時にはまず 0 にしなければ、前までのカウントに更に加算していくことになるのは当然でしょう。 ループが終われば使った変数が勝手に 0 になったりはしません。変数は、代入するコードを書かない限り変化しません。 またついでですが、現在の質問のコードはまだバグがあります。 また、判定部分ではありませんが『そのマスは埋まってます。』が埋まってなくても表示されるようですが、それで良いのでしょうか。
haru0

2022/12/14 19:30

>適度に休憩・リフレッシュしてからやった方が結果的に効率が良いことはほぼ常識です。 無茶苦茶反省しています。 >count1 や count2 は 外側のループでが示す ごもっともです。気づきませんでした。 >現在の質問のコードはまだバグがあります。 すみません。全く分かりません。Xcodeを使っているのですが、エラーや警告は一切出ていませんし、ゲームは正常に動いてました。 どの点なのか教えてもらえないでしょうか?本当にすみません。
jimbe

2022/12/14 19:45 編集

バグというのは、コンパイルエラー等とは違ってツールで簡単に見付かるものではありません。だからこそ時には銀行のシステムが長時間止まるような事態も発生するのです。 count1 や 2 の判定が 2 重ループの外にあります。両カウンタはそれぞれの 2 重ループの内側で何度も 0 になっては加算を繰り返しており、 2 重ループが終わったときに設定されているのは 2 重ループの最後のループの結果だけです。 それを判定するということは、最後以外の行や列は判断出来ていないということです。
haru0

2022/12/14 19:46

>『そのマスは埋まってます。』 これも色々と試してみましたが、全く分かりません。どの様に数字を入力したらそうなるかだけでも教えてもらえないでしょうか?本当にお願いします。
jimbe

2022/12/14 20:06 編集

そちらでは出ていないでしょうか。だとしたら質問に提示されているコードはそちらとは違うのでしょうか。 範囲外を入力すると『入力された値が不正です』になりますが範囲内を入力すると『そのマスは埋まってます』になって、でも実際は埋まってなければ先に進みます。 原因は、埋まっていますの printf が範囲チェックの if の else にあるからでしょう。 本当は else if で、埋まっていたら printf を実行となるのではないでしょうか。
haru0

2022/12/14 20:28

「そのマスは埋まってます。」はこれでいいでしょうか?何度も試しましたが何故か自分のコードではそのようなエラーは出ませんでした。因みに具体的に何と入力しましたか? またcount1, count2を一つ目のループの後に入れてみましたが、まだ正常に動かないので考え直します。
haru0

2022/12/14 20:39

この場合breakはどこに書けばいいのでしょうか?
jimbe

2022/12/14 20:52 編集

そのマスは埋まっていますのメッセージは直ったようです。やはりそちらの最新のコードと質問のコードに解離があったのですね。 count の問題は、 判定の if を中に入れてしまうと、ターンの while を抜けるためだった break が 2 重ループの外側のループを抜けるものに役割が変わってしまうためです。 解決方法の1つは、中と外の両方に同じ判定の if ( とその中の break) を置き、ただ printf だけはどちらかにだけとすることです。 内側のループの結果でその行または列のカウンタが n であることが判った時点で外側のループを抜けてしまえば、カウンタはそのままなのでその外の判定に再び引っ掛かってその break で while を抜けるはずです。
thkana

2022/12/14 22:09

> 15時間ほどこのコードと睨めっこしていた なんか本当に「睨めっこ」(とロシアンルーレット的にコードを変更してみるとか)しかしてないんじゃないかと想像してしまったり。 コードを見て何しているかわかる人は睨めっこでもいいんだけれど、よほどのベテランか天才にしか無理と割り切って、睨めっこはある程度で諦めて視点を変えてみないと... ・先に日本語で書いてそれを翻訳、というのを紹介したけれど、その逆をやってみる(一つ一つの動作を日本語に(頭の中でなく)書き下して、それを通しで読んでみる)とか、 ・コンピュータになったつもりで、変数の変化を一つ一つ書き出してみるとか あと、夜は寝たほうがいい... 頑張った時間と思考力の衰えの損得を秤にかけるとプラスになることは多くはないです。
haru0

2022/12/14 23:31

>ロシアンルーレット的にコードを変更してみる 本当にこの様なことをやっていました。 >日本語で書いてそれを翻訳 アドバイス通り書いて、流れを理解を深めようとしてみました。願望で書いていた箇所のコードなどが結構あることに気がつきました。 >夜は寝たほうがいい その通りだと思います。無茶苦茶反省しています。
jimbe

2022/12/15 03:36 編集

何故 count3 と count4 の判定の if から break を消してしまったのでしょうか。 break が無ければ、斜めで勝ちが決まった時に while を抜けない=ゲームが続いてしまいます。 >無茶苦茶反省しています 長時間労働はしないというのは実際には結果論的な所で、納期が迫っているのに手を休めて間に合わなかったらというプレッシャーの真っ最中に1人でその判断が出来るかというと実はかなり難しいです。 どちらの判断をしても結果間に合った後なら「休憩[取っていれば/取っていて]良かったなぁ」と言える訳で、間に合わなかったら「[取らなかったから/取ったから]悪かったのか」という後悔になってしまうものです。 プロジェクト全体の責任を取れる立場の人間が労働基準とか倫理とか効率とかで納品先にも納得して貰って、上からの指示として『休めさせられる』ことになればプレッシャーは大分減るでしょうが…日本人の良くない傾向ですよね。
guest

回答1

0

ベストアンサー

例えば以下の判定部分を見てみましょう。
最初の for には「横方向の行をn行分チェック」と書かれています。
本当に n 行分チェックが実行されているでしょうか? count の変化と if による判定は妥当でしょうか?

プログラムが想定通りに動かない時、一番面倒なのが作った本人の思い込みです。考えた通りに動くように書いたはず・つもりですので、大量の「わ」の中に1つだけ「ね」があっても分からないみたいに(まぁ"ね"があると分かっているだけで難易度は相当下がりますが)、細かい所の違いが見えてきません。
一旦頭をリセットして、他人の目・頭で知らないコードを解析するように少しずつ動作を調べていくことをやってみるのも必要になりまず。

c

1 int count = 0; 2 3 for(y = 0; y < n; y++){//横方向の行をn行分チェック。countで数えてその回数とnが同じであれば勝ちになるはず。 4 for(x = 0; x < n; x++){ 5 if(board[y][x] == kigou){//あとでkigouに変える 6 count++; 7 } 8 } 9 if(count == n){ 10 printf("%cの勝ちです!\n", kigou); 11 } 12 break; 13 }

ついでみたいになりますが

c

1 count = 0; 2 for (a = 0; a < n; a++){//右上から左下に向かってチェックする。countで数えてその回数とnが同じであれば勝ちになるはず。 3 if(board[n - 1 - a][n - 1 - a] == kigou); 4 }

右上から左下になっていますか? 数えてますか?


大体整ったようですので、さらに纏めてみた感じのを提示させて頂きます。
共通として表示部分だけを関数化していますが、他の部分は質問のコードに似せる感じにしています。
(Windows/Cygwin/Eclipse で確認しています)

c

1#include <stdio.h> 2 3//2箇所で表示するため、共通処理として関数化 4void print_board(int n, char board[n][n]) { 5 for(int y = 0; y < n; y ++) { 6 for(int x = 0; x < n; x++) { 7 printf("%c", board[y][x]); 8 } 9 printf("\n"); 10 } 11 printf("\n"); 12} 13 14int main() { 15 int n; 16 printf("マスの数を入力してください:"); 17 scanf("%d", &n); 18 19 //ボードを作る 20 char board[n][n]; 21 for(int i = 0; i < n; i++) { 22 for(int j = 0; j < n; j++) { 23 board[i][j] = '_'; 24 } 25 } 26 print_board(n, board); 27 28 /* 29 <del>while</del>forの中でやること 30 1.◯を入力する(◯スタート。偶数回目であれば◯、奇数回目であればXを入力。1回目からスタート?それとも0回目?調べる) 31 2.◯の場所が(A)範囲内で(B)空白なのかどうか確認 32 3.◯を記入して、ボードを表示 33 4.勝敗を判断 34 5.ターンを進める 35 <del>6.1に戻る</de> ← ループの中でやることでは無いのでこれは不要, 逆にループをいつ終わるのかを書いておく必要がある 36 */ 37 char player = ' '; //元 kigou 38 int match_over = 0; //決着が着く(どちらかが勝つ)と 1 39 for(int turn = 0; turn < n * n && match_over == 0; turn++) { //turn は元 while_count. ターンが過ぎるか決着が着いたら終わる. [5.ターンを進める](turn++) を含む 40 printf("\n%d周目始まる\n", turn); 41 42 //このターンのプレイヤー 43 if(turn % 2 == 0) { 44 player = 'O'; 45 } else { 46 player = 'X'; 47 } 48 49 //[1.入力する]+[2.入力チェック] 50 int x, y; 51 while(1) { 52 printf("x座標を0〜%dから入力してください:", n - 1); 53 scanf("%d", &x); 54 printf("y座標を0〜%dから入力してください:", n - 1); 55 scanf("%d", &y); 56 57 if(x < 0 || x > n - 1 || y < 0 || y > n - 1) { 58 printf("入力された値が不正です。\n"); 59 } else if(board[y][x] != '_') { 60 printf("そのマスは埋まってます。\n"); 61 } else { 62 printf("\n"); 63 break; //エラーでは無い=正常な入力なら入力ループを抜ける 64 } 65 } 66 67 //[3.◯xを記入して、ボードを表示] 68 board[y][x] = player; 69 print_board(n, board); 70 71 //[4.勝敗の判断] 縦横斜めが同じかどうかをループで判断。各カウントを一度に行う。 72 int count[n + n + 2]; //[0~n-1]:水平, [n~n+n-1]:垂直, [n+n~n+n+1]:斜め 73 //クリア 74 for(int i = 0; i < n + n + 2; i++) { 75 count[i] = 0; 76 } 77 //カウント 78 for(int y = 0; y < n; y++) { 79 for(int x = 0; x < n; x++) { 80 if(board[y][x] != player) { 81 continue; //条件に合わない場合に早々にスキップすることで、コードのインデントを深くしない(下の方で'}'が並ぶと分かり難くなる) 82 } 83 count[y]++; //水平 84 count[n + x]++; //垂直 85 if(x == y) { //斜め(\) 86 count[n + n]++; 87 } 88 if(n - 1 - x == y) { //斜め(/) 89 count[n + n + 1]++; 90 } 91 } 92 } 93 //チェック 94 for(int i = 0; i < n + n + 2; i++) { 95 if(count[i] == n) { 96 match_over = 1; //決着が着いた. ここで turn のループを break しなくても、 turn のループ条件に引っ掛かって終わってくれる. 97 break; 98 } 99 } 100 printf("\n%d周目終わり\n", turn); 101 } 102 103 if(match_over == 1) { 104 printf("%cの勝ちです!\n", player); 105 } else { 106 printf("引き分けです\n"); 107 } 108 return 0; 109}

投稿2022/12/14 16:15

編集2022/12/29 07:30
jimbe

総合スコア12646

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

haru0

2022/12/15 22:18

本当に無茶苦茶ありがとうございました。 jimbeさんのまとめたコードすごく勉強になります!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問