pub mod types;
pub mod expected_points;
pub mod accuracy;
pub mod attackers;
pub mod defenders;
pub mod piece_safety;
pub mod piece_trapped;
pub mod danger_levels;
pub mod point_loss;
pub mod brilliant;
pub mod great;
pub use types::{
BoardPiece, ClassificationContext, ClassificationKind, Evaluation, MoveClassification,
};
pub use accuracy::{get_move_accuracy, PlayerAccuracy};
pub use expected_points::{get_expected_points, get_expected_points_loss};
use crate::board::Board;
pub fn classify(
board_before: &Board,
board_after: &Board,
ctx: &ClassificationContext,
) -> ClassificationKind {
if ctx.is_book {
return ClassificationKind::Book;
}
if ctx.is_forced {
return ClassificationKind::Forced;
}
if brilliant::is_brilliant(board_before, board_after, ctx) {
return ClassificationKind::Brilliant;
}
if great::is_great(board_before, ctx) {
return ClassificationKind::Great;
}
let mut kind = point_loss::point_loss_classify(ctx);
kind = refine_good_and_miss(kind, ctx);
if is_risky(&kind, board_after, ctx) {
return ClassificationKind::Risky;
}
kind
}
fn refine_good_and_miss(
kind: ClassificationKind,
ctx: &ClassificationContext,
) -> ClassificationKind {
match kind {
ClassificationKind::Okay if ctx.point_loss < 0.08 && ctx.point_loss >= 0.045 => {
ClassificationKind::Good
}
ClassificationKind::Best => {
if let (Some(Evaluation::Mate(prev_m)), Evaluation::Centipawn(_)) =
(ctx.second_best_eval, ctx.eval_after)
&& *prev_m > 0 && ctx.point_loss > 0.01 {
return ClassificationKind::Miss;
}
ClassificationKind::Best
}
other => other,
}
}
fn is_risky(
kind: &ClassificationKind,
board_after: &Board,
ctx: &ClassificationContext,
) -> bool {
let is_sound = matches!(
kind,
ClassificationKind::Best
| ClassificationKind::Excellent
| ClassificationKind::Good
| ClassificationKind::Okay
);
if !is_sound {
return false;
}
let unsafe_pieces = piece_safety::get_unsafe_pieces(board_after, ctx.color, None);
!unsafe_pieces.is_empty()
}