/// <summary> /// 指し手を生成する /// /// from : 盤上の升のみでなく手駒もありうる /// to : 盤上の升 /// promote : 成るかどうか /// </summary> /// <param name="from"></param> /// <param name="to"></param> /// <param name="promote"></param> /// <returns></returns> public static Move MakeMove(SquareHand from, SquareHand to, bool promote) { // ありえないはずだが…。 if (to.IsDrop()) { return(Move.NONE); } var to2 = (Square)to; if (from.IsDrop()) { // 打ちと成りは共存できない if (promote) { return(Move.NONE); } return(MakeMoveDrop(from.ToPiece(), to2)); } else { var from2 = (Square)from; if (promote) { return(MakeMovePromote(from2, to2)); } else { return(MakeMove(from2, to2)); } } }
/// <summary> /// 盤面のsqの升(手駒も含む)がクリックされた /// </summary> /// <param name="sq"></param> public void OnBoardClick(SquareHand sq) { var pos = ViewModel.ViewModel.gameServer.Position; var state = ViewModel.viewState; var pc = pos.PieceOn(sq); //Console.WriteLine(sq.Pretty()); switch (state.state) { case GameScreenViewStateEnum.Normal: { // 掴んだのが自分の駒であるか if (pc != Piece.NO_PIECE && pc.PieceColor() == pos.sideToMove) { pick_up(sq); // sqの駒を掴んで行き先の候補の升情報を更新する } break; } case GameScreenViewStateEnum.PiecePickedUp: { // 次の4つのケースが考えられる // 1.駒を掴んでいる状態なので移動先のクリック // 2.自駒を再度掴んだ(掴んでいたのをキャンセルする) // 3.別の自分の駒を掴んだ(掴み直し) // 4.無縁の升をクリックした(掴んでいたのをキャンセルする) // 1. 駒の移動 // いま掴んでいる駒の移動できる先であるのか。 var bb = state.picked_piece_legalmovesto; if (!sq.IsDrop() && bb.IsSet((Square)sq)) { state.picked_to = sq; move_piece(state.picked_from, state.picked_to); } // 2. 掴んでいた駒の再クリック else if (sq == state.picked_from) { StateReset(); } // 3. 別の駒のクリック else if (pc != Piece.NO_PIECE && pc.PieceColor() == pos.sideToMove) { pick_up(sq); } // 4. 掴む動作のキャンセル else { StateReset(); } break; } case GameScreenViewStateEnum.PromoteDialog: { // PromoteDialogを出していたのであれば、 switch (state.promote_dialog_selection) { case Model.Resource.PromoteDialogSelectionEnum.NO_SELECT: break; // 無視 case Model.Resource.PromoteDialogSelectionEnum.CANCEL: // キャンセルするので移動の駒の選択可能状態に戻してやる。 StateReset(); break; // 成り・不成を選んでクリックしたのでそれに応じた移動を行う。 case Model.Resource.PromoteDialogSelectionEnum.UNPROMOTE: case Model.Resource.PromoteDialogSelectionEnum.PROMOTE: var m = Util.MakeMove(state.picked_from, state.picked_to, state.promote_dialog_selection == Model.Resource.PromoteDialogSelectionEnum.PROMOTE); ViewModel.ViewModel.gameServer.DoMoveCommand(m); StateReset(); break; } break; } } }
/// <summary> /// sqの駒を掴む /// sqの駒が自駒であることは確定している。 /// 行き先の候補の升情報を更新する。 /// </summary> /// <param name="sq"></param> public void pick_up(SquareHand sq) { var gameServer = ViewModel.ViewModel.gameServer; if (!(gameServer.CanUserMove && !gameServer.EngineInitializing)) { return; } var pos = ViewModel.ViewModel.gameServer.Position; // この駒をユーザーが掴んで動かそうとしていることを示す ViewModel.viewState.picked_from = sq; ViewModel.viewState.picked_to = SquareHand.NB; ViewModel.viewState.state = GameScreenViewStateEnum.PiecePickedUp; // デバッグ用に出力する。 //Console.WriteLine("pick up : " + sq.Pretty() ); // 簡単に利きを表示する。 // ここで連続王手による千日手などを除外すると // 「ユーザーが駒が動かせない、バグだ」をみたいなことを言い出しかねない。 // 移動後に連続王手の千日手を回避していないという警告を出すようにしなくてはならない。 // 合法手を生成して、そこに含まれるものだけにする。 // この生成、局面が変わったときに1回でいいような気はするが.. // 何回もクリックしまくらないはずなのでまあいいや。 int n = MoveGen.LegalAll(pos, moves_buf, 0); var is_drop = sq.IsDrop(); var pt = pos.PieceOn(sq).PieceType(); Bitboard bb = Bitboard.ZeroBB(); // 生成されたすべての合法手に対して移動元の升が合致する指し手の移動先の升を // Bitboardに反映させていく。 for (int i = 0; i < n; ++i) { var m = moves_buf[i]; if (is_drop) { // 駒の打てる先。これは合法手でなければならない。 // 二歩とか打ち歩詰めなどがあるので合法手のみにしておく。 // (打ち歩詰めなので打てませんの警告ダイアログ、用意してないので…) // 合法手には自分の手番の駒しか含まれないのでこれでうまくいくはず if (m.IsDrop() && m.DroppedPiece() == pt) { bb |= m.To(); } } else { // 駒の移動できる先 if (!m.IsDrop() && m.From() == (Square)sq) { bb |= m.To(); } } } ViewModel.viewState.picked_piece_legalmovesto = bb; ViewModel.viewState.state = GameScreenViewStateEnum.PiecePickedUp; // この値が変わったことで画面の状態が変わるので、次回、OnDraw()が呼び出されなくてはならない。 ViewModel.dirty = true; }