This commit is contained in:
Simon Gardling 2025-02-11 16:18:47 -05:00
parent 657bb967a0
commit d03cdde5ad
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
5 changed files with 94 additions and 62 deletions

14
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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<Chain, 8>;
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<Piece>; BOARD_SIZE]; BOARD_SIZE],
@ -68,10 +109,12 @@ impl Board {
self
}
pub fn all_positions() -> impl Iterator<Item = (usize, usize)> {
(0..BOARD_SIZE).flat_map(|i| (0..BOARD_SIZE).map(move |j| (i, j)))
}
pub fn possible_moves(&self, color: Piece) -> impl Iterator<Item = (usize, usize)> + 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<(usize, usize)>> = 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,

View File

@ -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();
}

View File

@ -1,48 +1,31 @@
use either::Either;
use std::{iter::Rev, ops::RangeInclusive};
pub fn split_from<T>(min: T, max: T, x: T) -> [impl Iterator<Item = T> + Clone; 2]
pub fn split_from<T>(range: RangeInclusive<T>, x: T) -> [impl Iterator<Item = T> + Clone; 2]
where
T: num::Integer + Copy,
RangeInclusive<T>: Iterator<Item = T> + DoubleEndedIterator,
Rev<RangeInclusive<T>>: Iterator<Item = T>,
{
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<T>(
i: T,
j: T,
min_i: T,
min_j: T,
max_i: T,
max_j: T,
) -> [impl Iterator<Item = (T, T)> + Clone; 4]
where
T: num::Integer + Copy,
RangeInclusive<T>: Iterator<Item = T> + DoubleEndedIterator,
Rev<RangeInclusive<T>>: Iterator<Item = T>,
{
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<T>(
i_chains: [impl Iterator<Item = T> + Clone; 2],
@ -60,26 +43,40 @@ where
mod test {
use super::*;
pub fn diag_test_helper<T>(
i: T,
j: T,
range_i: RangeInclusive<T>,
range_j: RangeInclusive<T>,
) -> [impl Iterator<Item = (T, T)> + Clone; 4]
where
T: num::Integer + Copy,
RangeInclusive<T>: Iterator<Item = T> + DoubleEndedIterator,
Rev<RangeInclusive<T>>: Iterator<Item = T>,
{
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::<Vec<usize>>),
split_from(0..=6, 2).map(Iterator::collect::<Vec<usize>>),
[vec![1, 0], vec![3, 4, 5, 6]]
);
assert_eq!(
split_from(0, 6, 0).map(Iterator::collect::<Vec<usize>>),
split_from(0..=6, 0).map(Iterator::collect::<Vec<usize>>),
[vec![], vec![1, 2, 3, 4, 5, 6]]
);
assert_eq!(
split_from(0, 6, 6).map(Iterator::collect::<Vec<usize>>),
split_from(0..=6, 6).map(Iterator::collect::<Vec<usize>>),
[vec![5, 4, 3, 2, 1, 0], vec![]]
);
// test out-of-bounds and also generics
assert_eq!(
split_from::<i16>(-1i16, 4i16, 10i16).map(Iterator::collect::<Vec<i16>>),
split_from::<i16>(-1i16..=4i16, 10i16).map(Iterator::collect::<Vec<i16>>),
[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::<Vec<(usize, usize)>>),
diag_test_helper(2, 3, 0..=7, 0..=7).map(Iterator::collect::<Vec<(usize, usize)>>),
[
vec![(1, 2), (0, 1)],
vec![(3, 4), (4, 5), (5, 6), (6, 7)],