From 4ee7f8a9ac846b309d317427e6de5384e70f8669 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 6 Mar 2024 09:55:47 -0800 Subject: [PATCH] Minor cleanup & utilities --- src/agents/human.rs | 2 +- src/agents/util/partials.rs | 91 ++++++++++++++++++++----------------- src/board/board.rs | 26 ++++++++++- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/agents/human.rs b/src/agents/human.rs index 498b91a..54a7363 100644 --- a/src/agents/human.rs +++ b/src/agents/human.rs @@ -195,7 +195,7 @@ impl Human { impl Agent for Human { fn name(&self) -> &'static str { - "Player" + "Human" } fn player(&self) -> Player { diff --git a/src/agents/util/partials.rs b/src/agents/util/partials.rs index dd07cb6..8eb3f3d 100644 --- a/src/agents/util/partials.rs +++ b/src/agents/util/partials.rs @@ -1,10 +1,10 @@ use itertools::Itertools; -use std::num::NonZeroU8; +use std::{mem::swap, num::NonZeroU8}; use crate::{board::Board, util::Symb}; -/// Returns an iterator of (sort, coords, char_idx, f32) for each empty slot in the listed partials. -/// - sort is the index of this slot. +/// Returns an iterator of (idx, coords, char_idx, f32) for each empty slot in the listed partials. +/// - idx is the index of this slot in the board string. /// - coords are the coordinate of this slot's partial /// - char_idx is the index of this slot in its partial /// - f32 is the influence of this slot @@ -51,17 +51,14 @@ pub fn free_slots_by_influence(board: &Board) -> Option> { Some(slots) } -/// Find the maximum possible value of the given board -#[allow(dead_code)] -pub fn maximize_value(board: &Board) -> Option { +/// Find the minimum or maximum possible value of the given board, +/// without adding any operations. Returns None if we couldn't find +/// a best board. +pub fn best_board_noop(board: &Board, minimize: bool) -> Option { let n_free = board.get_board().iter().filter(|x| x.is_none()).count(); - // Assume we have 10 or fewer available slots - if n_free >= 10 { - panic!() - } - - let available_numbers = (0..=9) + // TODO: fix zero division + let available_numbers = (1..=9) .map(|x| match x { 0 => Symb::Zero, x => Symb::Number(NonZeroU8::new(x).unwrap()), @@ -69,19 +66,36 @@ pub fn maximize_value(board: &Board) -> Option { .filter(|x| !board.contains(*x)) .collect::>(); + if n_free > available_numbers.len() { + return None; + } + let slots = free_slots_by_influence(board)?; let all_symbols = { - // We need this many from the bottom, and this many from the top. - let neg_count = slots.iter().filter(|(_, x)| *x <= 0.0).count(); - let pos_count = slots.iter().filter(|(_, x)| *x > 0.0).count(); + let mut a = { + // Number of slots we want to minimize + 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 - .iter() - .take(neg_count) - .chain(available_numbers.iter().rev().take(pos_count).rev()); + if minimize { + swap(&mut neg_count, &mut pos_count); + } - 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 // and count the number of elements in each group .iter() @@ -101,37 +115,32 @@ pub fn maximize_value(board: &Board) -> Option { // of this set of sets .multi_cartesian_product() .map(|x| x.iter().flatten().cloned().collect_vec()) - .map(|v| slots.iter().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()) + // Finally, attach the coordinate of each slot to each symbol + .map(|v| slots.iter().map(|x| x.0).zip(v).collect_vec()) + .collect_vec() }; let mut best_board = None; let mut best_value = None; - for i in all_symbols { - let mut i_iter = i.iter(); - let filled = Board::from_board(board.get_board().map(|x| match x { - None => i_iter.next().cloned(), - _ => x, - })); + for i_iter in all_symbols { + let mut tmp_board = board.clone(); + for (i, s) in i_iter { + tmp_board.get_board_mut()[i] = Some(s); + } - let val = filled.evaluate(); + let val = tmp_board.evaluate(); if let Some(val) = val { - if let Some(best) = best_value { - if val > best { + if minimize { + if best_value.is_none() || val < best_value.unwrap() { best_value = Some(val); - best_board = Some(filled) + best_board = Some(tmp_board) } } else { - best_value = Some(val); - best_board = Some(filled) + if best_value.is_none() || val > best_value.unwrap() { + best_value = Some(val); + best_board = Some(tmp_board) + } } } } diff --git a/src/board/board.rs b/src/board/board.rs index e2e98d3..36912f9 100644 --- a/src/board/board.rs +++ b/src/board/board.rs @@ -1,6 +1,6 @@ use anyhow::Result; use itertools::Itertools; -use std::fmt::{Display, Write}; +use std::fmt::{Debug, Display, Write}; use termion::color::{self, Color}; 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)] impl Board { pub fn new() -> Self { @@ -93,6 +99,24 @@ impl Board { &mut self.board } + /// Get the index of the ith empty slot + pub fn ith_empty_slot(&self, mut idx: usize) -> Option { + 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 { self.free_spots == 0 }