こんにちは､ そしてようこそ｡
チックタックトーの課題解決策を提供したいと思います｡
さて､ 与えられた問題には多くの解決策があることを忘れないでください｡
これは､ まさに私のソリューションになりそうです｡
もし､ あなたが良いプログラミングの習慣に従い､ c言語を正しい方法で使っている限り､
あなたの解決策が異なっていても､ それはそれで結構なことです｡
私は自分の解決策を提示しただけです｡
きっと改善されるはずです｡
では､ さっそく始めましょう｡
新しいプロジェクトを作成します｡ これは明らかにチックタックトーと呼ばれるもので､
これから変調を加えようとします｡
講義の説明のように分解してみました｡
いくつかのモジュールというか､ いくつかの関数を作るだけです｡
これで大丈夫､ うまくいくといいんですけどね｡
私は慣れたもので､ すべての命名規則でキャメルケースを使用しています｡
必要であれば､ これを複数のソースファイルに分割することも可能です｡
だから､ 私たちのメインがあるのです｡
このprintfを取り除きましょう｡
追加でインクルードしようと思っています｡
これによって､ 基本的にシステムの整理ができ､ いくつかの素晴らしいことができるようになります｡
そこで､ この2つのライブラリに加え､ conioも入れてみましょう｡ h.
そして､ 私がやりたいこと､ この問題を攻略したいこと｡
できる限りモジュール化するようにしたい｡
1次元の配列を使うと書きましたが､ 2次元の配列の方がより効果的かもしれません｡
しかし､ ここではゲームボード､ 三目並べを表す1次元の配列を使って実装してみます｡
これをグローバル変数として作成することにします｡
ここでグローバル変数を使っているのは､
cでデータを参照渡しする概念を示していないからです｡
混乱させたくないから｡
ここではグローバルデータを使う必要はなく､
関数にデータを渡すことができますが､ 参照渡しを使う必要があります｡
ポインターを回さなければならないのですが､ まだそのような話は一切していません｡
だから､ グローバルなデータを使うことにしています｡
グローバルデータというのは､ 通常はあまり良いことではないのですが､
学習､ 除算､ 関数の利用方法を説明するために､ グローバルデータを使用する必要があります｡
ここで使うグローバルデータは､ グリッドを表す正方形､
三目並べを表すものです｡
これは､ 0要素を使いたくないので､
主に10要素の一次元の領域になります｡
この配列を使って､ ゲームをするための数字を選ぶんだ｡
私のコーディネートも可能になりそうです｡
1から9までの値だけで初期化することにします｡
そしてこれは､ xとoを入れる必要があるので､
文字配列にする予定です｡
まだ文字配列の話はしていませんが､ これで一文字を入れることができるようになります｡
文字を含む以外はintsの配列の概念と同じです｡
文字は､ シングルクォートで囲むことを忘れないでください｡
So one 2 3 9 the, 0 there the o I'm just gonna skip, I'm not even
going to use, because I want to use the ones.
プログラミングが少し楽になります｡
ユーザーが入力した値をインデックスとして､ 有効なインデックスとして使うことができるんだ｡
また､ プレイヤーターンでの選択用に2つのグローバル変数を作成する必要がありそうです｡
つまり､ キーボードで入力した内容が選択肢となるわけです｡
プレイヤーは､ プレイヤー1､ プレイヤー2のどちらの手番かを表すだけです｡
それ以外のグローバルデータは必要ない｡
ここに関数の宣言があればいいんです｡
まずは3つの関数を作ってみます｡
というわけで､ 3つの関数宣言をすることにします｡
優勝のチェック用に1本持っておこうと思います｡
データが取れないから｡
intを返します｡
そのデータを一切返さず､ ただ正しい文字でボードを表示するものがあります｡
また､ データも一切取らない｡
あと､ 盤面に印をつけるだけの機能も｡
これは､ ユーザーの入力を受けます｡
そして､ 入力された内容に応じて､ 配列の正しい位置にxやoを配置し､
ディスプレイボードを使ってその配列を表示します｡
ここではマークボードと言い､ 実際にxやoでマークを取ることになります｡
だから､ これを再利用できるんです｡
プレイヤー1にもプレイヤー2にも再利用できますね｡
3 関数の宣言は､ プログラムのどこにでも関数を配置できるように､
上のほうで宣言しておきました｡
そして､ これらの関数のうち2つはデータを返さず､ 1つだけがパラメータを取っていることにお気づきでしょう｡
ゲームプレイを実装する前に､ これらの機能を実装してみましょう｡
そこでまず､ check for winを実装してみましょう｡
この機能は､ 基本的に盤面をチェックして､ 当たりがあるかどうかを確認するところで動作する予定です｡
だから､ if文が多く含まれることになる｡
私はこのコードをかなり改善できるかもしれません｡
でも､ 関数の先頭には必ずコメントを入れるべきですから､
ここにコメントを入れておきます｡
これらはかなり簡略化されています｡
関数名があるはずです｡
これは基本的に､ この関数が､ ゲームが終わった場合はゲームの状態を1､
勝った場合はその旨を､ ゲームが進行中の場合は結果をマイナス1､ ゲームが終わって引き分けだった場合は0を返すと言っているのです｡
つまり､ この関数のチェックはint型を返しているのです｡
そこで､ 先にこれを定義しておくことにする｡
そして､ これは配列を見て､ そこにスポットがあるかどうか､
3xがマッチしているかどうかを確認するために行われます｡
だから､ いろいろなif文があるんです｡
では､ どうすれば､ その選手がそうであるかどうかを知ることができるのだろうか｡
まあ､ 対角線上の行か列のどちらかでマッチすれば1かどうかはわかるんですけどね｡
だから､ その状況をひとつひとつ確認していかなければならないのです｡
そして､ 配列に基づいて列の行がわかる｡
つまり､ 1マス目と2マス目が同じで､ 2マス目と3マス目が同じなら､
最初の行で一致したことになります｡
そこで､ ここで戻り値も作っておきます｡
一番上に､ 戻り値ということで､ これを0に初期化します｡
ゲームオーバーのため0に初期化します｡
そして､ 1列目で一致した場合は､ 1つ返す､
ゲームオーバーになる｡
そして､ 他の一連のelse ifにマッチさせなければなりません｡ 実は､ 私は1つを返したいのではなく､
戻り値を設定したいのです｡
なぜなら､ 返すことはできても､ プログラムが難しくなってしまうからです｡
そうでなければ､ 他の行をすべてチェックすることになります｡
というわけで､ 冗長なコードが多いので､ ここにコードをコピー＆ペーストします｡
面白いのは､ 引き分けになった場合だけです｡
だから､ 受賞者のために､ すべてのelse ifを入れよう｡
そして､ 違う値を返すようにもしています｡
else ifをたくさん並べる代わりに､ アンパサンドを並べることもできますが､
それだとさらに読みづらくなります｡
つまり､ 1行目に一致するものがあれば
そして､ インデントが正しいかどうか確認しましょう｡
2行目にマッチングがあれば､ 2行目がそうなっているのです｡
3行目で一致した場合は､ 読みやすくするために新しい行を追加して返します｡
37行目では､ 1､ 4､ 7を合わせているように見えます｡
ということは､ これは対角線上の試合､ リターン1のように見えます｡
40､ 2 5と8を合わせています｡
それは中央が下がっているのが一致しているように見えるので､ 2列目ですね｡
3 6､ 他の試合も見ることができます｡
基本的に行､ 列､ 対角線のすべてのマッチをキャッチする必要があります｡
そして､ このようなステートメントをすべて配列で見ることができるようにしたのです｡
さて､ 一致しなかったり､ 引き分けたりした場合にわかることは､ すべての数字を比較して､
基本的に初期化された数字でないことを確認することです｡
つまり､ 配列の正方形に1 2 3 1 2 3 4 5 6 8 9のいずれかが含まれていなければ､
0かxが含まれているので､ 引き分けということになる｡
そこで､ もうひとつの else if
を書いて､ 基本的に引き分けであることを示し､ 戻り値を 0 に設定することができます｡
そしてまた､ これは基本的にこの最後のelse if､ つまり数値が初期化されたときと同じかどうかをチェックしているだけなのです｡
全部違うなら､ 0を返せばいいということです｡
それを1枚貼ってみましょう｡
そうでなくても､ 引き分けで勝敗がつかないということは､ まだ終わってないということですからね｡
だから､ 最後のelse文は､ 戻り値が負の値に等しいというだけでよい｡
なぜなら､ この関数は､ ゲームが進行中であればマイナス1､ ゲームが終了していればマイナス0､
結果が出なければマイナス1を返しているからです｡
そして最後に､ 戻り値を返したいのです｡
だからここでは､ return､ 戻り値ということにしています｡
そして､ これで機能が完成します｡
そして､ このようなことをしているときに気づくのは､ 私たちは分割して征服しているということです｡
今､ 私たちが集中しているのは､ 勝ちがあるかどうか､ 盤面を確認することです｡
このように小さな塊で作業を行い､ 常にテストを行うのです｡
これにより､ 設計に集中することができ､ また､ ミスを減らしたり､ 切り分けたりすることも容易にできるようになりました｡
ゲームボードの実装が終わったので､ ディスプレイボードの実装に進みたいと思います｡
ディスプレイボードは､ ボードを見せるだけです｡
つまり､ これが本当に必要なのは､ 配列を特定のフォーマットで描画することだけなのです｡
そこで､ display boardという関数を書きます｡
データを返すわけでもなく､ パラメータを受け取るわけでもない｡
機能ナンバーワンなのは､ 何度も盤面を描く必要があるからで､
また､ 特定の作業を行うからこその良さがあるのです｡
さて､ この中にはprintf関数がたくさん入っています｡
しかも､ フォーマットがたくさん入っていることになる｡
だから､ コピーして貼り付けるんです｡
ここで特徴的なのは､ 毎回画面をクリアしていることです｡
でも､ システムとかウィンドウズとか言って､ clsと言えば､ 画面がクリアになりますよね｡
ただ､ それだけで､ よりきれいになります｡
あとはひたすらprintfsを連発します｡
ゲームのタイトルをプリントアウトするのです｡ プレイヤー1と0をプリントアウトするのです｡
そして､ ボードをプリントアウトするのです｡
ボードには平行棒をいくつかつけてきれいに整形し､
配列の中のすべての値をプリントアウトしていきます｡
そして､ その値がxか0であれば､ 表示されることになります｡
もしそうでなければ､ 数字が表示されるので､ その数字を使って描画するオプションを選択することができます｡
さて､ ボードの表示が終わったので､ 今度はボードをマークする最後の関数を作成したいと思います｡
マーク板は配列に特定の値をセットして､ xかoのどちらかでマークを打ち込むわけです｡
だから､ キャラクターが必要になってくるんです｡
これが何をするかというと､ 他のif文がたくさん出てくるのです｡
このマークボードでは､ 基本的にいくつかのコメントを与えて､
これは配列の正しい場所に正しい文字xまたはoを持つボードを設定するつもりであると言いたいのです｡
ということで､ ご理解いただけたでしょうか｡
ご覧の通り､ 私のコメントの最後を飾るコードになりました｡
ここでは2行になるようにきれいにフォーマットしたいと思います｡
そして､ これはユーザーが何を入力したかを確認するために行われるものです｡
つまり､ ユーザーが入力したものに対して､ 2つのグローバルデータ値があることを思い出してください｡
また､ 現在のプレーヤーの値も持っています｡
つまり､ 選択肢は基本的にキーボードから入力されるものになるわけです｡
これはmain関数で実装する予定なので､ まだ実装していません｡
Playerは現在のプレーヤーになります｡
マークを作るには､ この両方が必要なんです｡
これからやることは､ else文､ if else文の束を用意することです｡
ユーザーが入力したものである選択肢を確認するのです｡
そして､ もし選択肢が1に等しければ､ そのマスに何かを描きたい｡
また､ その広場を利用できるようにしたい｡
つまり､ 1であるにもかかわらず､ 現在1でないということは､ 誰かがすでにxかoをそこに置いたということであり､
私たちはそれを使うことができないのです｡
1に等しく､ 1が入力されたなら､ それは配列を特定の値に設定できることを意味します｡
oとxのどちらかで渡されたものを配列に設定することになります｡
そして､ これを各選択肢に対して繰り返すのです｡
選択肢 2 3 4 5 6 8 9 の場合｡
だから､ 同じことをするelse ifがたくさんある｡
ユーザーが選択肢2を入力した場合､ インデントがうまくいっていることと､
2番マスが空いていることを確認し､ ここで渡したもので2番マスをマークすることにしています｡
そして､ どのプレイヤーのターンかによって､ anやxを渡すことになります｡
さて､ この関数で最後にやらなければならないのは､ バリデーション（検証）です｡
もし､ 1〜9以外で通過したのなら､ それは無効な手です｡
最後のelse文は､ 無効な動きというメッセージを出力し､ プレーヤーを1つ下にマークします｡
なぜなら､ プレイヤーは1か2のどちらかだと記憶しているからです｡
そして､ プレーヤーをマークバックし､ main関数のループ内でそれをインクリメントします｡
そして､ 最後のget charは､ プログラムを一時停止して､ 無効な動きの後にユーザがリターンを押さなければならないようにするものです｡
そうしないと､ 無効な手が表示され､ また盤面を描き直すので､
すぐに消去されてしまうのです｡
だから､ シャアはそれを一時停止するんだ｡
別の値を入力するために､ 強制的にエンターボタンを押させるのです｡
そして､ これが私たちの3つの機能です｡
この関数は何も返さないことを覚えておいてください｡
だから､ 一番下に置いておけばいいんです｡
あとは､ 実際にゲームプレイを実装していくことですね｡
ゲームプレイは､ メイン機能に入ります｡
ゲームプレイでは､ ユーザーに数字を入力してもらい､ 盤面を描画し､
勝敗をチェックするところです｡
ここで2つのローカル変数を作成し､ プレーヤーを初期化します｡
ここでは､ 整数のIを作成します｡
これはループに使用されます｡
そして､ マークは､ 誰の番かによって､ xかoのどちらかになるのです｡
マークはxかoになると思います｡
マークは×か○になりそうです｡
そして選択肢は､ ユーザーがキーボードから入力するものになる｡
だから､ 2つのローカル変数｡
また､ グローバル変数であるプレーヤーを設定します｡
最初はプレイヤー1人でゲームを始めることになります｡
そして､ ループを組む｡
演奏が終わるまで､ 基本的にループすることになります｡
ここでは､ 少なくとも1回は実行させたいので､ do whileループを使用します｡
一度は盤面を表示させたい｡
だから､ 盤面を表示して､ 基本的に引き分けか､
勝ちが出るまでゲームをするつもりです｡
だから､ I isがマイナス1に等しい限りは､ やるつもりです｡
マイナス1は何を表しているかというと､ ゲームが進行していることを表しています｡
Iは､ winをチェックするための戻り値になる予定です｡
これはすぐにわかると思いますが､ 基本的にゲームオーバーになるまで同じことを繰り返し行うループがある場合です｡
ゲームもマイナスになったら終わりですしね｡
では､ まず何をしなければならないか｡
まず最初にしなければならないのは､ ボードを表示することです｡
だから､ 機能表示板を呼び出せばいいだけなんだけどね｡
そして､ プレーヤーの計算をしなければならない｡
そして､ プレイヤーはプレイヤー1からスタートする｡
ここでは三項演算子を使って､
もしプレイヤーがmod 2なら､ プレイヤーは1に等しく､ そうでなければ2に等しいと言うつもりです｡
これは､ 正しいプレーヤーを判断するための方法です｡
ループを繰り返すたびに､ 1→2､ 2→1と変化していきます｡
プレイヤー1としてスタートすると､ プレイヤーmod2､
1÷2､ 1÷2の余りが何､ 1ってことになるんです｡
だから､ 1ならプレイヤー1になってしまう｡
そして､ 次の反復処理に戻ると､ playerはインクリメントされるので2になります｡
2 mod 2は0です｡
つまり､ 1に等しくなければ､ プレーヤーはプレーヤー2と等しくなるわけです｡
プレイヤーMOD2が1の場合のみ､ プレイヤー1に設定されるんです｡
覚えておいてください､ そのターナーオペレーターの希望を｡
次に必要なことは､ ユーザーからの入力を得ることです｡
そこで､ ここでは､ ユーザーに数字を入力してもらうと言うことにします｡
プレイヤーのパーセントdをプレイヤー1か2のどちらかと言い､ 数字を入力し､ プリントアウトするプレイヤーをプレイヤー1か2にします｡
そして､ scanfを使ってユーザーからの入力を読み込む｡
そして､ これは整数で､ ボード上の1から9までのスポットを表すことになる｡
そして､ それを選択変数に格納する｡
チョイスはインチキなので､ アドレスでパスして､ チョイスと言わなければならないのです｡
文字列の場合のみ､ 私はアドレスを渡さないことにしています｡
そうすれば､ そのマークが何であるかは基本的に判断できます｡
選手によってマークが×か×かを判断する必要があるんです｡
そこで､ 再び三項演算子を使うことにする｡
そして､ マークにはxかoのどちらかを割り当てることにしています｡
プレーヤーが1と同じなら､ x､ 大文字のxをマークするように割り当てます｡
そうでなければ､ 大文字のoを割り当てます｡
ですから､ この三項演算子は､ 条件に基づいて変数を割り当てる必要がある場合に非常に便利です｡
繰り返しますが､ これはプレイヤーが1に等しければ､ マークはxに等しくなると言っているのです｡
さもなければ､ oと同じになる｡
クエスチョンマークはブール評価が真であるかどうか､ コロンは偽であるかどうかを表します｡
正しい文字を得たので､ xかoを渡してmark boardと言うだけで､
碁盤に印をつける関数を呼び出すことができます｡
私が盤面に印をつけ､ 彼らが選択した後､ 当選者がいるかどうかをチェックしなければなりません｡
私は自分の変数Iを持っていますが､ これは本当におそらくもっと良い名前であるべきです｡
ステータスチェック､ ゲームチェック､ ゲームステータスと命名しよう｡
そして､ ゲームステータスは1マイナス1か0に等しくなる｡
ゲームステータスは､ 勝利のためのチェックから返されるものと同じになる予定です｡
なぜなら､ この関数は0かマイナスの1を返すことを覚えておいてください｡
結果が出てゲームが終了した場合は1､ ゲーム進行中の場合はマイナス1､ 結果が出ずに終了した場合は0となります｡
さて､ ゲームのステータスを保存したので､ ループの条件も変更したいと思います｡
つまり､ ゲームステータスがマイナス1になったら､ ループから飛び出すわけです｡
そうでなければ､ プレイヤーの手番を変えるだけでいいのです｡
プレイヤーの手番を変えるにはどうすればいいかというと､ プレイヤーを1つインクリメントすればいいのです｡
そうすれば､ 次は上がって2になり､ プレイヤーは2と同じになり､
また下がる｡
無効な試みをした場合､ 無効なテンプレートで何が起こるかというと､
プレイヤー数が減少します｡
プレイヤーカウントをデクリメントすると､ 正しいプレイヤーに戻る｡
つまり､ 1なら0にデクリメントすれば､ 次のターンは1になるわけで､ 実質的にはまた同じターンになるわけです｡
だからこそ､ インクリメントするのです｡
プレイヤー2であれば､ 1つずつ増やしていきます｡
そして､ どうなるかというと､ 2÷2は0になります｡
そのため､ 実際にはプレイヤーを1に戻すことになります｡
そういうことなんです｡
しかし､ これはすべての理屈です｡
ここでは､ いろいろなことをやりました｡
このようなコードとテストを一度に書いてはいけないのです｡
コードを走らせながら常にテストするという割り切った作業をしていたはずです｡
では､ この解決策を提供しますが､ 実行してテストしてみましょう｡
チャレンジレクチャーでのデモと同じように使えるようになるんです｡
しかし､ もう一度､ このコードを勉強して､ 何が起こっているのか理解するようにしてください｡
配列の使用と分割統治法ですが､ このメインメソッドの見栄えの良さをご覧ください｡
メインメソッドで何が行われているかを知るのはとても簡単です｡
ディスプレイボード､ チェンジプレーヤー､ ここにコメントを入れたくなるのではないでしょうか｡
ターンを変えて､ ここでもインプットする｡
そして､ プレイヤーのターンによって､ 正しい文字を設定する､
正しい文字を設定する｡
機能が説明的なので､ 本当はこんなコメントも必要ないんです｡
マークボード､ ユーザーの選択または無効な選択に基づいてボードを設定します｡
そして､ 勝利のためのゲームステータス・チェック､ かなり自明なことです｡
そして､ ゲームオーバーになったら飛び出す｡
これを作ろう｡
うまくいけば､ エラーや警告が出ずに済みます｡
では､ 実行してみましょう｡ デモと同じように動作することが確認できると思います｡
まず最初に､ 画面を表示することになります｡
そして､ エンターを印刷することができるようになるのです｡
そして､ 私たちのグリッド､ 1 2 3 4 5 6 7 8 9 の配列があります｡
だから､ プレイヤー1が何かを入力し､ 私が何かを入力するのは有効だと言われるのです｡
そして､ それが描画され､ 画面がクリアになる｡
だから､ 素敵に見えるんです｡
そして今､ 3の中にxがある｡
今度はプレイヤー2の番です｡
無効なものをやらせてしまう｡
無効な動きと書いてあり､ 一時停止しています｡
リターンを押すと､ もう一度プレイヤー2になるんです｡
今､ 私は1を打つと､ 1つの場所にoで再描画します｡
そして､ 勝ち負けが決まるまでこれを続けるのです｡
だから､ Oそうの勝者を引き起こしましょう｡
今､ xは､ 2 oは5､ xは6､ oは9になります｡
3 0を引き分けただけでゲームオーバー｡
そこで忘れていたのが､ 当選したお祝いを表示するためのwhileループの後です｡
そして､ そこでやらなければならないのは､ ゲームの状況を確認することです｡
ゲームステータスが1になれば､ プレイヤー1がいることになり､ 最後のプレイヤーが勝利することになる｡
そこで､ 選手の勝率dをプリントアウトすることになる｡
そうでなければ､ ドローをプリントアウトするだけです｡
なぜなら､ もしループを抜けてステータスがマイナスになったとしても､
ゲームが終了して結果が出たことは分かっているので､ 結果を表示するために最後のif文が必要だからです｡
では､ 今度は勝者としてもう一度実行してみましょう｡
そして今､ 私たちは実際に誰かが勝つのを見ることができるのです｡
だから､ ここに有効な数字を入れ､ xのクイックウィンを引き起こし､
実際にこのプログラムでやらなければならないのです｡
これでプレイヤー1が勝ち､ またまたいい音を奏でた｡
というわけで､ 長い講義になりました｡
長い講義になり､ 申し訳ありません｡
このコードを勉強してください､ ソースファイルとして提供します｡
うまくいけば､ 分割統治における手法について多くのことを学ぶことができます｡
ありがとうございました｡