/// <summary> /// [UI thread] : 盤面が右クリックされたときに呼び出されるハンドラ /// </summary> /// <param name="p"></param> public void OnRightClick(Point p) { p = InverseAffine(p); SquareHand sq = BoardAxisToSquare(p); OnBoardRightClick(sq); }
/// <summary> /// [UI thread] : 盤面のsqの升(手駒・駒箱の駒も含む)が右クリックされた /// </summary> /// <param name="sq"></param> public void OnBoardRightClick(SquareHand sq) { if (gameServer.InTheBoardEdit) { // -- 盤面編集中 var pos = gameServer.Position; // 盤上の駒はクリックされるごとに先手→先手成駒→後手→後手成駒のように駒の変化 if (sq.IsBoardPiece()) { var pc = pos.PieceOn(sq); var rp = pc.RawPieceType(); // 玉であっても手番変更は出来るのでNO_PIECE以外であれば処理対象。 if (pc != Piece.NO_PIECE) { Piece nextPc; // 成っていない駒なら、成駒に。成っている駒なら相手番の成っていない駒に。 if (pc.CanPromote()) { nextPc = pc.ToPromotePiece(); } else { nextPc = Util.MakePiece(pc.PieceColor().Not() /*相手番の駒に*/, pc.RawPieceType()); } BoardEditCommand((raw_pos) => { raw_pos.board[(int)sq] = nextPc; }); } } //Console.WriteLine(sq.Pretty()); } }
/// <summary> /// 盤面がドラッグされたときに呼び出されるハンドラ /// </summary> /// <param name="p1">ドラッグ開始点</param> /// <param name="p2">ドラッグ終了点</param> public void OnDrag(Point p1, Point p2) { /// 座標系を、affine変換(逆変換)して、盤面座標系(0,0)-(board_img_width,board_image_height)にする。 var p1_t = InverseAffine(p1); var p2_t = InverseAffine(p2); // 盤面(手駒を含む)のどこの升からどこの升にドラッグされたのかを調べる SquareHand sq1 = BoardAxisToSquare(p1_t); SquareHand sq2 = BoardAxisToSquare(p2_t); // 同じ升がクリックされていれば、これはOnClick()として処理してやる。 // 単一クリックが間違えて、ドラッグになってしまった可能性が高い。 // sq1 == SquareHand.NBの場合もそう。 if (sq1 == sq2) { // affine変換前のもの OnClick(p1); return; } else { // 通常の移動であるが、これが駒の移動の条件を満たすことを確認しなければならない。 // p1がクリックされたあとにp2がクリックされたことにしてお茶を濁す OnClick(p1); OnClick(p2); // 簡単なhackだが、これでだいたい意図通りの動作になるようだ。 } // デバッグ用にドラッグされた升の名前を出力する。 //Console.WriteLine(sq1.Pretty() + '→' + sq2.Pretty()); }
public static Move MakeMove(SquareHand from, SquareHand to, bool promote) { if (!to.IsBoardPiece()) { return(Move.NONE); } var to_ = (Square)to; if (from.IsHandPiece()) { Piece pr = from.ToPiece(); return(MakeMoveDrop(pr, to_)); } else if (from.IsBoardPiece()) { var from_ = (Square)from; if (promote) { return(MakeMovePromote(from_, to_)); } else { return(MakeMove(from_, to_)); } } return(Move.NONE); }
/// <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.IsBoardPiece()) { return(Move.NONE); } var to2 = (Square)to; if (from.IsHandPiece()) { // 打ちと成りは共存できない 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の手駒に対して、その駒種を返す /// sqは手駒か駒箱の駒でないといけない。 /// </summary> /// <param name="sq"></param> /// <returns></returns> public static Piece ToPiece(this SquareHand sq) { Debug.Assert(!IsBoardPiece(sq)); return((sq < SquareHand.HandWhite) ? HandExtensions.HandPiece[sq - SquareHand.HandBlack] : HandExtensions.HandPiece[sq - SquareHand.HandWhite]); }
/// <summary> /// sqの手駒に対して、その駒種を返す /// sqは手駒でないといけない。 /// /// 先後の区別はない。Piece.PAWN ~ Piece.GOLDまでの値が返る。 /// </summary> /// <param name="sq"></param> /// <returns></returns> public static Piece ToPiece(this SquareHand sq) { Debug.Assert(PieceColor(sq) != Color.NB); return((sq < SquareHand.HandWhite) ? (Piece)((sq - SquareHand.HandBlack) + Piece.PAWN) : (Piece)((sq - SquareHand.HandWhite) + Piece.PAWN)); }
/// <summary> /// sqに対してどちらのColorの手駒を表現しているのかを返す。 /// 盤上の升ならColor.NBを返す。 /// </summary> /// <param name="sq"></param> /// <returns></returns> public static Color PieceColor(this SquareHand sq) { if (sq < SquareHand.SquareNB) { return(Color.NB); // 盤面 } return((sq < SquareHand.HandWhite) ? Color.BLACK : Color.WHITE); }
public static void CapturePiece(SquareHand sq, Piece pt, PieceNo pn, bool box2dEnable) { var pickedTo = Pieces[(int)pn]; var sr = pickedTo.GetComponent <SpriteRenderer>(); sr.sprite = SpriteManager.GetSprite(pt); sr.flipX = sr.flipY = !sr.flipX; pickedTo.transform.position = PositionConst.SquareToPosition(sq); pickedTo.GetComponent <BoxCollider2D>().enabled = box2dEnable; }
/// <summary> /// Squareを綺麗に出力する(USI形式ではない) /// 日本語文字での表示になる。例 → 8八 , 先手歩 (先手の手駒の歩) /// </summary> /// <param name="sq"></param> /// <returns></returns> public static string Pretty(this SquareHand sq) { var c = PieceColor(sq); if (c == Color.NB) { return(((Square)sq).Pretty()); } return(c.Pretty() + ToPiece(sq).Pretty()); }
/// <summary> /// PickedFromが適切であるか /// </summary> /// <param name="from"></param> /// <returns></returns> public bool IsOkPickedFrom(SquareHand from) { Piece pc = Position.PieceOn(from); if (pc == Piece.NO_PIECE) { return(false); } return(pc.PieceColor() == Position.sideToMove); }
public static void MovePiece(SquareHand sq, Piece pc, PieceNo pn, bool newsprite) { var pickedFrom = Pieces[(int)pn]; if (newsprite) { pickedFrom.GetComponent <SpriteRenderer>().sprite = SpriteManager.GetSprite(pc.Type()); } // 駒をsqに移動 pickedFrom.transform.position = PositionConst.SquareToPosition(sq); }
/// <summary> /// sqの手駒に対して、その駒種を返す /// sqは手駒か駒箱の駒でないといけない。 /// </summary> /// <param name="sq"></param> /// <returns> /// 返し値について先後の区別はない。 /// 手駒に対しては、Piece.PAWN ~ Piece.GOLDまでの値が返る。 /// 駒箱の駒に対しては、Piece.PAWN ~ Piece.KINGまでの値が返る。 /// </returns> public static Piece ToPiece(this SquareHand sq) { Debug.Assert(!IsBoardPiece(sq)); if (IsHandPiece(sq)) { return((sq < SquareHand.HandWhite) ? (Piece)((sq - SquareHand.HandBlack) + Piece.PAWN) : (Piece)((sq - SquareHand.HandWhite) + Piece.PAWN)); } // is BoxPiece return((Piece)((sq - SquareHand.PieceBox) + Piece.PAWN)); }
/// <summary> /// 盤面がクリックされたときに呼び出されるハンドラ /// </summary> /// <param name="p"></param> public void OnClick(Point p) { /// 座標系を、affine変換(逆変換)して、盤面座標系(0,0)-(board_img_width,board_image_height)にする。 p = InverseAffine(p); // 盤面(手駒を含む)のどこの升がクリックされたのかを調べる SquareHand sq = BoardAxisToSquare(p); OnBoardClick(sq); // デバッグ用にクリックされた升の名前を出力する。 //Console.WriteLine(sq.Pretty()); }
void UndoMove(Move move, bool overwrite = true) { --CurrentValue; if (overwrite) { RemoveItem(MoveList.Count - 1, 1); MoveList.RemoveAt(MoveList.Count - 1); } ScreenControl.Interactable(MyColor == SColor.NB, Winner != SColor.NB); if (move.IsSpecial()) { return; } var us = Position.sideToMove.Not(); var st = Position.State(); Debug.Assert(move == st.lastMove); Square to = move.To(); Piece toPc = Position.PieceOn(to); PieceNo pn = Position.PieceNoOn(to); SquareHand from = move.IsDrop() ? Util.MakeSquareHand(us, move.DroppedPiece()) : (SquareHand)move.From(); Piece fromPc = move.IsPromote() ? toPc.RawType() : toPc; PiecePrefabs.MovePiece(from, fromPc, pn, move.IsPromote()); Piece capPc = st.capturedPiece; if (capPc != Piece.NO_PIECE) { // 駒台の駒をtoに移動 Piece pr = capPc.RawType(); PieceNo pn2 = Position.HandPieceNo(us, pr, Position.Hand(us).Count(pr) - 1); Debug.Assert(pn2 != PieceNo.NONE); PiecePrefabs.CapturePiece((SquareHand)to, capPc.Type(), pn2, MyColor == SColor.NB); // テキストを非表示にする // Counters(Util.MakeSquareHand(us, pr)).text = null; } BoardManager.UnDoMove(); }
/// <summary> /// sqの駒を掴む(盤面編集用に) /// </summary> /// <param name="sq"></param> public void pick_up_for_edit(SquareHand sq) { var pos = gameServer.Position; // この駒をユーザーが掴んで動かそうとしていることを示す viewState.picked_from = sq; viewState.picked_to = SquareHand.NB; viewState.state = GameScreenControlViewStateEnum.PiecePickedUp; // 移動先の候補表示は必要ない(自由編集なので) viewState.picked_piece_legalmovesto = Bitboard.ZeroBB(); // この値が変わったことで画面の状態が変わるので、次回、OnDraw()が呼び出されなくてはならない。 Dirty = true; }
/// <summary> /// PickedToが適切であるか /// </summary> /// <param name="from"></param> /// <returns></returns> public bool IsOkPickedTo(SquareHand to) { if (!to.IsBoardPiece()) { return(false); } Piece pc = Position.PieceOn(to); if (pc == Piece.NO_PIECE) { return(true); } return(pc.PieceColor() == Position.sideToMove.Not()); }
/// <summary> /// [UI thread] : 盤面がクリックされたときに呼び出されるハンドラ /// </summary> /// <param name="p"></param> public void OnClick(Point p, bool dragged = false) { var config = TheApp.app.Config; // マウスドラッグが許可されていないなら無視する。 if (dragged && config.EnableMouseDrag == 0) { return; } /// 座標系を、affine変換(逆変換)して、盤面座標系(0,0)-(board_img_width,board_image_height)にする。 p = InverseAffine(p); // 盤面(手駒を含む)のどこの升がクリックされたのかを調べる SquareHand sq = BoardAxisToSquare(p); if (lastClickedSq == sq && dragged) { // クリックするときにマウスが微小に動き、ドラッグ動作になっているだけだと思われるので、 // 操作性の観点から、このクリックはすべて無効化されるべき。 } else { // 移動不可の升に移動させている場合、それはユーザーの誤操作の可能性があるので // このクリックを無視する。 if (dragged && viewState.state == GameScreenControlViewStateEnum.PiecePickedUp && sq < SquareHand.SquareNB && !gameServer.InTheBoardEdit && // 盤面編集中だけは不法な移動を許可 !viewState.picked_piece_legalmovesto.IsSet((Square)sq) ) { return; } OnBoardClick(sq); } // クリックの起点なので升を記録しておく。 if (!dragged) { lastClickedSq = sq; } // デバッグ用にクリックされた升の名前を出力する。 //Console.WriteLine(sq.Pretty()); }
public static Vector3 SquareToPosition(SquareHand sq) { if (sq.IsBoardPiece()) { return(SquareToPosition((Square)sq)); } int index = (int)sq.ToPiece(); if (sq < SquareHand.HandWhite) { return(new Vector3(RookHandX + HAND_X[index], RookHandY, 0)); } else { return(new Vector3(-RookHandX - HAND_X[index], -RookHandY, 0)); } }
/// <summary> /// sqの描画する場所を得る。 /// Config.BoardReverseも反映されている。 /// </summary> /// <param name="sq"></param> /// <returns></returns> private Point PieceLocation(SquareHand sq) { var reverse = TheApp.app.config.BoardReverse; var color = sq.PieceColor(); Point dest; if (color == ShogiCore.Color.NB) { // 盤面の升 Square sq2 = reverse ? ((Square)sq).Inv() : (Square)sq; int f = 8 - (int)sq2.ToFile(); int r = (int)sq2.ToRank(); dest = new Point(board_location.X + piece_img_size.Width * f, board_location.Y + piece_img_size.Height * r); } else { if (reverse) { color = color.Not(); } var v = (TheApp.app.config.KomadaiImageVersion == 1) ? 0 : 1; var pc = sq.ToPiece(); if (color == ShogiCore.Color.BLACK) { // Point型同士の加算は定義されていないので、第二項をSize型にcastしている。 dest = hand_table_pos[v, (int)color] + (Size)hand_piece_pos[v, (int)pc - 1]; } else { // 180度回転させた位置を求める // 後手も駒の枚数は右肩に描画するのでそれに合わせて左右のmarginを調整する。 dest = new Point( hand_table_pos[v, (int)color].X + (hand_table_size[v].Width - hand_piece_pos[v, (int)pc - 1].X - piece_img_size.Width - 10), hand_table_pos[v, (int)color].Y + (hand_table_size[v].Height - hand_piece_pos[v, (int)pc - 1].Y - piece_img_size.Height + 0) ); } } return(dest); }
/// <summary> /// [UI thread] : 盤面のsqの升(手駒・駒箱の駒も含む)が右クリックされた /// </summary> /// <param name="sq"></param> public void OnBoardRightClick(SquareHand sq) { var config = TheApp.app.config; if (config.InTheBoardEdit) { // -- 盤面編集中 var gameServer = ViewModel.ViewModel.gameServer; var pos = gameServer.Position; // 盤上の駒はクリックされるごとに先手→先手成駒→後手→後手成駒のように駒の変化 if (sq.IsBoardPiece()) { var pc = pos.PieceOn(sq); var rp = pc.RawPieceType(); // 玉は裏返らないし、敵玉にもならない。(それが出来てしまうと先手玉が2枚の局面が作れてしまうため) if (pc != Piece.NO_PIECE && rp != Piece.KING) { var RawBoard = new Piece[pos.RawBoard.Length]; pos.RawBoard.CopyTo(RawBoard, 0); Piece nextPc; // 成っていない駒なら、成駒に。成っている駒なら相手番の成っていない駒に。 if (pc.CanPromote()) { nextPc = pc.ToPromotePiece(); } else { nextPc = Util.MakePiece(pc.PieceColor().Not() /*相手番の駒に*/, pc.RawPieceType()); } RawBoard[(int)sq] = nextPc; var sfen = Position.SfenFromRawdata(RawBoard, pos.RawHands, pos.sideToMove, pos.gamePly); gameServer.SetSfenCommand(sfen); } } //Console.WriteLine(sq.Pretty()); } }
/// <summary> /// picked_fromとpicked_toをリセットして、 /// stateをNormalに戻す。 /// picked_piece_legalmovestoをZeroBB()にする。 /// (これが初期状態) /// </summary> public void Reset() { state = GameScreenViewStateEnum.Normal; picked_from = picked_to = SquareHand.NB; picked_piece_legalmovesto = Bitboard.ZeroBB(); }
/// <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> /// 駒の移動 /// ただし成り・不成が選べるときはここでそれを尋ねるダイアログが出る。 /// また、連続王手の千日手局面に突入するときもダイアログが出る。 /// </summary> /// <param name="from"></param> /// <param name="to"></param> public void move_piece(SquareHand from, SquareHand to) { var state = ViewModel.viewState; // デバッグ用に表示する。 //Console.WriteLine(from.Pretty() + "→" + to.Pretty()); // この成る手を生成して、それが合法手であるなら、成り・不成のダイアログを出す必要がある。 // また、1段目に進む「歩」などは、不成はまずいので選択がいらない。 // Promoteの判定 var pos = ViewModel.ViewModel.gameServer.Position; var pro_move = Util.MakeMove(from, to, true); // 成りの指し手が合法であるかどうか var canPromote = pos.IsLegal(pro_move); // 不成の指し手が合法であるかどうか var unpro_move = Util.MakeMove(from, to, false); var canUnpro = pos.IsLegal(unpro_move); // canUnproとcanPromoteの組み合わせは4通り。 // 1. (false, false) // 2. (false, true) // 3. (true , false) // 4. (true , true) // 上記 1.と3. if (!canPromote) { // 成れないので成る選択肢は消して良い。 ViewModel.ViewModel.gameServer.DoMoveCommand(unpro_move); StateReset(); } // 上記 2. else if (!canUnpro && canPromote) { // 成るしか出来ないので、不成は選択肢から消して良い。 // 成れないので成る選択肢は消して良い。 ViewModel.ViewModel.gameServer.DoMoveCommand(pro_move); StateReset(); } // 上記 4. // これで、上記の1.~4.のすべての状態を網羅したことになる。 else // if (canPromote && canUnPro) { state.state = GameScreenViewStateEnum.PromoteDialog; state.moved_piece_type = pos.PieceOn(from).PieceType(); // この状態を初期状態にするのは少しおかしいが、どうせこのあとマウスを動かすであろうからいいや。 state.promote_dialog_selection = Model.Resource.PromoteDialogSelectionEnum.NO_SELECT; // toの近くに成り・不成のダイアログを描画してやる。 // これは移動先の升の少し下に出す。 // ただし、1段目であると画面外に行ってしまうので // 1,2段目であれば上に変位させる必要がある。 // あと、棋譜ウィンドウもこの手前に描画できないので // ここも避ける必要がある。 var dest = PieceLocation(to); if (dest.Y >= board_img_size.Height * 0.8) // 画面の下らへんである.. { dest += new Size(-130 + 95 / 2, -200); } else { dest += new Size(-103 + 95 / 2, +100); } if (dest.X < board_location.X) { dest += new Size(150, 0); } state.promote_dialog_location = dest; // toの升以外は暗くする。 state.picked_piece_legalmovesto = new Bitboard((Square)to); ViewModel.dirty = true; } }
/// <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; }
/// <summary> /// 駒の移動 盤面編集 /// </summary> /// <param name="from"></param> /// <param name="to"></param> public void move_piece_for_edit(SquareHand from, SquareHand to) { var pos = gameServer.Position; // 何にせよ、移動、もしくは交換をする。 // → 交換をする実装、不評なので、将棋所・ShogiGUIに倣い、手駒に移動させる。 var from_pc = pos.PieceOn(from); var from_pt = from_pc.PieceType(); var from_pr = from_pc.RawPieceType(); var to_pc = pos.PieceOn(to); var to_pr = to_pc.RawPieceType(); // 移動元と移動先が同じなら何もする必要はない。 if (from == to) { // このケースを除外しておかないと、toの駒を手駒に移動させる処理などで // from == toだと手駒が増えることになる。 // しかし、駒を掴んでいる状態が変化するのでこのときだけ再描画は必要。 Dirty = true; } else if (to.IsBoardPiece()) { if (from.IsBoardPiece()) { // -- 盤上と盤上の駒 #if false // 2駒交換する実装 → 不評なので手駒に移動させることに変更。 // fromとtoの升の駒を交換する。 // fromに駒があることは確定している。toに駒があろうがなかろうが良い。 BoardEditCommand(raw => { raw.board[(int)from] = to_pc; raw.board[(int)to] = from_pc; }); #endif #if true // toのほうが手駒に移動する実装 BoardEditCommand(raw => { raw.board[(int)from] = Piece.NO_PIECE; raw.board[(int)to] = from_pc; if (to_pr != Piece.NO_PIECE && to_pr != Piece.KING) { // 移動元の駒のcolorの手駒に移動させる。玉は、(欠落するので)勝手に駒箱に移動する。 raw.hands[(int)from_pc.PieceColor()].Add(to_pr); } }); #endif } else if (from.IsHandPiece()) { // -- 手駒を盤面に BoardEditCommand(raw => { #if false // 駒箱に移動する実装 → 不評なので手駒になるように変更 raw.hands[(int)from.PieceColor()].Sub(from_pt); raw.board[(int)to] = from_pc; // このtoの位置にもし駒があったとしたら、それは駒箱に移動する。 // (その駒が欠落するので..) #endif #if true // toのほうが手駒に移動する実装 raw.hands[(int)from.PieceColor()].Sub(from_pt); raw.board[(int)to] = from_pc; if (to_pr != Piece.NO_PIECE && to_pr != Piece.KING) { // 移動元の駒のcolorの手駒に移動させる。玉は、(欠落するので)勝手に駒箱に移動する。 raw.hands[(int)from.PieceColor()].Add(to_pr); } #endif }); } else if (from.IsPieceBoxPiece()) { // -- 駒箱の駒を盤面に // toにあった駒は駒箱に戻ってくるが、これは仕方がない。 BoardEditCommand(raw => raw.board[(int)to] = from_pc); } } else if (to.IsHandPiece()) { if (from.IsBoardPiece()) { // -- 盤上の駒を手駒に移動させる。 // 手駒に出来る駒種でなければキャンセル if (from_pt == Piece.KING) { return; } BoardEditCommand(raw => { raw.board[(int)from] = Piece.NO_PIECE; raw.hands[(int)to.PieceColor()].Add(from_pr); }); } else if (from.IsHandPiece()) { // -- 手駒を手駒に。手番が違うならこれは合法。 if (from.PieceColor() != to.PieceColor()) { BoardEditCommand(raw => { // 同種の駒が駒台から駒台に移動するので、to_prは関係ない。 raw.hands[(int)from.PieceColor()].Sub(from_pr); raw.hands[(int)to.PieceColor()].Add(from_pr); }); } else { // 手駒を同じ駒台に移動させることは出来ないし、 // 選び直しているのでは? if (to_pr != Piece.NO_PIECE) { pick_up_for_edit(to); return; } } } else if (from.IsPieceBoxPiece()) { // -- 駒箱の駒を手駒に // 玉は移動手駒に出来ない if (from_pt != Piece.KING) { BoardEditCommand(raw => raw.hands[(int)to.PieceColor()].Add(from_pr)); } } } else if (to.IsPieceBoxPiece()) { if (from.IsBoardPiece()) { // -- 盤上の駒を駒箱に BoardEditCommand(raw => raw.board[(int)from] = Piece.NO_PIECE); } else if (from.IsHandPiece()) { // -- 駒台の駒を駒箱に BoardEditCommand(raw => raw.hands[(int)from.PieceColor()].Sub(from_pr)); } else if (from.IsPieceBoxPiece()) { // 駒箱の駒を移動させることは出来ないし、 // 選び直しているのでは? if (to_pr != Piece.NO_PIECE) { pick_up_for_edit(to); return; } } } }
/// <summary> /// sqの描画する場所を得る。 /// reverse : 盤面を180度回転するのかのフラグ /// </summary> /// <param name="sq"></param> /// <param name="reverse"></param> /// <returns></returns> private Point PieceLocation(SquareHand sq, bool reverse) { Point dest; if (sq.IsBoardPiece()) { // -- 盤上の升 Square sq2 = reverse ? ((Square)sq).Inv() : (Square)sq; int f = 8 - (int)sq2.ToFile(); int r = (int)sq2.ToRank(); dest = new Point(board_location.X + piece_img_size.Width * f, board_location.Y + piece_img_size.Height * r); } else if (sq.IsHandPiece()) { // -- 手駒 var color = sq.PieceColor(); if (reverse) { color = color.Not(); } var v = PieceTableVersion; var pc = sq.ToPiece(); if (color == ShogiCore.Color.BLACK) { // Point型同士の加算は定義されていないので、第二項をSize型にcastしている。 dest = hand_table_pos[v, (int)color] + (Size)hand_piece_pos[v, (int)pc - 1]; } else { // 180度回転させた位置を求める // 後手も駒の枚数は右肩に描画するのでそれに合わせて左右のmarginを調整する。 dest = new Point( hand_table_pos[v, (int)color].X + (hand_table_size[v].Width - hand_piece_pos[v, (int)pc - 1].X - piece_img_size.Width - 10), hand_table_pos[v, (int)color].Y + (hand_table_size[v].Height - hand_piece_pos[v, (int)pc - 1].Y - piece_img_size.Height + 0) ); } } else { // -- 駒箱の駒 // 並び替える // pc : 歩=0,香=1,桂=2,銀=3,金=4,角=5,飛=6,玉=7にする。 var pc = (int)sq.ToPiece() - 1; if (pc == 6) { pc = 4; } else if (pc == 4 || pc == 5) { ++pc; } if (PieceTableVersion == 0) { // 駒箱の1段目に3枚、2段目に2枚、3段目に3枚表示する。 // 5を欠番にして2段目を2枚にする。 if (pc >= 5) { ++pc; } int file = pc % 3; int rank = pc / 3; int x = (int)(file * piece_img_size.Width * .8); int y = (int)(rank * piece_img_size.Height * .88); if (rank == 1) { x += (int)(piece_img_size.Width / 2 * 0.8); } dest = new Point( hand_box_pos[0].X + x, hand_box_pos[0].Y + y ); } else { int file = pc % 2; int rank = pc / 2; int x = (int)(file * piece_img_size.Width * .5); int y = (int)(rank * piece_img_size.Height * .65); dest = new Point( hand_box_pos[1].X + x, hand_box_pos[1].Y + y ); } } return(dest); }
/// <summary> /// 駒の移動 盤面編集 /// </summary> /// <param name="from"></param> /// <param name="to"></param> public void move_piece_for_edit(SquareHand from, SquareHand to) { var pos = gameServer.Position; // 何にせよ、移動、もしくは交換をする。 var from_pc = pos.PieceOn(from); var from_pt = from_pc.PieceType(); var from_pr = from_pc.RawPieceType(); var to_pc = pos.PieceOn(to); var to_pr = to_pc.RawPieceType(); if (to.IsBoardPiece()) { if (from.IsBoardPiece()) { // -- 盤上と盤上の駒 // fromとtoの升の駒を交換する。 // fromに駒があることは確定している。toに駒があろうがなかろうが良い。 BoardEditCommand(raw => { raw.board[(int)from] = to_pc; raw.board[(int)to] = from_pc; }); } else if (from.IsHandPiece()) { // -- 手駒を盤面に BoardEditCommand(raw => { raw.hands[(int)from.PieceColor()].Sub(from_pt); raw.board[(int)to] = from_pc; // このtoの位置にもし駒があったとしたら、それは駒箱に移動する。 // (その駒が欠落するので..) }); } else if (from.IsPieceBoxPiece()) { // -- 駒箱の駒を盤面に BoardEditCommand(raw => raw.board[(int)to] = from_pc); } } else if (to.IsHandPiece()) { if (from.IsBoardPiece()) { // -- 盤上の駒を手駒に移動させる。 // 手駒に出来る駒種でなければキャンセル if (from_pt == Piece.KING) { return; } BoardEditCommand(raw => { raw.board[(int)from] = Piece.NO_PIECE; raw.hands[(int)to.PieceColor()].Add(from_pr); }); } else if (from.IsHandPiece()) { // -- 手駒を手駒に。手番が違うならこれは合法。 if (from.PieceColor() != to.PieceColor()) { BoardEditCommand(raw => { // 同種の駒が駒台から駒台に移動するので、to_prは関係ない。 raw.hands[(int)from.PieceColor()].Sub(from_pr); raw.hands[(int)to.PieceColor()].Add(from_pr); }); } else { // 手駒を同じ駒台に移動させることは出来ないし、 // 選び直しているのでは? if (to_pr != Piece.NO_PIECE) { pick_up_for_edit(to); return; } } } else if (from.IsPieceBoxPiece()) { // -- 駒箱の駒を手駒に // 玉は移動手駒に出来ない if (from_pt != Piece.KING) { BoardEditCommand(raw => raw.hands[(int)to.PieceColor()].Add(from_pr)); } } } else if (to.IsPieceBoxPiece()) { if (from.IsBoardPiece()) { // -- 盤上の駒を駒箱に BoardEditCommand(raw => raw.board[(int)from] = Piece.NO_PIECE); } else if (from.IsHandPiece()) { // -- 駒台の駒を駒箱に BoardEditCommand(raw => raw.hands[(int)from.PieceColor()].Sub(from_pr)); } else if (from.IsPieceBoxPiece()) { // 駒箱の駒を移動させることは出来ないし、 // 選び直しているのでは? if (to_pr != Piece.NO_PIECE) { pick_up_for_edit(to); return; } } } StateReset(); }
/// <summary> /// 駒の移動 /// ただし成り・不成が選べるときはここでそれを尋ねるダイアログが出る。 /// また、連続王手の千日手局面に突入するときもダイアログが出る。 /// </summary> /// <param name="from"></param> /// <param name="to"></param> public void move_piece(SquareHand from, SquareHand to) { var state = viewState; var reverse = gameServer.BoardReverse; // デバッグ用に表示する。 //Console.WriteLine(from.Pretty() + "→" + to.Pretty()); // この成る手を生成して、それが合法手であるなら、成り・不成のダイアログを出す必要がある。 // また、1段目に進む「歩」などは、不成はまずいので選択がいらない。 // Promoteの判定 var pos = gameServer.Position; var pro_move = Util.MakeMove(from, to, true); // 成りの指し手が合法であるかどうか var canPromote = pos.IsLegal(pro_move); // 不成の指し手が合法であるかどうか var unpro_move = Util.MakeMove(from, to, false); var canUnpro = pos.IsLegal(unpro_move); // canUnproとcanPromoteの組み合わせは4通り。 // 1. (false, false) // 2. (false, true) // 3. (true , false) // 4. (true , true) // 上記 1.と3. if (!canPromote) { surpressDraw = true; StateReset(); // 成れないので成る選択肢は消して良い。 DoMoveCommand(unpro_move); } // 上記 2. else if (!canUnpro && canPromote) { surpressDraw = true; StateReset(); // 成るしか出来ないので、不成は選択肢から消して良い。 // 成れないので成る選択肢は消して良い。 DoMoveCommand(pro_move); } // 上記 4. // これで、上記の1.~4.のすべての状態を網羅したことになる。 else // if (canPromote && canUnPro) { // 成り・不成の選択ダイアログを出す state.state = GameScreenControlViewStateEnum.PromoteDialog; state.moved_piece_type = pos.PieceOn(from).PieceType(); state.moved_piece_color = pos.PieceOn(from).PieceColor(); // この状態を初期状態にするのは少しおかしいが、どうせこのあとマウスを動かすであろうからいいや。 state.promote_dialog_selection = PromoteDialogSelectionEnum.NO_SELECT; // toの升以外は暗くする。 state.picked_piece_legalmovesto = new Bitboard((Square)to); Dirty = true; } }
Paragraph Draw(Position pos, KifuHeader kifuHeader) { Color sideToView = Color.BLACK; // どちらの手番から見た盤面を出力するか int gamePly = pos.gamePly; // 手数が1以外なら直前の指し手を出力する const int boardTopMargin = 50; const int boardLeftMargin = 40; const int boardRightMargin = 40; // const int boardBottomMargin = 50; const int boardBorder = 30; // 座標の表示領域 const int blockSize = 60; // マス目 // const int boardSize = blockSize * 9 + boardBorder * 2; const int starSize = 5; const int pieceSize = 50; const int fontSize = 30; const int boardLeft = boardLeftMargin + boardBorder; const int boardRight = boardLeftMargin + boardBorder + blockSize * 9; const int boardTop = boardTopMargin + boardBorder; // const int boardBottom = boardTopMargin + boardBorder + blockSize * 9; string s(int i) => i.ToString(); // 直線の描写 Paragraph pathLineH(int x, int y, int h, string color = "#000000") => new Paragraph($"<path d=\"M {s(x)},{s(y)} h {s(h)}\" stroke=\"{color}\" />"); Paragraph pathLineV(int x, int y, int v, string color = "#000000") => new Paragraph($"<path d=\"M {s(x)},{s(y)} v {s(v)}\" stroke=\"{color}\" />"); // 円の描写 string circle(int cx, int cy, int r, string fill) => $"<circle cx=\"{s(cx)}\" cy=\"{s(cy)}\" r=\"{s(r)}\" fill=\"{fill}\" />"; Paragraph drawCircle(int cx, int cy, int r, string fill = "#000000") => new Paragraph(circle(cx, cy, r, fill)); // 四角形を描きます Paragraph drawSquare(int x, int y, int size, int storockWidth = 1, string color = "#000000") { var d = $"d=\"M {s(x)},{s(y)} h {s(size)} v {s(size)} h {s(-size)} z\""; var option = color == "#000000" ? "fill=\"none\" " : $"fill=\"{color}\" "; option += $"stroke-width=\"{storockWidth}\" stroke=\"#000000\""; return(new Paragraph($"<path {d} {option} />")); } // 駒形の五角形を描きます // x, yは図形の上端中央を表す Paragraph drawPentagon(int x, int y, bool isBlack = true, string color = "#000000") { var f = fontSize; int x1 = x; var y1 = y; int x2 = x + (int)(f * 0.38); var y2 = y + (int)(f * 0.2); int x3 = x + (int)(f * 0.45); var y3 = y + f; int x4 = x - (int)(f * 0.45); var y4 = y + f; int x5 = x - (int)(f * 0.38); var y5 = y + (int)(f * 0.2); var d = $"d=\"M {s(x1)},{s(y1)} L {x2},{y2} {x3},{y3} {x4},{y4} {x5},{y5} z\""; var fill = isBlack ? "" : "fill=\"none\""; var option = $"{fill} stroke-width=\"1\" stroke=\"{color}\""; return(new Paragraph($"<path {d} {option} />")); } // 文字の描写 #if NeglectEdge string text(int x, int y, int size, string t, string fill) => $"<text x=\"{s(x)}\" y=\"{s(y)}\" dominant-baseline=\"central\">{t}</text>"; string textEx(int x, int y, string t, int size) => $"<text x=\"{s(x)}\" y=\"{s(y)}\" font-size=\"{s(size)}\" dominant-baseline=\"central\">{t}</text>"; #else string text(int x, int y, string t) => $"<text x=\"{s(x)}\" y=\"{s(y)}\">{t}</text>"; string textEx(int x, int y, string t, int size) => $"<text x=\"{s(x)}\" y=\"{s(y)}\" font-size=\"{s(size)}\">{t}</text>"; #endif Paragraph drawText(int x, int y, string t) => new Paragraph(text(x, y, t)); Paragraph drawTextEx(int x, int y, string t, int size) => new Paragraph(textEx(x, y, t, size)); // 直前の指し手情報の描写 Paragraph drawState() { var move = pos.State().lastMove; var moveStr = ""; if (move != Move.NONE && move != Move.NULL && !move.IsSpecial()) { var lastPos = pos.Clone(); lastPos.UndoMove(); moveStr = lastPos.ToKi2(move); } Paragraph tempP = new Paragraph(); if (gamePly == 1) { } else if (move != Move.NONE && move != Move.NULL && !move.IsSpecial()) { var x = boardLeft + blockSize * 9 / 2; var y = fontSize + 10; var str1 = $"【図は{s(gamePly - 1)}手目 "; var str2 = $"{moveStr} まで】"; var str = $"{str1}{str2}"; tempP.Concat(textEx(x, y, str, fontSize)); // 駒文字はフォントで出した方が簡単だけど…… Color prevSide = pos.sideToMove == Color.BLACK ? Color.WHITE : Color.BLACK; var lenStr = GetShiftJISLength(str); var lenStr2 = GetShiftJISLength(str2); var px = x + (lenStr * fontSize / 4) - (lenStr2 * fontSize / 2) - fontSize / 2; var py = y - fontSize; tempP.Concat(drawPentagon(px, py, prevSide == Color.BLACK)); } else { var x = boardLeft + blockSize * 9 / 2; var y = fontSize + 10; var str = $"【図は{s(gamePly - 1)}手目まで】"; tempP.Concat(textEx(x, y, str, fontSize)); } tempP.InsertTag("g", "class=\"info\""); return(tempP); } // 駒の描写 Paragraph drawPiece(int file, int rank, string t, bool isReverse = false, bool isBold = false) { int baseX = boardRight + blockSize / 2; int baseY = boardTop - blockSize / 2; int offsetX = baseX - file * blockSize; int offsetY = baseY + rank * blockSize; int correct; // 駒はマス目の中央よりやや下に置く補正 #if NeglectEdge correct = (int)(pieceSize * 0.1); #else correct = (int)(pieceSize * 0.4); // IEでは縦のセンタリングが効かないのを無理矢理補正 #endif var tempP = drawText(offsetX, offsetY + correct, t); if (isReverse) { tempP.InsertTag("g", $"transform=\"rotate(180,{offsetX},{offsetY})\""); } if (isBold) { tempP.InsertTag("g", "class=\"pieceb\""); } return(tempP); } // 名前、持駒領域の描写 // とりあえず正位置でレンダリングして盤の中央で180回転させれば逆位置に行く // IE11/Edgeでは縦書き文字の位置が半文字分ほどずれる Paragraph drawHand(string hand, string name, bool isBlack, bool isReverse) { var x = boardRight + boardRightMargin; var y = boardTop + boardTopMargin + fontSize / 2; var offsetX = boardLeft + blockSize * 9 / 2; var offsetY = boardTop + blockSize * 9 / 2; var tempP = drawPentagon(x, y - fontSize * 5 / 4, isBlack); // 縦書きで複数文字レンダリングするとブラウザによって挙動が異なるが我慢する var handStr = "持駒"; var totalLength = (GetShiftJISLength(name) + GetShiftJISLength(handStr) + GetShiftJISLength(hand)) / 2; var renderSize = 0; // 文字数に応じて適当にフォントサイズを変更する if (totalLength <= 11) { renderSize = fontSize; } else if (totalLength <= 13) { renderSize = (int)(fontSize * 0.9); } else if (totalLength <= 15) { renderSize = (int)(fontSize * 0.8); } else { renderSize = (int)(fontSize * 0.75); } if (totalLength <= 17) { tempP.Concat(drawTextEx(x, y, $"{name} {handStr} {hand}", renderSize)); } else if (totalLength <= 19) { tempP.Concat(drawTextEx(x, y, $"{name} {handStr} {hand}", renderSize)); } else // 2段組みにする { tempP.Concat(drawTextEx(x + renderSize / 2, y, $"{name}", renderSize)); tempP.Concat(drawTextEx(x - renderSize / 2, y, $"{handStr} {hand}", renderSize)); } if (isReverse) { tempP.InsertTag("g", $"transform=\"rotate(180,{offsetX},{offsetY})\""); } return(tempP); } // 将棋盤の描写 // TODO: rank, fileの数字を描写する? Paragraph drawBoard() { var tempP = drawSquare(boardLeft, boardTop, blockSize * 9, 4); for (int i = 1; i < 9; ++i) { tempP.Concat(pathLineH(boardLeft, boardTop + blockSize * i, blockSize * 9)); tempP.Concat(pathLineV(boardLeft + blockSize * i, boardTop, blockSize * 9)); } tempP.Concat(drawCircle(boardLeft + blockSize * 3, boardTop + blockSize * 3, starSize)); tempP.Concat(drawCircle(boardLeft + blockSize * 3, boardTop + blockSize * 6, starSize)); tempP.Concat(drawCircle(boardLeft + blockSize * 6, boardTop + blockSize * 3, starSize)); tempP.Concat(drawCircle(boardLeft + blockSize * 6, boardTop + blockSize * 6, starSize)); return(tempP); } // 手駒の描写 Paragraph drawHands() { Hand b = pos.Hand(Color.BLACK); Hand w = pos.Hand(Color.WHITE); var tempP = drawHand(b == Hand.ZERO ? "なし" : b.Pretty2(), kifuHeader.PlayerNameBlack, true, sideToView == Color.WHITE); tempP.Concat(drawHand(w == Hand.ZERO ? "なし" : w.Pretty2(), kifuHeader.PlayerNameWhite, false, sideToView == Color.BLACK)); tempP.InsertTag("g", "class=\"hand\""); return(tempP); } // 盤上の駒の描写 Paragraph drawBoardPiece() { var tempP = new Paragraph(); var lastMove = pos.State().lastMove; for (SquareHand sqh = SquareHand.SquareZero; sqh < SquareHand.SquareNB; ++sqh) { var pi = pos.PieceOn(sqh); if (pi != Piece.NO_PIECE) { var sq = (Square)sqh; var file = sideToView == Color.BLACK ? (int)sq.ToFile() + 1 : 9 - (int)sq.ToFile(); var rank = sideToView == Color.BLACK ? (int)sq.ToRank() + 1 : 9 - (int)sq.ToRank(); char[] piChar = { pi.Pretty2() }; var piStr = new string(piChar); var isReverse = pi.PieceColor() == Color.WHITE; if (sideToView == Color.WHITE) { isReverse = !isReverse; } // 直前の指し手を強調する bool isBold = gamePly != 1 && lastMove != Move.NONE && !lastMove.IsSpecial() && sq == pos.State().lastMove.To(); if (isBold) { tempP.Concat(drawSquare(boardRight - file * blockSize, boardTop + (rank - 1) * blockSize, blockSize, 1, "#ffff80")); } tempP.Concat(drawPiece(file, rank, piStr, isReverse, isBold)); } } tempP.InsertTag("g", "class=\"piece\""); return(tempP); } var p = new Paragraph(); p.Concat(MakeStyle()); p.Concat(drawState()); p.Concat(drawHands()); p.Concat(drawBoardPiece()); p.Concat(drawBoard()); return(p); }