From d03cdde5ad36c6d60f9af455f5825c496787596e Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Tue, 11 Feb 2025 16:18:47 -0500 Subject: [PATCH] touchups --- Cargo.lock | 14 +++++++++ Cargo.toml | 2 ++ src/board.rs | 81 ++++++++++++++++++++++++++++++++-------------------- src/main.rs | 4 +-- src/misc.rs | 55 +++++++++++++++++------------------ 5 files changed, 94 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7404b8..85f0bea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "autocfg" version = "1.4.0" @@ -69,6 +75,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.169" @@ -152,7 +164,9 @@ dependencies = [ name = "othello" version = "0.1.0" dependencies = [ + "arrayvec", "either", + "lazy_static", "num", "rand", "rayon", diff --git a/Cargo.toml b/Cargo.toml index a29ee79..05560b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,9 @@ edition = "2021" debug = true [dependencies] +arrayvec = "0.7.6" either = "1.13.0" +lazy_static = "1.5.0" num = "0.4" rand = "0.9" rayon = "1.10" diff --git a/src/board.rs b/src/board.rs index a655e63..bdd0ee7 100644 --- a/src/board.rs +++ b/src/board.rs @@ -2,10 +2,51 @@ use crate::{ misc::{diag_raw, split_from}, piece::Piece, }; -use std::{cmp::Ordering, fmt}; - +use arrayvec::ArrayVec; +use lazy_static::lazy_static; +use std::{cmp::Ordering, collections::HashMap, fmt}; pub const BOARD_SIZE: usize = 8; +/// A chain of positions across the board +type Chain = ArrayVec<(usize, usize), BOARD_SIZE>; + +/// A collection of chains (up vert, down vert, left horiz, right horiz, diagonals....) +type ChainCollection = ArrayVec; + +lazy_static! { + /// Precompute all possible chains for each position on the board + pub static ref ADJ_LOOKUP: HashMap<(usize, usize), ChainCollection> = { + let mut output = HashMap::new(); + for (i, j) in Board::all_positions() { + let (i_chain, j_chain) = ( + split_from(0..=BOARD_SIZE - 1, i), + split_from(0..=BOARD_SIZE - 1, j), + ); + + let mut chains: ChainCollection = ArrayVec::new_const(); + + chains.extend( + i_chain + .clone() + .map(|range| range.map(move |i| (i, j))) + .map(Iterator::collect), + ); + chains.extend( + j_chain + .clone() + .map(|range| range.map(move |j| (i, j))) + .map(Iterator::collect), + ); + + // handle diagonals + chains.extend(diag_raw(i_chain, j_chain).map(Iterator::collect)); + output.insert((i, j), chains); + } + + output + }; +} + #[derive(Copy, Clone, PartialEq, Eq)] pub struct Board { board: [[Option; BOARD_SIZE]; BOARD_SIZE], @@ -68,10 +109,12 @@ impl Board { self } + pub fn all_positions() -> impl Iterator { + (0..BOARD_SIZE).flat_map(|i| (0..BOARD_SIZE).map(move |j| (i, j))) + } + pub fn possible_moves(&self, color: Piece) -> impl Iterator + use<'_> { - (0..BOARD_SIZE) - .flat_map(|i| (0..BOARD_SIZE).map(move |j| (i, j))) - .filter(move |(i, j)| self.would_prop(*i, *j, color)) + Self::all_positions().filter(move |(i, j)| self.would_prop(*i, *j, color)) } /// Returns a mutable reference to a place on the [`Board`] @@ -138,29 +181,7 @@ impl Board { // NOTE! got it down to 24.86% (61.1% decrease) with allocator optimizations // IDEAS: early-exit from each chain so we don't have to call `diag` (which allocs a lot and uses a lot of cycles) fn propegate_from_dry(&self, i: usize, j: usize, starting_color: Piece) -> Vec<(usize, usize)> { - // Create all chains from the piece being propegated from in `i` and `j` coordinates - let (i_chain, j_chain) = ( - split_from(0, BOARD_SIZE - 1, i), - split_from(0, BOARD_SIZE - 1, j), - ); - - let mut chains: Vec> = Vec::with_capacity(8); - - chains.extend( - i_chain - .clone() - .map(|range| range.map(move |i| (i, j))) - .map(Iterator::collect), - ); - chains.extend( - j_chain - .clone() - .map(|range| range.map(move |j| (i, j))) - .map(Iterator::collect), - ); - - // handle diagonals - chains.extend(diag_raw(i_chain, j_chain).map(Iterator::collect)); + let chains = ADJ_LOOKUP.get(&(i, j)).unwrap(); // Longest chain is (BOARD_SIZE - 2) as there needs to be the two pieces containing it let mut fill: Vec<(usize, usize)> = Vec::with_capacity((BOARD_SIZE - 2) * chains.len()); @@ -176,9 +197,7 @@ impl Board { // get history of this chain if let Some(history) = chain.get(..chain_length) { // fill all opposite colors with this color - for &(i_o, j_o) in history { - fill.push((i_o, j_o)); - } + fill.extend(history); } // either the other pieces were replaced, or this was an invalid chain, diff --git a/src/main.rs b/src/main.rs index 5c9bb14..6506e54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,8 @@ mod piece; fn main() { let player1 = complexagent::ComplexAgent::new(Piece::Black); // let player2 = complexagent::ComplexAgent::new(Piece::White); - let player2 = agent::ManualAgent::new(Piece::White); - // let player2 = agent::RandomAgent::new(Piece::White); + // let player2 = agent::ManualAgent::new(Piece::White); + let player2 = agent::RandomAgent::new(Piece::White); let mut game = Game::new(Box::new(player1), Box::new(player2)); game.game_loop(); } diff --git a/src/misc.rs b/src/misc.rs index e330e3b..75806e0 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,48 +1,31 @@ use either::Either; use std::{iter::Rev, ops::RangeInclusive}; -pub fn split_from(min: T, max: T, x: T) -> [impl Iterator + Clone; 2] +pub fn split_from(range: RangeInclusive, x: T) -> [impl Iterator + Clone; 2] where T: num::Integer + Copy, RangeInclusive: Iterator + DoubleEndedIterator, Rev>: Iterator, { - let in_range = (x >= min) && (x <= max); + let in_range = range.contains(&x); + let (start, end) = (*range.start(), *range.end()); // RangeInclusive (1..=0), has 0 elements let base = Either::Right(T::one()..=T::zero()); [ - if in_range && x > min + T::one() { - Either::Left((min..=(x - T::one())).rev()) + if in_range && x > start + T::one() { + Either::Left((start..=(x - T::one())).rev()) } else { base.clone() }, - if in_range && x + T::one() < max { - Either::Right((x + T::one())..=max) + if in_range && x + T::one() < end { + Either::Right((x + T::one())..=end) } else { base }, ] } -pub fn diag_test_helper( - i: T, - j: T, - min_i: T, - min_j: T, - max_i: T, - max_j: T, -) -> [impl Iterator + Clone; 4] -where - T: num::Integer + Copy, - RangeInclusive: Iterator + DoubleEndedIterator, - Rev>: Iterator, -{ - let i_chains = split_from(min_i, max_i, i); - let j_chains = split_from(min_j, max_j, j); - diag_raw(i_chains, j_chains) -} - pub fn diag_raw( i_chains: [impl Iterator + Clone; 2], @@ -60,26 +43,40 @@ where mod test { use super::*; + pub fn diag_test_helper( + i: T, + j: T, + range_i: RangeInclusive, + range_j: RangeInclusive, + ) -> [impl Iterator + Clone; 4] + where + T: num::Integer + Copy, + RangeInclusive: Iterator + DoubleEndedIterator, + Rev>: Iterator, + { + diag_raw(split_from(range_i, i), split_from(range_j, j)) + } + #[test] fn split_test() { assert_eq!( - split_from(0, 6, 2).map(Iterator::collect::>), + split_from(0..=6, 2).map(Iterator::collect::>), [vec![1, 0], vec![3, 4, 5, 6]] ); assert_eq!( - split_from(0, 6, 0).map(Iterator::collect::>), + split_from(0..=6, 0).map(Iterator::collect::>), [vec![], vec![1, 2, 3, 4, 5, 6]] ); assert_eq!( - split_from(0, 6, 6).map(Iterator::collect::>), + split_from(0..=6, 6).map(Iterator::collect::>), [vec![5, 4, 3, 2, 1, 0], vec![]] ); // test out-of-bounds and also generics assert_eq!( - split_from::(-1i16, 4i16, 10i16).map(Iterator::collect::>), + split_from::(-1i16..=4i16, 10i16).map(Iterator::collect::>), [const { Vec::new() }; 2] ); } @@ -87,7 +84,7 @@ mod test { #[test] fn diag_test() { assert_eq!( - diag_test_helper(2, 3, 0, 0, 7, 7).map(Iterator::collect::>), + diag_test_helper(2, 3, 0..=7, 0..=7).map(Iterator::collect::>), [ vec![(1, 2), (0, 1)], vec![(3, 4), (4, 5), (5, 6), (6, 7)],