ページ 1 / 1
オセロのプログラム制作過程2
Posted: 2011年6月16日(木) 03:55
by 小人
またトピックが長くなってしまった事とオセロが遊べる段階に入ったので立て直します。
AIの強化に成功しました。
多少戦える子になりました。
添付したファイルで実際に遊べると思いますので、興味を持たれた方は遊んでやって下さい。
ソースコードはまだコメント付けてないため、付けてから載せます。
Re: オセロのプログラム制作過程2
Posted: 2011年6月16日(木) 11:34
by softya(ソフト屋)
とりあえずサウンドは、PlaySoundMemだけに頼らないでPlaySoundFileも使ってください。最初の読み込みが減ります。
それと音楽がうまく鳴らないのはSound_Title();を毎フレーム呼び出しているので毎フレーム音楽が頭から再生されようとするからです。そのせいでTITLE を抜けるとSound_Title();が呼ばれなくなるのでやっと最後まで再生されます。
case TITLE :時の最初の1フレーム目だけSound_Title();するようにフラグ制御してください。
ゲームとしてのタイミング制御は常に重要なポイントなので、フレームごとに何をすべきかを常に考えるようにしてくださいね。
AIに関しては先読み&再帰呼び出しがキーワードでしょう。
Re: オセロのプログラム制作過程2
Posted: 2011年6月16日(木) 22:03
by 小人
ソースコード出来たので添付しておきます。
Sound.cppに関しては上手いこと書けたかな・・・。
本当はDraw関係の関数もこうあるべきなんですよね。
次からはその事を念頭に置きつつプログラムを仕上げたいです。
エラーはもう無いみたいなので後は改善点のみでしょうか。。
・CPUのAIの更なる強化
・演出
少なくとも現在のコマ数表示は取り入れないと。。
思考時間はWaitTimer( 1000 );で簡単に実装出来ました。
Re: オセロのプログラム制作過程2
Posted: 2011年6月17日(金) 01:26
by softya(ソフト屋)
大分出来てきましたね。
で、AIですがひっくり返した石も全て評価値に組み込んだほうが良いと思いますが、ひっくり返した石の場合は評価点が違ってくると思うのでそこは別の評価関数が必要かなと思います。
Re: オセロのプログラム制作過程2
Posted: 2011年6月17日(金) 22:36
by 小人
ひっくり返した石にも評価値を与えるという事でしょうか?
今やろうと思ってたのは
CPUの入力ターンの時
「置ける場所の評価値×係数(値は未定)」 + 「その場所に置いた後、相手の置ける場所の評価値合計を±逆にする」
という感じにしようかなと。
自分はオセロも不得意、というより全然やった事ないためアルゴリズムの考え方が自己流になってしまうのが宜しくないです。
サイトも色々見ては回ったのですが・・・(αβ法やその他色々)考え方は分かるのですが、具体的なソースコードが浮かんで来なかったため。。
SLGのAIは何となく分かるのですが。
敵Z 味方A,B
Aのターン
自分のHPを判断(一定以下なら下がる)
攻撃の思考
ZのHPを判定
倒しきれる→攻撃
満タンに近い→攻撃
一定ライン→Zへ攻撃した後、(Zの前にBのターンが来るなら)味方BのターンでZを倒しきれるなら攻撃。倒しきれない場合は待機や移動など。
こういうのにもαβ法など必要なのでしょうか?
無くても作れそうな気がしますが・・・。
Re: オセロのプログラム制作過程2
Posted: 2011年6月17日(金) 23:43
by softya(ソフト屋)
>ひっくり返した石にも評価値を与えるという事でしょうか?
与えたほうがより最善手を選べる気がします。
ただ、私もオセロの思考ルーチンは作ったことがありません(^^;
まぁ、αβ法やミニマックス法を理解しなくても5~6手先まで読むと遅いですが少しは強くなります。
まず小人さんの思う2手先まで読む方法で試してみてはどうでしょうか?
オセロには、こんな手もあるよって例。
「アルゴリズム解説」
http://sealsoft.jp/thell/algorithm.html
小人 さんが書きました:SLGのAIは何となく分かるのですが。
敵Z 味方A,B
Aのターン
自分のHPを判断(一定以下なら下がる)
攻撃の思考
ZのHPを判定
倒しきれる→攻撃
満タンに近い→攻撃
一定ライン→Zへ攻撃した後、(Zの前にBのターンが来るなら)味方BのターンでZを倒しきれるなら攻撃。倒しきれない場合は待機や移動など。
たぶん、それだと2対2以上の場合に通用しない気がします。
スーパファミコンウォーズとか結構シビアな状況で敵が来てほしくないところに攻撃しますからね。
攻撃後のリスクもちゃんと計算するために、AI内で何手か先読みを実行している可能性が高いです。
まぁ、いろんなコトを考えている人がいるということで参考リンク。
「戦術SLGの作り方(コンピュータの思考ルーチン)」
http://www5f.biglobe.ne.jp/~kenmo/progr ... puter.html
「戦術SLGの作り方(コンピュータの思考ルーチン2)」
http://www5f.biglobe.ne.jp/~kenmo/progr ... uter2.html
まぁ、RTSやSLGを作るのなら、そこを作りこまずに何処をアピールするんだって事なんですが。
何にしても思考ルーチンって難しいんですよね。
試行錯誤で作っていくしか無いですが、作っていくうちにノウハウは溜まります。
なので、色々あがいていみると後々の為に良い経験になるのです。
Re: オセロのプログラム制作過程2
Posted: 2011年6月18日(土) 03:06
by 小人
探索はバグが潜伏しているため、デバックが一番大変 と何処かのサイトに書いてありましたが、まさにその通りになりました;;
なるべく原型を崩さず、追加する形で改変したのですが・・・。
CPUの3手目で必ず無限ループに入ってしまいますorz
どうやらCPUの思考内ボード情報と現実のボード情報が食い違っている?(予想ですが)
デバッグで掘り下げてみても中々具体的な原因が見つからず、困りました・・・。
For文の後にIf文が来て、その後にまたFor文&If文 なんてバグの温床にして下さいと言わんばかりの書き方がいけない様な気もしますが。。
一応CPU_AI.cppについて簡単な説明
最初に
・現在のボード情報を保存
・サーチ関数によって置ける場所を手当たり次第漁り、見つけた場所をFlipで返してしまう。(一手進める)
・この状態を保存関数その2に記憶
・相手の立場になって置ける場所を探索。あれば評価関数その2へ送る。
・サーチ関数を通すとボード情報がFLAGだらけになるので、その都度保存関数からボード情報を元に戻す。
・一番最初のボード情報に戻す
のループです。
Re: オセロのプログラム制作過程2
Posted: 2011年6月18日(土) 12:18
by softya(ソフト屋)
こちらで調べることも出来ますが調べ方をお教えします。
・コンソールデバッグを使ってAI中のボードの状態をコンソールに表示し続けてください。
・無限ループになったら、すべて中断でプログラムを中断します。
この時アセンブラ画面が出ても慌てずにウィンドウの下の方にあるスレッドタブを選択してメインスレッドをダブルクリックします。
その時もアセンブラ画面なら、アセンブラコード上で右クリックして「ソースコードへ移動」してソースコードを表示させます。
あとはステップ実行して無限ループの原因を探ってください。
もし、ProcessLoop()関数内だとしたら、switch(func_state)とか適当なところにブレークポイントを貼ってF5で実行し直せば追っかけられるようになります。
ところで、小人さんはBoard_CPU_get2()がどういう動作をするか直感的に分かりますか?
あと半年ほど間を開けた後に見ても分かると思いますか?英語的に意味の通る文を関数名にしてみましょう。
この関数の名前の問題は何処から何をgetしてくる関数か分からないことです。
そしてgetやget2やgiveの手順を間違えてもプログラムで何のガード処理も行われていないことも問題です。
get2なしにgive2を動かない様にgetなしにgiveが動かないようにガードしてみてください。
まぁ、その前にget2やgive2は不要です。
スタックというアルゴリズムの採用を検討したほうが良いと思います。
Re: オセロのプログラム制作過程2
Posted: 2011年6月18日(土) 15:54
by 小人
デバックは以前教わった方法も合わせ調べてみたのですが、一体何処を調べれば良いのかが分からず。。
CPUの入力関数が終わる手前にSearch関数へ送っている座標が見当違いだったり、座標は正しいがSearch関数側でボードを調べてみるとその座標には既にFLAGが立っていたりして訳が分からない状態ですorz
どう見てもボードの情報コピーしている関数がおかしいと、配列の0から最後までコピーを監視していたのですが、特に異常も無く・・・。
まぁ間違いなく原因はボードの情報を行き来させている関数だと思うので、また重点的に調べてみます。
他にご指摘頂いた所は修正してみました。
自信は無いですが、一応載せておきます。
Re: オセロのプログラム制作過程2
Posted: 2011年6月18日(土) 16:14
by 小人
解決しました。
昔のコード見直してみたら
valX に h を valY に w を代入している事に気付き、その通りにしたら動きました。
何故逆になっているのだろう・・・此処だけ。
追記
強化したのを添付しておきます。
自分には強くなったかどうか分かりませんでしたが(笑
Re: オセロのプログラム制作過程2
Posted: 2011年6月18日(土) 18:07
by softya(ソフト屋)
前にも書きましたがh,wは止めましょう。それだけミスが生じる余地があるんじゃないですか?
なぜcpuXとcpuYとかじゃダメなのでしょうか?何でも一文字で片付けようとすると無理が出ます。
参考例。
コード:
for( cpuY = 1 ; cpuY < BAN_HEIGHT -1 ; cpuY++ ){ //座標を一つづつサーチ関数へ送る
for( cpuX = 1 ; cpuX < BAN_WIDTH -1 ; cpuX++ ){
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
CPU_AI(cpuX,cpuY), //置ける場所の座標を送る
Board_Flip(Tekikoma), //コマを返し、一手進んだ状態にする
Board_CPU_BanData_push(SECOND); //一手進んだ状態のボード情報を保存
for( playerY = 1; playerY < BAN_HEIGHT -1 ; playerY++ ){ //座標を一つづつサーチ関数へ送る
for( playerX = 1 ; playerX < BAN_WIDTH -1 ; playerX++ ){
if(Board_Search(playerX,playerY,Mykoma) >= 1){
CPU_AI2(playerX,playerY,cpuX,cpuY), //相手の置ける座標を送り総合評価
Board_CPU_BanData_pop(SECOND); //ボードの情報を戻す
}
}
}
Board_CPU_BanData_pop(FIRST); //Flip関数によって進んだボードを、元のボードの状態に戻す
VAL_POINT = -100; //評価値初期化
MAX_USER = -100;
}
}
}
それとCPU_input関数内でpush/popでちゃんと対になっていません(実行されない場合あり)。
気持ち悪いでのちゃんと対にして、FIRST/SECONDのパラメータを不要にしましょう(自動管理)。
Board_CPU_BanData_push(FIRST);がCPU_input()になくてGame.cppにあるのも変です。ちゃんと対にしましょう。
それと、
static int valX=0; //AI関数用の座標
static int valY=0;
static int VAL_POINT = -100;
static int MAX_USER = -100; //プレーヤーの評価の最大値
static int TOTAL = -220;
がグローバル変数的な扱いで美しく有りません(シンプルではない・変数の更新が直感的ではない)。
もっと引数や戻り値にして変数の引渡しを明確をすることと評価点の処理をCPU_input側にしたらどうでしょうか?
総合評価点(TOTAL)をCPU_AI2で計算している事自体が変ですし、整理すればCPU_AI2は不要では?
私としては全部CPU_input関数のローカル変数に出来ると思います。
ちなみにCPU_BanData[h][w]を3次元にすれば、Board_CPU_BanData_push/Board_CPU_BanData_popの中でしている変なことしなくて良いですよ。
それとプレイしてみましたが、最後の最後の1コマ空いた状態でCPUがパスする状態になるとCPUが無限ループしました。バグが潜んでいる様です。
あと、あまり強く有りませんでした。角を取られやすいのは弱点だと思います。
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 01:36
by 小人
すみません。
以前言われた所は直したのですが、全てに共通してという事を理解していませんでした。
すぐに修正します。
PushとPopが対になっているというのはどういった状態なのでしょうか?
同じループ内で存在していなければならない・・・?
理解出来ずすみません。
自動管理についてC言語の本で調べたりググってみたのですが分からず終いでorz
申し訳ないのですが、簡単に説明して頂けたら幸いです。
おっしゃる通り変でした。
少なくともCPUのターンならCPU_input関数終了前にFlipを呼び出してからBanData_Pushを呼び出せばちゃんと対?に出来ます。
しかしUSER側のBoard_CPU_BanData_pushに関しては上手く消す方法が思い浮かばず・・・すみません。
残しておいたらやはり不味いでしょうか。
確かにその部分は気になってました。
深夜に突貫で思うままに書きなぐった結果ですorz
引数や戻り値でどうにか出来そうですね。
すみません、頑張ってみます。
変な事というのは配列の後に配列を入れてる事でしょうか?
スタックを調べていたらその様な事が書かれていたのですが、確かにバグの温床にもなり兼ねないし変でした。
三次元配列というのはまだ書籍でも目にした事がないため、勉強してみます。
バグ潜んでますね。
多分上記の事柄を整理すれば解決・・・すると信じてます。
確かに弱いです。
一応角は取られない様に気を使っていたつもりだったのですが、あっさり取れます。
これはもう二手先じゃなく三手四手と読んでいけばある程度は解決すると思うので、まずは手数を増やしていってもバグらない様にしっかりとした骨組みを建設したいと思います。
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 01:59
by 小人
追記
三次元配列で管理するというのはつまり
今まで通りのBanData[10][10]
を
CPU_BanData[10][10][X]
とする事で Xの値が0の時はFIRSTの配列を
Xの値が1の時はSECONDの配列を入れる事が出来る。
という認識で正しいでしょうか?
X,Y軸にZ軸を加えて、Z軸方向に1進む度に盤面が一つ出来ていくイメージなのですが。
これならおっしゃる通り処理が簡略化出来、助かるのですが・・・。
また変な事をしでかすと厄介なので、御回答よろしくお願いします。
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 02:37
by 小人
思っていたより素直に修正されてくれたので出来た所のみ添付しておきます。
対にするという事と三次元配列は未解決ですm(_ _)m
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 11:48
by softya(ソフト屋)
とりあえず、スタック処理のサンプルです。pushのみ。
コード:
#define STACK_LEVEL 4
static int CPU_BanData[ STACK_LEVEL ] [ CPU_BAN_HEIGHT ][ CPU_BAN_WIDTH ] = {0};
static int BanData_stack_count = 0;
// CPU思考中のボード情報を保存する関数
void Board_CPU_BanData_push(void){
int x,y; //BanDataカウント用変数
// スタックの深さチェック
if( BanData_stack_count >= STACK_LEVEL ) {
DebugBreak();//限界を超えたのでデバッグブレーク
}
// スタックに退避する。
for( y=0 ; y<BAN_HEIGHT ; y++ ){ //ボードの情報を保っておく
for( x=0 ; x < BAN_WIDTH ; x++ ){
CPU_BanData[BanData_stack_count][y][x] = BanData[y][x];
}
}
// スタックを進める。
BanData_stack_count++;
}
三次元の場合、Zを一番最初に持ってくるのがメモリ配列上の都合で相応しいです。
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 12:32
by softya(ソフト屋)
スタックを対にしたバージョン。
ちなみに元からある致命的なバグとして、最後の1コマがCPUに回ってきた場合に仮想プレーヤーが置ける場所が無いのではまります。
コード:
//CPUの入力関数
int CPU_input(int Tekikoma){
int end = 0;
int cpuX,cpuY,playerX,playerY;
int Mykoma;
int valX=0; //評価値の高かった座標
int valY=0;
int CPU_val_point = -100; //評価値の保存用関数
int PLAYER_val_point = -100;
int total = -9999; //評価値合計の最大値
if(Tekikoma == BLACK){
Mykoma = WHITE;
}else{
Mykoma = BLACK;
}
for( cpuY = 1 ; cpuY < BAN_HEIGHT -1 ; cpuY++ ){ //座標を一つづつサーチ関数へ送る
for( cpuX = 1 ; cpuX < BAN_WIDTH -1 ; cpuX++ ){
// 最初の状態を保存
Board_CPU_BanData_push();
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
CPU_val_point = CPU_AI(cpuX,cpuY), //置ける場所の座標を送る
Board_Flip(Tekikoma), //コマを返し、一手進んだ状態にする
// 評価値初期化
CPU_val_point = -100;
PLAYER_val_point = -100;
for( playerY = 1; playerY < BAN_HEIGHT -1 ; playerY++ ){ //座標を一つづつサーチ関数へ送る
for( playerX = 1 ; playerX < BAN_WIDTH -1 ; playerX++ ){
// CPUの置いた状態を保存。
Board_CPU_BanData_push();
// 仮想プレーヤーが置いてみる。
if(Board_Search(playerX,playerY,Mykoma) >= 1){
PLAYER_val_point = CPU_AI(playerX,playerY); //相手の置ける座標を送り総合評価
if(total < (CPU_val_point - PLAYER_val_point)){ //総合評価。値が大きければ更新
total = CPU_val_point - PLAYER_val_point,
valY = cpuY, valX = cpuX; //評価座標の更新
}
}
// CPUの置いた状態に戻す。
Board_CPU_BanData_pop();
}
}
}
// 最初の状態に戻す。
Board_CPU_BanData_pop();
}
}
WaitTimer( 1000 ); //思考時間を1秒持たせる
// 評価ポイント有り?
if( total != -9999 ) {
// コマを置く。
if(Board_Search(valX, valY, Tekikoma) >= 1){ //評価値の一番高かった座標にコマを置く
Board_Flip(Tekikoma), //コマを返す
end = 1;
}
}
return end;
}
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 16:25
by 小人
あーなるほど、ちゃんと対にすればこの++,--だけでちゃんと機能するんですね・・・orz
スタックの事を勉強してから似たような仕組み(二次元配列でしたが)を作ってみたのですが、上手く行かなかったのはデータの出し入れの順番がおかしかったせいでした。
最後のバグについては昨日気付いて修正しました。
ついでにプレーヤーがパスになる様な座標にCPUが置ける時は率先してその座標にコマを置く様にも設定してみました。
が、あまり上手くいっているか分からないため軽く添削して下さると助かります。
今三次元の配列を調整中なので、もう暫くしたら添付しますm(_ _)m
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 16:57
by 小人
出来ましたので添付します。
これよりAIを強化していく場合はCPU_inputのFor文を同じ様にどんどん増やしていく形で出来そうですが、そこで上手く再帰処理を使うのでしょうか。
しかし再帰呼び出しをするとPopとPushの対になっているバランスが壊れそうですね・・・。分かりませんorz
自分としてはこのままFor文を増やしていく方が分かり易いですが。
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 17:14
by softya(ソフト屋)
小人 さんが書きました:出来ましたので添付します。
これよりAIを強化していく場合はCPU_inputのFor文を同じ様にどんどん増やしていく形で出来そうですが、そこで上手く再帰処理を使うのでしょうか。
しかし再帰呼び出しをするとPopとPushの対になっているバランスが壊れそうですね・・・。分かりませんorz
自分としてはこのままFor文を増やしていく方が分かり易いですが。
再帰呼び出しでバランスは崩れませんよ。
あとfor文を増やす事はお勧めしません。今後のためにも再帰処理を使ってみてください。
それと評価のために、評価値の処理も見直したほうが良いかも知れません。
CPUの評価値とPLAYERの評価値を同等に扱うのも変えてみるとか。
前に私が言った途中のひっくり返しのコマも評価するとか。
いろいろ試せることはあると思います。
2種類の思考ルーチンを作って対戦させてみるのも面白いかと思います。
Re: オセロのプログラム制作過程2
Posted: 2011年6月19日(日) 23:33
by 小人
再帰は確かに重要そうなので今必死に取り掛かってます。
評価値も見直したいのですが、ひっくり返した石をどう評価に組み込めば良いのでしょう?
例えばCPUが白の時 [3][5]が黒なら評価値5にする・・・の様な?
でもその評価値をどう計算に盛り込んだら良いのだろう・・・などと考えておりました。
CPU対CPUはもう大幅な書き直しになっちゃいますね。
でも面白そうです。
Re: オセロのプログラム制作過程2
Posted: 2011年6月20日(月) 00:34
by softya(ソフト屋)
調べてみたのですが、ひっくり返すコマ数を評価するよりも評価値を可変したほうが良いようです。
「アルゴリズム解説」
http://uguisu.skr.jp/othello/algo.html
まぁ、あまり深みには成ることはしなくて良いのでもう少し賢くする程度でやめた方が良いでしょうね。
再帰で10手先を読むと重そうなので、6手先ぐらいで。
あとはやはり、アルファ・ベータ法などの出番のようです。
>CPU対CPUはもう大幅な書き直しになっちゃいますね。
それほどでも無いと思うのですが?
Re: オセロのプログラム制作過程2
Posted: 2011年6月20日(月) 02:32
by 小人
ゲームスタートしてコマを置いた瞬間CPUが全てコマを置き終了画面へ・・・。
そもそも再帰のやり方や書き方がこれで合っているのかも分からないですが。
取り敢えず評価関数についてはまだイジってないです。
コード:
#include "CPU_AI.h"
#include "Board.h"
//プロトタイプ宣言
static int CPU_AI(int,int);
static int CPU_fact(int,int,int,int);
static int PLAYER_fact(int,int,int,int,int,int);
static int valX=0; //評価値の高かった座標
static int valY=0;
static int total = -220; //評価の合計値
static int search_count = 0; //プレーヤーがパスかどうか判断する変数
//CPUの入力関数
int CPU_input(int Tekikoma){
int end = 0;
int cpuX = 1;
int cpuY = 1;
int Mykoma;
if(Tekikoma == BLACK){
Mykoma = WHITE;
}else{
Mykoma = BLACK;
}
CPU_fact(cpuX,cpuY,Tekikoma,Mykoma); //再帰呼び出し
WaitTimer( 1000 ); //思考時間を1秒持たせる
if(Board_Search(valX, valY, Tekikoma) >= 1){ //評価値の一番高かった座標にコマを置く
Board_Flip(Tekikoma), //コマを返す
end = 1;
}
return end;
}
//CPU入力サイドの再帰関数
int CPU_fact(int cpuX ,int cpuY,int Tekikoma,int Mykoma){
int playerX =1;
int playerY =1;
int CPU_val_point = -100;
// 最初の状態を保存
Board_CPU_BanData_push();
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
//置ける場所の座標を評価関数へ送る
CPU_val_point = CPU_AI(cpuX,cpuY),
//コマを返し、一手進んだ状態にする
Board_Flip(Tekikoma);
search_count = 0;
PLAYER_fact(playerX,playerY,CPU_val_point,Mykoma,cpuX,cpuY);
}
if(search_count == 0){
total = 300, //プレーヤーがパスか試合終了手前の状況のため、totalの値を最大に
valY = cpuY, valX = cpuX; //してその座標にコマを置いてしまう
}
if(cpuY == BAN_HEIGHT -1){
return 0;
}
cpuX++;
if(cpuX == BAN_WIDTH -1){
cpuY++,cpuX = 1;
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
return CPU_fact(cpuX,cpuY,Tekikoma,Mykoma);
}
//仮想プレーヤー入力サイドの再帰関数
int PLAYER_fact(int playerX,int playerY,int CPU_val_point,int Mykoma,int cpuX,int cpuY){
int PLAYER_val_point = -100;
int PLAYER_val_point_save;
// 最初の状態を保存
Board_CPU_BanData_push();
//一手先の状態でプレーヤーのコマを置く場所を仮想
if(Board_Search(playerX,playerY,Mykoma) >= 1){
search_count++;
PLAYER_val_point_save = PLAYER_val_point;
//相手の置ける座標を送り総合評価
PLAYER_val_point = CPU_AI(playerX,playerY);
if(PLAYER_val_point_save < PLAYER_val_point){
if(total < (CPU_val_point - PLAYER_val_point)){ //総合評価。値が大きければ更新
total = CPU_val_point - PLAYER_val_point,
valY = cpuY, valX = cpuX; //評価座標の更新
}
}
}
if(cpuY == BAN_HEIGHT -1){
return 0;
}
cpuX++;
if(cpuX == BAN_WIDTH -1){
cpuY++,cpuX = 1;
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
return PLAYER_fact(playerX,playerY,CPU_val_point,Mykoma,cpuX,cpuY);
}
//CPUのAI関数
static int CPU_AI(int x,int y){
int val_point = 0;
int val_table[BAN_HEIGHT][BAN_WIDTH] = //評価配列
{
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100}
};
val_point = val_table[y][x]; //評価値保存
return val_point; //評価値を返す
}
Re: オセロのプログラム制作過程2
Posted: 2011年6月20日(月) 11:07
by softya(ソフト屋)
とりあえずちゃんとした再帰呼び出しになっていないです。
昔のコードを活かしてCPU評価とPLAYER評価を1セットのまま、更に先読みのため再帰処理を行うようにしたほうが良いでしょう。
それとBoard_CPU_BanData_pop()ぜずにもどる場合があるので、非常にマズイです。
[補足]正確に言うと再帰的にプレーヤー処理が無限ループになっています。
よく分からなかったら再帰を一度取りやめて、とりあえずCPU→Player→CPU→Playerの階層をfor文で作ってみてください。
これがちゃんと動いたら再帰処理に再分解しましょう。
再帰に関して参考サイト。
「C言語編 第56章 再帰呼び出し」
http://www.geocities.jp/ky_webid/c/056.html
「C言語入門 8.関数」
http://c-production.com/contents/c/sec08.html#03
「ハノイの塔を攻略せよ!【Windowsプログラミング研究所】」
http://www13.plala.or.jp/kymats/study/C ... Hanoi.html
Re: オセロのプログラム制作過程2
Posted: 2011年6月20日(月) 19:45
by 小人
こんな感じで如何でしょう?
コード:
//CPU思考入力の再帰関数
int CPU_fact(int cpuX ,int cpuY,int Tekikoma,int Mykoma){
int playerX;
int playerY;
int CPU_val_point = -100;
int PLAYER_val_point = -100;
int PLAYER_val_point_save;
// 最初の状態を保存
Board_CPU_BanData_push();
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
//置ける場所の座標を評価関数へ送る
CPU_val_point = CPU_AI(cpuX,cpuY),
//コマを返し、一手進んだ状態にする
Board_Flip(Tekikoma);
search_count = 0;
for( playerY = 1; playerY < BAN_HEIGHT -1 ; playerY++ ){ //座標を一つづつサーチ関数へ送る
for( playerX = 1 ; playerX < BAN_WIDTH -1 ; playerX++ ){
//一手進んだ状態のボード情報を保存
Board_CPU_BanData_push();
//一手先の状態でプレーヤーのコマを置く場所を仮想
if(Board_Search(playerX,playerY,Mykoma) >= 1){
search_count++;
PLAYER_val_point_save = PLAYER_val_point;
//相手の置ける座標を送り総合評価
PLAYER_val_point = CPU_AI(playerX,playerY);
if(PLAYER_val_point_save < PLAYER_val_point){
if(total < (CPU_val_point - PLAYER_val_point)){ //総合評価。値が大きければ更新
total = CPU_val_point - PLAYER_val_point,
valY = cpuY, valX = cpuX; //評価座標の更新
}
}
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
}
}
if(search_count == 0){
total = 300, //プレーヤーがパスか試合終了手前の状況のため、totalの値を最大に
valY = cpuY, valX = cpuX; //してその座標にコマを置いてしまう
}
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
if(cpuY == BAN_HEIGHT -1){
return 0;
}
cpuX++;
if(cpuX == BAN_WIDTH -1){
cpuY++,cpuX = 1;
}
return CPU_fact(cpuX,cpuY,Tekikoma,Mykoma);
}
Re: オセロのプログラム制作過程2
Posted: 2011年6月20日(月) 19:53
by softya(ソフト屋)
そもそも
return CPU_fact(cpuX,cpuY,Tekikoma,Mykoma);
が変なんですね。
CPU→Player→CPU→Player
とネストするのに最後に
return CPU_fact(cpuX,cpuY,Tekikoma,Mykoma);
が出てきたら階層呼び出しになっていません。
PLAYER_fact()とCPU_fact()の先頭にコンソールデバッグ法でprintf()を書いてみてください。
※ 前にお教えした方法です
http://dixq.net/forum/blog.php?u=114&b=982&c=2。今後のためにこのコンソールは残しておいてくださいね。
ただし、
コード:
#ifdef _DEBUG
AllocConsole();
freopen("CONOUT$", "w", stdout); //標準出力をコンソールにする
#endif
で囲めばデバッグビルドだけ有効になりますので、こうしておいてください。
Re: オセロのプログラム制作過程2
Posted: 2011年6月21日(火) 22:12
by 小人
そちらのコードは何処に書けば宜しいでしょうか?
main、CPU_AIヘッダやファイルなど色々試しましたが、何処もコンパイルエラーになってしまったため。
while前や後で値を表示出来るのは大変便利そうです。
Re: オセロのプログラム制作過程2
Posted: 2011年6月21日(火) 22:39
by softya(ソフト屋)
小人 さんが書きました:そちらのコードは何処に書けば宜しいでしょうか?
main、CPU_AIヘッダやファイルなど色々試しましたが、何処もコンパイルエラーになってしまったため。
while前や後で値を表示出来るのは大変便利そうです。
ワーニングはでるけど実行出来るはずですけど・・・・。
メインループの前に入れてくださいね。
Re: オセロのプログラム制作過程2
Posted: 2011年6月21日(火) 22:59
by 小人
あぁ、ごめんなさい
#ifdef _DEBUG
#endif
の組み合わせがあったものでてっきりファイルの頭に置くもんだと勘違いしてましたorz
今からプリントを試して参りますm(_ _)m
Re: オセロのプログラム制作過程2
Posted: 2011年6月21日(火) 23:26
by 小人
totalの初期化を忘れていただけで、それを書き加えた所問題なく動くようにはなりました。
ただソフト屋さんのご指摘があった通り、再帰の書き方が絶対におかしいですね。
これじゃ簡単に深くまで探索出来ない(再帰は簡単に探索可能なのがメリットなのに)です。
とりあえず今の所この状態からどうやって更に先読みするか考えてみます。
Re: オセロのプログラム制作過程2
Posted: 2011年6月21日(火) 23:36
by softya(ソフト屋)
流れとしては、
・CPUの一番深い部分から、仮想PLAYERを。
・仮想PLAYERの一番深い部分から、仮想CPUを。
呼び出さないといけません。
ただし、引数に階層をしめす値が必要です(指定階層で止めるため)。
Re: オセロのプログラム制作過程2
Posted: 2011年6月21日(火) 23:51
by 小人
多分ソフト屋さんのおっしゃってる事に近いことを出来ていた気がします。
stack_countはスタティックなグローバル変数です。
コード:
//CPU思考入力の再帰関数
int CPU_fact(int cpuX ,int cpuY,int Tekikoma,int Mykoma){
int playerX;
int playerY;
int CPU_val_point = -100;
int PLAYER_val_point = -100;
int PLAYER_val_point_save;
// 最初の状態を保存
Board_CPU_BanData_push();
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
//置ける場所の座標を評価関数へ送る
CPU_val_point = CPU_AI(cpuX,cpuY),
//コマを返し、一手進んだ状態にする
Board_Flip(Tekikoma);
search_count = 0;
for( playerY = 1; playerY < BAN_HEIGHT -1 ; playerY++ ){ //座標を一つづつサーチ関数へ送る
for( playerX = 1 ; playerX < BAN_WIDTH -1 ; playerX++ ){
//一手進んだ状態のボード情報を保存
Board_CPU_BanData_push();
printf("CPUが仮に置いた場所「座標x:%d,y座標:%d」\n",cpuX,cpuY);
//一手先の状態でプレーヤーのコマを置く場所を仮想
if(Board_Search(playerX,playerY,Mykoma) >= 1){
search_count++;
stack_count++;
if(stack_count == 1){
Board_Flip(Mykoma);
CPU_fact(1,1,Tekikoma,Mykoma);
}
PLAYER_val_point_save = PLAYER_val_point;
//相手の置ける座標を送り総合評価
PLAYER_val_point = CPU_AI(playerX,playerY);
if(PLAYER_val_point_save < PLAYER_val_point){
if(total < (CPU_val_point - PLAYER_val_point)){ //総合評価。値が大きければ更新
total = CPU_val_point - PLAYER_val_point,
printf("仮想プレーヤーの入力可能「座標x:%d,y座標:%d」\n",playerX,playerY),
valY = cpuY, valX = cpuX; //評価座標の更新
}
}
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
}
}
if(search_count == 0){
total = 300, //プレーヤーがパスか試合終了手前の状況のため、totalの値を最大に
valY = cpuY, valX = cpuX; //してその座標にコマを置いてしまう
}
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
if(cpuY == BAN_HEIGHT -1){
return 0;
}
cpuX++;
if(cpuX == BAN_WIDTH -1){
cpuY++,cpuX = 1;
}
return CPU_fact(cpuX,cpuY,Tekikoma,Mykoma);
}
あまり手はくわえてませんが、今この状態だと
「一手先と二手先の評価を度外視して三手と四手だけ判断している」
ため駄目なのですが。
不思議なもので、前よりは強くなっている気がします。
一手と二手の評価をどうにかして深い階層の所まで送らないとですね・・・。
多分stack_countを使ったif文を作成すれば行けるような気がするので頑張ってみますm(_ _)m
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 00:38
by softya(ソフト屋)
これってCPU→仮想PLAYER→CPU→仮想PLAYERってネストになってますか?
まず、CPUが置いて、その上で仮想PLAYERが置いて、更にCPUが置いて、更に仮想プレイヤーが置かないといけません。
それぞれx,yのループが必要なので、関数呼び出しを分解すると4x2のループの深さになるはずなんですが?
ループ回数を計算してみる、それぞれ8回ループすると一番深いところで 8x8x8x8x8x8x8x8と8の8乗回のループになるはずです。
return CPU_fact(cpuX,cpuY,Tekikoma,Mykoma);
ってのも絶対出現しないはずなのですが・・・。
あと、私のところでは「CPUが仮に置いた場所」だけが表示されて無限ループになります。
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 00:56
by softya(ソフト屋)
とりあえず動きました。
で、CPU_factの頭をこんな風にしてみてください。
コード:
//CPU思考入力の再帰関数
int CPU_fact(int cpuX ,int cpuY,int Tekikoma,int Mykoma){
int playerX;
int playerY;
int CPU_val_point = -100;
int PLAYER_val_point = -100;
int PLAYER_val_point_save;
printf( "CPU_fact cpuX=%d, cpuY=%d, 敵=%d 自分=%d stack=%d\n", cpuX, cpuY, Tekikoma, Mykoma, stack_count );
で他のprintfを一時的に//コメントにしてみてください。
これは、意図した動きなのでしょうか?
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 01:11
by 小人
あぁ削除したのですが、間に合いませんでしたかorz
自分もおかしい事に気付き、今直している所です。
多分根本的に構造がおかしいのですが、何だか後少しでちゃんと先読みの出来るプログラムが出来上がる様な気がして・・・。
明日までには完成させて動きを見たいです。
その上でこれじゃアカンという事でしたら、書き直していきたいと思いますm(_ _)m
お手を煩わせてしまってすみません。
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 01:42
by 小人
駄目でした。
これ以上はいじっても悪くなる一方なので此処で止めておきます。
もう全面的に書き直すべきだと思いますが、書き直す際にこの形は守ってください等御座いましたらおっしゃって頂けると幸いです。
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 11:27
by softya(ソフト屋)
削除する場合は、理由を書いて残してくださいね。
基本削除は禁止されていますので。
どう説明しようか思案中。
CPU_AIは、少なくとも再帰呼び出しにする前のちゃんと動いてたコードからやり直してみませんか?
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 11:33
by 小人
以後気をつけます。
再帰にして色々といじる前のソースコードでしたら保存してあります
(再帰にしたら絶対上手くいかないだろうなと思っていたので)
以前ソフト屋さんのおっしゃった様に、この状態からFor文とIf文で更に先読みさせたプログラムを作る事から始めた方が宜しいでしょうか?
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 11:41
by softya(ソフト屋)
>以前ソフト屋さんのおっしゃった様に、この状態からFor文とIf文で更に先読みさせたプログラムを作る事から始めた方が宜しいでしょうか?
それも考えましたがやめておきましょう。
まず再帰前のコードでCPUループの部分とPlayerループの部分でプログラム構造的に同じでない部分を関数に分離してみましょう。
その時に不要な物が無いかも再確認します。
その結果、CPUループの部分とPlayerループの部分が同じ形に成らないかみてみましょう。
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 16:21
by 小人
このような感じでしょうか・・・?
不要な物なのかの判別はつきませんでしたorz
ループ文はかなり似てますね。
ループ自体の関数を作って、それに引数としてcpuXやplayerXなど渡しても問題なさそうです。
コード:
#include "CPU_AI.h"
#include "Board.h"
//プロトタイプ宣言
static int CPU_AI(int,int);
static int CPU_val(int,int,int);
static int PLAYER_val(int,int,int,int,int,int);
static int valX=0; //評価値の高かった座標
static int valY=0;
static int total = -220; //評価値合計の最大値
static int PLAYER_val_point_save;
//CPUの入力関数
int CPU_input(int Tekikoma){
int end = 0;
int cpuX,cpuY,playerX,playerY;
int Mykoma;
int CPU_val_point; //評価値の保存用変数
int PLAYER_val_point = -100;
int search_count = 0; //プレーヤーがパスかどうか判断する変数
if(Tekikoma == BLACK){
Mykoma = WHITE;
}else{
Mykoma = BLACK;
}
for( cpuY = 1 ; cpuY < BAN_HEIGHT -1 ; cpuY++ ){ //座標を一つづつサーチ関数へ送る
for( cpuX = 1 ; cpuX < BAN_WIDTH -1 ; cpuX++ ){
// 最初の状態を保存
Board_CPU_BanData_push();
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
CPU_val_point = CPU_val(cpuX,cpuY,Tekikoma);
search_count = 0;
for( playerY = 1; playerY < BAN_HEIGHT -1 ; playerY++ ){ //座標を一つづつサーチ関数へ送る
for( playerX = 1 ; playerX < BAN_WIDTH -1 ; playerX++ ){
//一手進んだ状態のボード情報を保存
Board_CPU_BanData_push();
//一手先の状態でプレーヤーのコマを置く場所を仮想
if(Board_Search(playerX,playerY,Mykoma) >= 1){
search_count++;
PLAYER_val_point = PLAYER_val(playerX,playerY,CPU_val_point,PLAYER_val_point,cpuX,cpuY);
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
}
}
if(search_count == 0){
total = 300, //プレーヤーがパスか試合終了手前の状況のため、totalの値を最大に
valY = cpuY, valX = cpuX; //してその座標にコマを置いてしまう
}
}
//Flip関数によって進んだボードを、元のボードの状態に戻す
Board_CPU_BanData_pop();
}
}
WaitTimer( 1000 ); //思考時間を1秒持たせる
if(Board_Search(valX, valY, Tekikoma) >= 1){ //評価値の一番高かった座標にコマを置く
Board_Flip(Tekikoma), //コマを返す
end = 1;
}
return end;
}
//CPUのAI関数
static int CPU_AI(int x,int y){
int val_point = 0;
int val_table[BAN_HEIGHT][BAN_WIDTH] = //評価配列
{
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100}
};
val_point = val_table[y][x]; //評価値保存
return val_point; //評価値を返す
}
int CPU_val(int cpuX,int cpuY,int Tekikoma){ //一時的に作った関数
int CPU_val_point=-100; //評価値の初期化
PLAYER_val_point_save = -100;
//置ける場所の座標を評価関数へ送る
CPU_val_point = CPU_AI(cpuX,cpuY),
//コマを返し、一手進んだ状態にする
Board_Flip(Tekikoma);
return CPU_val_point;
}
int PLAYER_val(int playerX,int playerY,int CPU_val_point,int PLAYER_val_point,int cpuX,int cpuY){
//相手の置ける座標を送り総合評価
PLAYER_val_point = CPU_AI(playerX,playerY);
if(PLAYER_val_point_save < PLAYER_val_point){
PLAYER_val_point_save = PLAYER_val_point;
if(total < (CPU_val_point - PLAYER_val_point)){ //総合評価。値が大きければ更新
total = CPU_val_point - PLAYER_val_point,
valY = cpuY, valX = cpuX; //評価座標の更新
}
}
return PLAYER_val_point;
}
これとは全く別にポインタについての質問があるのですが、別のトピックを立てて質問をした方が宜しいでしょうか?
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 17:06
by softya(ソフト屋)
ポインタの質問はたぶん長くなるので別トピックで質問用のプログラムを作られた方が良いと思います。
1)CPU_valとPlayer_valが何をする関数か関数名の再検討をお願いします。関数名と内部の処理が食い違います(他の関数名も気になるんですけどね)。名詞+名詞だけじゃなくて動詞もいれてくださいね。
2)評価する関数を一本化(CPU_valとPlayer_val) & 引数減らし & ファイル先頭のstatic変数を関数内staticに移せる気がするのですが。本当にCPU_input()から引数で貰う必要のある変数は何個くらいでしょうか?
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 18:22
by 小人
(1)一時的に適当な名前を付けてしまいました。直します。
(2)一本化出来そうだなぁ確かにと思い考えていたのですが、これが一本化出来るならCPU_AIの中に収める事が可能なんじゃないかって考え、その方向で頑張ってみます。
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 18:39
by softya(ソフト屋)
小人 さんが書きました:(2)一本化出来そうだなぁ確かにと思い考えていたのですが、これが一本化出来るならCPU_AIの中に収める事が可能なんじゃないかって考え、その方向で頑張ってみます。
後々の為、xxx_valとCPU_AIはとりあえず1本化はしないでください。
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 19:14
by 小人
引数・・・・・・・・・増えました・・・・・orz;
グローバルstaticを使用しない場合で結構考えたのですが、このような方法しか浮かびませんでした・・・・・・。
コード:
#include "CPU_AI.h"
#include "Board.h"
//プロトタイプ宣言
static int CPU_AI(int,int);
static void Val_point_check(int,int,int,int,int*,int*,int*,int*);
//CPUの入力関数
int CPU_input(int Tekikoma){
int end = 0;
int cpuX,cpuY,playerX,playerY;
int Mykoma;
int valX=0; //評価値の高かった座標
int *p_valX=&valX;
int valY=0;
int *p_valY=&valY;
int total = -220; //評価値合計の最大値
int *p_total = &total;
int PLAYER_val_point_save;
int *p_PLAYER_val_point_save = &PLAYER_val_point_save;
int search_count = 0; //プレーヤーがパスかどうか判断する変数
if(Tekikoma == BLACK){
Mykoma = WHITE;
}else{
Mykoma = BLACK;
}
for( cpuY = 1 ; cpuY < BAN_HEIGHT -1 ; cpuY++ ){ //座標を一つづつサーチ関数へ送る
for( cpuX = 1 ; cpuX < BAN_WIDTH -1 ; cpuX++ ){
// 最初の状態を保存
Board_CPU_BanData_push();
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
//コマを返し、一手進んだ状態にする
Board_Flip(Tekikoma);
search_count = 0;
for( playerY = 1; playerY < BAN_HEIGHT -1 ; playerY++ ){ //座標を一つづつサーチ関数へ送る
for( playerX = 1 ; playerX < BAN_WIDTH -1 ; playerX++ ){
//一手進んだ状態のボード情報を保存
Board_CPU_BanData_push();
//一手先の状態でプレーヤーのコマを置く場所を仮想
if(Board_Search(playerX,playerY,Mykoma) >= 1){
search_count++;
Val_point_check(playerX,playerY,cpuX,cpuY,p_PLAYER_val_point_save,p_total,p_valX,p_valY);
printf("関数後のtotal:%d",*p_total);
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
}
}
if(search_count == 0){
total = 300, //プレーヤーがパスか試合終了手前の状況のため、totalの値を最大に
valY = cpuY, valX = cpuX; //してその座標にコマを置いてしまう
}
}
//Flip関数によって進んだボードを、元のボードの状態に戻す
Board_CPU_BanData_pop();
}
}
WaitTimer( 1000 ); //思考時間を1秒持たせる
printf("最終決定valX:%d,valY:%d",valX,valY);
if(Board_Search(valX, valY, Tekikoma) >= 1){ //評価値の一番高かった座標にコマを置く
Board_Flip(Tekikoma), //コマを返す
end = 1;
}
return end;
}
//CPUのAI関数
static int CPU_AI(int x,int y){
int val_point = 0;
int val_table[BAN_HEIGHT][BAN_WIDTH] = //評価配列
{
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100}
};
val_point = val_table[y][x]; //評価値保存
return val_point; //評価値を返す
}
void Val_point_check(int playerX,int playerY,int cpuX,int cpuY,int *p_PLAYER_val_point_save,int* p_total,int *p_valX,int *p_valY){
int CPU_val_point=-100; //評価値の初期化
int PLAYER_val_point = -100;
//置ける場所の座標を評価関数へ送る
CPU_val_point = CPU_AI(cpuX,cpuY);
//相手の置ける座標を送り総合評価
PLAYER_val_point = CPU_AI(playerX,playerY);
if(*p_PLAYER_val_point_save < PLAYER_val_point){
*p_PLAYER_val_point_save = PLAYER_val_point;
if(*p_total < (CPU_val_point - PLAYER_val_point)){ //総合評価。値が大きければ更新
*p_total = CPU_val_point - PLAYER_val_point,
*p_valY = cpuY, *p_valX = cpuX; //評価座標の更新
}
}
printf("関数中のtotal:%d",*p_total);
}
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 19:35
by softya(ソフト屋)
Val_point_checkは少し違いますね。値or評価・位置・検査ですよね。並びが変なのかな?
こうしましょう。
評価値は構造体ポインタで持ちまわってください。Val_point_checkに対して一個一個の引数は禁止します。
Val_point_check関数の引数はこうしてください。
Val_point_check(x,y,&valStruct,level);
levelは思考ルーチンの深さです。これで、Val_point_check内部の動作を変えてください。
[補足]前回のようにCPUと仮想Playerの両方で呼び出してください。
Re: オセロのプログラム制作過程2
Posted: 2011年6月22日(水) 21:25
by 小人
出来ました。
一応ゲームプレイした限りでは最後まで出来ました。
ただ何故か前より弱くなったような・・・?(自分が慣れたのか・・・)
コード:
#include "CPU_AI.h"
#include "Board.h"
typedef struct val_struct{
int cpuX;
int cpuY;
int valX;
int valY;
int cpu_val_point;
int player_val_point;
int total;
int player_val_point_save;
}val_struct;
//プロトタイプ宣言
static int CPU_AI(int,int);
static void Check_val_point(int,int,val_struct *p_valst,int);
//CPUの入力関数
int CPU_input(int Tekikoma){
int end = 0;
int cpuX,cpuY,playerX,playerY;
int Mykoma;
int level = 0;
val_struct valstruct ={0,0,0,0,-100,-100,-220,-100};
int search_count = 0; //プレーヤーがパスかどうか判断する変数
if(Tekikoma == BLACK){
Mykoma = WHITE;
}else{
Mykoma = BLACK;
}
for( cpuY = 1 ; cpuY < BAN_HEIGHT -1 ; cpuY++ ){ //座標を一つづつサーチ関数へ送る
for( cpuX = 1 ; cpuX < BAN_WIDTH -1 ; cpuX++ ){
// 最初の状態を保存
Board_CPU_BanData_push();
if(Board_Search(cpuX,cpuY,Tekikoma) >= 1){
//コマを返し、一手進んだ状態にする
Board_Flip(Tekikoma);
level = 0;
valstruct.player_val_point_save = -100;
Check_val_point(cpuX,cpuY,&valstruct,level);
search_count = 0;
for( playerY = 1; playerY < BAN_HEIGHT -1 ; playerY++ ){ //座標を一つづつサーチ関数へ送る
for( playerX = 1 ; playerX < BAN_WIDTH -1 ; playerX++ ){
//一手進んだ状態のボード情報を保存
Board_CPU_BanData_push();
//一手先の状態でプレーヤーのコマを置く場所を仮想
if(Board_Search(playerX,playerY,Mykoma) >= 1){
search_count++;
level++;
Check_val_point(playerX,playerY,&valstruct,level);
printf("関数後のtotal:%d",valstruct.total);
}
//ボードの情報を戻す
Board_CPU_BanData_pop();
}
}
if(search_count == 0){
valstruct.total = 300,//プレーヤーがパスか試合終了手前の状況のため、totalの値を最大にしてその座標にコマを置いてしまう
valstruct.valY = cpuY, valstruct.valX = cpuX;
}
}
//Flip関数によって進んだボードを、元のボードの状態に戻す
Board_CPU_BanData_pop();
}
}
WaitTimer( 1000 ); //思考時間を1秒持たせる
printf("最終決定valX:%d,valY:%d",valstruct.valX,valstruct.valY);
if(Board_Search(valstruct.valX, valstruct.valY, Tekikoma) >= 1){ //評価値の一番高かった座標にコマを置く
Board_Flip(Tekikoma), //コマを返す
end = 1;
}
return end;
}
//CPUのAI関数
static int CPU_AI(int x,int y){
int val_point = 0;
int val_table[BAN_HEIGHT][BAN_WIDTH] = //評価配列
{
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 5, -5, 3, 3, 3, 3, -5, 5, -100},
{-100, 20, -5, 15, 3, 3, 15, -5, 20, -100},
{-100,-20, -40, -5, -5, -5, -5, -40, -20, -100},
{-100,120, -20, 20, 5, 5, 20, -20, 120, -100},
{-100,-100,-100,-100,-100,-100,-100,-100,-100,-100}
};
val_point = val_table[y][x]; //評価値保存
return val_point; //評価値を返す
}
void Check_val_point(int x,int y,val_struct *p_valst,int level){
if(level == 0){
//置ける場所の座標を評価関数へ送る
p_valst->cpu_val_point = CPU_AI(x,y);
p_valst->cpuX = x,p_valst->cpuY = y;
}else{
//相手の置ける座標を送り総合評価
p_valst->player_val_point = CPU_AI(x,y);
if(p_valst->player_val_point_save < p_valst->player_val_point){
p_valst->player_val_point_save = p_valst->player_val_point;
if(p_valst->total < (p_valst->cpu_val_point - p_valst->player_val_point)){ //総合評価。値が大きければ更新
p_valst->total = p_valst->cpu_val_point - p_valst->player_val_point,
p_valst->valY = p_valst->cpuY, p_valst->valX = p_valst->cpuX; //評価座標の更新
}
}
}
printf("関数中のtotal:%d",p_valst->total);
printf("関数中のlevel:%d",level);
}
Re: オセロのプログラム制作過程2
Posted: 2011年6月23日(木) 00:19
by softya(ソフト屋)
次は、一階層増やしてCPU→Player→CPUにしてみましょう。
あと
コード:
if(search_count == 0){
valstruct.total = 300,//プレーヤーがパスか試合終了手前の状況のため、totalの値を最大にしてその座標にコマを置いてしまう
valstruct.valY = cpuY, valstruct.valX = cpuX;
}
これ逆に弱くしてませんかね?
Playerが置けないパターンの中でも最良手を選ぶべきだと思うのですが。
Re: オセロのプログラム制作過程2
Posted: 2011年6月23日(木) 00:37
by 小人
相手が置けない場所に置けば良いや等と安直に考えておりました。
終了手前で無限ループ入らない様にという役目もありますので、後々直していきたいと思います。
階層を増やすのはFor文If文の今まで通りで大丈夫ですよね?
一応その方向で進めてみます。
Re: オセロのプログラム制作過程2
Posted: 2011年6月23日(木) 04:03
by 小人
評価値が安定しなかったので、しっかり値を追って整理したらそこそこ戦えるAIになりました。
しかしソースコードが見るも無残な姿に・・・・。
それと角周辺がまだおかしい置き方する事がたまにあります。
Re: オセロのプログラム制作過程2
Posted: 2011年6月23日(木) 12:27
by softya(ソフト屋)
とりあえず、再帰コードのための布石ですのでfor,for,for,for,for,forの6重の深さにしてください。CPU→仮想Player→CPUが大前提です。
それとplayer_val_point_saveとcpu_val_point_save系は一旦外してみましょうか。
あと
int cpu_val_point_first;
int player_val_point_first;
int cpu_val_point_second;
は配列に出来るのでは?
あとcpu_val_point_firstとcpu_val_point_secondは足せば良いので分離する必要もないような。
Re: オセロのプログラム制作過程2
Posted: 2011年6月23日(木) 23:32
by 小人
評価値が適当になっておりますが、取り敢えず訂正しました。
Re: オセロのプログラム制作過程2
Posted: 2011年6月24日(金) 00:44
by softya(ソフト屋)
そうですね。
では、この構造を保ったまま再帰化に挑戦してみてください。
途中で問題があれば、なにが邪魔しているか同じ処理に出来ないか考えてみてください。
出来ないことを保留のまま、形を保って再帰化してもらえば良いです。
悩むとすれば、search_countの扱いだと思いますが。
ちなみに、Check_val_pointはほとんど同じ処理なのでもっと共通に出来るのではないですか?
p_valst->val_point_array[0] = CPU_AI(x,y);
p_valst->val_point_array[1] = CPU_AI(x,y);
とか、
p_valst->val_point_array[0] - p_valst->val_point_array[1] + p_valst->val_point_array[2]
とか。
このままだと思考の深さを変えるたびに修正が必要になります。
Re: オセロのプログラム制作過程2
Posted: 2011年6月24日(金) 03:22
by 小人
配列の添字[]の中身を
奇数の時はお互いを足し
偶数の時もお互いを足すけれど、後で上の値から差し引く
という形にすれば良いのかなぁ等と漠然に考えておりました。
共通化出来そうなんですよね、おっしゃる通り。
最初の評価値 += 三階層目の評価値
の様な形にすれば行けそうな気もしますが、色々試してみます。
(そもそもこの評価システム自体が気に入らないというか、再帰の邪魔をしている気がしてならないです。もっと上手い評価の方法を思いつければ良いのですが)
取り敢えず再帰に挑戦するに当たって以前と同じ様な没にならないために、確認を・・・。
作るのはFor文2つづつを一まとめにした再帰関数ですよね?
つまり今回の場合だと再帰関数が三階層になる。
再帰関数の形は以前と同じで良いのでしょうか?
returnの後の形がおかしいとのご指摘があり、自分も気になっていたので。
再帰とは今後絶対長いお付き合いになっていく事は知っているので、是が非でもオセロの段階である程度習得したいと思っておりますm(_ _)m
Re: オセロのプログラム制作過程2
Posted: 2011年6月24日(金) 11:32
by softya(ソフト屋)
じゃあ、とりあえず2つのforセットごとにこのプログラム構造のまま関数に分けてみてください。
cpu1
player1
cpu2
って感じです。これが再帰のための前準備になります。
戻り値に何を返すかは熟考をお願いします。
Re: オセロのプログラム制作過程2
Posted: 2011年7月17日(日) 04:21
by 小人
お久しぶりです。
前回構造体とポインタの質問をして後
これならいけるとしゃしゃってソフト屋さんに言われた通り、関数分けをしていたプログラムを塗り潰す用に再帰関数に書き換えてしまったのですが、上手く行きませんでした。
以前の再帰と違って今回のは初手すら打ってくれない状態です。
ただ以前より全体のプログラムとしての形は良くなっている様な気がします。
再帰でお約束の無限ループがエラーの原因で、どうやら最下層まではしっかり探索してくれているのですが、最初の階層に戻った後バグります。
というよりループを脱出してくれないのですよね・・・。
脱出条件を設けてあるのですが、何故かループ脱出の値まで進んだ後また値が減るという・・・。
現在printfにて何が起こっているのかある程度分かり易いと思いますので、デバック開始からデバック中止、でログを見て頂けると幸いです。
このくらい自力で解決出来ると思っていたのですが、前回の質問から日が経っても全く解決に至らず質問させて頂きました。
コメントもなるべく書き加えておきました。
どうぞ宜しくお願いしますm(_ _)m
Re: オセロのプログラム制作過程2
Posted: 2011年7月17日(日) 11:24
by softya(ソフト屋)
うーん。こんなに複雑なプログラムになるはず無いのですが・・・。
6/24版をまず3つの関数に分けてみてもらえますか?