cetkaik_naive_representation 1.3.0

A naïve representation of cetkaik
Documentation
use cetkaik_fundamental::{Color, Profession};
use cetkaik_traits::{IsBoard, IsField, IsPieceWithSide};

/// Describes which player it is
/// /どちら側のプレイヤーであるかを指定する。
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Side {
    /// The player whose pieces point upward in your perspective, i.e. yours.
    /// /君の視点で駒が上を向いている駒、つまり、君の駒。
    Upward,

    /// The player whose pieces point downward in your perspective, i.e. the opponent's.
    /// /君の視点で駒が下を向いている駒、つまり、相手の駒。
    Downward,
}

impl std::ops::Not for Side {
    type Output = Side;

    fn not(self) -> Self::Output {
        match self {
            Side::Upward => Side::Downward,
            Side::Downward => Side::Upward,
        }
    }
}

/// Describes a piece that is not a Tam2 and points downward (i.e. opponents).
/// /駒のうち、皇ではなくて、下向き(つまり相手陣営)のものを表す。
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct NonTam2PieceDownward {
    /// color of the piece/駒の色
    pub color: Color,
    /// profession of the piece/駒の職種
    pub prof: Profession,
}

/// Describes a piece that is not a Tam2 and points upward (i.e. yours).
/// /駒のうち、皇ではなくて、上向き(つまり自分陣営)のものを表す。
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct NonTam2PieceUpward {
    /// color of the piece/駒の色
    pub color: Color,
    /// profession of the piece/駒の職種
    pub prof: Profession,
}

impl From<NonTam2PieceUpward> for Piece {
    fn from(from: NonTam2PieceUpward) -> Piece {
        Piece::NonTam2Piece {
            color: from.color,
            prof: from.prof,
            side: Side::Upward,
        }
    }
}

impl From<NonTam2PieceDownward> for Piece {
    fn from(from: NonTam2PieceDownward) -> Piece {
        Piece::NonTam2Piece {
            color: from.color,
            prof: from.prof,
            side: Side::Downward,
        }
    }
}

/// Describes a piece on the board.
/// /盤上に存在できる駒を表現する。
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Piece {
    /// Tam2, a special piece belonging to both sides. Both players can move it.
    /// /皇(たむ)。自分も相手も動かすことができる共有の駒である。
    Tam2,

    /// All the other usual pieces that belong to a single side.
    /// /残りの全ての普通の駒。片方の陣営にのみ属する。
    NonTam2Piece {
        /// color of the piece/駒の色
        color: Color,
        /// profession of the piece/駒の職種
        prof: Profession,

        /// which side the piece belongs to
        /// /駒の所属側。どちらの陣営に属しているのかを表す。
        side: Side,
    },
}

impl Piece {
    /// Checks whether the piece is a Tam2.
    /// /皇であるかどうかの判定
    #[must_use]
    pub const fn is_tam2(self) -> bool {
        match self {
            Piece::Tam2 => true,
            Piece::NonTam2Piece { .. } => false,
        }
    }

    /// Checks whether the piece has a specific color. Tam2 has neither color.
    /// /駒が特定の色であるかを調べる。皇は赤でも黒でもない。
    #[must_use]
    pub fn has_color(self, clr: Color) -> bool {
        match self {
            Piece::Tam2 => false,
            Piece::NonTam2Piece { color, .. } => color == clr,
        }
    }

    /// Checks whether the piece has a specific profession.
    /// /駒が特定の職種であるかを調べる。
    #[must_use]
    pub fn has_prof(self, prf: Profession) -> bool {
        match self {
            Piece::Tam2 => false,
            Piece::NonTam2Piece { prof, .. } => prof == prf,
        }
    }

    /// Checks whether the piece belongs to a specific side. Tam2 belongs to neither side.
    /// /駒が特定の側のプレイヤーに属するかどうかを調べる。皇はどちらの陣営にも属さない。
    #[must_use]
    pub fn has_side(self, sid: Side) -> bool {
        match self {
            Piece::Tam2 => false,
            Piece::NonTam2Piece { side, .. } => side == sid,
        }
    }
}

#[must_use]
fn rotate_piece_or_null(p: Option<Piece>) -> Option<Piece> {
    let p = p?;
    match p {
        Piece::Tam2 => Some(p),
        Piece::NonTam2Piece { prof, color, side } => Some(Piece::NonTam2Piece {
            prof,
            color,
            side: !side,
        }),
    }
}

/// Denotes the position of a square by [row, col].
/// /マス目の相対座標を [row, col] で表す。
///
pub type Coord = [usize; 2];

/// Serializes [`Coord`](./type.Coord.html) in JSON-style.
/// /[`Coord`](./type.Coord.html) を JSON スタイルで文字列にする。
/// # Examples
/// ```
/// use cetkaik_naive_representation::relative::*;
///
/// assert_eq!(serialize_coord([5,6]), "[5,6]")
/// ```
#[must_use]
pub fn serialize_coord(coord: Coord) -> String {
    format!("[{},{}]", coord[0], coord[1])
}

/// Rotates the coordinate with the center of the board as the center of rotation.
/// /盤の中心を基準に、座標を180度回転させる。
#[must_use]
pub const fn rotate_coord(c: Coord) -> Coord {
    [(8 - c[0]), (8 - c[1])]
}

/// Checks if the square is a tam2 nua2 (tam2's water), entry to which is restricted.
/// /マスが皇水(たむぬあ)であるかどうかの判定
#[must_use]
#[allow(clippy::nonminimal_bool)]
pub const fn is_water([row, col]: Coord) -> bool {
    (row == 4 && col == 2)
        || (row == 4 && col == 3)
        || (row == 4 && col == 4)
        || (row == 4 && col == 5)
        || (row == 4 && col == 6)
        || (row == 2 && col == 4)
        || (row == 3 && col == 4)
        || (row == 5 && col == 4)
        || (row == 6 && col == 4)
}

const fn serialize_side(side: Side) -> &'static str {
    match side {
        Side::Upward => "",
        Side::Downward => "",
    }
}

/// Serializes [`Piece`](./enum.Piece.html).
/// /[`Piece`](./enum.Piece.html) を文字列にする。
/// # Examples
/// ```
/// use cetkaik_fundamental::*;
/// use cetkaik_naive_representation::relative::*;
///
/// assert_eq!(serialize_piece(Piece::Tam2), "皇");
/// assert_eq!(serialize_piece(Piece::NonTam2Piece {
///     prof: Profession::Uai1,
///     color: Color::Kok1,
///     side: Side::Downward
/// }), "赤将↓");
/// ```
#[must_use]
pub fn serialize_piece(p: Piece) -> String {
    match p {
        Piece::Tam2 => "".to_string(),
        Piece::NonTam2Piece { prof, color, side } => format!(
            "{}{}{}",
            cetkaik_fundamental::serialize_color(color),
            cetkaik_fundamental::serialize_prof(prof),
            serialize_side(side)
        ),
    }
}

/// Describes the board, the 9x9 squares, in terms of relative coordinates.
/// /盤、つまり、9x9のマス目を、相対座標で表す。
#[derive(Clone, Debug, Eq, PartialEq, Hash, Copy)]
pub struct Board(pub [SingleRow; 9]);

/// Describes a single row made up of 9 squares.
/// /横一列の9マス、を表す。
pub type SingleRow = [Option<Piece>; 9];

/// Describes the field, which is defined as a board plus each side's hop1zuo1.
/// /フィールドを表す。フィールドとは、盤に両者の手駒を加えたものである。
#[derive(Debug, Clone, Hash)]
pub struct Field {
    /// board/盤
    pub current_board: Board,

    /// hop1zuo1 for the Upward (i.e. you)/Upward側(あなた)の手駒
    pub hop1zuo1of_upward: Vec<NonTam2PieceUpward>,

    /// hop1zuo1 for the Downward (i.e. opponent)/Downward側(相手)の手駒
    pub hop1zuo1of_downward: Vec<NonTam2PieceDownward>,
}

/// Returns the initial configuration as specified in the y1 huap1 (the standardized rule).
/// The red king points upward (i.e. you)
/// /官定で定められた初期配置を与える。赤王が自分側にある。
#[must_use]
pub fn yhuap_initial_board_where_red_king_points_upward() -> Board {
    rotate_board(&yhuap_initial_board_where_black_king_points_upward())
}

/// Returns the initial configuration as specified in the y1 huap1 (the standardized rule).
/// The black king points upward (i.e. you)
/// /官定で定められた初期配置を与える。黒王が自分側にある。
#[must_use]
#[allow(clippy::too_many_lines)]
pub const fn yhuap_initial_board_where_black_king_points_upward() -> Board {
    Board([
        [
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kua2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Maun1,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kaun1,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Uai1,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Io,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Uai1,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kaun1,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Maun1,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kua2,
                side: Side::Downward,
            }),
        ],
        [
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Tuk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Gua2,
                side: Side::Downward,
            }),
            None,
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Dau2,
                side: Side::Downward,
            }),
            None,
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Dau2,
                side: Side::Downward,
            }),
            None,
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Gua2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Tuk2,
                side: Side::Downward,
            }),
        ],
        [
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Nuak1,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Downward,
            }),
        ],
        [None, None, None, None, None, None, None, None, None],
        [
            None,
            None,
            None,
            None,
            Some(Piece::Tam2),
            None,
            None,
            None,
            None,
        ],
        [None, None, None, None, None, None, None, None, None],
        [
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Nuak1,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kauk2,
                side: Side::Upward,
            }),
        ],
        [
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Tuk2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Gua2,
                side: Side::Upward,
            }),
            None,
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Dau2,
                side: Side::Upward,
            }),
            None,
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Dau2,
                side: Side::Upward,
            }),
            None,
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Gua2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Tuk2,
                side: Side::Upward,
            }),
        ],
        [
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kua2,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Maun1,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Kaun1,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Kok1,
                prof: Profession::Uai1,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Io,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Uai1,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kaun1,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Maun1,
                side: Side::Upward,
            }),
            Some(Piece::NonTam2Piece {
                color: Color::Huok2,
                prof: Profession::Kua2,
                side: Side::Upward,
            }),
        ],
    ])
}

impl Field {
    /// Add a piece to one's hop1zuo1.
    /// /手駒に駒を追加する。
    pub fn insert_nontam_piece_into_hop1zuo1(
        &mut self,
        color: Color,
        prof: Profession,
        side: Side,
    ) {
        match side {
            Side::Upward => self
                .hop1zuo1of_upward
                .push(NonTam2PieceUpward { color, prof }),
            Side::Downward => self
                .hop1zuo1of_downward
                .push(NonTam2PieceDownward { color, prof }),
        }
    }
}

/// Rotates a board.
/// /盤を180度回転させ、自分陣営と相手陣営を入れ替える。
#[must_use]
pub fn rotate_board(b: &Board) -> Board {
    let mut ans: Board = Board([
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
    ]);
    for i in 0..9 {
        for j in 0..9 {
            ans.0[i][j] = rotate_piece_or_null(b.0[8 - i][8 - j]);
        }
    }
    ans
}

/// Calculates the distance between two points.
/// The distance is defined as the larger of the difference between either the x or y coordinates.
/// /2点間の距離(x座標の差およびy座標の差のうち小さくない方)を計算する。
/// # Examples
/// ```
/// use cetkaik_naive_representation::relative::*;
/// assert_eq!(5, distance([4,5], [4,0]));
/// assert_eq!(3, distance([4,5], [1,2]));
/// assert_eq!(3, distance([1,2], [4,5]));
/// ```
///
/// # Panics
/// Panics if the `Coord` is so invalid that it does not fit in `i32`.
/// /`Coord` に入っている座標が `i32` に収まらないほど巨大であれば panic する。
#[must_use]
pub fn distance(a: Coord, b: Coord) -> i32 {
    let [x1, y1] = a;
    let [x2, y2] = b;

    let x_distance = (i32::try_from(x1).unwrap() - i32::try_from(x2).unwrap()).abs();
    let y_distance = (i32::try_from(y1).unwrap() - i32::try_from(y2).unwrap()).abs();

    x_distance.max(y_distance)
}

/// Describes a move denoted in absolute coordinates.
/// /絶対座標で書かれた指し手を表す。
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum PureMove {
    /// A non-Tam2 piece moves from a square on a board to another square without stepping.
    /// /皇ではない駒が、盤上から盤上に踏越えなしで移動する。
    NonTamMoveSrcDst {
        /// origin/開始点
        src: Coord,
        /// final destination/終了点
        dest: Coord,
        /// whether a water-entry ciurl is required/入水判定が必要かどうか
        is_water_entry_ciurl: bool,
    },
    /// A non-Tam2 piece moves from a square on a board to another square, during which it steps another piece and does a finite movement.
    /// /皇ではない駒が、盤上から盤上に踏越えを伴いながら移動し、踏越え後は有限移動をする。
    NonTamMoveSrcStepDstFinite {
        /// origin/開始点
        src: Coord,
        /// via point/経由点
        step: Coord,
        /// destination/終了点
        dest: Coord,
        /// whether a water-entry ciurl is required/入水判定が必要かどうか
        is_water_entry_ciurl: bool,
    },
    /// A non-Tam2 piece moves from a square on a board to another square, during which it steps another piece and tries to do a directional, infinite movement.
    /// /皇ではない駒が、盤上から盤上に踏越えを伴いながら移動し、踏越え後は無限移動をしようとする。
    InfAfterStep {
        /// origin/開始点
        src: Coord,
        /// via point/経由点
        step: Coord,
        /// the planned LOCATION. After casting the sticks, some rules necessitates that you go to the destination or to the direction that you have declared beforehand.
        /// Hence the confusing name.
        /// /行く予定の場所。踏越え判定後の移動先は、ルールによっては「計画したマス」である必要があったり「計画した方角」である必要があったりする。
        planned_direction: Coord,
    },
    /// A non-Tam2 piece moves from hop1zuo1 to a square on a board.
    /// /皇ではない駒が、手駒から盤上に移動する。
    NonTamMoveFromHopZuo {
        /// color/駒の色
        color: Color,
        /// profession/駒の役職
        prof: Profession,
        /// destination/終了点
        dest: Coord,
    },
    /// A Tam2 moves from a square on a board to another square without stepping.
    /// /皇が盤上から盤上に踏越えなしで移動する。
    TamMoveNoStep {
        /// origin/開始点
        src: Coord,
        /// first destination/一回目の終了点
        first_dest: Coord,
        /// second destination/二回目の終了点
        second_dest: Coord,
    },
    /// A Tam2 moves from a square on a board to another square. In the former half of its movement, it steps another piece.
    /// /皇が盤上から盤上に移動し、一回目の移動の最中に踏越えをする。
    TamMoveStepsDuringFormer {
        /// origin/開始点
        src: Coord,
        /// via point/経由点
        step: Coord,
        /// first destination/一回目の終了点
        first_dest: Coord,
        /// second destination/二回目の終了点
        second_dest: Coord,
    },
    /// A Tam2 moves from a square on a board to another square. In the latter half of its movement, it steps another piece.
    /// /皇が盤上から盤上に移動し、二回目の移動の最中に踏越えをする。
    TamMoveStepsDuringLatter {
        /// origin/開始点
        src: Coord,
        /// via point/経由点
        step: Coord,
        /// first destination/一回目の終了点
        first_dest: Coord,
        /// second destination/二回目の終了点
        second_dest: Coord,
    },
}

impl PureMove {
    /// Serializes [`PureMove`](./enum.PureMove.html) in textual form.
    /// # Examples
    /// ```
    /// use cetkaik_fundamental::*;
    /// use cetkaik_naive_representation::*;
    /// use cetkaik_naive_representation::relative::*;
    ///
    /// assert_eq!(PureMove::InfAfterStep {
    ///     src: [0, 4],
    ///     step: [1, 3],
    ///     planned_direction: [1, 2]
    /// }.serialize(), "[0,4]片[1,3]心[1,2]");
    ///
    /// assert_eq!(PureMove::NonTamMoveFromHopZuo {
    ///     color: Color::Huok2,
    ///     prof: Profession::Gua2,
    ///     dest: [8, 1]
    /// }.serialize(), "黒弓[8,1]");
    ///
    /// assert_eq!(PureMove::NonTamMoveSrcDst {
    ///     src: [0, 4],
    ///     dest: [1, 2],
    ///     is_water_entry_ciurl: true
    /// }.serialize(), "[0,4]片[1,2]水");
    ///
    /// assert_eq!(PureMove::NonTamMoveSrcStepDstFinite {
    ///     src: [0, 4],
    ///     step: [1, 3],
    ///     dest: [1, 2],
    ///     is_water_entry_ciurl: false
    /// }.serialize(), "[0,4]片[1,3][1,2]");
    ///
    /// // Note that [] denotes the first destination.
    /// // Since the first destination is neither the stepping square nor the final square,
    /// // it is not to be written in the standard notation.
    /// // Hence this additional information is denoted by [].
    /// assert_eq!(PureMove::TamMoveStepsDuringFormer {
    ///     src: [1, 0],
    ///     step: [2, 1],
    ///     first_dest: [2, 0],
    ///     second_dest: [1, 1]
    /// }.serialize(), "[1,0]皇[2,1][[2,0]][1,1]");
    ///
    /// assert_eq!(PureMove::TamMoveNoStep {
    ///     src: [1, 0],
    ///     first_dest: [2, 0],
    ///     second_dest: [1, 0]
    /// }.serialize(), "[1,0]皇[[2,0]][1,0]");
    ///
    /// assert_eq!(PureMove::TamMoveStepsDuringLatter {
    ///     src: [1, 0],
    ///     first_dest: [2, 0],
    ///     step: [2, 1],
    ///     second_dest: [1, 1]
    /// }.serialize(), "[1,0]皇[[2,0]][2,1][1,1]");
    /// ```
    #[must_use]
    pub fn serialize(self) -> String {
        match self {
            PureMove::InfAfterStep {
                src,
                step,
                planned_direction,
            } => format!(
                "{}{}{}",
                serialize_coord(src),
                serialize_coord(step),
                serialize_coord(planned_direction)
            ),
            PureMove::NonTamMoveFromHopZuo { color, prof, dest } => format!(
                "{}{}{}",
                cetkaik_fundamental::serialize_color(color),
                cetkaik_fundamental::serialize_prof(prof),
                serialize_coord(dest)
            ),
            PureMove::NonTamMoveSrcDst {
                src,
                dest,
                is_water_entry_ciurl,
            } => format!(
                "{}{}{}",
                serialize_coord(src),
                serialize_coord(dest),
                if is_water_entry_ciurl { "" } else { "" }
            ),
            PureMove::NonTamMoveSrcStepDstFinite {
                src,
                dest,
                is_water_entry_ciurl,
                step,
            } => format!(
                "{}{}{}{}",
                serialize_coord(src),
                serialize_coord(step),
                serialize_coord(dest),
                if is_water_entry_ciurl { "" } else { "" }
            ),
            PureMove::TamMoveNoStep {
                src,
                first_dest,
                second_dest,
            } => format!(
                "{}皇[{}]{}",
                serialize_coord(src),
                serialize_coord(first_dest),
                serialize_coord(second_dest)
            ),
            PureMove::TamMoveStepsDuringFormer {
                src,
                first_dest,
                second_dest,
                step,
            } => format!(
                "{}{}[{}]{}",
                serialize_coord(src),
                serialize_coord(step),
                serialize_coord(first_dest),
                serialize_coord(second_dest)
            ),
            PureMove::TamMoveStepsDuringLatter {
                src,
                first_dest,
                second_dest,
                step,
            } => format!(
                "{}皇[{}]{}{}",
                serialize_coord(src),
                serialize_coord(first_dest),
                serialize_coord(step),
                serialize_coord(second_dest)
            ),
        }
    }
}

impl IsBoard for Board {
    type PieceWithSide = Piece;

    type Coord = Coord;

    fn peek(&self, c: Self::Coord) -> Option<Self::PieceWithSide> {
        self.0[c[0]][c[1]]
    }

    fn pop(&mut self, c: Self::Coord) -> Option<Self::PieceWithSide> {
        let o = self.peek(c);
        self.0[c[0]][c[1]] = None;
        o
    }

    fn put(&mut self, c: Self::Coord, p: Option<Self::PieceWithSide>) {
        self.0[c[0]][c[1]] = p;
    }

    fn assert_empty(&self, c: Self::Coord) {
        assert!(self.peek(c).is_none());
    }

    fn assert_occupied(&self, c: Self::Coord) {
        assert!(self.peek(c).is_some());
    }

    type EmptySquaresIter = std::vec::IntoIter<[usize; 2]>;

    fn empty_squares(&self) -> Self::EmptySquaresIter {
        let mut ans = vec![];
        for row in 0..9 {
            for column in 0..9 {
                let coord = [row, column];
                if self.peek(coord).is_none() {
                    ans.push(coord);
                }
            }
        }
        ans.into_iter()
    }
}



impl IsField for Field {
    type Board = Board;
    type Coord = Coord;
    type PieceWithSide = Piece;
    type Side = Side;

    fn move_nontam_piece_from_src_to_dest_while_taking_opponent_piece_if_needed(
        &self,
        src: Self::Coord,
        dest: Self::Coord,
        whose_turn: Self::Side,
    ) -> Result<Self, &'static str>
    where
        Self: std::marker::Sized,
    {
        let mut new_self = self.clone();
        let src_piece =
            new_self.current_board.0[src[0]][src[1]].ok_or("src does not contain a piece")?;

        let Piece::NonTam2Piece { color: _color, prof: _prof, side } = src_piece
        else {
            return Err("Expected a NonTam2Piece to be present at the src, but found a Tam2")
        };

        if whose_turn != side {
            return Err("Found the opponent piece at the src");
        }

        let maybe_captured_piece = new_self.current_board.0[dest[0]][dest[1]];
        new_self.current_board.0[dest[0]][dest[1]] = Some(src_piece);

        if let Some(captured_piece) = maybe_captured_piece {
            match captured_piece {
                Piece::Tam2 => return Err("Tried to capture a Tam2"),
                Piece::NonTam2Piece {
                    color: captured_piece_color,
                    prof: captured_piece_prof,
                    side: captured_piece_side,
                } => {
                    if captured_piece_side == whose_turn {
                        return Err("Tried to capture an ally");
                    }
                    match whose_turn {
                        Side::Downward => new_self.hop1zuo1of_downward.push(NonTam2PieceDownward {
                            color: captured_piece_color,
                            prof: captured_piece_prof,
                        }),
                        Side::Upward => new_self.hop1zuo1of_upward.push(NonTam2PieceUpward {
                            color: captured_piece_color,
                            prof: captured_piece_prof,
                        }),
                    }
                }
            }
        }
        Ok(new_self)
    }

    fn as_board(&self) -> &Self::Board {
        &self.current_board
    }

    fn as_board_mut(&mut self) -> &mut Self::Board {
        &mut self.current_board
    }

    #[must_use]
    fn search_from_hop1zuo1_and_parachute_at(
        &self,
        color: Color,
        prof: Profession,
        side: Side,
        to: Coord,
    ) -> Option<Self> {
        match side {
            Side::Upward => {
                let mut new_self = self.clone();
                let index = new_self
                    .hop1zuo1of_upward
                    .iter()
                    .position(|x| *x == NonTam2PieceUpward { color, prof })?;
                new_self.hop1zuo1of_upward.remove(index);

                if self.current_board.0[to[0]][to[1]].is_some() {
                    return None;
                }
                new_self.current_board.0[to[0]][to[1]] =
                    Some(Piece::NonTam2Piece { color, prof, side });
                Some(new_self)
            }
            Side::Downward => {
                let mut new_self = self.clone();
                let index = new_self
                    .hop1zuo1of_downward
                    .iter()
                    .position(|x| *x == NonTam2PieceDownward { color, prof })?;
                new_self.hop1zuo1of_downward.remove(index);

                if self.current_board.0[to[0]][to[1]].is_some() {
                    return None;
                }
                new_self.current_board.0[to[0]][to[1]] =
                    Some(Piece::NonTam2Piece { color, prof, side });
                Some(new_self)
            }
        }
    }
}

impl IsPieceWithSide for Piece {
    type Side = Side;

    fn match_on_piece_and_apply<U>(
        self,
        f_tam: &dyn Fn() -> U,
        f_piece: &dyn Fn(Color, Profession, Self::Side) -> U,
    ) -> U {
        match self {
            Piece::Tam2 => f_tam(),
            Piece::NonTam2Piece { color, prof, side } => f_piece(color, prof, side),
        }
    }
}