WinCEでプログラムを、、っつー話
on Casiopeia E-500


杉将棋 Ver 0.7.7 2000/10/30

Casiopeia E-500でプログラミングして遊ぼうっつー・・・・(^-^;;
ちょこちょこ更新中

What's New 2000/10/10(Tue)

最新版(Ver 0.7.7)ダウンロード (WinCE 2.11以降(PsPC) MIPS(動作未確認))
最新版(Ver 0.7.7)ダウンロード (WinCE 2.11以降(PsPC) SH3(動作未確認))
最新版(Ver 0.7.7)ダウンロード (WinCE 3.0以降(PtPC) MIPS(動作未確認))
最新版(Ver 0.7.7)ダウンロード (WinCE 3.0以降(PtPC) SH3(いちおう動作確認))
※まだまだ不安定、、コケます。。そしたらリセットしてください。。。
よくコケル状態のままほったらかしだったのだが、Jornada548を手に入れたもので、 ふとコンパイルなぞしてみたので、ちょっと公開してみようかな、、という気に、、、
ご意見ご希望などありましたら、うえの掲示板にどうぞ。。
そうそう、めちゃ弱です、、、(^-^;;
2001/02/08
最新版(Ver 0.7.7)ダウンロード (WinCE 2.0以降(H/PC) MIPS(動作未確認))
最新版(Ver 0.7.7)ダウンロード (WinCE 2.0以降(H/PC) SH3(動作未確認))
付け焼刃に駒のビットマップの縦を短くしただけです。。しかも、 私はHPCを持っていないので、動作確認もしていませんので、 これで画面に収まっているかわかりません。。。2001/04/15

H/PCのSH3用の実行ファイルが間違っていました。正しいものに入れ替えました。2001/04/25

2000/10/06(Fri) 第1回 とりあえず基本設計
2000/10/06(Fri) 第2回 とりあえずビットマップ
2000/10/06(Fri) 第3回 とりあえず入力
2000/10/07(Sat) 第4回 とりあえず思考ルーチン
2000/10/09(Mon) 第5回 とりあえずマルチスレッド
2000/10/10(Tue) 第6回 とりあえずマシな思考ルーチン
2000/10/xx(xx) 第7回 とりあえず今後の構想なぞ

2000/10/06(Fri) 第1回 とりあえず基本設計
思い立ってからずいぶんと時間が経ってしまったが、Cassiopeiaで動くネイティブコードの 将棋プログラムを作ってみようか、、という気になった。

Cassiopeia E500を買ったばかりの頃に、PocketCで将棋を作ってはみたものの、 PocketCがインタープリタに近い実装であったため、処理速度に問題があり、 とてもじゃないが将棋の思考ルーチンに耐えるような代物ではなかった。。。
131MHzというクロックを持ったVR4121、、おまえの力はそんなものか!?ってーのを 見たいのと、単にWindowsのプログラムをしてみたいっつーので、ネイティブで 実装だっっ!!と、なった次第。

プログラミングWindowsCE (インプレス 8800円)とMFCによるウィンドウズプログラミング (アスキー 9000円くらい)もだいぶ前に買ってあるし、なんとかなるだろう!!
(と、いいつつ、実は会社の教育で受けた資料をもとに復習も1日くらいやっていたり する、、、(^-^;;; )


さて、どんな構造にしようかな?
将棋の思考ルーチン自体は、弱いけど、何度も何度も作っているので、 まあこれはいままで通りで良いでしょう。盤面と持ち駒の情報とどちらの番なのか、 を引数として渡すと、次のとるべき1手を返して来るという関数を作る。
こまかい内容はCassiopeiaで将棋を作ろうっつーコンピュータ 将棋を見てくだされ、、、、。
強くするのは、ある程度動いてからだな。 まずは、3手全件検索+αβ枝苅りに駒得と互いの玉からの 距離による静的評価としよう。。。で、あとまわし。

で、この思考ルーチンと、GUIの部分をWindowsの上にどう載せるか?

MFCでプログラムするのに考えなきゃならない(そーでもない?)のは、 ドキュメント/ビュー構造をどうすっか、ってことなのかな。 うーん、とりあえず、ビューは駒の移動などのGUIで良いけど、 じゃあ盤面をドキュメントにする?盤面を入出力ファイルにしても良いけど、 どーせなら「棋譜」をアプリケーションとしての入出力データとしたい。。。 じゃあ、盤面のデータはビューに持たせちゃって、あとからドキュメントとして 棋譜の入出力を装備しよう。うむ。

さしあたって、盤情報は上にのっかっている駒を8ビットで表現するとして unsigned char ban[9*9]、持ち駒は、飛〜歩の7種類が2人分なので、 int moti[2*7]で良いか。。。
1つの駒を表す8ビットは、上位(値の大きい方)から、 駒なら1桝なら0、人間の駒0コンピュータの駒1、普通0成り1、 予約が2ビットで、あとの3ビットで8種類の駒を表現する。 0:玉、1:飛、2:角、3:金、4:銀、5:桂、6:香、7:歩だ。
それぞれのビットをいちいちビット操作してとりだすのも面倒なので、 ビットフィールドを使って定義しよう。でも、値として直接指定もしたいので、 さらに共用体にしとくか。
こんな感じ、、
union KOMA_CORE {
  struct {
    //  VC++では、宣言した順で下位(評価値の小さい)bitから割り当てる
    unsigned char kind:3,
                  spare:2,
                  nari:1,
                  owner:1,
                  valid:1;
    } bit;
    unsigned char val;
};
どうやらVC++では、ビットフィールドへの格納順は宣言したものから下位ビットに 割り当てられるようだ。。。

さて、ドキュメント部分はおいとくとして、ビュー部分に思考ルーチンを くっつける?
なんだか美しくない気もするが、、アプリケーションとして見た場合、 コンピュータの思考を開始するきっかけは、「人間が手を指し終えた」という のをきっかけとするべきだろうな。。。
それでいいのか?とりあえず良いことにしよう。

さて、GUIのほうは、インスタンスの初期化でビットマップやら読み込んで(^-^;、 いろいろ値を初期化して(ぉいぉいそんなんでいーのか?)、ってやっちゃおう。
イベントは、ペンのタップ(WM_LBUTTONUP)だけ拾えばいいかな。
移動元を指定してから、移動先を指定、という感じに2回のタップで 駒の移動ができるようにしよう。でもって、移動元と移動先のマスは、 赤かなんかで枠を表示しよう。

あぁ、そうそう、当然 WM_PAINTも拾うわけだが、Viewのクラスメンバに 盤面と持ち駒の情報を持たせておいて、それを使って1画面フルに描き直そう。
もちろん、WM_LBUTTONUPで駒を移動したときには、この盤面と持ち駒情報を アップデートしとくと。

で、合法な手として移動元と移動先が決定したら、 そのままWM_LBUTTONのハンドラから思考ルーチンを呼んでしまえ。(^-^;;

こんなとこかなー。
2000/10/06(Fri) 第2回 とりあえずビットマップ
さて、いきなり動かせるバイナリができちゃって、そこから付け足しプログラミングが できるところがMFCの良いところ。。。とりあえず Win32/MIPSで MFCを使った プロジェクトっつーことでウィザードを用いてプロジェクトを作成。 プロジェクトの名称は「shogi」。 shogiView.cpp、shogiDoc.cppなど、いろいろと見なれたファイルがいくつか 自動的に作成され、 あたりまえに 問題なくビルドが終って、Cassiopeiaと同期後にバイナリが転送されて、 Cassiopeiaで実行可能になる。で、実行。からっぽのなんにもないアプリケーションが ちゃんと動きました。ふむ。

じゃあ、まずは、GUIから作ろうね。で、駒のビットマップを表示してみたいなと。。。

さて、困ったのは、2冊のたよりになるぶ厚い本、、かたやMFCによるプログラミングの話で、 残念ながらWindowsCEプログラミングの本は、MFCはいっさい使っていない。。。 これでこの後、さんざん悩むことに、、、
WinCEでサポートしている機能なのかどうかは、WinCEの本でわかる。 というか、ベースはWinCEの本で理解して、それをどうやってMFCにするのかっつーのを、 MFCの本と、VC++のオンラインマニュアルの情報をもとに「推理」しながら トライ&エラーで進めたわけだ。。。とほほ。ま、この苦労も楽しいんだけどね。

さて、WinCE本でのビットマップあたりの解説を読むと、、細かいところはすっとばして ようするに、引数がファイル名でHWNDを返すSHLoadDIBitmap関数を使ってビットマップを 読み込み、で、描画用のDCからCreateCompatibleDCでコピーしたDCに さっき読み込んだビットマップを 指定して、で、描画用のDCでBitBltをしろ、、と。さぁ、ちっともわかりません。。(^-^;;
だいたい、MFCではDCでなくてCDCっす。いちいち引数が微妙に違ってます。。。 で、オンラインマニュアルでCDCのあたりをずらーーっとながめると、、 LoadBitmapというメンバ関数がありますね。。でも引数はリソースID、、 リソースエディタで作れば読み込めるのかな?

ここは七転八倒した挙げ句、これで絵がでました。。
(1) ペイントブラシなどでつくったビットマップを、リソースにインポートして 名前を付ける(例としてIDB_KOMA_BMPとする)。
(2) CShogiViewクラスに、CBitmapのメンバ変数(bmp)を追加
(3) Viewクラスのコンストラクタ(せめてOnCreateでやるべきか?)で、 その変数から、bmp.LoadBitmap(IDB_KOMA_BMP)などとしてビットマップを読み込む
(4) OnDrawの延長で、OnDrawの引数になっているCDC *pDCを用いて、
  CDC dcMem;
  CBitmap *pOldBmp;
  dcMem.CreateCompatibleDC(pDC);
  pOldBmp = dcMem.SelectObject(&bmp);
  pDC->BitBlt(target.x, target.y, width, height, &dcMem, 0, 0, SRCCOPY);
  dcMem.SelectObject(pOldBmp);
  dcMem.DeleteDC();
これでどうにかなりました。。。

で、CShogiViewクラスに駒用のCBitmap型の変数を、bmp[上下2種][普通と成りで2種][8種類の駒] という2x2x8の3次元の変数にして、コンストラクタでずらずらとならべて ビットマップを読み込むようにしたです。。。

美しくない。(^-^;;

で、駒描画用のメンバ関数DispKomaをCDC, 駒の種類, 位置を引数として もらって描画するというように作りました。あと、 盤面の駒と、持ち駒を同じインタフェースで描画できるように、 ラッピングの関数をDispBan(CDC, CPoint, COLORREF)として作成し、 これは指定y座標が、0〜8の範囲なら盤面で、-1や、9の場合、持ち駒と いうルールにして、具体的な画面の座標に変換してからDispKomaを呼ぶっつーものです。
そうそう、盤面や持ち駒の位置をさくっと変更できるように、 CShogiViewクラスにCRect型で、rectBanとrectMoti[2]という2つの変数を作成して、 盤と持ち駒表示領域の座標をいれておきます。DispBan関数では、この2つの 座標を元にDispKoma関数に渡す具体的な座標を決めています。
で、DispBanAllという引数のない関数を最上位にして盤面を全部描き、 同様にDispMotiAllという関数も最上位で持ち駒を全部描くと、、 この2つをOnDrawから呼び出して、必要に応じて全画面を描画しなおすことが できるようにしました。

あとは、DispBanで持ち駒を描画するときは、ある種類の持ち駒の数が1のときは 駒の絵のみ、2以上なら右側に個数を表示するようにしました。
そうそう、テキストの表示はどうすんだ?

テキストはこれでいいみたい、
  TCHAR buf[適当];   // WinCEでは文字列は全てTCHAR (Unicode)とする
  pDC->SetTextColor(RGB(255,255,255));    // 白
  pDC->SetBkColor(RGB(100,100,50));       // 暗い黄色
  wsprintf(buf, _T("%d"), num_moti);      // 持ち駒の数をテキスト(Unicode)に
  pDC->DrawText(buf, CRect(x1,y1,x2,y2),  // 指定した枠(CRect)内に、
            DT_SINGLELINE|DT_BOTTOM);     // 一行で、下側に描画

ちなみに盤の桝目は、こんな感じ、
  CPen pen(PS_SOLID, 1, color);  // 指定されたcolorで、
  pDC->SelectObject(&pen);       // 上記penを設定
  pDC->Rectangle(x1,y1,x2,y2);   // 枠(と中身も)描く
  DispKoma(pDC, ..... );         // その内側におさまるように駒を描く

あとは、消すために塗りつぶしを使っていて、
  pDC->FillSolidRect(CRect(x1,y1,x2,y2), color); 
必要な部品はこんなとこだろうか。
2000/10/06(Fri) 第3回 とりあえず入力
見た感じ将棋盤っぽくなって、しめしめといったところ。。 俄然やる気がわいてきますね〜〜〜〜。
じゃ、お次はペンでの入力で駒を移動できるようにしますか。

とくに問題にならないので、さくさくと作り込みます。
ClassWizardで、CShogiViewでWM_LBUTTONUPにコマンド関数を付け足します。 これはOnLButtonUpという関数になります。 これだけでこの関数がペンタップにより呼び出されるようになります。
あとは、状態の遷移を考えながら必要な変数をCShogiViewに追加してやります。
1回目のタップで移動元を選択して、2回目で移動先を選択するわけですから、 座標を格納できる変数が2つあれば良いでしょう。。

っつーことで、CPoint curpointと、CPoint oldpointをPrivateなメンバ変数として追加。
x座標が-1だったら無効ってことにして、x (-1:無効, 0〜8)、y (-1〜9)という値を 保持させるようにします。
初期値をcurpoint, oldpointとも x, yともに-1にしておいて、 OnLButtonUp関数の中で、rectBan.PtInRect(point)と、rectMoti[0].PtInRect(point) の2つの関数を用いて(持ち駒領域は、人間側の持ち駒領域だけでよい)、 タップされた座標が盤面上なのか、持ち駒領域上なのか、 それ以外なのかを判別します。CRectクラスのメンバ関数PtInRectは、 CPointクラスで指定した座標が領域内にあるかどうか判定してくれます。これは便利。
で、盤面上なら、盤の位置のオフセットを引いてから駒のサイズでタップされた座標 を割算して、どのマスを指定したのかを計算して、curpointに格納します。 そうそう、curpointを変更する前にoldpointに古いcurpointをコピーしておきます。
それから、oldpoint.xを参照してそれが-1であれば1回目のタップであることがわかり、 それ以外なら、oldpoint = curpointを一度は通ったということなので、 2回目のタップであることが分ります。

とりあえず、curpointやoldpointのx座標を判定して-1でなければ有効な 指定があるわけで、そのマスを赤くするためにDispBan関数に赤を指定して 呼び出し、curpointやoldpointを描画します。

描画に使うCDCは、CDC *dc = this->GetDC()でとってきました、、あってるかな?

さて、oldpoint.xが-1以外であれば、2回タップし終ったということなので、 駒を移動させた盤面を作らなければなりません。。。
将棋の駒は当然好き勝手に移動できるわけではないですが、とりあえずチェックはなし。

で、union KOMA_CORE k_src, k_destを作っておいて、 k_src.val = ban[oldpoint.y*9+oldpoint.x].valで、 k_dest.val = ban[curpoint.y*9+curpoint.x].valと。 k_dest.bit.validが0でなければ、そこに駒があるということなので、 moti[0*7 + k_dest.bit.kind-1]++して持ち駒を増やします。 でもって、ban[curpoint.y*9+curpoint.x].val = k_src.val、 ban[oldpoint.y*9+oldpoint.x].val = 0x00として移動完了です。
でもって、oldpoint, curpointをDispBanで桝を赤く表示して、 あと持ち駒が増えたなら、持ち駒のところを描画しなおして、、、、
問題ないっすね。。。

同様の処理を持ち駒領域に対しても行ないます。
持ち駒と盤面上の駒の扱いを座標が異なるだけで細かいことはDispBanで隠蔽 して同じ扱いにしてますので、oldpoint = curpointっつーことが できるわけっす。

あとは、盤面上でも持ち駒領域でもない無効領域をタップした場合は、 入力のキャンセルとみなして、DispBanでoldpointの桝を黒くしてから、 curpoin.xとoldpoint.xを-1にして、入力前の状態にします。。。
ついでに、 有効なoldpoinがあって、かつcurpointが持ち駒領域だった場合、 移動先が持ち駒領域なんで、これは無視ってことで、無効領域と
こんなとこかな〜〜〜〜
2000/10/07(Sat) 第4回 とりあえず思考ルーチン
さてと、自分ばっかり動いても将棋になりません。。。
でまぁ、思考ルーチン入れる前に、コンピュータの移動の軌跡も人間側の 時のように表示するようにしましょう。。。人間を赤にしたから、 コンピュータは青にでもしとくか。

思考ルーチンは、別ファイルに作るとして、とりあえずトップの関数の インタフェースだけ作っておくか。
  cpu_think(int player, union KOMA_CORE *ban, int *moti, struct TE *te_p);
こんな感じでいいかなー。
struct TEってやつは、
struct KOMA_POS {
	int x, y;
};

struct TE {
	unsigned char com;    // COM_MOVE=1:移動、COM_HARU=2:貼り
	struct KOMA_POS from; // 移動元座標
	struct KOMA_POS to;   // 移動先座標
	int nari;             // 成る/成らず
	union KOMA_CORE koma; // 貼る場合の駒
};
これでいいか。

で、思考ルーチンの中身だけど、

(続く)
2000/10/09(Mon) 第5回 とりあえずマルチスレッド

2000/10/10(Tue) 第6回 とりあえずマシな思考ルーチン

2000/10/xx(xx) 第7回 とりあえず今後の構想なぞ