recognize clearing the bottom matrix row in evaluator
This commit is contained in:
parent
082fa62add
commit
1784647146
|
@ -2,6 +2,8 @@
|
|||
//! anytime algorithm that will provide a suggestion for the next move. It may be
|
||||
//! repeatedly polled by the `think` method in order to attempt to improve the suggestion.
|
||||
|
||||
use core::ops::Range;
|
||||
|
||||
use alloc::collections::BinaryHeap;
|
||||
use alloc::vec::Vec;
|
||||
use mino::matrix::Mat;
|
||||
|
@ -103,34 +105,25 @@ impl Evaluator {
|
|||
}
|
||||
}
|
||||
|
||||
fn evaluate(&self, mat: &Mat, queue: Queue<'_>) -> i32 {
|
||||
// FIXME: the old blockfish has two special edge cases for rating nodes that is
|
||||
// not done here.
|
||||
//
|
||||
// 1. nodes that reach the bottom of the board early ("solutions") are highly
|
||||
// prioritized. this is done by using the piece count *as the rating* in order to
|
||||
// force it to be extremely low, as well as sorting solutions by # of pieces in
|
||||
// case there are multiple. according to frey, this probably causes blockfish to
|
||||
// greed out in various scenarios where it sees a path to the bottom but it is not
|
||||
// actually the end of the race. part of the issue is of course that it isn't
|
||||
// communicated to blockfish whether or not the bottom of the board is actually
|
||||
// the end of the race, but also that the intermediate steps to get to the bottom
|
||||
// may be suboptimal placements when it isn't.
|
||||
//
|
||||
// 2. blockfish would actually average the last two evaluations and use that as
|
||||
// the final rating. this is meant as a concession for the fact that the last
|
||||
// placement made by the bot is not actually a placement we are required to make,
|
||||
// since in reality there is going to be the opportunity to hold the final piece
|
||||
// and use something else instead. so the 2nd to last rating is important in cases
|
||||
// where the last piece leads to suboptimal board states which may be able to be
|
||||
// avoided by holding the last piece. i think this improves the performance only
|
||||
// slightly, but it is also a bit of a hack that deserves further consideration.
|
||||
|
||||
fn evaluate(&self, mat: &Mat, queue: Queue<'_>, cleared: &Range<i16>) -> (bool, i32) {
|
||||
let pcnt = self.root_queue_len.saturating_sub(queue.len());
|
||||
|
||||
if self.greed() && cleared.contains(&0) {
|
||||
// cleared the bottom row of the matrix, which must be the last line of cheese
|
||||
// in the race. piece count is negated so that less pieces is better (larger
|
||||
// value).
|
||||
return (true, -(pcnt as i32));
|
||||
}
|
||||
|
||||
let score = features(mat, pcnt).evaluate(&self.weights);
|
||||
|
||||
// larger (i.e., further below the root score) is better
|
||||
self.root_score - score
|
||||
// larger (further below the root score) is better
|
||||
(false, self.root_score - score)
|
||||
}
|
||||
|
||||
fn greed(&self) -> bool {
|
||||
// TODO: make this parameter configurable on `Bot` initialization
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,10 +217,10 @@ impl SegmentedAStar {
|
|||
}
|
||||
|
||||
let mut work = 0;
|
||||
let evaluate = |mat: &Mat, queue: Queue<'_>| {
|
||||
let evaluate = |mat: &Mat, queue: Queue<'_>, clr: &Range<i16>| {
|
||||
// each evaluated board state = +1 unit work
|
||||
work += 1;
|
||||
eval.evaluate(mat, queue)
|
||||
eval.evaluate(mat, queue, clr)
|
||||
};
|
||||
|
||||
let expanded = cand.expand(arena, trans, evaluate);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Graph data structures used by `Bot` in its search algorithm.
|
||||
|
||||
use core::ops::Range;
|
||||
|
||||
use mino::matrix::{Mat, MatBuf};
|
||||
use mino::srs::{Piece, PieceType, Queue};
|
||||
|
||||
|
@ -13,7 +15,7 @@ pub(crate) struct Node {
|
|||
matrix: *const Mat,
|
||||
queue: RawQueue,
|
||||
edge: Option<Edge>,
|
||||
rating: i32,
|
||||
rating: (bool, i32),
|
||||
// currently there is no need to store a node's children, but maybe this could change
|
||||
// in the future.
|
||||
}
|
||||
|
@ -38,7 +40,8 @@ impl Node {
|
|||
pub fn alloc_root<'a>(arena: &'a Arena, matrix: &Mat, queue: Queue<'_>) -> &'a Self {
|
||||
let matrix = copy_matrix(arena, matrix);
|
||||
let queue = copy_queue(arena, queue);
|
||||
Node::alloc(arena, matrix, queue, i32::MIN, None)
|
||||
let rating = (false, i32::MIN);
|
||||
Node::alloc(arena, matrix, queue, rating, None)
|
||||
}
|
||||
|
||||
// `matrix` and `queue` must be allocated inside `arena`
|
||||
|
@ -46,7 +49,7 @@ impl Node {
|
|||
arena: &'a Arena,
|
||||
matrix: &'a Mat,
|
||||
queue: Queue<'a>,
|
||||
rating: i32,
|
||||
rating: (bool, i32),
|
||||
edge: Option<Edge>,
|
||||
) -> &'a Self {
|
||||
let matrix = matrix as *const Mat;
|
||||
|
@ -72,8 +75,7 @@ impl Node {
|
|||
}
|
||||
|
||||
pub fn is_terminal(&self) -> bool {
|
||||
// TODO: additional terminal-node conditions e.g. clears last row of garbage
|
||||
self.queue().is_empty()
|
||||
self.queue().is_empty() || self.rating.0
|
||||
}
|
||||
|
||||
/// Get the initial placement made after the root node which eventually arrives at
|
||||
|
@ -99,7 +101,7 @@ impl Node {
|
|||
mut evaluate: E,
|
||||
) -> impl Iterator<Item = &'a Node> + 'a
|
||||
where
|
||||
E: FnMut(&Mat, Queue<'_>) -> i32 + 'a,
|
||||
E: FnMut(&Mat, Queue<'_>, &Range<i16>) -> (bool, i32) + 'a,
|
||||
{
|
||||
let placements = self.queue().reachable().flat_map(|ty| {
|
||||
let locs = find_locations(self.matrix(), ty);
|
||||
|
@ -111,9 +113,7 @@ impl Node {
|
|||
placements.filter_map(move |placement| {
|
||||
matrix.copy_from(self.matrix());
|
||||
placement.cells().fill(&mut matrix);
|
||||
matrix.clear_lines();
|
||||
// TODO: the above call returns useful information about if this placement is
|
||||
// a combo & does it clear the bottom row of garbage
|
||||
let cleared = matrix.clear_lines();
|
||||
let queue = self.queue().remove(placement.ty);
|
||||
|
||||
trans
|
||||
|
@ -125,7 +125,7 @@ impl Node {
|
|||
// allocated on the arena, and this queue just aliases pointers
|
||||
// into self.queue.next
|
||||
queue,
|
||||
evaluate(&matrix, queue),
|
||||
evaluate(&matrix, queue, &cleared),
|
||||
Some(Edge {
|
||||
placement,
|
||||
parent: self.into(),
|
||||
|
@ -144,7 +144,11 @@ impl Node {
|
|||
|
||||
impl core::fmt::Debug for Node {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "Node {{ rating: {}", self.rating)?;
|
||||
write!(f, "Node {{ ")?;
|
||||
match self.rating {
|
||||
(false, h) => write!(f, "heuristic: {}", h),
|
||||
(true, n) => write!(f, "solution: {}", -n),
|
||||
}?;
|
||||
if let Some(pc) = self.root_placement() {
|
||||
write!(f, ", root_placement: {:?}", pc)?;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue