Minor cleanup & utilities

master
Mark 2024-03-06 09:55:47 -08:00
parent 89aba4f930
commit 4ee7f8a9ac
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
3 changed files with 76 additions and 43 deletions

View File

@ -195,7 +195,7 @@ impl Human {
impl Agent for Human { impl Agent for Human {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"Player" "Human"
} }
fn player(&self) -> Player { fn player(&self) -> Player {

View File

@ -1,10 +1,10 @@
use itertools::Itertools; use itertools::Itertools;
use std::num::NonZeroU8; use std::{mem::swap, num::NonZeroU8};
use crate::{board::Board, util::Symb}; use crate::{board::Board, util::Symb};
/// Returns an iterator of (sort, coords, char_idx, f32) for each empty slot in the listed partials. /// Returns an iterator of (idx, coords, char_idx, f32) for each empty slot in the listed partials.
/// - sort is the index of this slot. /// - idx is the index of this slot in the board string.
/// - coords are the coordinate of this slot's partial /// - coords are the coordinate of this slot's partial
/// - char_idx is the index of this slot in its partial /// - char_idx is the index of this slot in its partial
/// - f32 is the influence of this slot /// - f32 is the influence of this slot
@ -51,17 +51,14 @@ pub fn free_slots_by_influence(board: &Board) -> Option<Vec<(usize, f32)>> {
Some(slots) Some(slots)
} }
/// Find the maximum possible value of the given board /// Find the minimum or maximum possible value of the given board,
#[allow(dead_code)] /// without adding any operations. Returns None if we couldn't find
pub fn maximize_value(board: &Board) -> Option<Board> { /// a best board.
pub fn best_board_noop(board: &Board, minimize: bool) -> Option<Board> {
let n_free = board.get_board().iter().filter(|x| x.is_none()).count(); let n_free = board.get_board().iter().filter(|x| x.is_none()).count();
// Assume we have 10 or fewer available slots // TODO: fix zero division
if n_free >= 10 { let available_numbers = (1..=9)
panic!()
}
let available_numbers = (0..=9)
.map(|x| match x { .map(|x| match x {
0 => Symb::Zero, 0 => Symb::Zero,
x => Symb::Number(NonZeroU8::new(x).unwrap()), x => Symb::Number(NonZeroU8::new(x).unwrap()),
@ -69,19 +66,36 @@ pub fn maximize_value(board: &Board) -> Option<Board> {
.filter(|x| !board.contains(*x)) .filter(|x| !board.contains(*x))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if n_free > available_numbers.len() {
return None;
}
let slots = free_slots_by_influence(board)?; let slots = free_slots_by_influence(board)?;
let all_symbols = { let all_symbols = {
// We need this many from the bottom, and this many from the top. let mut a = {
let neg_count = slots.iter().filter(|(_, x)| *x <= 0.0).count(); // Number of slots we want to minimize
let pos_count = slots.iter().filter(|(_, x)| *x > 0.0).count(); let mut neg_count = slots.iter().filter(|(_, x)| *x <= 0.0).count();
// Number of slots we want to maximize
let mut pos_count = slots.iter().filter(|(_, x)| *x > 0.0).count();
let mut a_iter = available_numbers if minimize {
.iter() swap(&mut neg_count, &mut pos_count);
.take(neg_count) }
.chain(available_numbers.iter().rev().take(pos_count).rev());
let mut g = slots available_numbers
.iter()
.take(neg_count)
.chain(available_numbers.iter().rev().take(pos_count).rev())
.collect_vec()
};
if !minimize {
a.reverse();
}
let mut a_iter = a.into_iter();
slots
// Group slots with equal weights // Group slots with equal weights
// and count the number of elements in each group // and count the number of elements in each group
.iter() .iter()
@ -101,37 +115,32 @@ pub fn maximize_value(board: &Board) -> Option<Board> {
// of this set of sets // of this set of sets
.multi_cartesian_product() .multi_cartesian_product()
.map(|x| x.iter().flatten().cloned().collect_vec()) .map(|x| x.iter().flatten().cloned().collect_vec())
.map(|v| slots.iter().zip(v).collect_vec()) // Finally, attach the coordinate of each slot to each symbol
.collect_vec(); .map(|v| slots.iter().map(|x| x.0).zip(v).collect_vec())
.collect_vec()
// Sort these vectors so the order of values
// matches the order of empty slots
g.iter_mut()
.for_each(|v| v.sort_by(|(a, _), (b, _)| b.0.partial_cmp(&a.0).unwrap()));
g.into_iter()
.map(|v| v.into_iter().map(|(_, s)| s).collect_vec())
}; };
let mut best_board = None; let mut best_board = None;
let mut best_value = None; let mut best_value = None;
for i in all_symbols { for i_iter in all_symbols {
let mut i_iter = i.iter(); let mut tmp_board = board.clone();
let filled = Board::from_board(board.get_board().map(|x| match x { for (i, s) in i_iter {
None => i_iter.next().cloned(), tmp_board.get_board_mut()[i] = Some(s);
_ => x, }
}));
let val = filled.evaluate(); let val = tmp_board.evaluate();
if let Some(val) = val { if let Some(val) = val {
if let Some(best) = best_value { if minimize {
if val > best { if best_value.is_none() || val < best_value.unwrap() {
best_value = Some(val); best_value = Some(val);
best_board = Some(filled) best_board = Some(tmp_board)
} }
} else { } else {
best_value = Some(val); if best_value.is_none() || val > best_value.unwrap() {
best_board = Some(filled) best_value = Some(val);
best_board = Some(tmp_board)
}
} }
} }
} }

View File

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use itertools::Itertools; use itertools::Itertools;
use std::fmt::{Display, Write}; use std::fmt::{Debug, Display, Write};
use termion::color::{self, Color}; use termion::color::{self, Color};
use super::{PlayerAction, TreeElement}; use super::{PlayerAction, TreeElement};
@ -74,6 +74,12 @@ impl Display for Board {
} }
} }
impl Debug for Board {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self, f)
}
}
#[allow(dead_code)] #[allow(dead_code)]
impl Board { impl Board {
pub fn new() -> Self { pub fn new() -> Self {
@ -93,6 +99,24 @@ impl Board {
&mut self.board &mut self.board
} }
/// Get the index of the ith empty slot
pub fn ith_empty_slot(&self, mut idx: usize) -> Option<usize> {
for (i, c) in self.board.iter().enumerate() {
if c.is_none() {
if idx == 0 {
return Some(i);
}
idx -= 1;
}
}
if idx == 0 {
Some(self.board.len() - 1)
} else {
None
}
}
pub fn is_done(&self) -> bool { pub fn is_done(&self) -> bool {
self.free_spots == 0 self.free_spots == 0
} }