From c44fc69893e7783221b2fb0e584bc235f268afce Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Sat, 15 Feb 2025 10:17:13 +0100 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 427 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 ++ src/card.rs | 208 +++++++++++++++++++++++++ src/f3.rs | 236 +++++++++++++++++++++++++++++ src/main.rs | 44 ++++++ 6 files changed, 925 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/card.rs create mode 100644 src/f3.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e9a91af --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,427 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "perfectset" +version = "0.1.0" +dependencies = [ + "nalgebra", + "num", + "rand", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy 0.8.17", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom", + "zerocopy 0.8.17", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "simba" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wide" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a69c948 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "perfectset" +version = "0.1.0" +edition = "2021" + +[dependencies] +nalgebra = "0.33.2" +num = "0.4.3" +rand = "0.9.0" diff --git a/src/card.rs b/src/card.rs new file mode 100644 index 0000000..dd2fa5e --- /dev/null +++ b/src/card.rs @@ -0,0 +1,208 @@ +use crate::f3::F3; +use nalgebra::{Matrix4, Vector4}; +use num::traits::Zero; +use rand::distr::{Distribution, StandardUniform}; +use rand::Rng; +use std::fmt::{Display, Formatter}; + +#[derive(Copy, Clone)] +pub struct Card { + v: Vector4, +} + +impl Card { + pub fn new(number: u8, shading: u8, color: u8, shape: u8) -> Self { + Card { + v: Vector4::::new( + number.try_into().unwrap(), + shading.try_into().unwrap(), + color.try_into().unwrap(), + shape.try_into().unwrap(), + ), + } + } +} + +impl Display for Card { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let number = match self.v.x.into() { + 0 => "one", + 1 => "two", + 2 => "three", + _ => unreachable!(), + }; + let shading = match self.v.y.into() { + 0 => "solid", + 1 => "striped", + 2 => "open", + _ => unreachable!(), + }; + let color = match self.v.z.into() { + 0 => "red", + 1 => "green", + 2 => "purple", + _ => unreachable!(), + }; + let shape = match self.v.w.into() { + 0 => "ovals", + 1 => "squiggles", + 2 => "diamonds", + _ => unreachable!(), + }; + write!(f, "{}-{}-{}-{}", number, shading, color, shape) + } +} + +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> Card { + Card { + v: Vector4::::from_fn(|_, _| rng.random()), + } + } +} + +pub struct CardSet { + cards: Vec, +} + +impl CardSet { + pub fn new(cards: &[Card]) -> Self { + CardSet { + cards: Vec::from(cards), + } + } + + fn grid(&self) -> [[[[bool; 3]; 3]; 3]; 3] { + let mut grid = [[[[false; 3]; 3]; 3]; 3]; + for card in &self.cards { + let number: usize = card.v.x.into(); + let shading: usize = card.v.y.into(); + let color: usize = card.v.z.into(); + let shape: usize = card.v.w.into(); + grid[number][shading][color][shape] = true; + } + grid + } + + pub fn score(&self) -> f32 { + let grid = self.grid(); + let mut bins = [[[[0u8; 3]; 3]; 3]; 4]; + for number in 0..3 { + for shading in 0..3 { + for color in 0..3 { + for shape in 0..3 { + if grid[number][shading][color][shape] { + bins[0][number][shading][color] += 1; + bins[1][number][shading][shape] += 1; + bins[2][number][color][shape] += 1; + bins[3][shading][color][shape] += 1; + } + } + } + } + } + let mut score = 0f32; + for bin in bins.as_flattened().as_flattened().as_flattened() { + if !bin.is_zero() { + let p = f32::from(*bin) / 20.0; + score -= p * f32::log2(p); + } + } + score + } + + pub fn print_grid(&self) { + let grid = self.grid(); + println!("+---+---+---+"); + for number in 0..3 { + for color in 0..3 { + print!("|"); + for shading in 0..3 { + for shape in 0..3 { + if grid[number][shading][color][shape] { + print!("X"); + } else { + print!(" "); + } + } + print!("|"); + } + println!(""); + } + println!("+---+---+---+"); + } + println!("score: {}", self.score()); + } +} + +pub struct CardMapping { + m: Matrix4, + v: Vector4, +} + +impl CardMapping { + pub fn map_single(&self, card: Card) -> Card { + Card { + v: self.m * card.v + self.v, + } + } + + pub fn map(&self, cs: CardSet) -> CardSet { + CardSet { + cards: cs + .cards + .into_iter() + .map(|card| self.map_single(card)) + .collect(), + } + } +} + +// Matrix4::determinant() only works for floating point types :( +fn determinant(m: &Matrix4) -> F3 { + let d_12 = m.m33 * m.m44 - m.m43 * m.m34; + let d_13 = m.m23 * m.m44 - m.m43 * m.m24; + let d_14 = m.m23 * m.m34 - m.m33 * m.m24; + let d_23 = m.m13 * m.m44 - m.m43 * m.m14; + let d_24 = m.m13 * m.m34 - m.m33 * m.m14; + let d_34 = m.m13 * m.m24 - m.m23 * m.m14; + let d_1 = m.m22 * d_12 - m.m32 * d_13 + m.m42 * d_14; + let d_2 = m.m12 * d_12 - m.m32 * d_23 + m.m42 * d_24; + let d_3 = m.m12 * d_13 - m.m22 * d_23 + m.m42 * d_34; + let d_4 = m.m12 * d_14 - m.m22 * d_24 + m.m32 * d_34; + m.m11 * d_1 - m.m21 * d_2 + m.m31 * d_3 - m.m41 * d_4 +} + +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> CardMapping { + loop { + let m = Matrix4::::from_fn(|_, _| rng.random()); + if !determinant(&m).is_zero() { + return CardMapping { + m: m, + v: Vector4::::from_fn(|_, _| rng.random()), + }; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::rng; + + #[test] + fn mapping_invertable() { + let mut rng = rng(); + for _ in 0..1000 { + let mapping: CardMapping = rng.sample(StandardUniform); + for i in 0..3u8.pow(4) { + let card = Card::new(i / 3 / 3 / 3, i / 3 / 3 % 3, i / 3 % 3, i % 3); + if !card.v.is_zero() { + assert_ne!(mapping.map_single(card).v, mapping.v); + } + } + } + } +} diff --git a/src/f3.rs b/src/f3.rs new file mode 100644 index 0000000..14dd57b --- /dev/null +++ b/src/f3.rs @@ -0,0 +1,236 @@ +use num::traits::{One, Zero}; +use rand::distr::{Distribution, StandardUniform}; +use rand::Rng; +use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct F3 { + s: i8, +} + +impl F3 { + pub const ELEMENTS: [F3; 3] = [F3 { s: 0 }, F3 { s: 1 }, F3 { s: 2 }]; +} + +impl From for usize { + fn from(f: F3) -> usize { + f.s as usize + } +} + +impl TryFrom for F3 { + type Error = &'static str; + + fn try_from(i: u8) -> Result { + if i > 2 { + Err("out of range") + } else { + Ok(F3 { s: i as i8 }) + } + } +} + +impl Add for F3 { + type Output = F3; + + fn add(self, rhs: Self) -> Self::Output { + F3 { + s: (self.s + rhs.s).rem_euclid(3), + } + } +} + +impl AddAssign for F3 { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Zero for F3 { + fn zero() -> Self { + F3 { s: 0 } + } + + fn is_zero(&self) -> bool { + *self == Self::zero() + } +} + +impl Sub for F3 { + type Output = F3; + + fn sub(self, rhs: Self) -> Self::Output { + F3 { + s: (self.s - rhs.s).rem_euclid(3), + } + } +} + +impl SubAssign for F3 { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl Mul for F3 { + type Output = F3; + + fn mul(self, rhs: Self) -> Self::Output { + F3 { + s: (self.s * rhs.s).rem_euclid(3), + } + } +} + +impl MulAssign for F3 { + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl One for F3 { + fn one() -> Self { + F3 { s: 1 } + } + + fn is_one(&self) -> bool { + *self == Self::one() + } +} + +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> F3 { + F3 { + s: rng.random_range(0..3), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn add_associative() { + for a in F3::ELEMENTS { + for b in F3::ELEMENTS { + for c in F3::ELEMENTS { + assert_eq!((a + b) + c, a + (b + c)); + } + } + } + } + + #[test] + fn add_identity() { + for a in F3::ELEMENTS { + assert_eq!(F3::zero() + a, a); + assert_eq!(a + F3::zero(), a); + } + } + + #[test] + fn add_inverse() { + for a in F3::ELEMENTS { + let mut has_inverse = false; + for b in F3::ELEMENTS { + if a + b == F3::zero() { + assert!(!has_inverse); + has_inverse = true; + } + } + assert!(has_inverse); + } + } + + #[test] + fn sub_is_add_inverse() { + for a in F3::ELEMENTS { + for b in F3::ELEMENTS { + assert_eq!(a + b - b, a); + assert_eq!(a - b + b, a); + } + } + } + + #[test] + fn add_commutative() { + for a in F3::ELEMENTS { + for b in F3::ELEMENTS { + assert_eq!(a + b, b + a); + } + } + } + + #[test] + fn mul_associative() { + for a in F3::ELEMENTS { + for b in F3::ELEMENTS { + for c in F3::ELEMENTS { + assert_eq!((a * b) * c, a * (b * c)); + } + } + } + } + + #[test] + fn mul_identity() { + for a in F3::ELEMENTS { + assert_eq!(F3::one() * a, a); + assert_eq!(a * F3::one(), a); + } + } + + #[test] + fn mul_inverse() { + for a in F3::ELEMENTS { + if !a.is_zero() { + let mut has_inverse = false; + for b in F3::ELEMENTS { + if a * b == F3::one() { + assert!(!has_inverse); + has_inverse = true; + } + } + assert!(has_inverse); + } + } + } + + #[test] + fn mul_commutative() { + for a in F3::ELEMENTS { + for b in F3::ELEMENTS { + assert_eq!(a * b, b * a); + } + } + } + + #[test] + fn distributive() { + for a in F3::ELEMENTS { + for b in F3::ELEMENTS { + for c in F3::ELEMENTS { + assert_eq!(a * (b + c), a * b + a * c); + } + } + } + } + + #[test] + fn add_is_like_set() { + let e = F3::ELEMENTS; + + assert_eq!(e[0] + e[0] + e[0], F3::zero()); + assert_eq!(e[1] + e[1] + e[1], F3::zero()); + assert_eq!(e[2] + e[2] + e[2], F3::zero()); + assert_eq!(e[0] + e[1] + e[2], F3::zero()); + + assert_ne!(e[0] + e[0] + e[1], F3::zero()); + assert_ne!(e[0] + e[0] + e[2], F3::zero()); + assert_ne!(e[1] + e[1] + e[0], F3::zero()); + assert_ne!(e[1] + e[1] + e[2], F3::zero()); + assert_ne!(e[2] + e[2] + e[0], F3::zero()); + assert_ne!(e[2] + e[2] + e[1], F3::zero()); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..504889b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,44 @@ +mod card; +mod f3; + +use crate::card::{Card, CardMapping, CardSet}; +use rand::distr::StandardUniform; +use rand::{rng, Rng}; + +fn main() { + let mut rng = rng(); + let mut cards = CardSet::new(&[ + Card::new(0, 0, 0, 1), + Card::new(0, 0, 1, 0), + Card::new(0, 0, 1, 2), + Card::new(0, 0, 2, 1), + Card::new(0, 1, 1, 1), + Card::new(0, 2, 0, 0), + Card::new(0, 2, 0, 2), + Card::new(0, 2, 2, 0), + Card::new(0, 2, 2, 2), + Card::new(1, 0, 1, 1), + Card::new(1, 2, 1, 1), + Card::new(2, 0, 0, 0), + Card::new(2, 0, 0, 2), + Card::new(2, 0, 2, 0), + Card::new(2, 0, 2, 2), + Card::new(2, 1, 1, 1), + Card::new(2, 2, 0, 1), + Card::new(2, 2, 1, 0), + Card::new(2, 2, 1, 2), + Card::new(2, 2, 2, 1), + ]); + cards.print_grid(); + + let mut max_score = cards.score(); + loop { + let mapping: CardMapping = rng.sample(StandardUniform); + cards = mapping.map(cards); + let score = cards.score(); + if score > max_score { + max_score = score; + cards.print_grid(); + } + } +}